AGP 8.0 路由框架新思路
本文作者
作者:Aleyn
链接:
https://juejin.cn/post/7374677514536812571
本文由作者授权发布。
前言
AGP 8.0以后移除了 Transform API。官方文档也给出了替代的方式 AsmClassVisitorFactory,但是这种方式我以前的文章也有说过,只适合对已知的类做插桩或者转换。像路由框架是需要对整个项目的类进行扫描的,遍历完成之后拿到了类信息,再进行插桩。所以只能自定义 Task 来实现。这里是自定义Tssk官方例子。
https://github.com/android/gradle-recipes/blob/agp-8.4/transformAllClasses/build-logic/plugins/src/main/kotlin/ModifyClassesTask.kt
其中有一天,就因为频繁运行项目测试,这个编译慢的问题拖到我晚上10 点才搞完需求。然后10 点下班去骑我的风驰电掣的小电摩,结果电池还被人给偷了 ............................................................。
AGPBI: {"kind":"warning","text":"Expected stack map table for method with non-linear control flow.","sources":[{"file":"D:\\Android\\Project\\PicMe\\app\\build\\intermediates\\classes\\devGoogleDebug\\ALL\\classes.jar"}],"tool":"D8"}
字节dexBuilder优化
https://juejin.cn/post/6854573211548385294?searchId=20240530153818D983C10C7C2F3A451665#heading-23
得物优化
https://mp.weixin.qq.com/mp/wappoc_appmsgcaptcha?poc_token=HOVnXWajz5k5DMUq95D-ak5rbekO-4QzxgurvrV3&target_url=https%3A%2F%2Fmp.weixin.qq.com%2Fs%2F414nz4T-_KyH42xo_mdj-w%3F
jar 输入相比于 目录输入来说增量编译效果非常差,那么可以想到 hook TransformInvocation 中的 input 方法,动态将 project 的 jar 类型输入(JarInput)映射为一个 目录输入(DirectoryInput),那么子模块修改对应代码时,只重新编译目录中被修改的 class 为 dex(而不是原来的整个 jar 内所有 class 重新执行 dex 编译),整体 dex 重新编译的数量将大幅度减少。
这种方案,适合体量非常大的项目,是要入侵到 AGP 的编译流程的。目前只是写一个路由插件,如果要这样搞,成本太高了。只能换其他思路。
variant.artifacts
.forScope(ScopedArtifacts.Scope.ALL)
.use(taskProvider)
.toTransform(
ScopedArtifact.CLASSES,
LRouterClassTask::allJars,
LRouterClassTask::allDirectories,
LRouterClassTask::output
)
variant.artifacts
.forScope(ScopedArtifacts.Scope.PROJECT)
.use(taskProvider)
.toTransform(
ScopedArtifact.CLASSES,
LRouterClassTask::allJars,
LRouterClassTask::allDirectories,
LRouterClassTask::output
)
这条 Issues 下边xiaoyvyv 提供的修改建议。就是以上的思路。
https://github.com/aleyn97/router/issues/6
改完之后,编译速度提升很多,修改代码只会影响当前Project。
variant.instrumentation.transformClassesWith(
LRouterAsmClassVisitor::class.java,
InstrumentationScope.PROJECT
) {}
新创建LRouterAsmClassVisitor 类, 对所有Project 都进行注册。然后在 createClassVisitor 方法里通过 ClassVisitor 来把类信息写到缓存文件中去。
androidComponents.onVariants { variant ->
// ......
val generatedDir = "generated/ksp/" // ksp 生成目录
variant.instrumentation.transformClassesWith(
LRouterAsmClassVisitor::class.java,
InstrumentationScope.PROJECT
) { param ->
param.genDirName.set(generatedDir) // 目录名称参数
val list = project.rootProject.subprojects.plus(project)
.map { it.layout.buildDirectory.dir(generatedDir).get() } // 过滤所有 KSP 生成目录
param.inputFiles.set(list) // 设置所有子模块和主模块的生成目录
}
variant.instrumentation.setAsmFramesComputationMode(FramesComputationMode.COPY_FRAMES)
variant.instrumentation.excludes.addAll(
"androidx/**",
"android/**",
"com/google/**",
)
}
internal const val GENERATE_INJECT = "com.router.LRouterGenerateImpl" // 待插桩类
abstract class LRouterAsmClassVisitor : AsmClassVisitorFactory<ParametersImpl> {
override fun createClassVisitor(
classContext: ClassContext,
nextClassVisitor: ClassVisitor
): ClassVisitor {
if (classContext.currentClassData.className == GENERATE_INJECT) {
val inputFiles = parameters.get().inputFiles.get() //取出所有 KSP 生成目录
val genDirName = parameters.get().genDirName.get()
return InsertCodeVisitor(nextClassVisitor, inputFiles, genDirName)// 插桩操作
}
return nextClassVisitor
}
override fun isInstrumentable(classData: ClassData): Boolean {
return classData.className == "com.router.LRouterGenerateImpl"
}
}
interface ParametersImpl : InstrumentationParameters {
@get:Internal
val genDirName: Property<String>
@get:Internal
val inputFiles: ListProperty<Directory>
}
InsertCodeVisitor 类的代码不贴了,有点多,点链接进去看吧。https://github.com/aleyn97/router/blob/main/plugin/src/main/java/com/aleyn/router/plug/visitor/InsertCodeVisitor.kt
最后推荐一下我做的网站,玩Android: wanandroid.com ,包含详尽的知识体系、好用的工具,还有本公众号文章合集,欢迎体验和收藏!
推荐阅读:
扫一扫 关注我的公众号
如果你想要跟大家分享你的文章,欢迎投稿~
┏(^0^)┛明天见!