返回

告别 Java:Android gRPC 生成纯 Kotlin 代码配置指南

Android

告别 Java 依赖:为 Android gRPC 生成纯 Kotlin 代码

在使用 gRPC 的 Android 项目中,你可能遇到过这样的情况:明明整个项目是 Kotlin 优先,但构建脚本却同时生成了 Java 和 Kotlin 两种代码。这不仅增加了构建时间和项目体积,还可能带来维护上的困扰。如果你希望 Protobuf 消息定义和 gRPC 服务接口(Stubs)都只生成纯 Kotlin 代码,该怎么做呢?

问题在哪?

我们来看看常见的情况。通常,Android 项目使用 gRPC 时,会在 build.gradle.kts 文件中配置 Protobuf 插件,就像 grpc-kotlin 官方示例那样。

这个默认配置通常会包含 protoc-gen-grpc-java(生成 Java gRPC Stub)和 protoc-gen-grpc-kotlin(生成 Kotlin gRPC Stub)的插件。同时,为了生成 Protobuf 消息本身的类,可能还会依赖 protoc-gen-javalite (针对 Android 优化的 Java Protobuf 代码生成器) 或者 protoc-gen-kotlin

问题来了:这些配置放在一起,构建系统就会 dutifully 地执行所有指令,结果就是 Java 和 Kotlin 代码都被生成出来了。即使你的项目代码完全是 Kotlin,底层的 Protobuf 消息类和一部分 gRPC 代码可能还是 Java 的。

为什么默认会生成 Java 代码?

这主要是历史原因和插件设计导致的。

  1. grpc-java 是基础: grpc-kotlin 在早期是构建在 grpc-java 之上的。它生成的 Kotlin Stub 依赖 Java 生成的 Protobuf 消息类。所以,即使你只想用 Kotlin Stub,也需要 Java 消息类作为基础。
  2. Android 的特殊性: Android 开发通常使用 protobuf-javalite,这是一个轻量级的 Java Protobuf 运行时库,旨在减少方法数和代码体积。对应的代码生成器是 protoc-gen-javalite。所以,很多 Android gRPC 配置默认包含了它。
  3. 配置的叠加: protobuf Gradle 插件允许你同时配置多个代码生成器(包括内置的 java, kotlin 和通过 plugins 定义的 grpc, grpckt 等)。如果不显式地移除某个生成器,它就会被执行。

幸运的是,protobuf-kotlingrpc-kotlin 现在已经足够成熟,可以完全摆脱对 Java 生成代码的依赖,实现 Protobuf 消息和 gRPC Stubs 的纯 Kotlin 化。

实现纯 Kotlin 代码生成

答案是肯定的,完全可以配置构建脚本,让它只生成 Kotlin 版本的 gRPC Stubs 和 Protobuf 消息类。关键在于精确控制 protobuf Gradle 插件的行为。

你需要修改 app/build.gradle.kts (或者你模块对应的 build.gradle.kts) 文件中的 protobuf {} 配置块。

方案:调整 Gradle 插件配置

核心思路是:明确告诉 protobuf 插件,我们不要 Java 代码生成器,只要 Kotlin 代码生成器。

1. 原理和作用

  • 移除 Java 生成器: 通过配置 generateProtoTasks,我们可以移除默认的或显式配置的 Java 代码生成步骤 (无论是 java 还是 javalite)。
  • 启用 Kotlin 生成器: 同时,确保 kotlin (用于 Protobuf 消息) 和 grpckt (用于 Kotlin gRPC Stubs) 生成器被正确添加和启用。
  • 依赖匹配: 确保项目中包含了 protobuf-kotlingrpc-kotlin-stub 的运行时依赖,这样生成的 Kotlin 代码才能正常编译和运行。

2. 操作步骤和代码示例

假设你的 build.gradle.kts 文件结构与 grpc-kotlin 示例类似,找到 protobuf {} 配置块,进行如下修改:

// build.gradle.kts (app module)

plugins {
    id("com.android.application")
    kotlin("android")
    id("com.google.protobuf") version "0.9.4" // Make sure version is reasonably recent
}

// ... other configurations like android {}, dependencies {} ...

