官方
[抽丝剥茧带你探索 TheRouterTheRouter是货拉拉开源的路由库,也应用在货拉拉的产品中:如小拉出行用户端、司 - 掘金](juejin.cn/post/717125…
therouter: TheRouter 是一个 Kotlin 编写,由货拉拉技术开源的,用于 Android 模块化开发的一整套解决方案框架
Navigator · HuolalaTech/hll-wp-therouter-android Wiki
官方文档从实际解耦和的需求问题着手,直接从核心源码讲起实现原理。APT+ASM插桩技术。
therouter.gradle
- 参与编译模块的控制
- 注解处理器类型控制
- AGP版本兼容控制
- gradle版本控制
- 插件对外提供(groupid:id:version)
apt模块
- 源码生成方式:
- 直接用PrintStream字符串拼接方式生成代码。(通常还会有其他一些方式)
- 注解处理的两种实现:
- apt实现:TheRouterAnnotationProcessor
- ksp实现:TheRouterSymbolProcessor
- 在工程根目录下build.gradle切换成ksp
- classpath "com.google.devtools.ksp:symbol-processing-gradle-plugin:xxx"
- therouter.gradle通过USE_KAPT属性控制切换
plugin模块
业务看官方文档,在Transform阶段触发,主要是收集APT阶段生成的类信息,通过ASM方式注入代码到那个核心jar的核心类TheRouterServiceProvideInjecter中。ASM单独起了一篇
- AGP8.x与AGP7.x的兼容处理:TheRouterPlugin>AGP8Plugin>Plugin通过继承以及版本号比较向下兼容
- AGP7.x Transform方式实现:AGP7的registerTransform(标记废弃)以及TheRouterTransform>Transform(标记废弃)
- AGP8.x DefaultTask方式实现:AndroidComponentsExtension方式的任务TheRouterTask>Task
- gradle版本与AGP版本区分:
- 前边两种api方式其实AGP7.x都支持,只不过一种是废弃的,后一种也不是AGP8.x才有的。
- 为什么总是有那么多项目gradle与AGP以及jdk还有AS的版本要对应?从这里能够看到一点端倪。
生成代码的常见方式
除了使用 PrintStream
直接生成代码外,在 Kotlin 或 Java 项目里,还有多种常见的代码生成方式,下面为你详细介绍:
1. JavaPoet
JavaPoet 是 Square 公司开源的一个用于生成 Java 源代码的库,它提供了简洁的 API 来构建类、方法、字段等,支持类型安全和代码格式化。
添加依赖
在 build.gradle
中添加依赖:
dependencies {
implementation 'com.squareup:javapoet:1.13.0'
}
示例代码
import com.squareup.javapoet.*
import javax.lang.model.element.Modifier
fun main() {
// 创建一个方法
val method = MethodSpec.methodBuilder("helloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.returns(String::class.java)
.addStatement("return $S", "Hello, World!")
.build()
// 创建一个类
val type = TypeSpec.classBuilder("HelloWorld")
.addModifiers(Modifier.PUBLIC, Modifier.FINAL)
.addMethod(method)
.build()
// 创建一个 Java 文件
val javaFile = JavaFile.builder("com.example", type)
.build()
// 输出 Java 代码
javaFile.writeTo(System.out)
}
2. KotlinPoet
KotlinPoet 是 JavaPoet 的 Kotlin 版本,用于生成 Kotlin 源代码,使用方式和 JavaPoet 类似,能很好地与 Kotlin 项目集成。
添加依赖
在 build.gradle
中添加依赖:
dependencies {
implementation 'com.squareup:kotlinpoet:1.13.0'
}
示例代码
import com.squareup.kotlinpoet.*
fun main() {
// 创建一个函数
val function = FunSpec.builder("helloWorld")
.returns(String::class)
.addStatement("return %S", "Hello, Kotlin!")
.build()
// 创建一个 Kotlin 文件
val kotlinFile = KotlinFile.builder("com.example", "HelloKotlin")
.addFunction(function)
.build()
// 输出 Kotlin 代码
kotlinFile.writeTo(System.out)
}
3. 模板引擎
模板引擎能让你通过预定义的模板文件生成代码,常用的模板引擎有 Thymeleaf、Freemarker 和 Velocity 等。
Thymeleaf 示例
添加依赖:
dependencies {
implementation 'org.thymeleaf:thymeleaf:3.1.2.RELEASE'
}
创建模板文件 HelloWorldTemplate.java
:
java
package com.example;
public class HelloWorld {
public String helloWorld() {
return "$message";
}
}
生成代码:
import org.thymeleaf.TemplateEngine
import org.thymeleaf.context.Context
import org.thymeleaf.templatemode.TemplateMode
import org.thymeleaf.templateresolver.ClassLoaderTemplateResolver
fun main() {
val templateResolver = ClassLoaderTemplateResolver()
templateResolver.prefix = "templates/"
templateResolver.suffix = ".java"
templateResolver.templateMode = TemplateMode.TEXT
templateResolver.characterEncoding = "UTF-8"
val templateEngine = TemplateEngine()
templateEngine.templateResolver = templateResolver
val context = Context()
context.setVariable("message", "Hello, Thymeleaf!")
val result = templateEngine.process("HelloWorldTemplate", context)
println(result)
}
4. 手动字符串拼接
这是最基础的方式,直接使用字符串拼接来生成代码,不过代码可读性和可维护性较差,仅适用于简单场景。
fun generateCode(): String {
return """
package com.example;
public class HelloWorld {
public String helloWorld() {
return "Hello, Manual!";
}
}
""".trimIndent()
}
fun main() {
val code = generateCode()
println(code)
}
以上几种方法各有优缺点,你可以依据项目需求和个人喜好来选择合适的代码生成方式。
注解处理器
下面分别介绍 APT 与 KSP 插件的编写方法,以及如何实现二者混合使用并可切换。
APT 插件编写
1. 创建注解
首先定义需要处理的注解,例如:
package com.therouter.annotations
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.CLASS)
annotation class MyAnnotation
2. 创建注解处理器
继承 AbstractProcessor
类,实现注解处理逻辑:
package com.therouter.apt
import javax.annotation.processing.AbstractProcessor
import javax.annotation.processing.RoundEnvironment
import javax.annotation.processing.SupportedAnnotationTypes
import javax.annotation.processing.SupportedSourceVersion
import javax.lang.model.SourceVersion
import javax.lang.model.element.TypeElement
import com.therouter.annotations.MyAnnotation
@SupportedAnnotationTypes("com.therouter.annotations.MyAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_17)
class MyAnnotationProcessor : AbstractProcessor() {
override fun process(annotations: MutableSet<out TypeElement>?, roundEnv: RoundEnvironment): Boolean {
for (element in roundEnv.getElementsAnnotatedWith(MyAnnotation::class.java)) {
// 处理注解逻辑
processingEnv.messager.printMessage(javax.tools.Diagnostic.Kind.NOTE, "Processing ${element.simpleName}")
}
return true
}
}
3. 注册注解处理器
在 resources/META-INF/services
目录下创建 javax.annotation.processing.Processor
文件,内容如下:
hll-wp-therouter-android\apt\src\main\resources\META-INF\services\javax.annotation.processing.Processor
com.therouter.apt.MyAnnotationProcessor
KSP 插件编写
1. 添加 KSP 依赖
在模块的 build.gradle
中添加 KSP 依赖:
plugins {
id 'com.google.devtools.ksp'
}
dependencies {
ksp project(':ksp-processor')
}
2. 创建 KSP 处理器
实现 SymbolProcessor
接口:
package com.therouter.ksp
import com.google.devtools.ksp.processing.*
import com.google.devtools.ksp.symbol.KSAnnotated
import com.google.devtools.ksp.symbol.KSClassDeclaration
import com.therouter.annotations.MyAnnotation
class MyKspProcessor(
private val codeGenerator: CodeGenerator,
private val logger: KSPLogger
) : SymbolProcessor {
override fun process(resolver: Resolver): List<KSAnnotated> {
val symbols = resolver.getSymbolsWithAnnotation(MyAnnotation::class.qualifiedName!!)
symbols.filterIsInstance<KSClassDeclaration>().forEach { classDeclaration ->
logger.logging("Processing ${classDeclaration.simpleName.asString()}")
}
return emptyList()
}
}
class MyKspProcessorProvider : SymbolProcessorProvider {
override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
return MyKspProcessor(environment.codeGenerator, environment.logger)
}
}
3. 注册 KSP 处理器
在 resources/META-INF/services
目录下创建 com.google.devtools.ksp.processing.SymbolProcessorProvider
文件,内容如下:
src\main\resources\META-INF\services\com.google.devtools.ksp.processing.SymbolProcessorProvider
com.therouter.ksp.MyKspProcessorProvider
实现 APT 与 KSP 混合使用并可切换
1. 使用构建变量控制
在 build.gradle
中定义一个变量来控制使用 APT 还是 KSP:
android {
defaultConfig {
buildConfigField "boolean", "USE_KSP", "false"
}
}
if (BuildConfig.USE_KSP) {
plugins {
id 'com.google.devtools.ksp'
}
dependencies {
ksp project(':ksp-processor')
}
} else {
dependencies {
annotationProcessor project(':apt-processor')
}
}
2. 统一接口设计
为 APT 和 KSP 处理器设计统一的接口,方便在代码中调用:
package com.therouter.common
interface AnnotationProcessorInterface {
fun process()
}
3. 分别实现接口
在 APT 和 KSP 处理器中实现该接口:
class MyAnnotationProcessor : AbstractProcessor(), AnnotationProcessorInterface {
override fun process() {
// APT 处理逻辑
}
}
class MyKspProcessor : SymbolProcessor, AnnotationProcessorInterface {
override fun process() {
// KSP 处理逻辑
}
}
通过以上步骤,你可以分别编写 APT 和 KSP 插件,并且实现二者混合使用并可切换。
两种注解处理器的对比
APT(Annotation Processing Tool)和 KSP(Kotlin Symbol Processing)都是用于处理注解的工具,不过它们在多个方面存在差异,下面从不同维度详细介绍:
1. 出现背景与适用语言
- APT:是 Java 官方提供的注解处理工具,从 Java 5 开始引入。它主要用于处理 Java 代码里的注解,不过也能兼容 Kotlin 代码,只是在处理 Kotlin 代码时存在一些限制。
- KSP:由 Google 开发,专门为 Kotlin 设计的注解处理工具。它对 Kotlin 语言特性有更好的支持,能充分发挥 Kotlin 的优势。
2. 性能表现
- APT:基于 Java 编译器的抽象语法树(AST)进行处理,处理过程中会生成完整的 Java 字节码,这导致其处理速度较慢,尤其是在大型项目中,编译时间会显著增加。
- KSP:采用轻量级的符号处理机制,只分析必要的符号信息,不会生成完整的字节码,因此处理速度更快,能有效缩短编译时间。有测试表明,KSP 在某些项目中的编译速度比 APT 快数倍。
3. 对 Kotlin 特性的支持
- APT:由于是为 Java 设计的,对 Kotlin 特有的语法和特性(如协程、数据类、扩展函数等)支持不够完善,在处理这些特性时可能会出现问题或需要额外的处理逻辑。
- KSP:专门为 Kotlin 设计,能很好地支持 Kotlin 的各种语法和特性,开发者可以更方便地处理 Kotlin 代码中的注解。
4. API 设计
- APT:API 基于 Java 语言设计,相对复杂,学习成本较高。开发者需要熟悉 Java 编译器的相关概念和 API 才能进行注解处理开发。
- KSP:API 设计简洁,符合 Kotlin 的编程习惯,易于上手。它提供了更直观的符号访问和操作接口,降低了开发难度。
5. 代码生成方式
- APT:通常使用 JavaPoet 等库来生成 Java 代码,在生成 Kotlin 代码时需要额外的配置和处理。
- KSP:可以直接生成 Kotlin 代码,并且支持使用 KotlinPoet 库,与 Kotlin 项目的集成更加自然。
6. 错误处理
- APT:错误信息有时不够明确,尤其是在处理复杂注解和大型项目时,排查问题相对困难。
- KSP:提供了更详细、更友好的错误信息,有助于开发者快速定位和解决问题。
示例对比
APT 示例
import javax.annotation.processing.AbstractProcessor
import javax.annotation.processing.RoundEnvironment
import javax.annotation.processing.SupportedAnnotationTypes
import javax.annotation.processing.SupportedSourceVersion
import javax.lang.model.SourceVersion
import javax.lang.model.element.TypeElement
@SupportedAnnotationTypes("com.therouter.annotations.MyAnnotation")
@SupportedSourceVersion(SourceVersion.RELEASE_17)
class MyAnnotationProcessor : AbstractProcessor() {
override fun process(annotations: MutableSet<out TypeElement>?, roundEnv: RoundEnvironment): Boolean {
// 处理注解逻辑
return true
}
}
KSP 示例
import com.google.devtools.ksp.processing.*
import com.google.devtools.ksp.symbol.KSAnnotated
import com.therouter.annotations.MyAnnotation
class MyKspProcessor(
private val codeGenerator: CodeGenerator,
private val logger: KSPLogger
) : SymbolProcessor {
override fun process(resolver: Resolver): List<KSAnnotated> {
val symbols = resolver.getSymbolsWithAnnotation(MyAnnotation::class.qualifiedName!!)
// 处理注解逻辑
return emptyList()
}
}
综上所述,若项目以 Java 为主,且对性能要求不是特别高,可继续使用 APT;若项目是 Kotlin 项目,或者对编译性能有较高要求,建议使用 KSP。