告别 Java:Android gRPC 生成纯 Kotlin 代码配置指南
2025-03-28 04:25:28
告别 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 代码?
这主要是历史原因和插件设计导致的。
grpc-java
是基础:grpc-kotlin
在早期是构建在grpc-java
之上的。它生成的 Kotlin Stub 依赖 Java 生成的 Protobuf 消息类。所以,即使你只想用 Kotlin Stub,也需要 Java 消息类作为基础。- Android 的特殊性: Android 开发通常使用
protobuf-javalite
,这是一个轻量级的 Java Protobuf 运行时库,旨在减少方法数和代码体积。对应的代码生成器是protoc-gen-javalite
。所以,很多 Android gRPC 配置默认包含了它。 - 配置的叠加:
protobuf
Gradle 插件允许你同时配置多个代码生成器(包括内置的java
,kotlin
和通过plugins
定义的grpc
,grpckt
等)。如果不显式地移除某个生成器,它就会被执行。
幸运的是,protobuf-kotlin
和 grpc-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-kotlin
和grpc-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-stub
和protobuf-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 配置: 如果你只想为特定的构建变体(例如
debug
或release
)或源集生成代码,可以替换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
: 可以用来更精细地控制生成代码的输出目录,但通常插件的默认行为是合理的。
- 作用:配置 Gradle 如何为项目中的不同源代码集(source sets)运行
通过组合 builtins
和 plugins
的 create
和 remove
操作,你可以精确控制针对每个源集生成哪些语言的代码。
验证结果
修改完 build.gradle.kts
文件后,如何确认真的只生成了 Kotlin 代码呢?
- 清理项目: 在 Android Studio 的 Terminal 中运行
./gradlew clean
(macOS/Linux) 或gradlew clean
(Windows),或者使用 IDE 的 Clean Project 功能。这能确保移除旧的生成文件。 - 重新构建或生成 Proto: 运行
./gradlew build
或更具体地运行./gradlew :app:generateProto
(将:app
替换为你的模块名)。 - 检查生成目录: 查看
app/build/generated/source/proto/
目录。- 你应该能看到类似
debug/kotlin
和debug/grpckt
(或main/kotlin
,main/grpckt
) 的子目录,里面包含了生成的.kt
文件。 - 确认没有 类似
debug/java
或debug/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-kotlin
和protobuf-kotlin
的官方文档获取推荐的版本组合。 - 缓存问题: 有时候 Gradle 或 IDE 的缓存可能导致旧配置“阴魂不散”。如果
clean
后问题依旧,尝试更彻底的清理,如删除项目根目录下的.gradle
目录,以及 Android Studio 的File -> Invalidate Caches / Restart...
。
通过上述配置,你应该就能在 Android 项目中实现 gRPC 和 Protobuf 的纯 Kotlin 代码生成了,让你的 Kotlin 项目更加纯粹。