Gradle Kotlin DSL 获取Android插件变体:最佳实践
2025-01-05 22:50:30
Gradle Kotlin DSL 中获取 Android 插件变体
在编写 Gradle 插件时,获取 Android 插件提供的变体信息是一项常见任务。特别是当需要为每个构建变体注册自定义任务时,正确获取 applicationVariants
和 libraryVariants
就变得至关重要。本篇文章探讨如何使用 Kotlin DSL 在 Gradle 插件中实现这一目标。
问题分析
上述代码片段试图通过检查 project.extensions
中是否存在名为 "android" 的扩展,并进一步判断其是否包含 applicationVariants
或 libraryVariants
属性,来获取构建变体。这个方法存在几个潜在问题:
- 空安全:
findByName
返回可能为空的值,使用?.
进行链式调用固然可以防止空指针异常,但逻辑过于复杂且可能导致误判。 - 类型转换 :
applicationVariants
和libraryVariants
类型不明确。代码直接将其转换为Iterable<BaseVariant>
可能导致类型转换异常,特别是当目标项目是普通Android应用而不是lib时。 - 可读性: 过于复杂的条件判断,使得代码可读性较差。
简言之,问题核心在于使用 project.extensions.findByName("android")
并直接操作可能为空的对象及其属性来获得 Android 构建变体。
解决方案
以下是一些解决方案,这些方案提供了更为简洁,可靠的方式获取 Android 插件的构建变体:
方案一: 使用 extensions.getByType
extensions.getByType
方法能精确地根据类型获取扩展,这种方式可以更准确地访问 Android 插件提供的扩展,减少错误。同时,我们可利用 when
表达式来清晰地处理 application
或者 library
插件的差异。
import com.android.build.gradle.AppExtension
import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.api.BaseVariant
fun Project.registerCargoNdkTasks(ext: CargoNdkExtension) {
val variants: Iterable<BaseVariant>? = when {
project.extensions.findByType(AppExtension::class.java) != null ->
project.extensions.getByType(AppExtension::class.java).applicationVariants
project.extensions.findByType(LibraryExtension::class.java) != null ->
project.extensions.getByType(LibraryExtension::class.java).libraryVariants
else -> null
}
variants?.all { variant ->
val variantUpper = variant.name.capitalize()
project.tasks.register("buildCargoNdk$variantUpper", CargoNdkBuildTask::class.java) {
group = "Build"
description = "Build rust library for variant ${variant.name}"
setVariant(variant.name)
extension = ext
}
}
project.tasks.whenTaskAdded { task ->
variants?.forEach { variant ->
val variantName = variant.name
val variantUpper = variantName.capitalize()
val preTasks = listOf("compile${variantUpper}Sources", "merge${variantUpper}JniLibFolders")
if (task.name in preTasks) {
task.dependsOn("buildCargoNdk$variantUpper")
}
}
}
}
操作步骤:
- 将以上 Kotlin 代码添加到你的 Gradle 插件的实现文件中。
- 确保
com.android.build.gradle:gradle
库被正确添加到插件的 classpath 中,通常位于你的插件build.gradle.kts
或build.gradle
文件中,形如implementation("com.android.tools.build:gradle:版本号")
。 - 在你的主工程
build.gradle.kts
文件中应用插件,确保它能在合适的时机调用registerCargoNdkTasks
方法, 这样它就可以访问AndroidExtension
。
原理:
extensions.findByType(AppExtension::class.java)
和extensions.findByType(LibraryExtension::class.java)
精确匹配相应类型的扩展,使得代码逻辑更加可靠。when
表达式清楚地处理了application
或library
的差异。getByType
在找不到匹配类型时会抛出异常,通过在调用前使用findByType
来做检查,从而避免直接抛出异常。
方案二: 延迟配置 和 Provider
如果需要在插件配置阶段访问变体信息,而插件还未完全初始化,可以结合使用 Provider
和 afterEvaluate
方法延迟配置,可以有效地访问变体数据。
import com.android.build.gradle.AppExtension
import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.api.BaseVariant
import org.gradle.api.provider.Provider
fun Project.registerCargoNdkTasks(ext: CargoNdkExtension) {
val variantsProvider: Provider<Iterable<BaseVariant>?> = provider {
when {
project.extensions.findByType(AppExtension::class.java) != null ->
project.extensions.getByType(AppExtension::class.java).applicationVariants
project.extensions.findByType(LibraryExtension::class.java) != null ->
project.extensions.getByType(LibraryExtension::class.java).libraryVariants
else -> null
}
}
project.afterEvaluate {
val variants = variantsProvider.get()
variants?.all { variant ->
val variantUpper = variant.name.capitalize()
project.tasks.register("buildCargoNdk$variantUpper", CargoNdkBuildTask::class.java) {
group = "Build"
description = "Build rust library for variant ${variant.name}"
setVariant(variant.name)
extension = ext
}
}
project.tasks.whenTaskAdded { task ->
variants?.forEach { variant ->
val variantName = variant.name
val variantUpper = variantName.capitalize()
val preTasks = listOf("compile${variantUpper}Sources", "merge${variantUpper}JniLibFolders")
if (task.name in preTasks) {
task.dependsOn("buildCargoNdk$variantUpper")
}
}
}
}
}
操作步骤:
- 复制上面的代码到你的插件文件中。
- 与方案一相同,确认你
build.gradle
或build.gradle.kts
文件中包含了com.android.tools.build:gradle
库。 - 确保你的插件在需要访问 Android 扩展之前被应用到项目。
原理:
provider{ ... }
创建一个Provider,在需要时获取配置信息。afterEvaluate
延迟配置,确保android
插件已完全配置,使得获取到的 variants 为完整值,避免了空值问题,提高兼容性。
安全建议
- 异常处理 : 始终捕获可能的
NullPointerException
和TypeCastException
。虽然我们使用了findByType
并在返回前检查,但也并非百分百保证绝对安全,对于可能的其他场景进行额外的捕获操作可以避免插件的非正常崩溃。 - 配置校验 : 对插件中的可配置参数添加验证逻辑,确保传入的参数符合预期,能够提高插件健壮性。
总结
通过利用 Kotlin DSL 中提供的 extensions.getByType
或使用 provider
和 afterEvaluate
的组合,开发者能够更为精确可靠地获取 Android 插件的构建变体信息。 选择合适的方案取决于特定需求。 在配置时使用 getByType
,或者在需要配置完成的场景使用延迟配置和 Provider ,都能有效避免空值和类型转换异常,编写更加安全可靠的 Gradle 插件。