头图

Welcome to MAD Skills series of Gradle and AGP Building API. Through the last article " Gradle and AGP Build API: Configure your build file " you have already understood the basics of Gradle and how to configure Android Gradle Plugin. In this article, you will learn how to extend your build by writing your own plugins. If you prefer to learn about this content through video, please view it here

Android Gradle Plugin has provided stable extension points since version 7.0 for operating variant configurations and generated build products. Some parts of the API are only recently completed, so I will use AGP version 7.1 in this article (in Beta at the time of writing).

Gradle Task

I will start with a brand new project. If you want to learn synchronously, you can create a new project by selecting the basic Activity template.

Let's start by creating the Task and printing the output-yes, it is hello world. To do this, I will register a new Task in the build.gradle.kts file of the application layer and name it "hello" .

tasks.register("hello"){ }

Now that the Task is ready, we can print out "hello" and add the project name. Note that the current build.gradle.kts file belongs to the application module, so project.name will be the name of the current module "app". And if I use project.parent?.name , the name of the project will be returned.

tasks.register("hello"){
   println("Hello " + project.parent?.name)
}

It's time to run the Task. Check the Task list at this time, you can see that my Task is already listed in it.

△ 新的 Task 已经列在 Android Studio 的 Gradle 窗格中了

△ The new Task has been listed in the Gradle pane of Android Studio

I can double-click the hello Task or execute the task through the terminal, and observe the hello information it prints in the build output.

△ Task 在构建输出中打印的 hello 信息

△ Task prints the hello message in the build output

When viewing the log, I can see that this information was printed during the configuration phase. The configuration phase actually has nothing to do with the function of executing the Task (for example, printing Hello World in this example). The configuration phase is the phase in which Task configuration is performed to affect its execution. You can determine the input, parameters, and output locations of the Task at this stage.

No matter which Task is requested to run, the configuration phase will be executed. Performing time-consuming operations during the configuration phase can result in longer configuration times.

The execution of Task should only happen in the execution phase, so we need to move the print call to the execution phase. I can achieve this by adding doFirst() or doLast() functions, which can print hello messages at the beginning and end of the execution phase, respectively.

tasks.register("hello"){
   doLast {
       println("Hello " + project.parent?.name)
   }
}

When I run the Task again, I can see that the hello message is printed during the execution phase.

△ 现在 Task 会在执行阶段打印 hello 信息

△ Task will now print hello information during the execution phase

My custom task is currently located in the build.gradle.kts file. Adding a custom task to the build.gradle file is a convenient way to create a custom build script. However, as my plug-in code becomes more complex, this approach is not conducive to expansion. We recommend to place the custom Task and plug-in implementation in the buildSrc folder.

implements the plug-in 161cc1c968f414 in

Before writing more code, let's move the hello Task to buildSrc . I will create a new folder and name it buildSrc . build.gradle.kts file for the plug-in project so that Gradle will automatically add this folder to the build.

This is the top-level directory in the project root folder. Note that I don't need to add it as a module in my project. Gradle will automatically compile the code in the directory and add it to the classpath of your build script.

Next, I created a new src folder and a class HelloTask I changed the new class to abstract and made it inherit DefaultTask . Later, I will add a function taskAction @TaskAction , and migrate my custom Task code to this function.

abstract class HelloTask: DefaultTask() {   
   @TaskAction
   fun taskAction() {
       println("Hello \"${project.parent?.name}\" from task!")
   }
}

Now, my Task is ready. I will create a new plug-in class, which needs to implement the Plugin type and override the apply() function. Gradle will call this function and pass in the Project object. In order to register HelloTask , I need to call project.tasks register() and name this new Task.

class CustomPlugin: Plugin<Project> {
   override fun apply(project: Project) {
       project.tasks.register<HelloTask>("hello")
   }
}

At this point, I can also declare my Task as dependent on other Tasks.

class CustomPlugin: Plugin<Project> {
   override fun apply(project: Project) {
       project.tasks.register<HelloTask>("hello"){
           dependsOn("build")
       }
   }
}

Let's apply the new plug-in below. Note that if my project contains multiple modules, I can also reuse it by adding this plugin to other build.gradle files.