protobuf {
    protoc {
        // 配置 protoc 可执行文件来源
        // 通常从 Maven Central 下载
        artifact = "com.google.protobuf:protoc:3.25.1" // Use a recent version
    }
    plugins {
        // 定义 Kotlin gRPC Stub 代码生成器插件
        id("grpckt") {
            artifact = "io.grpc:protoc-gen-grpc-kotlin:1.4.1:jdk8@jar" // Use the latest grpc-kotlin version
        }
        // 注意:从 protobuf-gradle-plugin 0.9.0+ 开始,
        // kotlin 生成器通常作为内置插件使用,无需在此显式定义 `id("kotlin")`
        // 如果你的插件版本较旧或有特殊需求,可能需要如下定义:
        // id("kotlin") {
        //    artifact = "org.jetbrains.kotlin:protoc-gen-kotlin:1.9.x" // Check for compatible Kotlin Protobuf generator version if needed
        // }
    }
    generateProtoTasks {
        all().forEach { task ->
            // 清理可能存在的旧 Java 配置 (保险起见)
            // task.builtins.findByName("java")?.option("lite") // 如果之前有显式 java lite 配置,先注释或删除

            // 移除内置的 Java 代码生成器
            // !! 这是关键步骤,阻止生成 Java Protobuf 消息类 !!
            task.builtins.remove("java")

            // 配置 Kotlin Protobuf 消息类生成器 (使用内置的)
            task.builtins.create("kotlin") {
                // 如果是 Android 项目,通常不需要特殊 option,它会自动处理
                // 如果需要,可以添加 option,例如 option("lite") 但通常不必要了
            }

            // 配置 Kotlin gRPC Stub 代码生成器
            task.plugins.create("grpckt") {
                // 通常不需要额外配置
            }

            // 确保生成目录正确 (通常插件会自动处理)
            // task.builtins.getByName("kotlin").outputTarget = file("${project.buildDir}/generated/source/proto/${task.sourceSet.name}/kotlin")
            // task.plugins.getByName("grpckt").outputTarget = file("${project.buildDir}/generated/source/proto/${task.sourceSet.name}/grpckt")
        }
    }
}

// 确保你的 dependencies 块包含 Kotlin Protobuf 和 gRPC Kotlin 的运行时库
dependencies {
    implementation("io.grpc:grpc-kotlin-stub:1.4.1") // gRPC Kotlin Stub
    implementation("com.google.protobuf:protobuf-kotlin-lite:3.25.1") // Kotlin Protobuf 运行时 (lite for Android)
    // 或者如果不是 Android/lite: implementation("com.google.protobuf:protobuf-kotlin:3.25.1")

    implementation("io.grpc:grpc-okhttp:1.60.1") // gRPC 网络传输层
    implementation("javax.annotation:javax.annotation-api:1.3.2") // gRPC 可能需要的注解
    // ...其他依赖
}

代码解释:

  • protobuf { ... }: 这是配置 Protobuf Gradle 插件的主要区域。
  • protoc { artifact = "..." }: 指定 protoc 编译器的版本和来源。protoc 是 Protobuf 的核心编译器。
  • plugins { id("grpckt") { ... } }: 定义一个名为 grpckt 的代码生成插件。这里指向 protoc-gen-grpc-kotlin,即 Kotlin gRPC Stub 生成器。注意,Kotlin Protobuf 消息生成器 (protoc-gen-kotlin) 现在通常作为内置(builtins) 处理,不需要在这里定义。
  • generateProtoTasks { all().forEach { task -> ... } }: 这是配置代码生成任务的核心部分。all() 表示对所有源代码集(如 main, debug, release)应用这些配置。
  • task.builtins.remove("java"): 这行是最关键的改动。 它告诉插件,在执行这个任务时,不要使用名为 "java" 的内置代码生成器。这就阻止了 .java 文件的生成。
  • task.builtins.create("kotlin") { ... }: 添加并配置内置的 "kotlin" 生成器,用于生成 Protobuf 消息的 Kotlin 代码。通常不需要特别的 option。插件会处理好与 Kotlin 环境的集成。
  • task.plugins.create("grpckt") { ... }: 添加并配置我们之前定义的 grpckt 插件,用于生成 gRPC Service Stubs 的 Kotlin 代码。
  • 依赖关系: dependencies 部分需要包含 grpc-kotlin-stubprotobuf-kotlin-lite(或 protobuf-kotlin)。这为生成的 Kotlin 代码提供了必要的运行时库支持。

3. 安全建议

  • 依赖版本管理: 定期更新 protoc, protobuf-kotlin, grpc-kotlin 以及 protobuf-gradle-plugin 的版本,以获取最新的功能修复和安全补丁。使用像 versions 这样的 Gradle 插件可以帮助你检查依赖更新。
  • 构建环境一致性: 确保开发和 CI/CD 环境使用的 Gradle 和插件版本一致,避免因环境差异导致构建失败或行为不一致。

4. 进阶使用技巧

  • 自定义生成选项: Kotlin 生成器也支持一些选项,可以通过 task.builtins.getByName("kotlin").option("...") 来添加。不过,对于纯 Kotlin 生成,默认配置通常就足够了。可以查阅 protobuf-kotlin 的文档了解可用选项。
  • 针对特定 SourceSet 配置: 如果你只想为特定的构建变体(例如 debugrelease)或源集生成代码,可以替换 all() 为具体的选择器,例如 named("generateDebugProto") { ... }
  • 性能优化: Protobuf Gradle 插件提供了一些缓存和增量编译的优化。确保你的 Gradle 配置没有禁用这些特性(通常默认是启用的)。

