前言:力尽不知热 , 但惜夏日长。带你走进APT不寻常的实战
大多数注解处理器上手时:
- 接触的是 butterknife,> 可以自动生成 模版式代码 findViewById ,
- 或者EventBus这种去找到注解的类,找到注解的方法。
- 本文详细介绍注解处理器的用法,
自动生成 Repository
,或者根据一个接口类,生成另一个类来实现该接口的所有方法,当然这些方法的实现和参数是有固定模版式写法的。
本文示例介绍思路
一、前言
- APT注解处理器是什么?
APT
:即Annotation Processing Tool,即注解处理器,是在程序编译期间的一种处理注解的工具,确切的说它是javac的一个工具。注解处理器以Java代码或者Kotlin作为输入,生成.java或者.Kotlin文件作为输出. - Android中哪些框架中使用了APT?
像butterknife
框架,EventBus
框架,JetPack.Room
库,Glide
图片加载扩展OKhttp时,ARouter
路由框架,GreenDAO
数据库等框都使用过APT
- 本文带你走进APT哪种应用场景?
生成接口类实现,接口类实现是固定模板。 - 在Kotlin中叫作Kapt , Java中叫作APT
Kapt
:即:全称是 Kotlin annotation processing tool)就可以将 Java 注解处理器与 Kotlin 代码搭配使用了
由于现在大部分开发都是使用Kotlin开发,本文主要讲述的是Kapt的角度讲解实例,Java中大同小异。
本示例将会介绍:
- 网络请求框架的架构中 数据源提供Repository怎么根据Retrofit的Interface 接口自动生成。
- 同理可以扩展联想到自己项目中哪些模版式代码,接口的模版式实现也可以这样自动生成。
- Kotlin中Kapt的使用场景及
KotlinPoet
的相关使用。 - 同时思考某些网络接口数据可以单独解耦出模块来。
二 APT中基础介绍:元注解
-
1.
@Target
:目标,表示注解修饰的目标Java中写法
ElementType.ANNOTIONS_TYPE
: 目标是注解,给注解设置的注解
ElementType.CONSTRUCTOR
: 构造方法
ElementType.FIELD
: 属性注解
ElementType.METHOD
: 方法注解
ElementType.Type
: 类型如:类,接口,枚举
ElementType.PACKAGE
: 可以给一个包进行注解
ElementType.PARAMETER
: 可以给一个方法内的参数进行注解
ElementType.LOCAL_VARIABLE
: 可以给局部变量进行注解Kotlin的写法:
AnnotationTarget.CLASS
可以给一个类型进行注解,类、接口、对象、甚至注解类本身
AnnotationTarget.ANNOTATION_CLASS
: 可以给一个注解类进行注解 AnnotationTarget.TYPE_PARAMETER
: 泛型参数(暂未支持) AnnotationTarget.VALUE_PARAMETER
: 方法、构造函数的参数 AnnotationTarget.PROPERTY
: (计算)属性(该注解Java不可见) AnnotationTarget.PROPERTY_GETTER
: 属性getter方法
AnnotationTarget.PROPERTY_SETTER
: 属性setter方法
AnnotationTarget.FIELD
: 字段变量,包括PROPERTY的备用字段(backing field)
AnnotationTarget.LOCAL_VARIABLE
: 局部变量
AnnotationTarget.CONSTRUCTOR
: 构造函数
AnnotationTarget.FUNCTION
: 方法、函数(不包括构造函数)
AnnotationTarget.FILE
: 文件整体
AnnotationTarget.TYPE
: 泛型支持
AnnotationTarget.TYPEALIAS
: typealias类型
AnnotationTarget.EXPRESSION
: 表达式 -
2.
@Retention
:表示需要在什么级别保存该注解信息Java中
RetentionPolicy.SOURCE
:注解仅存在于源码中,编译器编译后的class中会消失,通常用于代码的检查,比如@overwrite,当父类或接口没有此方法时会编译失败。
RetentionPolicy.CLASS
:注解会存在编译后的class文件中,但是JVM不会加载,默认值,通常和RetentionPolicy.SOURCE无差别,只有在进行java字节码编程时会用到。 RetentionPolicy.RUNTIME
:注解会存在编译后的class文件中,并被JVM读取,通过反射方式获取注解的值,在Junit等测试框架中被大量使用
Kotlin中对应写法:
AnnotationRetention.SOURCE
AnnotationRetention.BINARY
AnnotationRetention.RUNTIME
3.@Document
:将注解包含到javaDoc中
4.@Inherit
:运行子类继承父类的注解
5.@Repeatable
:定义注解可重复
三、项目目录介绍
如上图所示,项目Demo 分为4个部分:
- networkApiData:Java Library,项目网络接口部分和 网络数据返回实体bean,可以将网络部分解耦出来
- annotations:Java Library, 注解模块
- annotation_compiler:Java Library 注解处理模块
- app:真实android app 工程
四、网络模块部分编写(networkApiData)
build.gradle里面添加 dependencies 里面 添加依赖
java
api project(path: ':annotations')
主要示例了Retrofit中请求接口写法
- 示例了Get请求
- 示例Post 请求 参数在url上
- 示例post 请求 post body
- 示例post 请求 post body 在Repository中的 第2种写法
less
interface Api {
//示例Get 请求
@GET("search/acjson?tn=resultjson_com&logid=12307192414549550342&ipn=rj&ct=201326592&is=&fp=result&fr=&cg=star&rn=30")
suspend fun get899(@Query("word") word: String, @Query("queryWord") queryWord: String, @Query("pn") pn: Int, @Query("gsm") gsm: String): BaseResponse<ArrayList<BaiduDataBean>>
//示例Post 请求 参数在url上
@FormUrlEncoded
@POST("https://www.wanandroid.com/user/register")
suspend fun register(
@Field("username") username: String,
@Field("password") password: String,
@Field("repassword") repassword: String
): String
//示例post 请求 post body
// 此处示例写法,这个真实post body 地址是不通的
@POST("https://www.wanandroid.com/user/register")
suspend fun testPostBody(@Body body: RequestBody): String
// 示例post 请求 post body
// 此处示例写法,这个真实post body 地址是不通的
@PostBody("{"ID":"Long","name":"String"}")
@POST("https://www.wanandroid.com/user/register")
suspend fun testPostBody222(@Body body: RequestBody): String
}
五、注解模块(annotations)
主要写了2个注解:
- CreateService 注解在网络接口类上面
- PostBody: 注解在post 请求body上面
less
@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class CreateService(val interfaceApi: String, val superClass: String) {
/**
* interfaceApi: 接口类名字,
* superClass : 自动生成的类的继承的父类
**/
}
less
@Target(AnnotationTarget.FUNCTION)
@Retention(AnnotationRetention.RUNTIME)
annotation class PostBody(val json: String) {
}
六、真实android app 工程 里面
build.gradle里面添加
bash
plugins {
id 'com.android.application'
id 'org.jetbrains.kotlin.android'
id 'kotlin-kapt'//添加 kapt
}
开启 kapt
ini
kapt {
generateStubs = true
}
dependencies 里面 添加依赖
java
implementation project(path: ':networkApiData')
kapt project(path: ':annotation_compiler')
然后随便代码工程目录里面写个类,类名可自定义:
kotlin
/**
* interfaceApi: 接口类名字,
* superClass : 自动生成的类的继承的父类
**/
@CreateService(interfaceApi = "com.wx.test.api.Api", superClass = "com.wx.kotlin_kapt_demo.data_source.repository.BaseRepository")
class KaptComponet {
}
七、注解处理模块(annotation_compiler)
主要负责自动成Repository 类
- build.gradle里面编写
php
plugins {
id 'java'
id 'java-library'
id 'kotlin'
id 'kotlin-kapt'
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
// annotationProcessor 'com.google.auto.service:auto-service:1.0-rc4' java 用法
// implementation 'com.google.auto.service:auto-service:1.0-rc4' java 用法
implementation "com.google.auto.service:auto-service:1.0-rc4" // kotlin 用法
kapt "com.google.auto.service:auto-service:1.0" // kotlin 用法
implementation "com.squareup:kotlinpoet:1.8.0"
implementation 'com.squareup.retrofit2:retrofit:2.9.0'
implementation project(path: ':annotations')
implementation project(path: ':networkApiData')
}
java {
sourceCompatibility = JavaVersion.VERSION_1_8
targetCompatibility = JavaVersion.VERSION_1_8
}
- 自动生成代码处理类 AptProcessor:
kotlin
@AutoService(Processor::class)
class AptProcessor : AbstractProcessor() {
private var mFiler: Filer? = null
private var mElementUtils: Elements? = null
private val gson by lazy { Gson() }
override fun init(processingEnv: ProcessingEnvironment?) {
super.init(processingEnv)
mFiler = processingEnv?.filer
mElementUtils = processingEnv?.elementUtils
}
//指定处理的版本
override fun getSupportedSourceVersion(): SourceVersion {
return SourceVersion.latestSupported()
}
//给到需要处理的注解
override fun getSupportedAnnotationTypes(): MutableSet<String> {
val types: LinkedHashSet<String> = LinkedHashSet()
getSupportedAnnotations().forEach { clazz: Class<out Annotation> ->
types.add(clazz.canonicalName)
}
return types
}
private fun getSupportedAnnotations(): Set<Class<out Annotation>> {
val annotations: LinkedHashSet<Class<out Annotation>> = LinkedHashSet()
// 需要解析的自定义注解
annotations.add(CreateService::class.java)
annotations.add(PostBody::class.java)
return annotations
}
/**
KotlinPoet 官方helloWorld示例:
val greeterClass = ClassName("", "Greeter")
val file = FileSpec.builder("", "HelloWorld")
.addType(TypeSpec.classBuilder("Greeter")
.primaryConstructor(FunSpec.constructorBuilder()
.addParameter("name", String::class).build())
.addProperty(PropertySpec.builder("name", String::class)
.initializer("name").build())
.addFunction(FunSpec.builder("greet")
.addStatement("println(%P)", "Hello, $name").build())
.build())
.addFunction(FunSpec.builder("main")
.addParameter("args", String::class, VARARG)
.addStatement("%T(args[0]).greet()", greeterClass).build())
.build()
file.writeTo(System.out)
------------------------------------------------------------------------------------------------------
class Greeter(val name: String) {
fun greet() {println("""Hello, $name""")}}
fun main(vararg args: String) {Greeter(args[0]).greet()}
*/
override fun process(annotations: MutableSet<out TypeElement>, roundEnvironment: RoundEnvironment): Boolean {
val elementsAnnotatedWith: Set<out Element> = roundEnvironment.getElementsAnnotatedWith(CreateService::class.java);
elementsAnnotatedWith.forEach { element ->
//得到包名
var e = element
while (e.kind != ElementKind.PACKAGE) {
e = e.enclosingElement
}
val packageName = (e as PackageElement).toString()
val service = element.getAnnotation(CreateService::class.java)
val funspecs = mutableListOf<FunSpec>()
try {
val apiClass = Class.forName(service.interfaceApi)
val mapMethod = mutableMapOf<String, String>()
apiClass.methods.forEach { m ->
m.annotations.forEach { an ->
if (an.annotationClass.simpleName == PostBody::class.java.simpleName) {
mapMethod[m.name] = (an as PostBody).json
}
}
}
apiClass.kotlin.members.forEach { m ->
when (m.name) {
"equals" -> ""
"hashCode" -> ""
"toString" -> ""
else -> {
if (mapMethod.containsKey(m.name)) {
val builder: FunSpec.Builder = FunSpec.builder(m.name)
val mapParams = gson.fromJson<Map<String, String>>(mapMethod[m.name], object : TypeToken<Map<String, String>>() {}.type)
val sb = StringBuilder()
sb.append("val map = mutableMapOf<String, Any>()\n")
mapParams?.forEach {
sb.append("map["${it.key}"]=${it.key}\n")
when (it.value) {
"String" -> {
builder.addParameter(it.key, String::class.java)//参数名,参数类型
}
"Int" -> {
builder.addParameter(it.key, Int::class.java)//参数名,参数类型
}
"Long" -> {
builder.addParameter(it.key, Long::class.java)//参数名,参数类型
}
"Double" -> {
builder.addParameter(it.key, Double::class.java)//参数名,参数类型
}
"Float" -> {
builder.addParameter(it.key, Float::class.java)//参数名,参数类型
}
"Boolean" -> {
builder.addParameter(it.key, Boolean::class.java)//参数名,参数类型
}
"Short" -> {
builder.addParameter(it.key, Short::class.java)//参数名,参数类型
}
// "String" -> {
// builder.addParameter(it.key, String::class.java)//参数名,参数类型
// }
}
}
sb.append("val result = service.${m.name}(com.wx.test.api.RequestBodyCreate.toBody(com.google.gson.Gson().toJson(map)))\n")
sb.append("return result")
builder.addModifiers(KModifier.SUSPEND)
.returns(m.returnType.asTypeName())//获取返回类型
.addStatement(sb.toString())
// .addModifiers(KModifier.OVERRIDE)
funspecs.add(builder.build())
} else {
val builder: FunSpec.Builder = FunSpec.builder(m.name)
val sb = StringBuilder()
sb.append("return service.${m.name}(")
for ((index, p) in m.parameters.withIndex()) {
p.name?.let {
builder.addParameter(it, p.type.asTypeName())//参数名,参数类型
sb.append("${p.name}")
if (index < m.parameters.size - 1)
sb.append(",")
}
}
sb.append(")")
builder.addModifiers(KModifier.SUSPEND)
.returns(m.returnType.asTypeName())//获取返回类型
.addStatement(sb.toString())
// .addModifiers(KModifier.OVERRIDE)
funspecs.add(builder.build())
}
}
}
}
} catch (e: Exception) {
e.printStackTrace()
}
val classNameOrigin = service.interfaceApi
val superClassNameOrigin = service.superClass
val index = classNameOrigin.lastIndexOf('.')
val indexS = superClassNameOrigin.lastIndexOf('.')
val className = classNameOrigin.substring(index + 1 until classNameOrigin.length)
var greeterClass = "${className}Repository";
val superClassName = ClassName(superClassNameOrigin.substring(0 until indexS), superClassNameOrigin.substring(indexS + 1 until superClassNameOrigin.length))
val superInterfaceClassName = ClassName(classNameOrigin.substring(0 until index), className)
val newSuperClassName = superClassName.parameterizedBy(superInterfaceClassName)
val typeSpecClassBuilder = TypeSpec.classBuilder(greeterClass)//类名
.primaryConstructor(//本类默认构造函数
FunSpec.constructorBuilder()
// .addParameter("retrofit", Retrofit::class)//构造函数里面参数
// .addAnnotation(Inject::class.java)//构造函数加注解
.build()
).superclass(newSuperClassName)//继承的父类
// .addSuperclassConstructorParameter("retrofit", Retrofit::class)//父类构造函数参数
// .addSuperinterface(superInterfaceClassName)//父类实现接口
funspecs.forEach {
typeSpecClassBuilder.addFunction(it)
}
val file = FileSpec.builder(packageName, greeterClass)
.addType(
typeSpecClassBuilder.build()
).build()
mFiler?.let { filer -> file.writeTo(filer) }
}
return true
}
private fun log(message: String) {
processingEnv.messager.printMessage(Diagnostic.Kind.NOTE, message)
}
}
八、查看自动生成的 Repository 类,及调用
- build app工程 , 或者点击 app task的 assembleDebug 如下图:
-
查看自动生成的类:
-
查看生成的代码:
kotlin
package com.wx.kotlin_kapt_demo.data_source.kapt
import com.wx.kotlin_kapt_demo.data_source.repository.BaseRepository
import com.wx.test.api.Api
import com.wx.test.api.`data`.BaiduDataBean
import com.wx.test.api.`data`.BaseResponse
import java.util.ArrayList
import kotlin.Int
import kotlin.Long
import kotlin.String
import okhttp3.RequestBody
public class ApiRepository : BaseRepository<Api>() {
public suspend fun get899(
word: String,
queryWord: String,
pn: Int,
gsm: String
): BaseResponse<ArrayList<BaiduDataBean>> = service.get899(word, queryWord, pn, gsm)
public suspend fun register(
username: String,
password: String,
repassword: String
): String = service.register(username, password, repassword)
public suspend fun testPostBody(body: RequestBody): String = service.testPostBody(body)
public suspend fun testPostBody222(ID: Long, name: java.lang.String): String {
val map = mutableMapOf<String, Any>()
map["ID"] = ID
map["name"] = name
val result = service.testPostBody222(com.wx.test.api.RequestBodyCreate.toBody(com.google.gson.Gson().toJson(map)))
return result
}
}
- app 工程中调用:
kotlin
class MainVIewModel : ViewModel() {
private val repository by lazy { ApiRepository() }
val liveDataImg by lazy { MutableLiveData<String>() }
fun requestTest() {
viewModelScope.launch(Dispatchers.IO) {
val result = repository.get899("西游记", "西游记", 1, "")
result?.data?.takeIf {
it.size > 0
}?.let {
liveDataImg.postValue(it[0].middleURL)
}
}
}
}
总结:
- 本文介绍了注解处理器基础用法
- 示例了它在架构中
Repository
类的自动生成 - 示例了
Retrofit
中接口类里面get请求,post请求,post body
的简单自动生成写法 - 示例了注解处理中
KotlinPoet
的应用
特别感谢参考文章
Android高级进阶系列:注解处理器APT用法详解
Kotlin 注解