在上一篇手写IOC注解,解决findViewById和点击事件(http://blog.csdn.net/wangwo1991/article/details/78811831)
博客中就实现了IOC注解,不过那里采用的是运行时注解的方式实现的,这里的手写ButterKnife将采用编译时注解的方式实现;运行时注解的方式实现过程中使用了不少反射,会对运行效率有影响,而编译时注解的方式实现对运行效率没有什么影响,像ButterKnife、Dragger、Retrofit等第三方架构都是采用编译时注解的方式实现的。
编译时注解的核心原理是依赖APT(Annotation Processing Tools)实现,编译时Annotation解析的基本原理是,在某些代码元素上(如类型、函数、字段等)添加注解,在编译时javac编译器会检测AbstractProcessor的子类,并且调用该类型的process函数,然后将添加了注解的所有元素都传递到process函数中,使得开发人员可以在编译器进行相应的处理。
APT(Annotation Processing Tools)是一种处理注解的工具,它对于源代码文件进行检测找出其中的Annotation,使用Annotation进行额外的处理;Annotation处理器在处理Annotation时可以根据源文件中的Annotation生成额外的源文件和其它的文件(文件具体内容由Annotation处理器的编译者决定),APT还会编译生成的源文件和原来的源文件,将它们一起生成class文件。
public class MainActivity extends AppCompatActivity {
TextView tv2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
new ViewBind().bind(this);
tv2.setText("TEXT");
}
class ViewBind{
public void bind(MainActivity activity){
activity.tv2= (TextView) activity.findViewById(R.id.t2);
}
}
}
看上面这段代码,在MainActivity类中新建一个内部类,在该内部类中的bind方法中通过findViewById方式实例化控件,并将实例化后的空间赋值给MainActivity中对应的成员变量,这样就达到了控件初始化的目的。
编译时注解大致也是采用这种方式,只不过是在通过javac编译时检测Annotation并生成相应的class文件以达到实例化的目的。
![](https://img.laitimes.com/img/9ZDMuAjOiMmIsIjOiQnIsICdzFWRoRXdvN1LclHdpZXYyd2LcBzNvwVZ2x2bzNXak9CX90TQNNkRrFlQKBTSvwFbslmZvwFMwQzLcVmepNHdu9mZvwFVywUNMZTY18CX052bm9CX90TUNVzaU1kdkNjW1ZkMkZXUYpVd1kmYr50MZV3YyI2cKJDT29GRjBjUIF2LcRHelR3LcJzLctmch1mclRXY39DOzQTMxkzMwEjNxITM3EDMy8CX0Vmbu4GZzNmLn9Gbi1yZtl2Lc9CX6MHc0RHaiojIsJye.jpg)
看上图会发现,编译后,会生成MainActivity.class文件和MainActivity$$ViewBinder.class文件,同时MainActivity.class中会产生ViewBind这个内部类,调用该内部类中的bind方法进行实例化。
下面来看下具体的实现过程:
一、新建所有需要的model文件
在项目工程中建立4个model文件,两个android model文件,两个java model文件;
二、配置环境
inject-compiler model文件中build.gradle文件
inject model文件中build.gradle文件
app model文件中build.gradle文件
项目中build.gradle文件和settings.gradle文件
上面图片红色方框就是要配置的地方
三、代码编写
inject model
public interface ViewBinder<T> {
void bind(T tartget);
}
public class InjectView {
public static void bind(Activity activity){
String clsName = activity.getClass().getName();
try {
Class<?> viewBidClass = Class.forName(clsName + "$$ViewBinder");
ViewBinder viewBinder = (ViewBinder) viewBidClass.newInstance();
viewBinder.bind(activity);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
}
}
inject-annotion model
@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
int value();
}
inject-compiler model
public class FileUtils {
public static void print(String text){
String path="C:\\Users\\Administrator\\Desktop\\log.txt";
File file=new File(path);
if(!file.exists()){
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
try {
FileWriter fileWriter=new FileWriter(file.getAbsoluteFile(),true);
fileWriter.write(text+"\n");
fileWriter.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
FileUtils就是一个写文件的工具类,编译时注解使用Log或者System.out.print输出是无效的,使用FileUtils将其输出到桌面,方便调试;
public class FieldViewBinding {
private String name;
private TypeMirror type;
private int resId;
public FieldViewBinding(String name, TypeMirror type, int resId) {
this.name = name;
this.type = type;
this.resId = resId;
}
public String getName() {
return name;
}
public TypeMirror getType() {
return type;
}
public int getResId() {
return resId;
}
}
@AutoService(Processor.class)
public class BindViewProcessor extends AbstractProcessor {
private Elements elementUtils;
private Types typeUtils;
private Filer filer;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
elementUtils = processingEnvironment.getElementUtils();
typeUtils = processingEnvironment.getTypeUtils();
filer = processingEnvironment.getFiler();
}
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> types = new LinkedHashSet<>();
types.add(BindView.class.getCanonicalName());
return types;
}
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.latestSupported();
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
Map<TypeElement, List<FieldViewBinding>> targetMap = new HashMap<>();
FileUtils.print("---------------------------> ");
for (Element element : roundEnvironment.getElementsAnnotatedWith(BindView.class)) {
FileUtils.print("element " + element.getSimpleName().toString());
TypeElement enClosingElement = (TypeElement) element.getEnclosingElement();
List<FieldViewBinding> list = targetMap.get(enClosingElement);
if (list == null) {
list = new ArrayList<>();
targetMap.put(enClosingElement, list);
}
int id = element.getAnnotation(BindView.class).value();
String fieldName = element.getSimpleName().toString();
TypeMirror typeMirror = element.asType();
FileUtils.print(" typeMirror " + typeMirror.getKind());
FieldViewBinding fieldViewBinding = new FieldViewBinding(fieldName, typeMirror, id);
list.add(fieldViewBinding);
}
for (Map.Entry<TypeElement, List<FieldViewBinding>> item : targetMap.entrySet()) {
List<FieldViewBinding> list = item.getValue();
if (list == null || list.size() == ) {
continue;
}
TypeElement enClosingElement = item.getKey();
String packageName = getPackageName(enClosingElement);
String complite = getClassName(enClosingElement, packageName);
ClassName className = ClassName.bestGuess(complite);
ClassName viewBinder = ClassName.get("com.example.mylibrary", "ViewBinder");
TypeSpec.Builder result = TypeSpec.classBuilder(complite + "$$ViewBinder")
.addModifiers(Modifier.PUBLIC)
.addTypeVariable(TypeVariableName.get("T", className))
.addSuperinterface(ParameterizedTypeName.get(viewBinder, className));
MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("bind")
.addModifiers(Modifier.PUBLIC)
.returns(TypeName.VOID)
.addAnnotation(Override.class)
.addParameter(className, "target", Modifier.FINAL);
for (int i = ; i < list.size(); i++) {
FieldViewBinding fieldViewBinding = list.get(i);
String pacckageNameString = fieldViewBinding.getType().toString();
ClassName viewClass = ClassName.bestGuess(pacckageNameString);
methodBuilder.addStatement("target.$L=($T)target.findViewById($L)", fieldViewBinding.getName()
, viewClass, fieldViewBinding.getResId());
}
result.addMethod(methodBuilder.build());
try {
JavaFile.builder(packageName, result.build())
.addFileComment("auto create make")
.build().writeTo(filer);
} catch (IOException e) {
e.printStackTrace();
}
}
return false;
}
private String getClassName(TypeElement enClosingElement, String packageName) {
int packageLength = packageName.length() + ;
return enClosingElement.getQualifiedName().toString().substring(packageLength).replace(".", "$");
}
private String getPackageName(TypeElement enClosingElement) {
return elementUtils.getPackageOf(enClosingElement).getQualifiedName().toString();
}
}
接下来在相应的activity中进行初始化调用就可以了
public class MainActivity extends AppCompatActivity {
@BindView(R.id.tv)
TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
InjectView.bind(this);
tv.setText("IOC注解");
}
}
效果如下:
源码地址:
https://pan.baidu.com/s/1bYxvKa