TheRouter代码学习

209 阅读6分钟

官方

TheRouter - 移动端模块化解决方案

[抽丝剥茧带你探索 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。