随笔 - 632  文章 - 17  评论 - 54  阅读 - 93万

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'

 

github源码传送

 

  

    

  

  

 

posted on   飘杨......  阅读(1234)  评论(0编辑  收藏  举报
编辑推荐:
· Linux系列:如何用 C#调用 C方法造成内存泄露
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
阅读排行:
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· 单线程的Redis速度为什么快?
· 展开说说关于C#中ORM框架的用法!
· Pantheons:用 TypeScript 打造主流大模型对话的一站式集成库
· SQL Server 2025 AI相关能力初探
< 2025年3月 >
23 24 25 26 27 28 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 1 2 3 4 5

点击右上角即可分享
微信分享提示