一、概述
在之前的一篇文章中,我們用反射實現了ButterKnife。但使用反射實現ButterKnife性能會受損。這一節我們使用AnnotationProcessor來實現一個ButterKnife(僅實現bindView作參考),在代碼編譯之前生成輔助類來幫助我們去掉繁瑣的findViewById操作,提高我們的開發效率,同時這個方案與性能五損耗。
下面我們看看具體怎樣實現。
二、案例實現
想要學會這個小例子,首先讀者需要具備以下幾個條件:
1.熟悉java的反射機制(在bindView的時候會用到反射)
2.熟悉自定義注解的用法
3.熟悉AnnotationProcessor(編譯時注解處理工具)
4.熟悉JavaPoet(用於生成java源文件的工具類,輔助library生成輔助類)
ps:默認以上四點大家都懂了,嘿嘿😋。
1.流程描述,參照ButterKnife的類庫結構
a.butterknife-library用於定義ButterKnife類用於用於綁定類文件
b.butterknife-annotation-library用於定義注解,此library中只定義了一個BindView注解
c.butterknife-compiler-library用於解析注解信息,並根據注解信息生成相應的輔助類
以上就是三個library的基本構成。我們依次簡單講一下,由於篇幅問題,我貼出關鍵的代碼,然后把源碼放到最后供大家下載參考。
ButterKnife.java
public class ButterKnife { public static void bind(Activity activity) { //獲取包名+類名 String className = activity.getClass().getName(); try { //利用反射創建一個實例對象 Class<?> newClass = Class.forName(className + "_ViewBinding"); newClass.getConstructor(activity.getClass()).newInstance(activity); } catch (Exception e) { e.printStackTrace(); } } }
定義了一個bind方法,並傳入一個對象。在bind方法中根據對象引用拿到class類然后再拿到類的包名+類名
然后根據Class.forName("包名+類名+_ViewBinding")拿到我們的輔助類,然后構造一個新對象並把原對象傳遞進去。如果AnnotationProcessor成功執行我們會的到一個className_ViewBinding的新類,本節中指的是MainActivity_ViewBinding.其生成類如下圖所示:
其本質上還是調用了View的findViewById方法來查找對應的View。
在在butterknife-annotation的library中我們僅僅只是定義了一個BindView的自定義注解,並標示這個注解可以一直存活到運行時(Retention),並且這個注解僅僅使用再屬性上(Target)。
@Target(ElementType.FIELD) @Retention(RetentionPolicy.RUNTIME) public @interface BindView { int value(); }
butterknife-compiler這個library比較重要,它是核心,通過AnnotationProcessor解析注解,並獲取注解元素,然后通過JavaPoet來生成輔助類。
想要使用AnnotationProcessor,需要集成AbstractProcessor,並在實現類上加上@AutoService(Processor.class)注解(當然也可以手動配置,我覺得直接使用注解會更方便)
@AutoService(Processor.class) public class ButterKnifeProcessor extends AbstractProcessor { private Elements elements; private Messager messager; private Filer filer; private Types types;
其主要處理注解的核心是在process方法中
@Override public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) { //通過RoundEnvironment掃描所有的注解文件,並獲取所有的注解字段 Map<TypeElement, List<FieldViewBinding>> targetMap = getTargetClassMap(roundEnvironment); //生成Java文件 createJavaFile(targetMap.entrySet()); return false; }
在getTargetMap()方法中會獲取所有的被BindView標注過的類,然后把注解的value、元素全限定名、元素類型封裝到FiledViewBinding中,並把所有的FiledViewBinding存入list集合,然后再以TypeElement為key,FiledViewBind集合為value存入targetMap集合並返回。
然后調用createJavaFile方法,並把map集合傳入進去,通過JavaPoet依次生成輔助類
//利用JavaPoet創建輔助文件 private void createJavaFile(Set<Map.Entry<TypeElement, List<FieldViewBinding>>> entries) { for (Map.Entry<TypeElement, List<FieldViewBinding>> entry : entries) { TypeElement typeElement = entry.getKey(); List<FieldViewBinding> list = entry.getValue(); if (list == null || list.size() == 0) { continue; } //獲取類的包名 String packageName = elements.getPackageOf(typeElement).getQualifiedName().toString(); //創建Java文件 String className = typeElement.getQualifiedName().toString().substring(packageName.length() + 1); //新類名,后面加上一個_ViewBinding用以區分 String newClassName = className + "_ViewBinding"; //javapoet中的類 MethodSpec.Builder methodBuilder = MethodSpec.constructorBuilder(). addModifiers(Modifier.PUBLIC)//添加公共構造函數 .addParameter(ClassName.bestGuess(className), "target");//添加參數 for (FieldViewBinding fieldViewBinding : list) { //獲取類的全名 String packageNameString = fieldViewBinding.getTypeMirror().toString(); ClassName viewClass = ClassName.bestGuess(packageNameString); methodBuilder.addStatement("target.$L=($L)target.findViewById($L)", fieldViewBinding.getFieldName(), viewClass, fieldViewBinding.getViewId()); } TypeSpec typeSpec = TypeSpec.classBuilder(newClassName).addModifiers(Modifier.PUBLIC, Modifier.FINAL) .addMethod(methodBuilder.build()) .build(); JavaFile javaFile = JavaFile.builder(packageName, typeSpec) .addFileComment("Generated code from Butter Knife. Do not modify!") .build(); try { javaFile.writeTo(filer); } catch (IOException e) { e.printStackTrace(); } } }
ButterKnife、BindView、ButterKnifeProcessor已經說完了,
下面看看怎樣使用:
1.配置配置app module的build.gradle文件
//添加butterknife依賴 implementation project(':butterknife') annotationProcessor project(':butterknife-compiler') implementation project(':butterknife-annotations')
2.在MainActivity中使用
public class MainActivity extends AppCompatActivity { @BindView(R.id.hello) TextView tv_hello; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); ButterKnife.bind(this); tv_hello.setText("您好啊"); tv_hello.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { Toast.makeText(MainActivity.this,"楊洛峋小寶寶真可愛",Toast.LENGTH_LONG).show(); } }); } }
到現在為止,整個自定義ButterKnife的制作就算完成了。
注意事項:
ps:gradle版本如果高於4.4在butterknife-compiler中的build.gradle需要配置成如下這樣:
apply plugin: 'java-library' dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.google.auto.service:auto-service:1.0-rc3' annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6' implementation 'com.google.auto:auto-common:0.8' implementation 'com.squareup:javapoet:1.8.0' implementation project(':butterknife-annotations') } sourceCompatibility = JavaVersion.VERSION_1_8 targetCompatibility = JavaVersion.VERSION_1_8
如果gradle版本低於4.4則下面這個東東可以刪除掉
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'