A hands on tutorial to get started with adding custom functionality to your Android builds using Kotlin

动手教程,开始使用Kotlin向您的Android版本添加自定义功能

While the first part of this series focused on setting up the basic structure of a custom Gradle plugin, we will now move on to turn our work into something useful. The goal for this next instalment will be to create a Task capable of generating resources at build-time, which we will then use in our app.

虽然本系列的第一部分着重于设置自定义Gradle插件的基本结构,但我们现在将继续把我们的工作变成有用的东西。 下一部分的目标是创建一个能够在构建时生成资源的Task,然后将其用于我们的应用程序中。

Once again, if you want to skip all the reading and get straight to the code you can find it all on GitHub.

再一次,如果您想跳过所有阅读内容并直接阅读代码,可以在GitHub上找到所有内容。

Picking up where we left off:

在我们离开的地方接机:

In the first part of this write up we created a simple Task whose only purpose was to create a “Hello world” text file in our project directory. What we want to do now is extend this and turn it into something a little more useful. Specifically we want to generate some project resources at build-time, which we can then use in our app. To keep things simple we are going to generate three simple colours, the same process however can be used to create other resource types too.

在本文的第一部分中,我们创建了一个简单的Task,其唯一目的是在我们的项目目录中创建“ Hello world”文本文件。 我们现在想要做的就是扩展它,并将其转变为更有用的东西。 具体来说,我们希望在构建时生成一些项目资源,然后可以在我们的应用程序中使用它们。 为了使事情简单,我们将生成三种简单的颜色,但是相同的过程也可以用于创建其他资源类型。

Let’s start!

开始吧!

Although Gradle will create our colours at build-time, we’ll still need provide their definitions so that we can refer to them in our code. To do this we’ll declare three new entries in our colors.xm file, just as we normally would, with the difference being that this time we will omit their values:

尽管Gradle将在构建时创建颜色,但是我们仍然需要提供它们的定义,以便我们可以在代码中引用它们。 为此,我们将像往常一样在colors.xm文件中声明三个新条目,不同之处在于这一次我们将省略它们的值:

<color name="color1"/>
<color name="color2"/>
<color name="color3"/>

Next, because we will want to see what we have created, let us prepare our Activity to show these colours. Open the main_activity.xml file and add three views, each to display one of the colours:

接下来,因为我们要查看创建的内容,所以让我们准备“活动”以显示这些颜色。 打开main_activity.xml文件并添加三个视图,每个视图显示一种颜色:

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"android:layout_width="match_parent"android:layout_height="match_parent"tools:context=".MainActivity"><Viewandroid:id="@+id/color_1_view"android:layout_width="match_parent"android:layout_height="100dp"android:background="@color/color1"app:layout_constraintBottom_toTopOf="@id/color_2_view"app:layout_constraintTop_toTopOf="parent" /><Viewandroid:id="@+id/color_2_view"android:layout_width="match_parent"android:layout_height="100dp"android:background="@color/color2"app:layout_constraintBottom_toTopOf="@id/color_3_view"app:layout_constraintTop_toBottomOf="@id/color_1_view" /><Viewandroid:id="@+id/color_3_view"android:layout_width="match_parent"android:layout_height="100dp"android:background="@color/color3"app:layout_constraintBottom_toBottomOf="parent"app:layout_constraintTop_toBottomOf="@id/color_2_view" />
</androidx.constraintlayout.widget.ConstraintLayout>

This part is done. If you now run the app you should see a blank screen. This is as expected because our colours do not yet have value. We’ll remedy that soon enough.

这部分完成。 如果现在运行该应用程序,应该会看到一个空白屏幕。 这是预期的,因为我们的颜色还没有价值。 我们将尽快对此进行补救。

A little cleanup:

一点清理:

Before we get into the nitty gritty of building our Task, let’s do some cleanup. First of all I created a new file called Extensions.kt. In this file I placed the two extensions we had defined in Part 1. Here I also added a simple function to write xml to a file with the necessary tags. The complete Extensions.kt file should look something like this:

在进入构建任务的精髓之前,让我们做一些清理。 首先,我创建了一个名为Extensions.kt的新文件。 在这个文件中,我放置了第1部分中定义的两个扩展。在这里,我还添加了一个简单的函数,用于将xml写入带有必需标记的文件中。 完整的Extensions.kt文件应如下所示:

package extensionsimport com.android.build.gradle.AppExtension
import com.android.build.gradle.BaseExtension
import com.android.build.gradle.FeatureExtension
import com.android.build.gradle.LibraryExtension
import com.android.build.gradle.api.BaseVariant
import org.gradle.api.DomainObjectSet
import org.gradle.api.GradleException
import org.gradle.api.Project
import java.io.Filefun Project.android(): BaseExtension {val android = project.extensions.findByType(BaseExtension::class.java)if (android != null) {return android} else {throw GradleException("Project $name is not an Android project")}
}fun BaseExtension.variants(): DomainObjectSet<out BaseVariant> {return when (this) {is AppExtension -> {applicationVariants}is FeatureExtension -> {featureVariants}is LibraryExtension -> {libraryVariants}else -> throw GradleException("Unsupported BaseExtension type!")}
}// New extension to write pre-formatted xml within resource tags
fun File.writeXlmWithTags(body: String) {("<?xml version=\"1.0\" encoding=\"utf-8\"?>\n" +"<resources>" +"$body\n" +"</resources>").also { resXml ->try {createNewFile()writeText(resXml)} catch (e: Exception) {throw GradleException(e.message)}}
}

Next we’ll actually remove most of what we had originally put in our plugin’s apply method as we will re-define our Task in a standalone class. When done, our plugin class will look like this:

接下来,我们实际上将删除我们最初在插件apply放入的大部分内容 方法,因为我们将在独立类中重新定义Task。 完成后,我们的插件类将如下所示:

import extensions.android
import extensions.variants
import org.gradle.api.Plugin
import org.gradle.api.Projectinternal class MyFirstPlugin : Plugin<Project> {override fun apply(project: Project) {project.android().variants().all { variant ->}}
}

Next we are going to add the new functionality for generating our resources.

接下来,我们将添加用于生成资源的新功能。

Create the new Task:

创建新任务:

We had originally declared the sole task of our plugin as a lambda. While this is fine for running quick experiments, it is always good practice to split every task of a plugin into a separate class. The following will take care of our current use-case:

我们最初将插件的唯一任务声明为lambda。 尽管这适合进行快速实验,但将插件的每个任务拆分为单独的类始终是一个好习惯。 以下内容将解决我们当前的用例:

import extensions.writeXlmWithTags
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.Input
import org.gradle.api.tasks.OutputFile
import org.gradle.api.tasks.TaskAction
import java.io.Fileinternal open class ColorsTask : DefaultTask(){@get:OutputFilelateinit var outputFile: File@get:Inputval colorsMap = mapOf("color1" to "#00ff00","color2" to "#ff0000","color3" to "#00ffff")@TaskActionfun makeResources() {colorsMap.entries.joinToString { (colorName, color) ->"\n    <color name=\"$colorName\">$color</color>"}.also { xml ->outputFile.writeXlmWithTags(xml)}}
}

Note the following:

请注意以下几点: