返回

Gradle Kotlin DSL 获取Android插件变体:最佳实践

Android

Gradle Kotlin DSL 中获取 Android 插件变体

在编写 Gradle 插件时,获取 Android 插件提供的变体信息是一项常见任务。特别是当需要为每个构建变体注册自定义任务时,正确获取 applicationVariantslibraryVariants 就变得至关重要。本篇文章探讨如何使用 Kotlin DSL 在 Gradle 插件中实现这一目标。

问题分析

上述代码片段试图通过检查 project.extensions 中是否存在名为 "android" 的扩展,并进一步判断其是否包含 applicationVariantslibraryVariants 属性,来获取构建变体。这个方法存在几个潜在问题:

  1. 空安全: findByName 返回可能为空的值,使用 ?. 进行链式调用固然可以防止空指针异常,但逻辑过于复杂且可能导致误判。
  2. 类型转换 : applicationVariantslibraryVariants 类型不明确。代码直接将其转换为 Iterable<BaseVariant> 可能导致类型转换异常,特别是当目标项目是普通Android应用而不是lib时。
  3. 可读性: 过于复杂的条件判断,使得代码可读性较差。

简言之,问题核心在于使用 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")
              }
         }
    }
}

操作步骤:

  1. 将以上 Kotlin 代码添加到你的 Gradle 插件的实现文件中。
  2. 确保 com.android.build.gradle:gradle 库被正确添加到插件的 classpath 中,通常位于你的插件 build.gradle.ktsbuild.gradle 文件中,形如 implementation("com.android.tools.build:gradle:版本号")
  3. 在你的主工程 build.gradle.kts 文件中应用插件,确保它能在合适的时机调用 registerCargoNdkTasks 方法, 这样它就可以访问 AndroidExtension

原理:

  • extensions.findByType(AppExtension::class.java)extensions.findByType(LibraryExtension::class.java) 精确匹配相应类型的扩展,使得代码逻辑更加可靠。
  • when表达式清楚地处理了applicationlibrary 的差异。
  • getByType 在找不到匹配类型时会抛出异常,通过在调用前使用 findByType 来做检查,从而避免直接抛出异常。

方案二: 延迟配置 和 Provider

如果需要在插件配置阶段访问变体信息,而插件还未完全初始化,可以结合使用 ProviderafterEvaluate 方法延迟配置,可以有效地访问变体数据。

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")
               }
            }
       }
     }
}

操作步骤:

  1. 复制上面的代码到你的插件文件中。
  2. 与方案一相同,确认你 build.gradlebuild.gradle.kts 文件中包含了 com.android.tools.build:gradle 库。
  3. 确保你的插件在需要访问 Android 扩展之前被应用到项目。

原理:

  • provider{ ... } 创建一个Provider,在需要时获取配置信息。afterEvaluate 延迟配置,确保 android 插件已完全配置,使得获取到的 variants 为完整值,避免了空值问题,提高兼容性。

安全建议

  • 异常处理 : 始终捕获可能的 NullPointerExceptionTypeCastException。虽然我们使用了 findByType 并在返回前检查,但也并非百分百保证绝对安全,对于可能的其他场景进行额外的捕获操作可以避免插件的非正常崩溃。
  • 配置校验 : 对插件中的可配置参数添加验证逻辑,确保传入的参数符合预期,能够提高插件健壮性。

总结

通过利用 Kotlin DSL 中提供的 extensions.getByType 或使用 providerafterEvaluate 的组合,开发者能够更为精确可靠地获取 Android 插件的构建变体信息。 选择合适的方案取决于特定需求。 在配置时使用 getByType,或者在需要配置完成的场景使用延迟配置和 Provider ,都能有效避免空值和类型转换异常,编写更加安全可靠的 Gradle 插件。