ButterKnife采用编译时注解,就是采用apt动态生成代码?
第一步:热身准备: 先要创建三个java library , 分别是 butterknife, butterknife-annotation, butterknife-compile
第二步:使用apt 动态生成,需要的gradle 配置
1. 在butterknife-compiler 的 build.gradle 中的
dependencies {
//apt 的依赖
compile 'com.squareup:javapoet:1.9.0'
//谷歌的一个库
compile 'com.google.auto.service:auto-service:1.0-rc2'
}
2. 在 build.gradle 中添加 mavenCentral()
3. 添加一些必要的配置
/**
* 解决 .addModifiers(Modifier.FINAL, Modifier.PUBLIC);报错的问题
* Gradle <= 2.2
* 1.App的build.gradle 中 apt project(':butterknife-compiler')
* 在 最上面加上 apply plugin: 'com.neenbedankt.android-apt'
* 2.在全局的 build.gradle repositories和 中 添加 mavenCentral()
第三步, 我们需要 在butterknife-annotation 创建 一个BindView注解
// SOURCE(.java) < CLASS(.java .class ) < RUNTIME(.java .class JVM) //SOURCE 进行 @Override @SuppressWarning 进行这样的检查的时候使用 //CLASS 进行一些预处理 比如动态生成辅助代码 //RUNTIME 需要在运行时动态获取注解信息的。 @Target(ElementType.FIELD) @Retention(RetentionPolicy.CLASS) //编译时, public @interface BindView { int value(); }
第四步,在 butterknife-compiler中创建下类
@AutoService(Processor.class) public class ButterKnifeProcessor extends AbstractProcessor { private Filer mFiler; private Elements mElementUtils; @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); mFiler = processingEnvironment.getFiler(); mElementUtils = processingEnvironment.getElementUtils(); } //1. 指定处理的版本 @Override public SourceVersion getSupportedSourceVersion() { //返回最高的版本 System.out.println("返回最高的版本"+SourceVersion.latestSupported()); return SourceVersion.latestSupported(); } //给定需要处理的注解 @Override public Set<String> getSupportedAnnotationTypes() { Set<String> types = new LinkedHashSet<>(); for (Class<? extends Annotation> annotation : getSupportedAnnotations()) { //annotation.getCanonicalName() 返回的是 com.butterknif_annotation.BindView(全路径) types.add(annotation.getCanonicalName()); } return types; } /** * 参考了 ButterKnife 的写法 * * @return */ private Set<Class<? extends Annotation>> getSupportedAnnotations() { Set<Class<? extends Annotation>> annotations = new LinkedHashSet<>(); //将所有带注解的参数 加入到集合中 annotations.add(BindView.class); return annotations; } @Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { // System.out.println("----------------------process----------------"); // System.out.println("----------------------process----------------"); // System.out.println("----------------------process----------------"); // System.out.println("----------------------process----------------"); //process 方法代表的是, 有注解就会进来,但是这里面是一团乱麻 //从所有的类中 找到带该参数的 变量 Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(BindView.class); // for (Element element : elements) { // //System.out.println("----------------------process----------------"+element.getSimpleName().toString()); //textView // Element enclosingElement = element.getEnclosingElement(); //com.mybutterknife.MainActivity // //System.out.println("----------------------process+enclosingElement----------------"+enclosingElement.toString()); // // } //解析 属性activity -- list Map<Element, List<Element>> elementMap = new LinkedHashMap<>(); for (Element element : elements) { //element 需要动态注册的变量 Element enclosingElement = element.getEnclosingElement(); // 类 key List<Element> viewBindListElements = elementMap.get(enclosingElement); // value 第一次取为空 if (viewBindListElements == null){ viewBindListElements = new ArrayList<>(); elementMap.put(enclosingElement, viewBindListElements); } viewBindListElements.add(element); } //生成代码 entrySet(key+value) keySet(key) for (Map.Entry<Element, List<Element>> entry : elementMap.entrySet()) { Element key = entry.getKey(); List<Element> value = entry.getValue(); /** * 解决 .addModifiers(Modifier.FINAL, Modifier.PUBLIC);报错的问题 * Gradle <= 2.2 * 1.App的build.gradle 中 apt project(':butterknife-compiler') * 在 最上面加上 apply plugin: 'com.neenbedankt.android-apt' * 2.在全局的 build.gradle repositories和 中 添加 mavenCentral() * 已经加上apt的版本: classpath 'com.neenbedankt.gradle.plugins:android-apt:1.8' * Gradle >= 2.2 * annotationProcessor */ //构造类 String activityClassNameStr = key.getSimpleName().toString(); //将一个 String 类型 转成 Object ClassName activityClassName = ClassName.bestGuess(activityClassNameStr); ClassName unbindClassName = ClassName.get("com.butterknife", "Unbinder"); TypeSpec.Builder classBuilder = TypeSpec.classBuilder(activityClassNameStr+"_ViewBinding") .addModifiers(Modifier.FINAL, Modifier.PUBLIC).addSuperinterface(unbindClassName); //构造属性 classBuilder.addField(activityClassName, "target", Modifier.PRIVATE); //普通方法 ClassName callSuperClassName = ClassName.get("android.support.annotation", "CallSuper"); MethodSpec.Builder unbindMethodBuilder = MethodSpec.methodBuilder("unbind") .addAnnotation(Override.class) .addAnnotation(callSuperClassName) .addModifiers(Modifier.PUBLIC); unbindMethodBuilder.addStatement("$L target = this.target", activityClassName); //构造函数 ClassName uiThreadClassName = ClassName.get("android.support.annotation", "UiThread"); MethodSpec.Builder constructorMethodBuilder = MethodSpec.constructorBuilder() .addAnnotation(uiThreadClassName) .addModifiers(Modifier.PUBLIC) .addParameter(activityClassName, "target"); constructorMethodBuilder.addStatement("this.target = target"); //findViewById 属性 for (Element element : value) { String fieldName = element.getSimpleName().toString(); ClassName utilsClassName = ClassName.get("com.mybutterknife", "Utils"); int resId = element.getAnnotation(BindView.class).value(); constructorMethodBuilder.addStatement("target.$L = $T.findViewById(target,$L)", fieldName, utilsClassName, resId); unbindMethodBuilder.addStatement("target.$L = null", fieldName); } classBuilder.addMethod(unbindMethodBuilder.build()); classBuilder.addMethod(constructorMethodBuilder.build()); //生成类 try { String packageName = mElementUtils.getPackageOf(key).getQualifiedName().toString(); JavaFile.builder(packageName, classBuilder.build()) .addFileComment("butterknife 自动生成") .build().writeTo(mFiler); } catch (IOException e) { e.printStackTrace(); System.out.println("翻车了"); } } return false; } }
需要注意的是
1.该类要继承 AbstractProcessor 并且 覆写 这个方法 process 在该方法中 动态创建类 。
2.在该类上添加注解 @AutoService(Processor.class)
3.process 方法默认返回 false 就可以了
第五步 在butterkenife中创建如下类 ,并在类中 通过反射 调用 通过apt动态生成的代码 。
/** * person.getClass().getName(): AnnotationJava.test.Person * person.getClass().getSimpleName(): Person * person.getClass().getCanonicalName(): AnnotationJava.test.Person */ public class ButterKnife { public static Unbinder bind(Activity activity){ try { Class<? extends Unbinder> bindClassName = (Class<? extends Unbinder>) Class.forName(activity.getClass().getName() + "_ViewBinding"); Constructor<? extends Unbinder> constructor = bindClassName.getDeclaredConstructor(activity.getClass()); Unbinder unbinder = constructor.newInstance(activity); return unbinder; } catch (Exception e) { e.printStackTrace(); } return Unbinder.EMPTY; } }
第六步, 需要特别注意的是 ,动态生成类需要是要如下接口
public interface Unbinder { @UiThread void unbind(); Unbinder EMPTY = new Unbinder() { @Override public void unbind() { } }; }
至于apt的api 可以参考如下网址
https://blog.csdn.net/xuguobiao/article/details/72775730
关于面试