Welcome to MAD Skills series , Gradle and AGP Build APIs. In the previous article, " Gradle and AGP Build API: How to Write a Plugin ", you learned how to write your own plugin, and how to use the Variants API .
If you prefer a video about this content, please here view.
In this article, you'll learn about Gradle's Tasks, Providers, Properties, and using Tasks for input and output. You'll also further refine your plugin and learn how to use the new Artifact API access various build artifacts.
Property
Suppose I want to create a plugin that automatically updates the version number specified in the application manifest file with the Git version. To achieve this, I need to add two Tasks to the build. The first task will get the Git version, and the second task will use the Git version to update the manifest file.
Let's start by creating a new task GitVersionTask
GitVersionTask needs to inherit DefaultTask and implement the annotated taskAction function. Below is the code to query the top of the Git tree.
abstract class GitVersionTask: DefaultTask() {
@TaskAction
fun taskAction(){
// 这里是获取树版本顶端的代码
val process = ProcessBuilder(
"git",
"rev-parse --short HEAD"
).start()
val error = process.errorStream.readBytes().toString()
if (error.isNotBlank()) {
System.err.println("Git error : $error")
}
var gitVersion = process.inputStream.readBytes().toString()
//...
}
}
I can't cache the version information directly because I want to store it in an intermediate file so that other tasks can also read and use the value. For this I need to use RegularFileProperty . Property can be used for input and output of Task. In this example, Property will be the container that renders the Task output. I created a RegularFileProperty and annotated it with @get:OutputFile. OutputFile is the markup annotation attached to the getter function. This annotation marks the Property as the output file for this Task.
@get:OutputFile
abstract val gitVersionOutputFile: RegularFileProperty
Now that I've declared the output of the Task, let's go back to the taskAction() function, where I'll access the file and write the text I want to store. In this example, I'll store the Git version, which is the output of the Task. To simplify the example, I replaced the code to query the Git version with a hardcoded string.
abstract class GitVersionTask: DefaultTask() {
@get:OutputFile
abstract val gitVersionOutputFile: RegularFileProperty
@TaskAction
fun taskAction() {
gitVersionOutputFile.get().asFile.writeText("1234")
}
}
Now that the Task is ready, let's register it in the plugin code. First, I would create a new plugin class ExamplePlugin
Plugin . If you are not familiar with the process of creating plugins in the buildSrc folder, you can review the previous two articles in this series: Gradle with AGP Build API: Configuring Your Build Files Gradle with AGP Build API: How to Write Plugin .
△ buildSrc folder
Next I would register GitVersionTask
and set the file Property
to output to an intermediate file in the build folder. I also set upToDateWhen
false
so that the output of the previous execution of this Task will not be reused. This also means that since the Task will not be up-to-date, it will be executed on every build.
override fun apply(project: Project) {
project.tasks.register(
"gitVersionProvider",
GitVersionTask::class.java
) {
it.gitVersionOutputFile.set(
File(
project.buildDir,
"intermediates/gitVersionProvider/output"
)
)
it.outputs.upToDateWhen { false }
}
}
After the Task is finished, I can check located build/intermediates
folder output
files. I just need to verify that the Task stores the value I hardcoded.
Next let's turn to the second task, which updates the version information in the manifest file. I named it ManifestTransformTask
and used two RegularFileProperty
objects as its input values.
abstract class ManifestTransformerTask: DefaultTask() {
@get:InputFile
abstract val gitInfoFile: RegularFileProperty
@get:InputFile
abstract val mergedManifest: RegularFileProperty
}
I'll use the first RegularFileProperty to read the contents of the output file generated by the GitVersionTask; the second RegularFileProperty to read the app's manifest file. Then I can replace the version number in the manifest file with the version number stored in the gitVersion variable in the gitInfoFile file.
@TaskAction
fun taskAction() {
val gitVersion = gitInfoFile.get().asFile.readText()
var manifest = mergedManifest.asFile.get().readText()
manifest = manifest.replace(
"android:versionCode=\"1\"",
"android:versionCode=\"${gitVersion}\""
)
}
Now, I can write to the updated manifest file. First, I would create another RegularFileProperty
for the output and @get:OutputFile
it with 061d6694f5811c.
@get:OutputFile
abstract val updatedManifest: RegularFileProperty
Note : I could have used VariantOutput to set the versionCode directly without rewriting the manifest file. But to show you how to use the Build Artifact transform, I'll go through this example to get the same effect.
Let's go back to plugins and tie everything together. I first get AndroidComponentsExtension . I want this new Task to execute after the AGP decides which variant to create, but before the values of the various objects are locked from being modified. onVariants()
callback is beforeVariants()
callback, which may remind you of the previous post .
val androidComponents = project.extensions.getByType(
AndroidComponentsExtension::class.java
)
androidComponents.onVariants { variant ->
//...
}
Provider
You can use Provider connect Property
to other Tasks that need to perform time-consuming operations (such as reading files or external input such as the network).
I would start by registering ManifestTransformerTask
. This Task depends on the gitVersionOutput
file, which is the output of the previous Task. I will be through the use of Provider
to access this Property
.
val manifestUpdater: TaskProvider = project.tasks.register(
variant.name + "ManifestUpdater",
ManifestTransformerTask::class.java
) {
it.gitInfoFile.set(
//...
)
}
Provider values may be used to specify the type of access, you can work with GET () function, operator function may be used (e.g. Map () and flatMap () ) converting the values for the new Provider
. When I reviewed the Property
interface, I found that it implements the Property
interface. You can lazily set values to Property
and access those values Provider
When I look at the return type of register() TaskProvider given type. I assigned it to a new val
.
val gitVersionProvider = project.tasks.register(
"gitVersionProvider",
GitVersionTask::class.java
) {
it.gitVersionOutputFile.set(
File(
project.buildDir,
"intermediates/gitVersionProvider/output"
)
)
it.outputs.upToDateWhen { false }
}
Now let's go back and set the input ManifestTransformerTask
When I try to Provider
the value from 061d6694f58401 to the input Property
, an error is generated. The lambda parameter of map()
T
) and the function produces a value of another type (eg S
).
△ Errors caused when using map()
However, in this case, the set function Provider
type 061d6694f58482. I can use the flatMap()
function, which also accepts a value of type T, but produces a S
type Provider
instead of directly producing a value of type S
it.gitInfoFile.set(
gitVersionProvider.flatMap(
GitVersionTask::gitVersionOutputFile
)
)
converts
Next, I need to tell the variant's product to use manifestUpdater
with the manifest file as input and the updated manifest file as output. Finally, I call the toTransform() ) function to convert the type of the single product.
variant.artifacts.use(manifestUpdater)
.wiredWithFiles(
ManifestTransformerTask::mergedManifest,
ManifestTransformerTask::updatedManifest
).toTransform(SingleArtifact.MERGED_MANIFEST)
When running this task, I can see that the version number in the app manifest file is updated to the value in the gitVersion
Note that I didn't explicitly ask for GitProviderTask
run. The task was executed because its output was ManifestTransformerTask
, which I requested to run.
BuiltArtifactsLoader
Let's add another Task to learn how to access the updated manifest file and verify that it was updated successfully. I will create a new task VerifyManifestTask
In order to read the manifest file, I need to access the APK file, which is the product of the build Task. To do this, I need the build APK folder as input to the Task.
Note that this time I used DirectoryProperty instead of FileProperty , because the SingleArticfact.APK object can represent the directory where the APK files will be stored after the build.
I also need a BuiltArtifactsLoader as the second input to the Task, which I will use to load the BuiltArtifacts object from the metadata file. The metadata file describes the file information in the APK directory. If your project contains elements such as native components, multiple languages, etc., each build can produce several APKs. BuiltArtifactsLoader abstracts the process of identifying each APK and its properties such as ABI and language.
@get:Internal
abstract val builtArtifactsLoader: Property<BuiltArtifactsLoader>
Time to implement the Task. First I load the buildArtifacts and make sure it contains only one APK, then load this APK as a File instance.
val builtArtifacts = builtArtifactsLoader.get().load(
apkFolder.get()
)?: throw RuntimeException("Cannot load APKs")
if (builtArtifacts.elements.size != 1)
throw RuntimeException("Expected one APK !")
val apk = File(builtArtifacts.elements.single().outputFile).toPath()
At this point, I can already access the manifest file in the APK and verify that the version has been updated successfully. To keep the example concise, I'll just check for the existence of the APK here. I've also added a "check manifest file here" reminder and printed a successful message.
println("Insert code to verify manifest file in ${apk}")
println("SUCCESS")
Now we go back to the code of the plugin to register this Task. In the plugin code, I register this Task as " Verifier
" and pass in the APK folder and the buildArtifactLoader
object for the current variant.
project.tasks.register(
variant.name + "Verifier",
VerifyManifestTask::class.java
) {
it.apkFolder.set(variant.artifacts.get(SingleArtifact.APK))
it.builtArtifactsLoader.set(
variant.artifacts.getBuiltArtifactsLoader()
)
}
When I run the Task again, I can see that the new Task loads the APK and prints a success message. Note that this time I still didn't explicitly request the manifest conversion to be performed, but since VerifierTask
requested the final version of the manifest artifacts, the conversion was done automatically.
Summary
My plugin contains three : 161d6694f588db first , the plugin will check the current Git tree and store the version in an intermediate file; followed by , the plugin will use the output of the previous step lazily and use a Provider The version numbers are updated to the current manifest file; last , the plugin will use another Task to access the build artifacts and check that the manifest file is updated correctly.
That's all there is to it! Starting with version 7.0, the Android Gradle plugin provides official extension points for you to write your own plugins. Using these new APIs, you can control build inputs, read, modify and even replace intermediate and final artifacts.
To learn more and learn how to keep your builds efficient, check out the official documentation and gradle-recipes .
You are welcome here to submit feedback to us, or share your favorite content and found problems. Your feedback is very important to us, thank you for your support!
**粗体** _斜体_ [链接](http://example.com) `代码` - 列表 > 引用
。你还可以使用@
来通知其他用户。