Android使用AnnotationProcessor模仿ButterKnife
一、概述
在之前的一篇文章中,我们用反射实现了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
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | 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)。
1 2 3 4 5 | @Target (ElementType.FIELD) @Retention (RetentionPolicy.RUNTIME) public @interface BindView { int value(); } |
butterknife-compiler这个library比较重要,它是核心,通过AnnotationProcessor解析注解,并获取注解元素,然后通过JavaPoet来生成辅助类。
想要使用AnnotationProcessor,需要集成AbstractProcessor,并在实现类上加上@AutoService(Processor.class)注解(当然也可以手动配置,我觉得直接使用注解会更方便)
1 2 3 4 5 6 | @AutoService (Processor. class ) public class ButterKnifeProcessor extends AbstractProcessor { private Elements elements; private Messager messager; private Filer filer; private Types types; |
其主要处理注解的核心是在process方法中
1 2 3 4 5 6 7 8 9 | @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依次生成辅助类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 | //利用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文件
1 2 3 4 | //添加butterknife依赖 implementation project( ':butterknife' ) annotationProcessor project( ':butterknife-compiler' ) implementation project( ':butterknife-annotations' ) |
2.在MainActivity中使用
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 | 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需要配置成如下这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 | 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则下面这个东东可以删除掉
1 | annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6' |
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库