background
All the dynamic libraries used by the company app are packaged only with Armeabi architecture. However,NDK 16 can no longer compile Armeabi library. After NDK 17, apK package configuration:
xternalNativeBuild {
cmake {
cppFlags "-std=c++11 -frtti -fexceptions"
abiFilters 'armeabi'//NDK 17 and above no longer support ABIs [mips64, armeabi, MIPS]}}Copy the code
Will directly report an error, specifically downloaded NDK 15 finally compiled armeabi library, but THE QA test found that the recorded sound was short and noisy, obviously there was no problem in my demo. Compared with the difference, I found that there was a problem with resampling the opUS package compiled under Armeabi architecture, and there was no problem with the One using Armeabi-V7A. Although adding armeabi-V7A architecture library can solve the problem, it will obviously increase the package size, and it is not particularly secure, and may crash on some phones. In the old days of eclipse, when you build a dynamic library and put it in libs directory, it will be automatically packaged into apk. At that time, before packaging apk, you can copy armeabi-v7a’s so into armeabi, but in the era of AndroidStudio, especially now In 3.5, Gradle has been upgraded to 5.4.1, Android After gradle has been upgraded to 3.5.0, it is no longer possible to use gradle plugins to manipulate the compilation process. After the dynamic libraries are packaged, manually create armeabi folder, copy armeabi-v7a so to Armeabi, and type in the package Aar or APK has the effect of supporting the ArmeABI architecture.
What can Gradle plug-ins do
I’ve seen some of the Gradle plugin products before, including:
- Modify the compiled APK file name
- The Router framework, which parses annotations in code and generates static classes
- Unburied,hook key class lifecycle methods
- In plug-in, rename the resource file
- Compress image resources at compile time (github.com/chenenyu/im…)
- .
All of these operations are based on the Gradle build life cycle and are performed after finding hook points. Similarly, I can find corresponding hook points and copy files
Create a Gradle plug-in
Gradle plugins include script plugins and object plugins. Script plugins are used to write a series of tasks in ordinary Gradle build scripts and apply from other Gradle build scripts. ‘xx.gradle’ references this script plug-in. Object plug-ins are classes that implement the org.gradle.api.plugin interface. We also need to implement the method void Apply (T target), where generics refer to objects to which the Plugin can be applied, which we would normally apply to a Project object. Write object plug-ins in a common way
- Directly in a Gradle script file
- In the buildSrc directory
- Since my plugins are project-specific, I will not mention object plugins for independent projects, but introduce the first two types:
Created directly in Gradle
Create a gradle interface.
//app.gradle
class CustomPluginInBuildGradle implements Plugin<Project> {
@Override
void apply(Project target) {
target.task('showCustomPluginInBuildGradle') {doLast {
println("task in CustomPluginInBuildGradle")}}}}Copy the code
Then refer to it by the plug-in class name:
//app.gradle
apply plugin: CustomPluginInBuildGradle
Copy the code
The task can then be executed directly with the result:
> Task :app:showCustomPluginInBuildGradle
task in CustomPluginInBuildGradle
Copy the code
Build in the buildSrc directory
The buildSrc directory is Gradle’s default directory for configuring custom plug-ins in your projects. Tasks defined in it can be used directly. First we need to create a Java Library Module(not Android Library, otherwise the build will fail) called buildSrc. The Java in the directory needs to be modified to groovy because it is using Groovy, the id symbol needs to be introduced, and the resources-> meta-INF ->gradle-plugins directory structure needs to be created
buildSrc
.gradle
build
libs
build.gradle
src
main
groovy
com.xxx.xxxplugin
UtilPlugin
resources
META_INF.gradle-plugins
myplugin.properties
Copy the code
Write the build. Gradle:
apply plugin: 'groovy'
repositories {
jcenter()
google()
mavenCentral()
}
dependencies {
implementation localGroovy() implementation gradleApi() // Optional implementation'com. Squareup. Okhttp3: okhttp: 3.12.1'
implementation 'org. Javassist: javassist: 3.22.0 - GA'
implementation 'com. Squareup. Okio: okio: 1.14.0'
implementation "Com. Android. Tools. Build: gradle: 3.1.4." "
}
Copy the code
UtilPlugin is a defined object plug-in that is defined in Resources for identification by ID
import org.gradle.api.Project
import org.gradle.api.Plugin
class UtilPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
project.task('showCustomPluginInBuildSrc') {
doLast {
println('task in CustomPluginInBuildSrc')}}}}Copy the code
Then look at myplugin.properties in Resources
implementation-class=com.com.xxx.xxxplugin.UtilPlugin
Copy the code
Where myplugin is the id recognized by other Gradle build scripts, implementation-class needs to set the full path name, of course, resources directory can not be created, we import in other build scripts, we need to import the full path name
apply plugin: 'myplugin'
Copy the code
Implement dynamic library copy
Example Obtain the copy execution time
Build life cycle
The essence of each build is to execute a series of tasks. Some tasks may depend on other tasks. Those without dependencies are always executed first, and each Task is executed only once. The build lifecycle consists of the following three phases:
Initialization
The build tool creates a Project instance from each build.gradle file. During initialization, the settings. gradle file at the root of the Project is executed to analyze which projects are involved in the build
include ':app'
include ':libraries:someProject'
Copy the code
Configuration
This phase creates and assigns tasks for each project by executing build scripts. Gradle file will be instantiated as a Gradle project object, and then the dependencies between projects will be analyzed. The dependency files will be downloaded, and the dependencies between tasks under the project will be analyzed
Execution
This is where tasks are actually executed, and Gradle determines which tasks need to be executed and in what order based on the dependencies. Task is the smallest execution unit in Gradle. All of our construction, compilation, packaging, debugging, test and so on execute a task. A project can have multiple tasks, and tasks can depend on each other. For example, I have two tasks, taskA and taskB. I specify that taskA depends on taskB and then execute taskA. In this case, taskB will be executed first and taskA will be executed after taskB is executed.
To find a place to Hook
We need to find hook points in the build life cycle to implement our operations. Gradle provides a rich set of hooks to help us customize our build logic to the needs of our project, as shown below:
There are two main ways to listen for these life cycles:
- Adding listeners
- Gradle and Project define the available hooks. Common hooks include:
Project
The lifecycle callback methods provided by Project are
Void afterEvaluate(Closure Closure) call void beforeEvaluate(Closure Closure) before Project is configuredCopy the code
BeforeEvaluate must be configured for child modules in the parent module’s build.gradle to take effect, because the build.gradle configuration in the current module is not configured itself, so it does not listen for it.
Settings. Gradle code:
include ":app"
Copy the code
Build. Gradle code:
//对子模块进行配置
subprojects { sub ->
sub.beforeEvaluate { proj ->
println "Subitem beforeEvaluate callback..."
}
}
println "Root project configuration begins --"
task rootTest {
println "Task Configuration in root project --"
doLast {
println "Execute root project task..."
}
}
println "End of root project configuration --"
Copy the code
App/build. Gradle code:
println "APP subproject configuration starts --"
afterEvaluate {
println APP subproject afterEvaluate callback...
}
task appTest {
println "Task configuration in APP subproject --"
doLast {
println "Perform sub-project tasks..."
}
}
println "End of APP subproject configuration --"
Copy the code
Run gradle -q in the root directory and the result is as follows:
Root project configuration start -- Task configuration in root project -- Root project configuration end -- subproject beforeEvaluate callback... APP subproject configuration start -- APP subproject task configuration -- APP subproject configuration end -- APP subproject afterEvaluate callback...Copy the code
Gradle
Gradle provides many life cycle callback methods, some of which are similar to those in Project:
// Child project must be set in root project to take effect. Void beforeProject(Closure Closure) // call afterProject(Closure Closure) afterProject is configured. // Call void buildStarted(Closure Closure) before the build starts // Call void buildFinished(Closure Closure) after the build finishes // Call void after all project configurations are complete ProjectsEvaluated (Closure Closure) // Called when all projects introduced in settings.gradle have been created, Void projectsLoaded(Closure Closure) //settings.gradle is called after configuration. For settings.gradle Settings only void settingsEvaluated(Closure Closure)Copy the code
- BeforeProject ()/afterProject() equals beforeEvaluate and afterEvaluate in Project
- SettingsEvaluated () Settings script is executed and Settings object is configured
- ProjectsLoaded () All the projects participating in the build are created from the Settings
- ProjectsEvaluated () All projects involved in the build have been evaluated
We modify setting.gradle as follows:
gradle.settingsEvaluated {
println "Settings: Perform settingsEvaluated..."
}
gradle.projectsLoaded {
println "Settings: Execute projectsLoaded..."
}
gradle.projectsEvaluated {
println "Settings: executive projectsEvaluated..."
}
gradle.beforeProject { proj ->
println "Settings: execution${proj.name} beforeProject"
}
gradle.afterProject { proj ->
println "Settings: execution${proj.name} afterProject"
}
gradle.buildStarted {
println "Construction begins..."
}
gradle.buildFinished {
println "Build finished..."
}
include ":app"
Copy the code
The result is as follows:
Settings: Perform settingsEvaluated... Settings: Execute projectsLoaded... Settings: performtestBeforeProject Root project Configuration Start -- Task configuration in the root project -- End of root project configuration -- Settings: ExecutedtestAfterProject Settings: Execute app beforeProject subproject beforeEvaluate callback... APP subproject configuration start -- APP subproject task configuration -- APP subproject configuration end -- APP afterProject afterEvaluate callback Settings: executive projectsEvaluated... End of build...Copy the code
As you can see, gradle.beforeProject is similar to project.beforeEvaluate, and afterProject is similar to afterEvaluate. Gradle also has a general method for setting life cycle listeners: addListener
TaskExecutionGraph
After being configured, Gradle generates directed acyclic graphs of all tasks. These are called task execution graphs. They determine the order in which tasks are executed, etc. Gradle also listens for the execution life cycle of a task.
Void afterTask(Closure Closure) void beforeTask(Closure Closure) // Call void beforeTask(Closure Closure) // All tasks to be executed have dependencies established between tasks void whenReady(Closure closure)Copy the code
Get the task execution graph with the gradle.gettaskGraph () method:
TaskExecutionGraph taskGraph = gradle.getTaskGraph()
taskGraph.whenReady {
println "task whenReady"
}
taskGraph.beforeTask { Task task ->
println "Task Name:${task.name} beforeTask"
}
taskGraph.afterTask { Task task ->
println "Task Name:${task.name} afterTask"
}
Copy the code
The order in which lifecycle callbacks are executed:
gradle.settingsEvaluated-> gradle.projectsLoaded-> gradle.beforeProject-> project.beforeEvaluate-> gradle.afterProject-> project.afterEvaluate-> gradle.projectsEvaluated-> gradle.taskGraph.graphPopulated-> gradle.taskGraph.whenReady-> gradle.buildFinishedCopy the code
Determining the hook
The most commonly used android applicationVariants, related:
In BaseVariant
Our dynamic library copy is with getExternalNativeBuildTasks () and getNdkCompile (), so we have in our gradle:
def copyJniDebugLib() {
println 'Copy file copyJniLib'
mkdir "build/intermediates/cmake/debug/obj/armeabi"
copy {
from file("build/intermediates/cmake/debug/obj/armeabi-v7a"//into is a method that specifies the destination of the copy > into the root project's output directory"build/intermediates/cmake/debug/obj/armeabi"
}
}
def copyJniReleseLib() {
println 'Copy file copyJniReleseLib'
mkdir "build/intermediates/cmake/release/obj/armeabi"
copy {
from file("build/intermediates/cmake/release/obj/armeabi-v7a"//into is a method that specifies the destination of the copy > into the root project's output directory"build/intermediates/cmake/release/obj/armeabi"
}
}
project.afterEvaluate {
externalNativeBuildRelease.doLast {
copyJniReleseLib()
}
externalNativeBuildDebug.doLast {
copyJniDebugLib()
}
}
Copy the code
Copy from Armeabi-v7a to Armeabi was successfully completed.
Adds Gradle progression
Gradle’s own domain objects are projects and tasks. Projects provide execution context for tasks, and all plugins either add properties for configuration or different tasks to the Project. A Task represents a logically independent execution process, such as compiling Java source code, copying files, packaging Jar files, or even executing a system command or calling Ant. In addition, a Task can read and set a Project’s Property to perform a specific operation.
The core concept
Projectobject
A custom Plugin class is created by implementing the Plugin interface and taking org.gradle.api.Project as a template parameter. The org.gradle.api.Project instance object is passed as a parameter to the void Apply (Project Project) function. According to the official website, it can be seen that Project is the main interface to interact with Gradle. You can use all Gradle features through Project, and Project has a one-to-one relationship with build.grale. We know more about Project from the build. Gradle file in the Project directory (because it is actually the delegate of the Project object, When the build process starts, Gradle instantiates org.gradle.api.Project based on the configuration in build. Gradle, which is essentially a container containing multiple tasks. All tasks are stored in TaskContainer, and the class diagram of the Project object looks like this:
Project configuration
In the build.gradle script file, you can not only configure individual projects, but also define common logic for project blocks, as defined below.
Common examples
Allprojects {repositoresrepositories {jCenter () Google ()}} mavenPublish { groupId = maven.config.groupId releaseRepo = maven.config.releaseRepo snapshotRepo = maven.config.snapshotRepo } }Copy the code
Task
Gradle Task API The default name of a Gradle build script is build. Gradle. When running the Gradle command in a shell, Gradle will look for a file named build. Gradle in the current directory. An atomic operation in Gradle is called a task, which is simply the smallest executable unit in a Gradle script. The following is a class diagram for Task.
The Task of the Actions
A Task consists of a sequence of actions. When running a Task, the sequence of actions in the Task is executed in sequence
Several common ways to write Task
task myTask1 {
doLast {
println "doLast in task1"
}
}
task myTask2 << {
println "doLast in task2"} // Use the project.task(String name) method to create project.task("myTask3").doLast {
println "doLast in task3"} // Use the TaskContainer. Create (String name) method to create project.tasks.create("myTask4").doLast {
println "doLast in task4"
}
project.tasks.create("myTask5") << {
println "doLast in task5"
}
Copy the code
Task action declarations currently contain two main methods:
- DoFirst equivalent operation abbreviation leftShift << (5.0 will be deprecated)
- When doLast defines a Task in Gradle, it can specify additional parameters as follows: Parameter name | | meaning default value | | — – | — – | — – | | name | | task name must be specified, Can’t be empty | | type | task parent | the default value is org. Gradle. API. DefaultTask | | overwrite | will replace existing task with the same | false | | group | task | null | belonging to a group name | the description | task description | null | | dependsOn | | task dependent task set no | | constructorArgs | | | free constructor parameters
The Task dependency
The order in which tasks are executed in Gradle is indeterminate. With dependencies between tasks, Gradle ensures that the dependent task will be executed first by the current task. A dependsOn() method for a task allows us to declare one or more task dependencies for our task.
task first{
doLast{
println("first")
}
}
task second{
doLast{
println("second")
}
}
task third{
doLast{
println("third")
}
}
task test(dependsOn:[second,first]){
doLast{
println("test1")
}
}
third.dependsOn(test)
Copy the code
Order of execution:
> Task :app:first
> Task :app:second
> Task :app:test1
> Task :app:third
Copy the code
The type of Task
Refer to Doc documentation for copy, JAR, Delete, and more
task copyFile(type: Copy) {
from 'xml'
into 'destination'
}
Copy the code
The custom Task
Gradle keyword to create through the task in the task, the default superclass is org. Gradle. API. DefaultTask, here defines some default behavior of the task. Consider the following example:
// A custom Task class that extends DefaultTask class SayHelloTask {String MSG ="default name"Int age = 18 // Constructor must be identified with @javax.inject.Inject annotation @javax.inject.Inject SayHelloTask(int age) {this.age = age} // Use the @taskAction annotation to identify the Task's action @taskAction voidsayHello() {
println "Hello $msg ! age is ${age}"}} task hello1() {constructorArgs () {task hello1();type: SayHelloTask, constructorArgs: [30]) // PasstypeThe task hello2() parameter specifies the parent class of the task, which can be modified in the configuration code.type: SayHelloTask, constructorArgs: [18]) {constructorTask (SayHelloTask, constructorArgs: [18]) {constructorTask (SayHelloTask, constructorArgs: [18])"hjy"
}
Copy the code
Execute these two tasks:
> Task :hello1
Hello default name ! age is 30
> Task :hello2
Hello hjy ! age is 18
Copy the code
The Task of class diagram
Gradle said is a Task of org. Gradle. API. Task interface, the default implementation is org. Gradle. API. DefaultTask class, the class diagram below
TaskContainer Interface parsing
TaskContianer is used to manage a collection of Task instances. You can get TaskContainer instances from project.gettasks ().
Org. Gradle. API. The tasks. TaskContainer interface: / / search task findByPath path: (String) : task getByPath (path: String) : Task getByName(name: String): Task withType(type: Class): TaskCollection matching(condition: Closure): TaskCollection matching(condition: Closure): TaskCollection matching(condition: Closure): TaskCollection matching(condition: Closure): TaskCollection matching(condition: Closure): TaskCollection matching String, configure: Closure): Task create(name: String,type: Class): Task create(options: Map<String, ? >): Task create(options: Map<String, ? >, configure: Closure): Task // Listen whenTaskAdded when tasks are added to TaskContainer (action: Closure) // getTasks(). WhenTaskAdded {task task -> println"The task ${task.getName()} is added to the TaskContainer"} // use create(name: String) to create getTasks().create("task1") // Use create(options: Map<String,? >) create getTasks (). Create ([name:"task2", group: "MyGroup", description: "This is the task2 description", dependsOn: ["task1"]) // create(options: Map<String,? >, configure: Closure) create getTasks().create()"task3", {
group "MyGroup"
setDependsOn(["task1"."task2"])
setDescription "This is task3 description"
})
Copy the code
By default, our common task is org. Gradle. API. DefaultTask type. However, gradle has a rich set of task types that we can use directly. To change the type of task, we can refer to the following example
task createDistribution(type:Zip){
}
Copy the code
For more information about the types of tasks, see gradle’s official documentation
Common Task:
Copy
Copy of the API documentation: docs.gradle.org/current/dsl… Replace the AndroidManifest file
task chVer(type: Copy) {// Specify Type as Copy task from"src/main/manifest/AndroidManifestCopy.xml"// Copy the androidmanifest.xml into directory SRC /main/manifest/'src/main'// Copy to the specified destination directory rename {String fileName -> // Rename the file fileName = when copying"AndroidManifest.xml"// Rename}}Copy the code
Replace so file
task chSo(type: Copy) {
from "src/main/jniLibs/test"/ / copytestFolder under all so files into"src/main/jniLibs/armeabi-v7a"// Copy to armeabi-v7a folder}Copy the code
Sync
This task is similar to the Copy task except that the source files are copied to the destination directory. All non-copied files in the destination directory will be deleted unless sync.preserve (org.gradle.api.action) is specified. Example:
task syncDependencies(type: Sync) {
from 'my/shared/dependencyDir'
into 'build/deps/compile'} // You can protect files that already exist in the target directory. Matching files will not be deleted. task sync(type: Sync) {
from 'source'
into 'dest'
preserve {
include 'extraDir/**'
include 'dir1/**'
exclude 'dir1/extra.txt'}}Copy the code
Sync API documentation: docs.gradle.org/current/dsl…
Zip
Create a ZIP archive file. The default compressed file type is ZIP. Example:
task zip(type: Zip) {
from 'src/dist'
into('libs')}Copy the code
Zip the API documentation: docs.gradle.org/current/dsl…
Task skills
Do you want to do multiple tasks if you have multiple tasks to do? You can call it once with a multitasking command.
gradlew task1 task2 [...]
Copy the code
Task name too long do not want to input so many words how to do? You can use simplified operations, but you must make sure that the characters that distinguish the task are unique, such as:
gradlew cV
Copy the code
What if you don’t want to type a command every time you pack? Custom tasks can be automatically executed each time you build.
AfterEvaluate {tasks.matching {// Start with process and end with ReleaseJavaRes or DebugJavaRes task date.name.'process') && (it.name.endsWith('ReleaseJavaRes') || it.name.endsWith
('DebugJavaRes'DependsOn (chVer, chSo))}. Each {task -> task. DependsOn (chVer, chSo)}}Copy the code
The complete build.gradle code:
apply plugin: 'com.android.application'
android {
compileSdkVersion 23
buildToolsVersion "23.0.3"
defaultConfig {
applicationId "com.skr.voip"
minSdkVersion 15
targetSdkVersion 19
}
buildTypes {
debug {
minifyEnabled false
}
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
}
dependencies {
// compile fileTree(dir: 'libs', include: ['*.jar'])
compile 'com. Android. Support: support - v4:23.4.0'. } task chVer(type: Copy) {
from "src/main/manifest/AndroidManifestCopy.xml"// Copy the androidmanifest.xml into directory SRC /main/manifest/'src/main'// Copy to the specified destination directory rename {String fileName -> // Rename the file fileName = when copying"AndroidManifest.xml"}} task chSo(type: Copy) {
from "src/main/jniLibs/test"/ / copytestFolder under all files into"src/main/jniLibs/armeabi-v7a"} afterEvaluate {tasks.matching {// Start with process and end with ReleaseJavaRes or DebugJavaRes it.name.startsWith('process') && (it.name.endsWith('ReleaseJavaRes') || it.name.endsWith
('DebugJavaRes'DependsOn (chVer, chSo))}. Each {task -> task. DependsOn (chVer, chSo)}}Copy the code
Incremental build of Task
Gradle supports a feature called up-to-date checking, also known as incremental builds. Gradle tasks cache the results of each run. The next run checks for any changes in the output and skips the run if there are no changes. This improves the build speed of Gradle. A task has inputs and outputs that affect its output. For example:
TaskInputs and TaskOutputs are introduced
How do you implement an incremental build that specifies at least one input and one output, task.getinputs () of type TaskInputs, task.getOutputs () of type TaskOuputs, Inputs and outputs support all types of data
task test1 {// Set inputs elsions. property("name"."hjy")
inputs.property("age", 20) // set elsion.file ("$buildDir/test.txt")
doLast {
println "exec task task1"
}
}
task test2 {
doLast {
println "exec task task2"}} // First run result > Task:test1
exec task task1
> Task :test2
exec task task2
BUILD SUCCESSFUL in0s 2 actionable tasks: 2 executed // Result of the second run > Task:test2
exec task task2
BUILD SUCCESSFUL in 0s
2 actionable tasks: 1 executed, 1 up-to-date
Copy the code
As you can see from the results, test1 Task does not run on the second run, but is marked as up-to-date, while test2 Task runs every time, which is a typical incremental build.
TaskInputs, taskOutputs note
Incremental builds can be implemented through Task annotations, which is a more flexible and convenient approach
Annotation name | Attribute types | describe |
---|---|---|
@Input | Arbitrary Serializable type | A simple input value |
@InputFile | File | An input file, not a directory |
@InputDirectory | File | An input directory, not a file |
@InputFiles | 可迭代 | File list containing files and directories |
@OutputFile | File | An output file, not a directory |
@OutputDirectory | File | An output directory, not a file |
@OutputFiles | Map < String, the File > or Iterable | Output file list |
@OutputDirectories | Map < String, the File > or Iterable | Output directory list |
Class SayHelloTask extends DefaultTask {// define @input String username; @input int age @outputDirectory File destDir; @TaskAction voidsayHello() {
println "Hello $username ! age is $age"
}
}
task test(type: SayHelloTask) {
age = 18
username = "hjy"
destDir = file("$buildDir/test")}Copy the code
Property
Ext namespace
Many model classes in Gradle provide special attribute support, such as Project. Inside Gradle, these properties are stored as key-value pairs. Using the Ext namespace, we can easily add attributes. The following methods are supported:
// Add a property named groupId in project called project.ext.groupid ="tech.easily"// Use ext block to add attribute ext{artifactId='EasyDependency'
config=[
key:'value']}Copy the code
Note that we only need to use the Ext namespace when declaring attributes, and that the ext namespace can be omitted when using attributes.
Properties file
As we often see in Android projects, we can create a new gradle.properties file at the root of the project and define simple key-value pair properties in the file. These properties can be accessed by gradle scripts in your project. As follows:
# gradle.properties
Note that the file's comments begin with #GroupId =tech. Easily artifactId=EasyDependency Copy codeCopy the code
The java.util.properties class can be used to dynamically create Properties files in code (for example, when customizing plug-ins) and read Properties from the files. Such as:
void createPropertyFile() {
def localPropFile = new File(it.projectDir.absolutePath + "/local.properties")
def defaultProps = new Properties()
if (!localPropFile.exists()) {
localPropFile.createNewFile()
defaultProps.setProperty("debuggable".'true')
defaultProps.setProperty("groupId", GROUP)
defaultProps.setProperty("artifactId", project.name)
defaultProps.setProperty("versionName", VERSION_NAME)
defaultProps.store(new FileWriter(localPropFile), "properties auto generated for resolve dependencies")}else {
localPropFile.withInputStream { stream ->
defaultProps.load(stream)
}
}
}
Copy the code
The important thing about properties is that properties can be inherited. Attributes defined in a project are automatically inherited by its children, regardless of which way we add attributes.
ExtensionContainer
The Extension profile
Gradle Extension is a Gradle Extension. By implementing a custom Extension, you can add android-like namespaces to Gradle scripts. Gradle can recognize these configurations and read them. Extensions are typically created using ExtensionContainer, a class that has a similar name to TaskContainer. TaskContainer is used to create and manage tasks, while ExtensionContainer is used to create and manage Extensions. ExtensionContainer objects are available through the following API of Project
ExtensionContainer getExtensions()
Copy the code
The Extension of simple
/ Define a normal Java class with two attributes class Foo {int age String username StringtoString() {
return "name = ${username}, age = ${age}"}} // Create an Extension getExtensions() called foo. Create ("foo", Foo) // Configure Extension Foo {age = 30 username ="hjy"
}
task testExt. DoLast {// Extension println project.foo}Copy the code
Foo is our custom Extension. The properties that can be configured in foo are the same as the fields in class foo. In build.gradle, you can access them directly through project. Each Extension is actually associated with a class, defined by a DSL in build.gradle, and Gradle will parse and generate an instance of an object from which we can retrieve our configured information. Project has an extended attribute configured through the Ext namespace. You can see ext is similar to this, except that ext can configure the attribute value of any key value pair. Here, we can only recognize the attribute value of the Java class we defined.
ExtensionContainer Main API and usage
<T> T create(String name, Class<T> type, Object... constructionArguments)
<T> T create(Class<T> publicType, String name, Class<? extends T> instanceType, Object... constructionArguments)
Copy the code
Let’s take a look at what all the API parameters mean.
- PublicType: The class type exposed by the Extension instance being created;
- Name: Indicates the name of the Extension to be created. The value can be any string that complies with the naming rules. Otherwise, an exception will be thrown.
- InstanceType: The class type of this Extension;
- ConstructionArguments: The constructor argument value of a class
One feature described in the official documentation is that Extension objects created by default implement the ExtensionAware interface and cite the source. The sample
// class Animal {String username int legs Animal(String name) {username = name} voidsetLegs(int c) {
legs = c
}
String toString() {
return "This animal is $username, it has ${legs} legs."Pig extends Animal {int age String owner (int age, String owner) {super(int age, String owner)"Pig")
this.age = age
this.owner = owner
}
String toString() {
return super.toString() + " Its age is $age, its owner is $owner."} // Create Extension with Animal type exposed. Create Extension with Animal type exposed. Animal aAnimal = getExtensions().create(Animal, tensions)"animal", Pig, 3, "hjy"Pig aPig = getExtensions().create(tensions, that is, over the tensions of aPig"pig", Pig, 5, "kobe"Animal {legs = 4} pig {setLegs 2 // This is the method call, i.esetLegs(2)
}
task testExt << {println aAnimal println aPig // Verify that the aPig object is a println of ExtensionAware type"aPig is a instance of ExtensionAware : ${aPig instanceof ExtensionAware}"
}
Copy the code
Increase the Extension
- The create() method creates and returns an Extension object,
- The add() method, the only difference is that it does not return an Extension object.
getExtensions().add(Pig, "mypig", new Pig(5, "kobe"))
mypig {
username = "MyPig"
legs = 4
age = 1
}
task testExt << {
def aPig = project.getExtensions().getByName("mypig")
println aPig
}
Copy the code
Find the Extension
Object findByName(String name)
<T> T findByType(Class<T> type<T> T getByType(Class<T>)type) // Can not find the exceptionCopy the code
Nested Extension method 1
A configuration like the following should be everywhere:
outer {
outerName "outer"
msg "this is a outer message."
inner {
innerName "inner"
msg "This is a inner message."}}Copy the code
It can be created in the following way
class OuterExt { String outerName String msg InnerExt innerExt = new InnerExt() void outerName(String name) { outerName MSG (String MSG) {this. MSG = MSG} Inner void inner(Action<InnerExt> Action) {action.execute(inner)} Name of the method name inner void inner Closure (c) {org. Gradle. Util. ConfigureUtil. Configure (c, innerExt)} StringtoString() {
return "OuterExt[ name = ${outerName}, msg = ${msg}]. "" + innerExt
}
}
class InnerExt {
String innerName
String msg
void innerName(String name) {
innerName = name
}
void msg(String msg) {
this.msg = msg
}
String toString() {
return "InnerExt[ name = ${innerName}, msg = ${msg}]. ""
}
}
def outExt = getExtensions().create("outer", OuterExt)
outer {
outerName "outer"
msg "this is a outer message."
inner {
innerName "inner"
msg "This is a inner message."
}
}
task testExt doLast {
println outExt
}
Copy the code
The key lies in the following methods
void inner(Action<InnerExt> action)
void inner(Closure c)
Copy the code
Inner defined inside outer,Gradle essentially calls outer. Inner (…) Method, whose argument is a Script Block, so you must define an inner method in the OuterExt class
Nested Extension mode 2 (NamedDomainObjectContainer)
Usage scenarios
Gradle Extension defines buildTypes as follows:
private final NamedDomainObjectContainer<BuildType> buildTypes;
Copy the code
BuildTypes is NamedDomainObjectContainer types, let’s look at buildTypes in Android is how to use, should be very familiar with the following code, which defines the debug, relase two kinds of packaging mode:
Android {buildTypes {release {// Whether to enable confounding minifyEnabledtrue// Enable ZipAlign to optimize zipAlignEnabledtrue// Remove shrinkResourcestrueProguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'Debug {signingConfig signingConfigs.hmiou}}Copy the code
When we create a new project, there will be two configurations: DEBUG and release by default. Can other names be changed for debug and release? Can other names be added to configure debug and release? If in doubt, you can actually verify:
- Debug and Release can be changed to other names, you can replace them with the name you like;
- You can add configurations with any number of different names, such as dev for a dev package;
- Configurable property can be the reference interface: com. Android. Builder. Model. The BuildType;
You can see how flexible it is to define different configurations for different scenarios, and each different namespace generates a BuildType configuration. To realize this function, you must use NamedDomainObjectContainer types.
What is NamedDomainObjectContainer just as its name implies is named domain object container, its main features are:
- Create an object instance of the specified type through the DSL
- The specified type must have a public constructor and must take a String name argument
- It is a container that implements the SortedSet interface, so the name attribute of all domain objects must be unique and sorted inside the container using the name attribute
named domain object container is a specialisation of NamedDomainObjectSet that adds the ability to create instances of the element type. Note that a container is an implementation of SortedSet, which means that the container is guaranteed to only contain elements with unique names within this container. Furthermore, items are ordered by their name.
Create NamedDomainObjectContainer
NamedDomainObjectContainer needed by the Project. The container (…). API, which is defined as:
<T> NamedDomainObjectContainer<T> container(Class<T> type)
<T> NamedDomainObjectContainer<T> container(Class<T> type, NamedDomainObjectFactory<T> factory)
<T> NamedDomainObjectContainer<T> container(java.lang.Class<T> type, Closure factoryClosure
Copy the code
Let’s take a concrete example:
Class TestDomainObj {// You must define a name attribute, Public TestDomainObj(String name) {this.name = name} void public TestDomainObj(String name) {this msg(String msg) { this.msg = msg } StringtoString() {
return "name = ${name}, msg = ${msg}"}} / / create an extended class TestExtension {/ / define a NamedDomainObjectContainer attribute NamedDomainObjectContainer < TestDomainObj >testDomains public TestExtension(Project Project) {// Through project.container(...) Method to create NamedDomainObjectContainer NamedDomainObjectContainer < TestDomainObj > domainObjs = project. The container (TestDomainObj)testDomains = domainObjs} // Let it support Gradle DSL syntax voidtestDomain(Action<NamedDomainObjectContainer<TestDomainObj>> action) {
action.execute(testDomains)
}
void test() {// Iterate through the named domain object container, printing out all the domain object valuestestDomains. All {data -> println data}}} // create a name named DomainstestThe Extension of deftestExt = getExtensions().create("test", TestExtension, project)
test {
testDomain {
domain2 {
msg "This is domain2"
}
domain1 {
msg "This is domain1"
}
domain3 {
msg "This is domain3"
}
}
}
task myTask doLast {
testExt.test()
}
Copy the code
The running results are as follows:
name = domain1, msg = This is domain1
name = domain2, msg = This is domain2
name = domain3, msg = This is domain3
Copy the code
Find and traverse
NamedDomainObjectContainer since is a container class, corresponding there will always be looking for container elements and ways to traverse the container:
Void all(Closure Action) void all(Closure Action) // findByName(String name) // findByName(String name) // findByName(String name)Copy the code
To continue with the previous example:
// Find TestDomainObj by nametestData = testDomains.getByName("domain2")
println "getByName: ${testData}"// Iterate through the named domain object container, printing out all the domain object valuestestDomains.all { data ->
println data
}
Copy the code
Note that Gradle iterates over container classes using each(Closure Action) and all(Closure Action), but we usually use all(…) for Closure action. To iterate over the container. all(…) What’s special about the iterative approach is that it iterates over elements that already exist in the container, as well as elements that are added at any subsequent time.
The Extension of the Android
We see android{} in Gradle.
android {
compileSdkVersion 29
buildToolsVersion "29.0.2"
defaultConfig {
applicationId "com.xxx.sdk.plugin"
minSdkVersion 21
targetSdkVersion 29
versionCode 1
versionName "1.0"
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'}}}Copy the code
DefaultConfig, productFlavors, signingConfigs, and buildTypes are flavors that can be found on the BaseExtension class.
private final DefaultConfig defaultConfig;
private final NamedDomainObjectContainer<ProductFlavor> productFlavors;
private final NamedDomainObjectContainer<BuildType> buildTypes;
private final NamedDomainObjectContainer<SigningConfig> signingConfigs;
public void defaultConfig(Action<DefaultConfig> action) {
this.checkWritability();
action.execute(this.defaultConfig);
}
public void buildTypes(Action<? super NamedDomainObjectContainer<BuildType>> action) {
this.checkWritability();
action.execute(this.buildTypes);
}
public void productFlavors(Action<? super NamedDomainObjectContainer<ProductFlavor>> action) {
this.checkWritability();
action.execute(this.productFlavors);
}
public void signingConfigs(Action<? super NamedDomainObjectContainer<SigningConfig>> action) {
this.checkWritability();
action.execute(this.signingConfigs);
}
Copy the code
For build.gradle we use the apply plugin: ‘com.android.application’ and for library Module we use the apply plugin: Library ‘com.android. Library ‘,AppPlugin is the implementation class of com.android. Application, LibraryPlugin is the implementation class of com.android. Library. Let’s look at how an Extension is created in AppPlugin:
public class AppPlugin extends BasePlugin implements Plugin<Project> {
@Inject
public AppPlugin(Instantiator instantiator, ToolingModelBuilderRegistry registry) {
super(instantiator, registry);
}
protected BaseExtension createExtension(Project project, ProjectOptions projectOptions, Instantiator instantiator, AndroidBuilder androidBuilder, SdkHandler sdkHandler, NamedDomainObjectContainer<BuildType> buildTypeContainer, NamedDomainObjectContainer<ProductFlavor> productFlavorContainer, NamedDomainObjectContainer<SigningConfig> signingConfigContainer, NamedDomainObjectContainer<BaseVariantOutput> buildOutputs, ExtraModelInfo extraModelInfo) {
return (BaseExtension)project.getExtensions().create("android", AppExtension.class, new Object[]{project, projectOptions, instantiator, androidBuilder, sdkHandler, buildTypeContainer, productFlavorContainer, signingConfigContainer, buildOutputs, extraModelInfo}); } public void apply(Project project) { super.apply(project); } // omit... }Copy the code
In the createExtension() method, you can see that you have created an Extension called Android of type AppExtension, AppExtension’s inheritance structure is AppExtension -> TestedExtension -> BaseExtension, so most of its implementation logic is implemented in BaseExtension. In the build.gradle file of the Android project, we configure relevant information using the Android {} node. From the AppPlugin, we can also see that the name of its Extension is Android, so the method of obtaining is as follows:
- project.extensions.getByName
- project.extensions.getByType
def getInfo() {/ / or directly to the project. The android BaseExtension extension = project. Extensions. GetByName ("android")
def android = project.extensions.getByType(AppExtension)
project.android
println "buildToolsVersion:${extension.buildToolsVersion}"
println "compileSdkVersion:${extension.getCompileSdkVersion()}"
println "applicationId:${extension.defaultConfig.applicationId}"
println "minSdkVersion:${extension.defaultConfig.minSdkVersion}"
println "targetSdkVersion:${extension.defaultConfig.targetSdkVersion}"
println "versionCode:${extension.defaultConfig.versionCode}"
println "versionName:${extension.defaultConfig.versionName}"
}
Copy the code
For more details, please refer to ASL
Gradle tip
Prints all tasks that a task depends on
Add the following code to build. Gradle that contains uploadArchives Task to print the uploadArchives dependencies.
void printTaskDependency(Task task) {
task.getTaskDependencies().getDependencies(task).any() {
println("> >${it.path}")
printTaskDependency(it)
}
}
gradle.getTaskGraph().whenReady {
printTaskDependency project.tasks.findByName('uploadArchives')}Copy the code
Run any gradle command./gradlew clean for your convenience to view the printed logs. UploadArchives relies on tasks.
Configuration on demand is an incubating feature. >>:opuslib:bundleReleaseAar >>:opuslib:mergeReleaseConsumerProguardFiles >>:opuslib:mergeReleaseGeneratedProguardFiles >>:opuslib:compileReleaseJavaWithJavac >>:opuslib:javaPreCompileRelease >>:opuslib:preReleaseBuild >>:opuslib:preBuild >>:opuslib:generateReleaseBuildConfig >>:opuslib:checkReleaseManifest >>:opuslib:preReleaseBuild ... >>:opuslib:preBuild >>:opuslib:preReleaseBuild >>:opuslib:preBuild >>:opuslib:preReleaseBuild >>:opuslib:preBuild >>:opuslib:androidSourcesJar >>:opuslib:androidJavadocsJar >>:opuslib:androidJavadocs >>:opuslib:androidSourcesJar >>:opuslib:androidJavadocsJar >>:opuslib:androidJavadocs > Task :opuslib:externalNativeBuildCleanDebug Clean ljmedia-lib armeabi Cleaning... 0 files. Clean ljmedia-lib armeabi-v7a Cleaning... 0 files. Clean ljmedia-lib arm64-v8a Cleaning... 0 files. Clean ljmedia-lib x86 Cleaning... 0 files. Clean ljmedia-lib x86_64 Cleaning... 0 files. Clean ljmedia-lib mips Cleaning... 0 files. Clean ljmedia-lib mips64 Cleaning... 0 files. > Task :opuslib:externalNativeBuildCleanRelease Clean ljmedia-lib armeabi Cleaning... 1 files. Clean ljmedia-lib armeabi-v7a Cleaning... 145 files. Clean ljmedia-lib arm64-v8a Cleaning... 145 files. Clean ljmedia-lib x86 Cleaning... 145 files. Clean ljmedia-lib x86_64 Cleaning... 145 files. Clean ljmedia-lib mips Cleaning... 0 files. Clean ljmedia-lib mips64 Cleaning... 0 files.Copy the code
reference
Source Code Analysis
- Android Gradle Plugin source code analysis
- Gradle Series 2 – Source code analysis
- Android Gradle Plugin source code analysis
- Android Plugin with Gradle
Plug-in Development
- Gradle common plug-in development mode
- Gradle plug-ins go from beginner to advanced
- Embrace Android Studio 5 :Gradle Plugin Development
- Custom Gradle plugin, how to hook system task and bytecode
Task related
- www.jianshu.com/p/cd1a78dc8…
- www.ezlippi.com/blog/2015/0…
- Blog.csdn.net/lzyzsd/arti…
Grammar related
Gradle Development Quick Start -DSL syntax principles and common API introduction