我正在参加「掘金·启航计划」
1、背景
近期在探索项目端架构升级,遇到不少历史遗留问题,比较典型的就是模块耦合、动态路由配置表冗余。同时,也在探索如何使用智能策略提供更好的基础体验,用好的技术方案做一些既要又要还要(老板们都是这么想)的事情。
无意看到了 TheRouter
,发现它的设计思路能解决目前项目所面临的问题,作者对端侧动态能力的发展方向上与我的思路不谋而合(端+云+智能策略),看完还是有点小兴奋。
于是,立马把代码拉下来看看,结果异常了~
2、问题
在拉取master
分支并运行后,发现如下问题:
3、分析
3-1 表面原因
从错误提示上看异常类在/build/generated/source/kapt/debug/
文件中,说明出问题的类是kapt
自动生成的代码。
再看看Test3#createTest5
这个函数实现,基本能确定:kapt
自动生成的类,在调用Test3#createTest5
时少了一个参数。
3-2 APT 逻辑
1、首先需要找到生成问题类的函数, 通过查找确定TheRouterAnnotationProcessor#genJavaFile
中生存了问题类(以下逻辑有删减):
private fun genJavaFile(
pageList: ArrayList<ServiceProviderItem>,
flowTaskList: MutableList<FlowTaskItem>,
actionInterceptorList: MutableList<ActionInterceptorItem>
) {
var ps: PrintStream? = null
try {
......
ps.println(") {")
ps.println("\t\t\t//type verification during compilation prevents the actual return type of the method from mismatching with the return type declared by the annotation")
if (serviceProviderItem.isMethod) {
ps.print(String.format("\t\t\t%s returnType = %s.%s(", serviceProviderItem.returnType,serviceProviderItem.className,serviceProviderItem.methodName))
}
......
for (count in serviceProviderItem.params.indices) {
if (!serviceProviderItem.params[count].trim { it <= ' ' }.isEmpty()) {
//参数强转
ps.print(String.format(Locale.getDefault(),"(%s) params[%d]",serviceProviderItem.params[count],count))
if (count != serviceProviderItem.params.size - 1) {
ps.print(", ")
}
}
}
ps.println(");")
ps.println("\t\t\tobj = (T) returnType;")
ps.print("\t\t} else ")
}
...
}
从上述逻辑可以了解到:serviceProviderItem.params
就是最终的参数表。
2、再断点调试发现createTest5
的参数理论上有两个:String
和 Context
,然而serviceProviderItem.params
中却只有一个, 所以问题基本能定位是serviceProviderItem.params
的赋值问题了。
3、serviceProviderItem.params
是在这个函数TheRouterAnnotationProcessor#handleMethodServiceProviderItem
中赋值的,通过解析注解中的信息赋值给 params 。
按照代码逻辑,这里 temp
的值应该是“@com.therouter.inject.ServiceProvider(params=java.lang.String,android.content.Context”
但是这里实际值是"@com.therouter.inject.ServiceProvider(params=java.lang.String"
,所以这里看起来是本地代码与项目代码不一致。
再看 commit 记录这个问题在之前涛哥有专门修过,少了一个空格符。
所以本地代码应该是 OK 的。
4、问题解决
1、把 apt 模块改成本地依赖再编译确实可以了。
问题是解决了,但是看注解的解析过程还是比较费劲且不太优雅。
5、优化一下
1、再看看解析上能怎么能优化一下,梳理一下TheRouterAnnotationProcessor#handleMethodServiceProviderItem
的核心逻辑。
使用了比较多index
和length
,解析逻辑相对复杂不太好理解。
2、回到要解析的字符串———“@com.therouter.inject.ServiceProvider(params=java.lang.String,android.content.Context,returnType=com.therouter.inject.ServiceProvider)”
,其实可以用正则直接找出对应的 Key-Value 赋值即可,可以增加代码可读性,同时解析逻辑复用度也相对高些:
6、提个PR
7、最后一点建议
最后,给 TheRouter 所提的动态路由能力提点建议:动态路由除了关注动态能力外,前期也建议做好配置热度跟踪。随着业务的迭代,如果没有对应的热度统计,后期大量的冗余配置会对配置的运行加载稳定性造成负面影响,同时过大的路由表会延长匹配时间进而影响页面打开速度等。同时,路由本身可以理解为APP运行的中枢神经,随着配置的增多,后期迁移、改动或替换的风险较高,所以应想办法在前期做好统计管理,避免配置冗余。