As a build tool, in addition to the tasks brought by plugins, we can also customize many variables, tasks, or inherit certain tasks to re-specify input and output files. First of all, we need to clarify the Gradle cycle. In short, there are three steps when a Gradle task is executed.
- Gradle will analyze the build script and generate instances of settings and project classes corresponding to the projects configured by setting.gradle.
- Initialize the configuration, configure the properties of the project object by executing the build.gradle script of each project, and also create and configure tasks and related information at this stage.
- Execute specific tasks, and perform a series of task executions through the dependencies between tasks.
After clarifying these, let's take a look at the related use of tasks. Here we create a new Android project, including an app and a library.
settings.gradle
include ':app', ':mylibrary'
Then our project has three project instances, rootProject, app, mylibrary
configuration variable
1. Define variables directly in the gradle file
We define it directly at the end of the app's build.gradle.
def para='123'
println para
Then we execute and
we can see the corresponding output
15:22:36: Executing task 'assembleDebug'...
Executing tasks: [assembleDebug]
Configuration on demand is an incubating feature.
G:\program\Android\sdk\platform-tools\adb.exe
123
:app:preBuild UP-TO-DATE
:mylibrary:preBuild UP-TO-DATE
:mylibrary:preDebugBuild UP-TO-DATE
:mylibrary:checkDebugManifest UP-TO-DATE
:mylibrary:processDebugManifest UP-TO-DATE
:app:preDebugBuild UP-TO-DATE
:mylibrary:compileDebugAidl UP-TO-DATE
.....
Obviously we can see that the output is before the task execution. This means that the code has already been executed during the configuration phase. Variables defined like this are only visible within that file
2. Use ext to define variables
It is also defined in the app's build.gradle as follows, and the assembleDebug task is also executed through the gradle window. If it remains unchanged, I will not go into details.
ext{
para1 ="456"
}
assert project.ext.para1=='456'
assert ext.para1=='456'
Variables defined in this way can be obtained in other modules, but they must be defined. For example, if they are defined in the app, they can be obtained in mylibrary, because the build.gralde of the app is executed first.
We can get the value in build.gradle in mylibrarya.
assert rootProject.childProjects.'app'.ext.para1=='456'
Similarly, we define ext in the build.gradle of the root directory, which is obtained through rootProject.ext.
(1) Add the config.gradle file
ext can be brought in by creating files individually. Add a config.gradle file in the root directory. The content is as follows:
ext{
pubg='pubg'
}
Then we add in build.gradle in the root directory
apply from: "config.gradle"
Writing in this way is similar to the principle of #include in C language, and is directly similar to pasting code. Then we can get variables in other modules.
assert rootProject.ext.pubg=='pubg'
3. Read the properties file
We create a new app.properties file in the app directory and enter the following content.
key.file=C\:\\work\\Key.jks
keyAlias=key
keyPassword=key7766
storePassword=key6677
Then we can read the file in build.grald to get the corresponding properties.
Properties properties = new Properties()
InputStream inputStream = file('app.properties').newDataInputStream() ;
properties.load(inputStream)
println properties['key.file']
println properties['keyAlias']
println properties['keyPassword']
println properties['storePassword']
result:
C:\work\Key.jks
key
key7766
key6677
Pay attention to the path of the file here. If it is the root directory, call rootProject.file(), and if it is the corresponding module directory, call file() directly. This method is a relative path corresponding to the project directory
Configure custom tasks
1. Simple definition of Tasks
There are two definitions
task A<<{
println 'A task'
}
task B{
println 'B task'
}
The difference between the two definitions is: B will be executed in the configuration phase, while A needs to specifically execute the A task or as a dependency of other tasks. In fact, this way of defining A will be removed in Gradle 5.0, so it is not recommended to use it. We can specify this by specifying doLast{}.
task A{
doLast {
println 'doLast'
}
doFirst {
println 'doFirst'
}
doLast {
println 'doLast1'
}
doLast {
println 'doLast2'
}
}
result:
doFirst
doLast
doLast1
doLast2
Specify task grouping and description
task B{
group "custom tasks"
description '任务描述'
}
When we synchronize, we can find custom groupings and descriptions in the window.
Pass arguments for project using the command line
task searchParameters {
doLast {
println project.hasProperty("pp")?pp:'can not find field'
}
}
Execute tasks with camelCase on the command line
gradle :app:sP -Ppp=nihao
result:
nihao
2. Use DefaultTask to add custom tasks
class IncrementTask extends DefaultTask {
@TaskAction
void run() {
println 'IncrementTask'
}
}
//方式一
tasks.create('increment', IncrementTask)
//方式二 理解为继承
task increment(type: IncrementTask) {
}
Similarly, we can specify grouping and description, which are added in the constructor of IncrementTask.
IncrementTask() {
group '自定义任务分组'
description '任务描述'
}
Use upToDate to skip tasks, which we also add in the constructor.
outputs.upToDateWhen { true }
In this way, it can be judged whether the task needs to be skipped for the second and subsequent executions according to the return value of the closure.
Specify the input and output files and attributes of the task
class IncrementTask extends DefaultTask {
@TaskAction
void run() {
println 'IncrementTask'
println inputs.files.first()
println inputs.files.singleFile
println outputs.files.first()
println inputs.getProperties().'prop1'
}
}
task increment(type: IncrementTask) {
inputs.file file('text')
inputs.property 'prop1','this is a register property'
outputs.file file('outputs')
}
result:
IncrementTask
C:\Users\TY\Desktop\memoryoptimize\TestApplication\app\text
C:\Users\TY\Desktop\memoryoptimize\TestApplication\app\text
C:\Users\TY\Desktop\memoryoptimize\TestApplication\app\outputs
this is a register property
3. Inherit the original task
Here we use the Gradle plugin's Zip task to package the apk file into a zip package. Add it to the app's build.gradle.
//在分析完成gradle 之后执行
afterEvaluate {
task zip(type: Zip,dependsOn:'assembleDebug') {
archiveName 'my.zip'
destinationDir file("${buildDir}/zip")
println tasks.getByName('assembleDebug')
from tasks.getByName('packageDebug').outputs.files[1]
}
}
By performing this task, we packaged all files under app\build\outputs\apk\debug into app\build\zip\my.zip. Inheriting the original task basically only needs to specify the input and output.
We can find some original tasks in gradle-4.4\src\core\org\gradle\api\tasks.
There are copy, delete, etc. in it, you can view the source code if you need to use it.
4. Task Dependency
Tasks can depend on each other, such as letting A be executed after B, or A must be executed after B is executed, etc. There are mainly the following types.
relation | discirbe |
---|---|
A dependsOn B | Task B must be executed before task A is specified |
A mustRunAfter B | Specify that the B task is executed before the A task, but it is not a dependency. If only the A task is executed, it is not necessary to execute B |
A shouldRunAfter B | Under parallel compilation, A is not necessarily executed after B |
A finalizedBy B | After A is executed, B must be executed |
simply do a test
task A {
doLast{
println 'A'
}
}
task B {
doLast{
println 'B'
}
}
task MyTask(dependsOn: [B, A]) {
doLast{
println 'mytask'
}
}
task finalized {
doLast{
println 'finalized'
}
}
A.finalizedBy finalized
result:
:app:A
A
:app:finalized
finalized
:app:B
B
:app:MyTask
mytask
Before MyTask is executed, A is executed, then finalized is executed, then B, and finally MyTask. There is a problem here. The dependency specified by MyTask is obviously [B, A], but the execution order is A->B. Here, if you want B to execute before A, you need to specify A dependsOn B additionally.