APT注解处理器之实现BufferKnife自动绑定View

21 阅读2分钟

前言

  相信开发安卓的小伙伴大多数都用过 BufferKnife 库,在 ViewBinding 出现之前,使用 BufferKnife 能够通过注解的方式实现控件和 id 的自动绑定,省去了 findviewByid 的麻烦,备受广大开发者的喜爱。本篇文章主要通过 APT 注解处理器自己实现 BufferKnife 绑定控件 id 的功能,对 APT 注解处理器注册和代码生成还不懂的小伙伴可以先查看前两天文章了解。

需求分析

要想实现一个 BufferKnife 的绑定功能,首先需要分析 BufferKnife 是如何实现 id 的绑定。

  1. 定义 @BindView 注解,并能接收控件 id。
  2. 实现一个 APT 注解处理器用来处理@BindView注解。

技术实现

1. 定义注解

定义一个 BindView 注解,用来绑定控件 ID。

@Target(ElementType.FIELD)
@Retention(RetentionPolicy.CLASS)
public @interface BindView {
    int value() default 0;
}

2. 创建 APT 注解处理器

创建 BindViewProcessor 类继承 AbstractProcessor,并实现其中部分方法。

  1. 初始化工具
override fun init(processingEnv: ProcessingEnvironment) {
        super.init(processingEnv)
        elementTools = processingEnv.elementUtils
        typeTool = processingEnv.typeUtils
        messager = processingEnv.messager
}
  1. 使用 HashMap 收集每个类中的 BindView 注解。
 val elements = roundEnv?.getElementsAnnotatedWith(BindView::class.java)
        if (elements.isNullOrEmpty()) return false
        val iterator = elements.iterator()
        while (iterator.hasNext()) {
            val next = iterator.next()
            if (next.kind == ElementKind.FIELD) {
                if (map.containsKey(next.enclosingElement)){
                    map[next.enclosingElement]?.add(next)
                }else{
                    val list = mutableListOf<Element>()
                    list.add(next)
                    map[next.enclosingElement] = list
                }
            } else {
                messager?.printMessage(Diagnostic.Kind.ERROR, "BindView 注解只能使用在成员属性上")
                return false
            }

        }
  1. 根据收集到的注解,遍历 map 生成对应的辅助类。其中${t.simpleName}Parameter 根据不同的 activity 生成不同的类。将同一个类中的所有注解进行处理。
 map.forEach { t, u ->
        
            val fileSpec = FileSpec.builder("com.cms.processor", "${t.simpleName}Parameter")
            val methodBuilder = FunSpec.builder("getView")
                .addParameter("target",Any::class)
                .addModifiers(KModifier.OVERRIDE)
            u.forEach {
                processBindView(it,methodBuilder)
            }
            val classBuilder = TypeSpec.classBuilder("${t.simpleName}$PARAMETER_CLASS_NAME")
                .addSuperinterface(ClassName.bestGuess(ProcessorConfig.TYPE_PARAMETERGET))
                .addFunction(methodBuilder.build())
                .build()

            val outDir = processingEnv.options[KAPT_KOTLIN_GENERATED_OPTION_NAME]
            fileSpec.addType(classBuilder).build().writeTo(File(outDir))

        }
  1. 其中 processBindView(it,methodBuilder)便是遍历 activity 中的所有 BindView 注解,根据注解信息生成 findViewByid。
 val methodContent = "activity."+element.simpleName.toString()+" = activity.findViewById("+bindView.value+")"
        methodBuilder.addStatement("val activity = target as %T",ClassName.bestGuess(element.enclosingElement.toString())  )
        methodBuilder.addStatement(methodContent)
  1. 生成的结果如下:
public class MainActivityParameter : ViewGet {
  public override fun getView(target: Any): Unit {
    val activity = target as MainActivity
    activity.tv = activity.findViewById(2131231187)
  }
}
  1. 得到生成的MainActivityParameter 类之后,接下来就是调用其中的 getView 方法对控件进行 findViewById。由于MainActivityParameter 这个类是在编译期生成的,可以通过反射调用该类的 getView 方法,并且传入 activity 作为参数。
     val forName = Class.forName("com.cms.processor.$name$parameterName")

            val newInstance = forName.newInstance()
            val viewGet = newInstance as ViewGet
            viewGet.getView(activity)
            val forName1 = Class.forName("com.cms.processor.$name$method")
            val method = (forName1.newInstance()) as InvokeMethod
            method.invoke(activity)
  1. 最后直接在 activity 中调用 BufferKnife 的 bind 方法即可。
     @BindView(value = R.id.tv)
     var tv: TextView? = null
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        ButterKnife.bind(this)
        tv?.text = "数据更改完成"
    }
  1. 整个过程最重要的是 APT 注解处理器的注册,注解的收集和 findViewById 代码的生成。最后直接通过反射调用即可完成。

总结

  APT 注解处理器技术在日常开发中被很多三方库使用,也是实现一些复杂插件的必备技术。通过学习 APT 注解处理器的实现过程便于对源码的理解。不懂的小伙伴抓紧学习吧!