plugins {
   id ("com.android.application")
   id ("org.jetbrains.kotlin.android")
}
apply<CustomPlugin>()
android {
  ...
}

Now, I will run the hello Task and observe the operation of the plug-in as before.

./gradlew hello

So far, I have moved my Task to buildSrc , let us go one step further and explore the new Android Gradle Plugin API. AGP provides extension points for the life cycle of its products when it is built.

Before starting to learn the Variant API, let us first understand what is Variant . Variants are different versions of your app that can be built. Suppose that in addition to a fully functional application, you also want to build a demo version of the application or an internal version for debugging. You can also target different target APIs or device types. Variants are a combination of multiple build types, such as debug and release, and product variants defined in the build script.

In your build file, there is no problem at all using a declarative DSL to add build types. However, it is impossible to let your plug-in affect the build in this way in the code, or it is difficult to express using declarative syntax.

AGP starts the build by parsing the build script and the attributes set in the android The new Variant API callback allows me to add the finalizeDSL() callback androidComponents In this callback, I can modify DSL objects before they are applied to Variant creation. I will create a new build type and set its properties.

val extension = project.extensions.getByName(
   "androidComponents"
) as ApplicationAndroidComponentsExtension

extension.finalizeDsl { ext->
   ext.buildTypes.create("staging").let { buildType ->
       buildType.initWith(ext.buildTypes.getByName("debug"))
       buildType.manifestPlaceholders["hostName"] = "example.com"
       buildType.applicationIdSuffix = ".debugStaging"
   }
}

Note that at this stage, I can create or register new build types and set their properties. At the end of the phase, AGP will lock the DSL objects so that they can no longer be changed. If I run the build again, I will see that the staging version of the application is built.

Now, suppose one of my tests fails. At this time, I want to disable unit tests to build an internal version to find out the problem.

To disable unit testing, I can use the beforeVariants() callback. This callback allows me to make such modifications VariantBuilder Here, I will check if the current variant is the one I created staging Next, I will disable unit testing and set a different version minSdk

extension.beforeVariants { variantBuilder ->
   if (variantBuilder.name == "staging") {
       variantBuilder.enableUnitTest = false
       variantBuilder.minSdk = 23
   }
}

After this stage, the list of components and the products to be created will be determined.

The complete code for this example is as follows. For more examples of this kind, please refer to Github gradle-recipes repository :

import com.android.build.api.variant.ApplicationAndroidComponentsExtension
import org.gradle.api.Plugin
import org.gradle.api.Project

class CustomPlugin: Plugin<Project> {
    override fun apply(project: Project) {
        project.tasks.register("hello"){ task->
            task.doLast {
                println("Hello " + project.parent?.name)
            }
        }

        val extension = project.extensions.getByName("androidComponents") as ApplicationAndroidComponentsExtension
        extension.beforeVariants { variantBuilder ->
            if (variantBuilder.name == "staging") {
                variantBuilder.enableUnitTest = false
                variantBuilder.minSdk = 23
            }
        }
        extension.finalizeDsl { ext->
            ext.buildTypes.create("staging").let { buildType ->
                buildType.initWith(ext.buildTypes.getByName("debug"))
                buildType.manifestPlaceholders["hostName"] = "internal.example.com"
                buildType.applicationIdSuffix = ".debugStaging"
                // 在后面解释 beforeVariants 时添加了本行代码。
                buildType.isDebuggable = true 
            }
        }
    }
}

summary

Write your own plugin, you can extend Android Gradle Plugin and customize your build according to your project needs!

In this article, you have learned how to use the new Variant API to AndroidComponentsExtension , use DSL objects to initialize Variant, affect the Variant that has been created, and their properties beforeVariants()

In the next article, we will further introduce the Artifacts API and show you how to read and convert products from your custom Task.

Welcome to click here to submit feedback to us, or share your favorite content or problems found. Your feedback is very important to us, thank you for your support!


Android开发者
404 声望2k 粉丝

Android 最新开发技术更新,包括 Kotlin、Android Studio、Jetpack 和 Android 最新系统技术特性分享。更多内容,请关注 官方 Android 开发者文档。