深入理解 protobuf {} 配置块

这个配置块是控制 protoc 编译器如何工作的关键。我们来拆解一下它的主要部分:

  • protoc { ... }:

    • 作用:定义 protoc 编译器本身。
    • artifact = "...": 指定从哪里下载 protoc 可执行文件。通常是从 Maven Central 获取特定版本的 com.google.protobuf:protoc
  • plugins { ... }:

    • 作用:定义要使用的 protoc 插件。这些插件是独立的程序或脚本,protoc 会调用它们来生成特定语言的代码。
    • id("pluginName") { ... }: 给插件起一个名字(如 grpckt)。
    • artifact = "...": 指定插件的来源,通常也是一个 Maven 库的 Jar 包。例如 io.grpc:protoc-gen-grpc-kotlin:version:classifier@jar 指向了 Kotlin gRPC Stub 生成器。classifier(如 jdk8)有时是必需的,@jar 表明我们直接需要这个 jar 文件。
  • generateProtoTasks { ... }:

    • 作用:配置 Gradle 如何为项目中的不同源代码集(source sets)运行 protoc 编译和代码生成任务。
    • all(): 对所有找到的源代码集(通常是 main,以及 debug, release 等变体)应用后续的配置。
    • task -> ...: 这里的 task 代表具体的 Protobuf 生成任务,例如 generateDebugProto, generateReleaseProto, generateMainProto
    • task.builtins: 访问内置于 protobuf-gradle-plugin 的代码生成器。常见的内置生成器有 java, kotlin, python 等。
      • create("name") { ... }: 启用并配置一个内置生成器。
      • remove("name"): 禁用一个内置生成器。这是我们移除 Java 生成的关键。
      • findByName("name"): 查找已配置的内置生成器(如果存在)。
      • option("key") / option("key", "value"): 向生成器传递参数。例如 java 生成器的 lite 选项。
    • task.plugins: 访问在顶层 plugins { ... } 块中定义的自定义插件。
      • create("pluginId") { ... }: 启用并配置一个之前通过 id(...) 定义的插件,这里的 "pluginId" 要和 plugins 块中定义的 id 匹配 (例如 "grpckt")。
    • outputBase, outputTarget: 可以用来更精细地控制生成代码的输出目录,但通常插件的默认行为是合理的。

通过组合 builtinspluginscreateremove 操作,你可以精确控制针对每个源集生成哪些语言的代码。

验证结果

修改完 build.gradle.kts 文件后,如何确认真的只生成了 Kotlin 代码呢?

  1. 清理项目: 在 Android Studio 的 Terminal 中运行 ./gradlew clean (macOS/Linux) 或 gradlew clean (Windows),或者使用 IDE 的 Clean Project 功能。这能确保移除旧的生成文件。
  2. 重新构建或生成 Proto: 运行 ./gradlew build 或更具体地运行 ./gradlew :app:generateProto (将 :app 替换为你的模块名)。
  3. 检查生成目录: 查看 app/build/generated/source/proto/ 目录。
    • 你应该能看到类似 debug/kotlindebug/grpckt (或 main/kotlin, main/grpckt) 的子目录,里面包含了生成的 .kt 文件。
    • 确认没有 类似 debug/javadebug/grpc 这样的目录包含 Protobuf 或 gRPC 生成的 .java 文件。如果这些目录不存在或者里面没有对应 proto 生成的 Java 代码,就说明配置成功了。

其他注意事项

  • IDE 同步: 修改 Gradle 文件后,记得点击 Android Studio 提示的 "Sync Now" 或手动执行 Gradle 同步 (File -> Sync Project with Gradle Files),让 IDE 识别新的配置和生成的源代码。
  • 依赖兼容性: 确保你使用的 grpc-kotlin, protobuf-kotlin, protobuf-java (如果间接依赖), protobuf-gradle-plugin 版本相互兼容。查阅 grpc-kotlinprotobuf-kotlin 的官方文档获取推荐的版本组合。
  • 缓存问题: 有时候 Gradle 或 IDE 的缓存可能导致旧配置“阴魂不散”。如果 clean 后问题依旧,尝试更彻底的清理,如删除项目根目录下的 .gradle 目录,以及 Android Studio 的 File -> Invalidate Caches / Restart...

通过上述配置,你应该就能在 Android 项目中实现 gRPC 和 Protobuf 的纯 Kotlin 代码生成了,让你的 Kotlin 项目更加纯粹。