注解处理器究竟承担了什么作用?
考虑清楚这个问题 对咱们的ksp完成 会有非常大的协助, 否则那一坨注解处理器的完成 你是根本不知道为什么要那样完成,自然ksp的完成你是无从下手的。
首要咱们来看一个典型的例子, 咱们有2个module 一个叫 moduleA 一个叫moduleB , moduleA中的类 想跳转到moudleB中的类 要怎样做? 一般都是
startActivity(B.class)
这就会带来一个问题了, moduleA 要引用到moduleB 中的类, 你只能让moduleA依靠moduleB, 那假如反过来呢?moduleB还得依靠moduleA了, 这种相互间的引用 必定是不可的了,那应该怎样做?
便是参阅Arouter的做法就能够了, 例如moduleB中的B.class 我想对外暴露 ,让别的module中能够跳转到我这个activity ,那就我在B.class中 加一个注解
这是大家最了解的代码了,那么要害的当地就在于, 我的注解处理器 究竟要做什么? 要生成什么样的类,来达到咱们终究的意图。
本质上来说,咱们一个apk,下面必定有多个module, 不论他们的依靠联系怎样,他的编译联系都是确认的,这句话怎样理解?
moduleA 编译今后生成一堆class文件,moduleB 编译今后也生成一堆class文件, 等等。 终究这些class文件 都是在咱们的app moudle 编译时汇总的, 考虑明白这个问题 那就好理解了,
回到前面的例子中,咱们在moduleB中 加了注解,然后咱们能够使用注解处理器 来生成一个类,这个类中保护一个map,这个map的key便是 咱们注解中的path的字符串之,value 则是本身咱们B.class
这样多个module 在app module 中汇总编译的时分 咱们就能够拿到一个巨大的map 这个map中key便是path的值,value 便是方针的class
之后跳转的时分只要在navigation中传递一下path的值,然后根据再到map中寻找到对应的class就能够了。
你看,arouter的注解处理器 是干啥的,咱们就想清楚了吧,便是生成一堆辅佐类,这个辅佐类的终究意图便是帮咱们生成path 和class的对应联系的
理想和实践中的不同
在上一个末节中,咱们基本弄清楚了arouter 注解处理器的作用, 可是仅靠这一节的知识要完全看懂arouter-compiler的代码仍是不够, 由于实践上arouter 的map生成要比 咱们前面一个末节 所说的要杂乱的多。为什么?
你细心考虑一下, 假如是多个module 都使用了route注解,那这些注解的类中的path的值 是不是有可能是重复的?
比方moduleB中 有一个类叫X.class 他的path是 /x1/xc moduleC中 有一个类叫Y.class 他的 path值也是 /x1/xc
这就会导致一个问题了,在app 编译的时分 同样一个path 会对应着2个class,此时跳转就会呈现错误了。
咱们来看下,Arouter中 是怎样规划来处理这个问题的 他首要引入了一个Group的概念, 比方咱们上面的path x1 便是group, 当然你也能够手动指定group ,可是意思都是一样的
首要生成一个名为
Arouter$$Root$$moduleName
的类,这个类继承的是IRouteRoot这个接口
这儿咱们要关注的是moduleName ,咱们在用annotaionProcessor 或许kapt 或许ksp的 这三种注解处理器的时分 都要传递一个参数的
然后再关注下 loadInto 这个办法
这个办法一看便是生成了一个map 对吧, 这个map的key 便是 group的值,而value则是注解处理器生成的一个类 完成了IRouteGroup接口
Arouter$$Group$$group的值
咱们来看一下这个类里边干了啥
这个类也有一个loadInfo 办法
它的key 便是path的值, value 便是RouteMeta目标,留意这个目标中就详细包含了Activity.class了,
所以Arouter 实践上便是把咱们的map给分了级,
首要是使用 moduleName 来生成 IRouteRoot的类 ,这样能够规避不同module之间有抵触的现象 其次是使用 group的概念 再次对路由进行分层, 这样一方面是降低抵触几率,别的一方面,使用group的概念,咱们还能够做路由的懒加载,究竟项目大了今后 一次性加载悉数路由信息也是有成本的,有了group的概念,
咱们就能够依照group的级别来加载了,实践上arouter本身路由加载也是这样做的。
路由使用group分组今后, 默认任何实践路由信息都不会加载, 当每次调用者发起一次路由加载事情时,都会依照group的信息来查找,第一次触发某个group 时,再去加载这个group下面的一切路由信息
ksp的基础完成
首要咱们新建一个module ,命名大家随意,留意这个module的build 文件写法即可
apply plugin: 'java'
apply plugin: 'kotlin'
compileJava {
sourceCompatibility = '1.8'
targetCompatibility = '1.8'
}
sourceSets.main {
java.srcDirs("src/main/java")
}
dependencies {
implementation 'com.alibaba:arouter-annotation:1.0.6'
implementation("com.squareup:kotlinpoet:1.11.0")
implementation("com.squareup:kotlinpoet-ksp:1.11.0")
implementation("com.squareup:kotlinpoet-metadata:1.11.0")
implementation 'com.alibaba:fastjson:1.2.69'
implementation 'org.apache.commons:commons-lang3:3.5'
implementation 'org.apache.commons:commons-collections4:4.1'
implementation("com.google.devtools.ksp:symbol-processing-api:1.6.20-1.0.5")
}
apply from: rootProject.file('gradle/publish.gradle')
其次,去meta-inf 下 新建一个文件,文件名是固定的
com.google.devtools.ksp.processing.SymbolProcessorProvider
里边的内容就简略了,把咱们的ksp注解处理器装备进去即可
com.alibaba.android.arouter.compiler.processor.RouteSymbolProcessorProvider
com.alibaba.android.arouter.compiler.processor.InterceptorSymbolProcessorProvider
com.alibaba.android.arouter.compiler.processor.AutowiredSymbolProcessorProvider
这儿要留意一下,即使是一个纯java代码的module 也能够使用ksp来生成代码的
注解处理器怎样debug?
注解处理器的代码其实还挺晦涩难明的,全靠日志打印很费事,这儿仍是会debug 比较好
略微装备一下即可, 然后打上断点,按下debug开关,rebuild 工程即可触发注解处理器的调试了
使用ksp 注解处理器来生成辅佐类
这儿篇幅有限, 咱们只做辅佐类的生成, 至于辅佐类里边的loadInto办法 咱们暂不做完成,详细的完成咱们留到下一篇文章再说,这一节只做一下 辅佐类生成这个操作
首要咱们来装备一下 使用ksp的module
ksp {
arg("AROUTER_MODULE_NAME", project.getName())
}
ksp project(':arouter-compiler')
然后要留意的是,即使是纯java代码的module 也能够使用ksp来生成代码的, 唯一要留意的是你需求在这个module下 添加
apply plugin: 'kotlin-android'
现在注解处理器也装备好了, 咱们就能够干活了。
先放一个基础类就行
class RouteSymbolProcessorProvider : SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
return RouteSymbolProcessor(environment.options, environment.logger, environment.codeGenerator)
}
}
第一步,咱们要取出moduleName,这个东西的作用前面现已介绍过了,
val moduleName = options[KEY_MODULE_NAME]
第二步,咱们要取出项目中 使用Route注解的类,拿到这些类的信息
// 取出来 使用route注解的
val symbols = resolver.getSymbolsWithAnnotation(Route::class.qualifiedName!!)
// 先取出来 有哪些 类用了Route注解
val elements = symbols.filterIsInstance<KSClassDeclaration>().toList()
第三步, 也是最要害的一步,咱们要取出Route 中的要害信息作一个map,key是group,value是path的list
其实也便是一个group 下面对应的一切path信息
这儿有几个要害点, 要把Route中的path和group的值 都提取出来, 假如没有指定group 则 path的第一段作为group的值
别的便是在取的时分 要判断一下 这个element的注解是不是Route注解, 由于一个类能够有多个注解,咱们要取特定的Route注解 才能取到咱们想要的值
要害代码
val map = mutableMapOf<String, List<String>>()
elements.forEach {
it.annotations.toList().forEach { ks ->
// 避免多个注解的情况
if (ks.shortName.asString() == "Route") {
var path = ""
var group = ""
ks.arguments.forEach { ksValueA ->
if (ksValueA.name?.asString() == "path") {
path = ksValueA.value as String
}
if (ksValueA.name?.asString() == "group") {
group = ksValueA.value as String
}
}
// 假如没有装备group 则去path中取
if (group.isEmpty()) {
group = path.split("/")[1]
}
if (map.contains(group)) {
map[group] = map[group]!!.plus(path)
} else {
map[group] = listOf(path)
}
}
}
}
第四步,咱们生成IRouteRoot 辅佐类
这儿有一个难点 便是 怎样写这个办法参数的类型
看下详细代码 怎样来处理这个问题
private fun String.quantifyNameToClassName(): com.squareup.kotlinpoet.ClassName {
val index = lastIndexOf(".")
return com.squareup.kotlinpoet.ClassName(substring(0, index), substring(index + 1, length))
}
// IRouteRoot 这个接口 办法参数的界说 MutableMap<String, Class<out IRouteGroup>>?
val parameterSpec = ParameterSpec.builder(
"routes",
MUTABLE_MAP.parameterizedBy(
String::class.asClassName(),
Class::class.asClassName().parameterizedBy(
WildcardTypeName.producerOf(
Consts.IROUTE_GROUP.quantifyNameToClassName()
)
)
).copy(nullable = true)
).build()
参数的这个问题处理掉今后 就很简略了
直接依照姓名规矩 生成一下 类即可
val rootClassName = "ARouter$$Root$$$moduleName"
val packageName = "com.alibaba.android.arouter"
val file = FileSpec.builder("$packageName.routes", rootClassName)
.addType(
TypeSpec.classBuilder(rootClassName).addSuperinterface(
com.squareup.kotlinpoet.ClassName(
"com.alibaba.android.arouter.facade.template",
"IRouteRoot"
)
).addFunction(
FunSpec.builder("loadInto").addModifiers(KModifier.OVERRIDE)
.addParameter(parameterSpec)
.addStatement("TODO()").build()
).build()
)
.build()
file.writeTo(codeGen, false)
最终一步, 咱们要生成IRrouteGroup的辅佐类,里边放入对应path的信息
这儿path的信息 我用注释表示下即可,
// 生成group 辅佐类
map.forEach { (key, value) ->
val rootClassName = "ARouter$$Group$$$key"
// IRouteGroup 这个接口 办法参数的界说 MutableMap<String,RouteMeta>?
val parameterSpec = ParameterSpec.builder(
"atlas",
MUTABLE_MAP.parameterizedBy(
String::class.asClassName(),
RouteMeta::class.asClassName()
).copy(nullable = true)
).build()
val packageName = "com.alibaba.android.arouter"
// val rootClass = com.squareup.kotlinpoet.ClassName("", rootClassName)
val file = FileSpec.builder("$packageName.routes", rootClassName)
.addType(
TypeSpec.classBuilder(rootClassName).addSuperinterface(
com.squareup.kotlinpoet.ClassName(
"com.alibaba.android.arouter.facade.template",
"IRouteGroup"
)
).addFunction(
FunSpec.builder("loadInto").addModifiers(KModifier.OVERRIDE)
.addParameter(parameterSpec)
.addComment("path: $value")
.addStatement("TODO()").build()
).build()
)
.build()
file.writeTo(codeGen, false)
}
最终看下完成作用:
对应的辅佐类 应该是都生成了:
path的信息:










