引言:
安卓中APT又叫Annotation Processing Tool,即注解处理器。Java中注解分为编译时注解和运行时注解,编译时注解无法通过反射的方式进行获取和处理,所以我们可以利用APT处理编译时注解。比如常见的三方库ButterKnife、ARouter均有使用到这种技术。通过注解处理器在编译时期处理注解,并动态的生成java类文件。
实现:
1、新建一个Android工程,在工程中创建两个Java/Kotlin lib,本项目采用Kotlin实现。
一个lib用来定义注解,另外一个定义processor。由于我们通常注解和处理器需要分开使用,所以分开定义。
2、注解的定义:
package com.example.annotation
import kotlin.reflect.KClass
@Target(AnnotationTarget.TYPE, AnnotationTarget.CLASS)
@Retention(AnnotationRetention.BINARY)
annotation class Register(val service: KClass<*>)
3、注解处理器的定义:
- 注解处理器需要创建java/kotlin lib进行编写的原因时,我们需要继承自AbstractProcessor(存在于jdk中,android sdk没有)
- 在注解处理器中,我们将获取被指定注解修饰的类(类A),并且根据这个类生成一个代理类,这个代理类实现了我们定义的接口,并且重写的方法将返回一个类A的对象。需要添加依赖:
implementation project(":annotation") //需要使用到注解
implementation "com.squareup:javapoet:1.13.0" //一个可以进行代码生成的库
- 我们还需要创建一个文件,指向我们自定义的注解处理器,这样在编译时,javac才会使用我们自定义的处理器处理注解。
创建如下目录并定义如下文件:
如果不想这样做的话,也可以直接使用三方库,具体使用方式这里不展开叙述:
//用于自动为 JAVA Processor 生成 META-INF 信息。
implementation 'com.google.auto.service:auto-service:1.0-rc3'
注解处理器的具体实现:
package com.example.processor
import com.example.annotation.Register
import com.squareup.javapoet.ClassName
import com.squareup.javapoet.JavaFile
import com.squareup.javapoet.MethodSpec
import com.squareup.javapoet.ParameterizedTypeName
import com.squareup.javapoet.TypeSpec
import javax.annotation.processing.AbstractProcessor
import javax.annotation.processing.Filer
import javax.annotation.processing.ProcessingEnvironment
import javax.annotation.processing.RoundEnvironment
import javax.lang.model.SourceVersion
import javax.lang.model.element.Modifier
import javax.lang.model.element.TypeElement
import javax.lang.model.util.ElementFilter
class ServiceProcessor: AbstractProcessor() {
private var filer: Filer? = null
private var interfaceClassName: ClassName = ClassName.get("com.example.apttest", "TestInterface") //我们的代理类将继承自这个接口
override fun init(processingEnv: ProcessingEnvironment?) {
super.init(processingEnv)
filer = processingEnv?.filer
}
override fun process(set: MutableSet<out TypeElement>?, roundEnvironmet: RoundEnvironment?): Boolean {
roundEnvironmet?.let {
val elements = it.getElementsAnnotatedWith(Register::class.java) //获取到所有被注解的元素
val typeElements = ElementFilter.typesIn(elements) //获取到TypeElement类型
for (typeElement in typeElements) {
val qualifiedName = typeElement.qualifiedName.toString() //类的完整名称
val className = qualifiedName.substring(qualifiedName.lastIndexOf(".") + 1) + "\$\$Binder"
val packageName = qualifiedName.substring(0, qualifiedName.lastIndexOf("."))
val presentClassName = ClassName.bestGuess(qualifiedName)
val builder = TypeSpec.classBuilder(className) //创建一个类文件
.addJavadoc("自动生成的类") //添加注释
.addModifiers(Modifier.FINAL, Modifier.PUBLIC) //声明访问权限
.addSuperinterface(ParameterizedTypeName.get(interfaceClassName, presentClassName)) //设置实现接口,第二个参数为泛型类型
val method = MethodSpec.methodBuilder("getInterface") //创建一个方法
.addAnnotation(Override::class.java)
.addModifiers(Modifier.PUBLIC)
.returns(presentClassName) //返回类型
.addStatement("return new \$T()", ClassName.get(typeElement)) //方法体
builder.addMethod(method.build())
try {
val javaFile = JavaFile.builder(packageName, builder.build()).build()
javaFile.writeTo(filer)
println("lqs: writeTo")
} catch (e: Exception) {
e.printStackTrace()
}
}
}
return true
}
override fun getSupportedSourceVersion(): SourceVersion {
return SourceVersion.latestSupported()
}
// 重写这个方法加入我们需要处理的注解类型
override fun getSupportedAnnotationTypes(): MutableSet<String> {
val set: HashSet<String> = HashSet()
set.add(Register::class.java.canonicalName)
return set
}
}
4、注解处理器的调用:
- 首先,我们要去添加注解,在app模块创建如下文件:
TestInterface即我们代理类需要实现的接口
package com.example.apttest
interface TestInterface<T> {
fun getInterface(): T
}
TestClass将会被我们定义的注解所修饰
package com.example.apttest
import android.content.Context
import android.widget.Toast
import com.example.annotation.Register
@Register(service = MainActivity::class)
class TestClass {
fun startService(context: Context) {
Toast.makeText(context, "hello", Toast.LENGTH_LONG).show()
}
}
- 我们还需要一个工具类来调用我们生成的代理类以获取一个类A的对象
package com.example.apttest
object TestUtil {
fun <T> getService(apiClazz: Class<T>): T? {
val className = apiClazz.canonicalName?.plus("\$\$Binder") ?: ""
var instance = Any()
try {
val clazz = Class.forName(className)
instance = clazz.newInstance()
if (instance is TestInterface<*>) {
val target = instance.getInterface()
if (target != null) {
instance = target
}
}
} catch (e: Exception) {
e.printStackTrace()
}
return instance as T?
}
}
- 在Activity中尝试调用:
package com.example.apttest
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
TestUtil.getService(TestClass::class.java)?.startService(this)
}
}
- 此时其实注解处理器并没有开始工作,还需要在build.gradle中进行注解处理器的配置:
kotlin支持使用kapt进行配置,java参考annotationProcessor
plugins {
id 'kotlin-kapt'
}
dependencies {
kapt project(":processor")
}
5、编译结果
我们生成的类在build目录下即可找到:
至此,我们通过注解处理器在编译阶段(build阶段)获取到了被注解修饰的类(类A),并生成了一个代理类实现指定接口返回了一个类A的对象,实现对类A中方法的调用。