Android apt 遇到问题的例子

Posted danfengw

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android apt 遇到问题的例子相关的知识,希望对你有一定的参考价值。

这篇apt 的Demo 是为了解释我在编写apt 代码时遇到的一个问题。问题链接,简单写一下步骤,解释下遇到的问题。 as 版本:4.1.1 (版本不同配置上可能会有所不同)
前言
这里就简单仿照ButterKnife写个BindV的注解。先看下最终的使用

class MainActivity : AppCompatActivity() 
    @BindV(R.id.tv)  // 1 注解
    lateinit var tv: TextView;

    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Binding.bind(this)  // 2 调用相关绑定逻辑,前期不需要关注
        tv.setText("哈哈哈111")
    

这里个点需要区分,如果你使用过ButterKnife 可以知道ButterKnife会自动给我门生成对应的Binding的文件,然后我们调用Bind方法就自动绑定了。这里我们也分成2个部分: 1 代码自动生成 2绑定逻辑

代码自动生成

1 创建3个lib

android Library: aptlib
Java or Kotlin Library: aptlib-anno (专门放我们编写的注解)
Java or Kotlin Library :aptlib-processor (编写动态生成文件的逻辑)
ps:必须是java library 有2个原因 apt 本来java 提供的,另外 Android库中不允许继承AbstractProcessor。

2 注解

aptlib-anno 库下面创建

BindV.kt

@Target(AnnotationTarget.FIELD)
@Retention(AnnotationRetention.SOURCE)
annotation class BindV(val value: Int)

3 注解动态生成关联文件

aptlib-processor 库

步骤一

创建相关目录,如下,在main目录下创建resources/META-INF/services
然后创建文件:javax.annotation.processing.Processor

步骤二

添加 上面2 注解 中创建的aptlib-anno 库 以及 javapoet的库

    implementation project(path: ':aptlib-anno')
    implementation 'com.squareup:javapoet:1.13.0'

aptlib-anno 库:因为生成代码的时候有个getSupportedAnnotationTypes方法需要返回需要支持的注解类型
javapoet 库: 自动生成代码需要借助的库

步骤三

编写自动生成文件的代码
BindingProcessor.kt

class BindingProcessor : AbstractProcessor() 
    var filer: Filer? = null

    override fun init(processorEnvironment: ProcessingEnvironment?) 
        super.init(processorEnvironment)
        filer = processorEnvironment?.filer
    

    override fun process(p0: MutableSet<out TypeElement>?, roundEnvironment: RoundEnvironment?): Boolean 
        roundEnvironment?.rootElements?.forEach  element ->
            val packageStr = element.enclosingElement.toString()
            val classStr = element.simpleName.toString()
            generateBindingFile(packageStr, classStr, element)
        
        return false
    

    private fun generateBindingFile(packageStr: String, classStr: String, element: Element) 
    	// 下面代码都是依赖的javapoet 库完成的,具体用法参考github上javapoet 链接
        val className = ClassName.get(packageStr, classStr + "_Binding")
        val constructBuilder = MethodSpec.constructorBuilder()
            .addModifiers(Modifier.PUBLIC)
            .addParameter(ClassName.get(packageStr, classStr), "activity")

        var hasBinding = false

        element.enclosedElements?.forEach  enclosedElement ->
            if (enclosedElement.kind == ElementKind.FIELD) 
                val bindV = enclosedElement.getAnnotation(BindV::class.java)
                bindV?.let 
                    hasBinding = true
                    constructBuilder.addStatement(
                        "activity.\\$N = activity.findViewById(\\$L)",
                        enclosedElement.simpleName,
                        bindV.value
                    )
                
            

        

        val classBuilder = TypeSpec.classBuilder(className)
            .addModifiers(Modifier.PUBLIC)
            .addMethod(constructBuilder.build())
            .build()

        if (hasBinding) 
            JavaFile.builder(packageStr, classBuilder)
                .build()
                .writeTo(filer)
        
    

    override fun getSupportedAnnotationTypes(): MutableSet<String> 
        return mutableSetOf(BindV::class.java.canonicalName)
    


步骤四

在步骤一:javax.annotation.processing.Processor 文件中添加我们自己编写的BindingProcessor.kt 的关联

com.example.aptlib_process.BindingProcessor

步骤五:测试代码自动生成使用

主项目中添加依赖

 implementation project(path: ':aptlib-anno')
 annotationProcessor project(path: ':aptlib-process')
public class MainActivity2 extends AppCompatActivity 
    @BindV(R.id.tv)
    TextView tv;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    

运行后可以看到自动生成的文件

public class MainActivity2_Binding 
  public MainActivity2_Binding(MainActivity2 activity) 
    activity.tv = activity.findViewById(2131231003);
  

从文件可以看出当我们调用MainActivity2_Binding的构造器的时候,findViewById的方法就可以被调用,因此接下来我们要做代码绑定相关的逻辑了

代码绑定

aptlib

步骤一

添加依赖

 api project(path: ':aptlib-anno'

api: 是为了后面让上层依赖 ,之前主目录也依赖了aptlib-anno,后面可以通过依赖aptlib替换掉aptlib-anno

步骤二

特别简单:

public class Binding 

    companion object 

        fun bind(activity: AppCompatActivity) 
        // 通过反射调用自动生成类的构造器
            try 
                val bindClass = Class.forName(activity.javaClass.canonicalName + "_Binding");
                val constructor = bindClass.getDeclaredConstructor(activity.javaClass)
                constructor.newInstance(activity)
             catch (e: Exception) 
                e.printStackTrace()
            

        
    

步骤三:测试代码绑定

替换依赖

// implementation project(path: ':aptlib-anno')
 implementation project(path: ':aptlib')
 annotationProcessor project(path: ':aptlib-process')

如果绑定成功,tv.setText("哈哈哈222");代码就不会报空指针

public class MainActivity2 extends AppCompatActivity 
    @BindV(R.id.tv)
    TextView tv;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        Binding.Companion.bind(this);
        tv.setText("哈哈哈222");
    


出现的问题

上面写完之后,会发现,如果我主目录中使用的是kotlin代码编写的kt文件,就不会自动编译生成对应的bingding文件,这是为啥呢?就比如我写的下面的代码,不会生成对应的MainActivity_Binding,运行起来会直接崩溃

class MainActivity : AppCompatActivity() 
    @BindV(R.id.tv)
    lateinit var tv: TextView;

    override fun onCreate(savedInstanceState: Bundle?) 
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        Binding.bind(this)
        tv.setText("哈哈哈111")
    

解决方法链接

以上是关于Android apt 遇到问题的例子的主要内容,如果未能解决你的问题,请参考以下文章

在Android中动态添加子项到ListView

怎样使用Android Studio引用本地aar文件

Android apt被注解为Kotlin写的代码时不能自动生成文件bug

在引用的项目中获取主项目的版本

注解处理器(APT)是什么?

设计模式-简单工厂模式例子