IRouteRoot的loadInto方法体内部逻辑
首先可以看下,arouter 之前的loadInto 方法里面 都包含了哪些因素
其实 归纳起来也不难,无非就是往一个map里面 put了 我们之前生成的group类而已。
key是 group的名称。
这个逻辑还是相对而言比较简单的, 我们这里唯一要注意的是 如何利用kotlinpoet 来生成对应的
xxx.class 这样的参数,这是唯一的一个难点
/**
* 生成Root类的loadInto方法
*/
private fun genLoadIntoFunSpecForRouteRoot(groupKeys: Set<String>): FunSpec {
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 funSpec = FunSpec.builder("loadInto").addModifiers(KModifier.OVERRIDE)
.addParameter(parameterSpec)
// 增加判空语句
funSpec.addStatement(
"""
if(routes==null) {return}
""".trimMargin()
)
// 增加 loadInto方法内部的put语句
groupKeys.forEach {
// 最重要的就是这里的写法, 注意 是ClassName 这个写法
funSpec.addStatement(
"routes.put(%S,%T::class.java)",
it,
ClassName(Consts.PACKAGE_OF_GENERATE_FILE, Consts.NAME_OF_GROUP + it)
)
}
return funSpec.build()
}
改完以后看一下 生成的kotlin 类长啥样
对比一下 java的写法 应该说 一模一样的了
IRouteGroup的loadInto方法
先看一下Group的loadinto 方法 都长啥样
我们可以看出来 这里的难点 在于 如何用Kotlinpoet 来生成RouteMeta的build语句
注意了这个build语句 复杂的其实是他的参数,关于参数 我们可以慢慢来,我们首先 关注好理解的参数
比如RouteType的这个参数,这个很好理解,毕竟你要知道 你这个注解是基于activity 还是fragment还是service,
第二个参数 对应activtiy的class ,这个也好理解,
第三个参数 path和第四个参数 分别是path 和group 这个也容易,
最后3个参数 paramsType priority extra 我们先跳过,这些不重要, 我们先实现好实现的,不好实现的 后面再补
这里最难的其实就是使用Route的注解的类 到底属于哪种类型?
我们首先看一下 RouteType中 总共定义了几种类型
其中我们主要关注的,使用过的其实就是4种, 分别是 activity,service,provider,fragment,
我们可以先定义一个固定的集合
val ROUTE_TYPE_LIST = setOf(
Consts.ACTIVITY,
Consts.FRAGMENT,
Consts.FRAGMENT_V4,
Consts.SERVICE,
Consts.IPROVIDER
)
这里唯一要注意的是 fragemnt 除了 android 本身的fragment以外,还有androidx的fragment, 你们如果 arouter没有做androidx改造 那这里应该还是support的fragemnt
剩下的就是想办法来找到 注解的类 到底是哪个类型,注意一个Class 他可以属于多种类型,这个可以理解吧, 比如一个Class Test,他可以继承多个接口,他的父类因为有层级关系 所以理论上也可以有多个父类, 譬如AppCompatActivity-FragmentActivity 等等
我们先写一段代码 把我们类的 父级关系都打印出来看看,看看我们的代码对不对
fun KSClassDeclaration.isSubclassOf(logger: KSPLogger): Int {
val superClass = superTypes.toMutableList()
val nameSets = mutableSetOf<String>()
// 如果没有super 就返回
if (superClass.isNullOrEmpty()) {
return -1
}
// 查看super的类型
var declaration = superClass.first().resolve().declaration
while (declaration is KSClassDeclaration) {
val name = declaration.qualifiedName?.asString() ?: ""
nameSets.add(name)
// 如果还有父类 就继续找下去
val typeList = declaration.superTypes.toList()
if (typeList.isNotEmpty()) {
declaration = typeList.first().resolve().declaration
} else {
break
}
}
logger.warn("${qualifiedName?.asString()} type is $nameSets")
return -1
}
可以看一下 执行效果:
这里应该能看出来了,我们是拿到了我们想要的结果的, 但是这里有细节要注意了,有人会问 你为什么要使用superClass的first()。这个superClass 什么情况下会有多个呢?为啥你这里一定要用first?
我们可以稍微改一下代码:
让这个函数 既有父类,也有接口的实现
然后我们打印一下他的size:
logger.warn("${qualifiedName?.asString()} has more superClass : ${superClass.size}")
这里明确能看到这个size就是3了,至于为啥是3,应该不用说了吧
对于既有父类,又有接口的情况, 这个superclass的size 一定是大于1的,
比如说 我们之前的代码 对SingerService的检测 看起来是没问题的:
但是如果我改一下代码:
他还能检测到这个service是一个IProvider类型的嘛?
显然这里是不能的!
所以为了保险,我们对superClass的检测还是要依托于循环,这样是最保险的
fun KSClassDeclaration.isSubclassOf(logger: KSPLogger): Int {
val superClass = superTypes.toMutableList()
val nameSets = mutableSetOf<String>()
// 如果没有super 就返回
if (superClass.isNullOrEmpty()) {
return -1
}
superClass.forEach {
// 查看super的类型
var declaration = it.resolve().declaration
while (declaration is KSClassDeclaration) {
val name = declaration.qualifiedName?.asString() ?: ""
nameSets.add(name)
// 如果还有父类 就继续找下去
val typeList = declaration.superTypes.toList()
if (typeList.isNotEmpty()) {
declaration = typeList.first().resolve().declaration
} else {
break
}
}
}
logger.warn("${qualifiedName?.asString()} type is $nameSets")
return -1
}
再看下结果:
这一次 我们就真正的拿到了我们想要的结果。
剩下的就简单了吧,我们只要判断一下这个类到底属于哪种类型就行了吧,这里有个技巧,因为我们多数的activity 是不会直接继承Acitivity这个类的,所以我们检测的list 可以多写一个activity,增加命中率, 避免多次循环
val ROUTE_TYPE_LIST = listOf(
Consts.ACTIVITY,
"androidx.appcompat.app.AppCompatActivity",
"androidx.core.app.ComponentActivity",
"androidx.fragment.app.FragmentActivity",
Consts.FRAGMENT,
Consts.FRAGMENT_V4,
Consts.SERVICE,
Consts.IPROVIDER
)
剩下的就是改造一下之前的函数,如果判断属于 这个list中的类型那就直接return 跳出 返回结果就行了
fun KSClassDeclaration.getRouteType(logger: KSPLogger): RouteType {
return when (isSubclassOf(ROUTE_TYPE_LIST, logger)) {
0, 1, 2, 3 -> RouteType.ACTIVITY
4, 5 -> RouteType.FRAGMENT
6 -> RouteType.SERVICE
7 -> RouteType.PROVIDER
else -> RouteType.UNKNOWN
}
}
fun KSClassDeclaration.isSubclassOf(keyList: List<String>, logger: KSPLogger): Int {
val superClass = superTypes.toMutableList()
// 如果没有super 就返回
if (superClass.isNullOrEmpty()) {
return -1
}
superClass.forEach {
// 查看super的类型
var declaration = it.resolve().declaration
while (declaration is KSClassDeclaration) {
val name = declaration.qualifiedName?.asString() ?: ""
val index = keyList.indexOf(name)
// 如果找到了属于keylist中的类型 那么就直接返回key
if (index != -1) {
logger.warn("${qualifiedName?.asString()} belongs list index:$index")
return index
}
// 如果还有父类 就继续找下去
val typeList = declaration.superTypes.toList()
if (typeList.isNotEmpty()) {
declaration = typeList.first().resolve().declaration
} else {
break
}
}
}
return -1
}
到这里我们最难的一个点就解决了,剩下的一个难点就是 如何生成RouteMeta 语句,以及如何在build语句中 添加我们对应的::class.java 参数
先理清一下思路 我们首先要收集足够多的RouteMeta信息
可以定义一个辅助类,来记录信息
data class RouteMetaInfo(
val elementClassName: ClassName,
val path: String,
val type: RouteType,
val group: String
)
然后在遍历element的时候 构造出一个map,key 就是group ,value就是对应的RouteMetaInfo的list
val mataMap = mutableMapOf<String, List<RouteMetaInfo>>()
最后就是最关键的如何生成loadInto方法的 方法体了
最关键还是关注下addStateMent的 写法
private fun genLoadIntoFunSpecForRouteGroup(group: String, pathList: List<String>, metaMap: Map<String, List<RouteMetaInfo>>): FunSpec {
val parameterSpec = ParameterSpec.builder(
"atlas",
MUTABLE_MAP.parameterizedBy(
String::class.asClassName(),
RouteMeta::class.asClassName()
).copy(nullable = true)
).build()
val funSpec = FunSpec.builder("loadInto").addModifiers(KModifier.OVERRIDE)
.addParameter(parameterSpec)
funSpec.addCode(
"""
if(atlas==null) {return}
""".trimIndent()
)
pathList.forEach { path ->
val metaInfo = metaMap[group]?.first { info ->
info.path == path && info.group == group
}
funSpec.addStatement(
"atlas.put(%S, %T.build(%T.${metaInfo!!.type}, %T::class.java, %S, %S, null,-1,-1))",
path,
RouteMeta::class,
RouteType::class,
metaInfo.elementClassName,
path,
group
)
}
return funSpec.build()
}
全部写完以后就可以看下效果了:
到这一步 我们的ksp 注解处理器就已经 初步完成了, 已经可以走通arouter项目中的测试用例了, 基本完成界面跳转之类的效果