The author

Hello everyone, my name is Jack Feng; I graduated from Guangdong University of Technology with a master’s degree in 20 years. I joined the 37 Mobile Games Android team in June 2020. Currently, I am mainly responsible for the development of overseas game release and Android.

Gradle

  • What is Gradle and what does it do?
  • Android Studio’s default build tool for building applications. The components are shown below:

  • Groovy core syntax: closures, data structures, and so on
  • Build script block: a script for the build.gradle project
  • Gradle API: Project, Task, Plugin, etc

2. Groovy Basics

Groovy features:

– JVM-based development language, execute: Groovy source file == “class bytecode ==” JVM processing execution; Or Groovy source file directly parsed execution (similar to Script) – seamlessly integrates all Java class libraries, but scripts are much cleaner than Java

1. String

(1) Definition of use

String (java.lang.string) + GString (Groovy String), common use of single quotation marks, double quotation marks, triple quotation marks note: single quotation marks are the same as Java double quotation marks. Double quotes, support parameter extension (implementation class will become GString), the extended string can be any expression, i.e. “${arbitrary expression}”; Three quotation marks, arbitrary format, no need to escape characters, specified output.

Sample code:

def str = 'a single \' \' " "string'
def str2 = "a double ' ' \" \" " +
        "string "
def str3 = '''a thuple ' ' " " 
string'''

println str
println str2
println str3

println str.class
println str2.class
println str3.class

def str4 = "double string : ${str2}"
println str4.class
Copy the code

Output result:

a single ' ' " "string
a double ' ' " " string 
a thuple ' ' " " 
string

class java.lang.String
class java.lang.String
class java.lang.String

class org.codehaus.groovy.runtime.GStringImpl
Copy the code

2, extension,

There are many ways to extend strings, from the following sources:

  • Java.lang. String: A Java native method.

  • DefaultGroovyMethods: A Groovy extension for all objects.

  • StringGroovyMethods: Inherits DefaultGroovyMethods and overrides the methods applicable to String usage. Here is an excerpt of the source code:

    package org.codehaus.groovy.runtime; public class StringGroovyMethods extends DefaultGroovyMethodsSupport { ... Capitalize the first letter of a String Public static String Capitalize (String self){.. } // Create a new CharSequence, which is the reverse of the string (backwards) public static CharSequence reverse(CharSequence self){.. } // iterate through the string line by line. public static <T> T eachLine(String self, int firstLine, @ClosureParams(value=FromString.class, options={"String","String,Integer"}) Closure<T> closure){.. } // Returns the result of calling the closure the first time a compiled regular expression appears in a string. public static String find(String self, Pattern pattern, @ClosureParams(value=SimpleType.class, options="java.lang.String[]") Closure closure) {.. }... }Copy the code

(1) Common type parameters use:

Def STR = "groovy Hello" // specify length and padding characters, Println str.center(18,'a') println str.padleft (18,'a') // String comparison operator def str2 = 'Hello' println STR > str2 Println STR [0] // Get a string println STR [0..1] // Delete the string println str-str2 // Print the string in reverse println str.reverse() Println str.isNumber() def str3 = "123" println str.capitalize() println str3.toInteger() println str3.toInteger().classCopy the code

(2) Combination with closure: The following example executes the find closure method output by passing a string argument path to the closure (of type Task).

task findFile{ String path = getRootDir().absolutePath+File.separator+"build.gradle" path.find { String filePath -> File  file = new File(filePath) if (file.exists()){ println "build.gradle in rootDir exists!" }}} // Compare the definition of a normal closure with def findFile = {... } findFile.call()Copy the code

Output result:

10:15:29: Executing task 'findFile'... Initialization begins... Configure project :Project01 build.gradle in rootDir exists! > Task :Project01:findFile up-to-date Gradle BUILD SUCCESSFUL in 93ms 10:15:30: Task Execution Finished 'findFile'Copy the code

Differences with Java (1) Differences with Java (2) Differences with Java (2)

2. Closure basics

A closure is essentially a block of code. Here are the basics of closures, which include:

  • Closure concept: Definition and invocation of closures
  • Closure parameters: normal parameters, implicit parameters
  • Closure return value: There is always a return value

(1) Definition

Definition and call: Parameters -> Body

${name} ${name} ${name} ${name} ${name} ${name} ${name} ${name} ${name} ${name} ${name} Def MyClouser = {String names,int ages -> println "2, println MyClouser:heloo ${names}, my ages is ${ages} "} def names = 'my_clouser' MyClouser(names,100) Def intclouser = {println "println intclouser: Hello ${it}"} itClouser('it_clouser') // Return null def returnClouser = {println "4, println returnClouser:hello ${it}" // return "hello ${it}"} def result = ReturnClouser ('return_clouser') printlnCopy the code

Output result:

1, println:clouser test 1, println:clouser call 2, println: MyClouser:heloo my_clouser, My ages is 100 3, println itClouser: Hello it_clouser 4, println returnClouser:hello return_clouser 5, println returnClouser result: nullCopy the code

(2) Use

Example: string in combination with closures

String STR = 'the 2 and 3 is 5' //1, multiply each {String temp -> print temp. Multiply (2)} Find {String s -> s.number ()} def list = str.findall {String s -> s.number ()} String s -> s.isnumber ()} println list.tolistString () def anyresult = str.any {String s -> s.isnumber (); Def everyresult = str.every {String s -> s.isnumber ()} println everyresult = str.every {String s -> s.isnumber () Def list2 = str.collect {it.toupperCase ()} println list2.tolistString ()Copy the code

Output result:

tthhee  22  aanndd  33  iiss  55
2
[2, 3, 5]
true
false
[T, H, E,  , 2,  , A, N, D,  , 3,  , I, S,  , 5]
Copy the code

(3) Closure variables

1) Before introducing the closure delegate strategy, here are three important variables for closures.

If you define a closure in a class or method, the values of the three variables (this, owner, delegate) are the same.

However, if the closure is nested within a closure, the values to which this and owner and delegate point are different, and if the delegate variable point is changed separately, all three values are different.

  • This, representing the class at the closure definition, cannot be modified
  • Owner, representing the class or object at the closure definition, cannot be modified
  • Delegate: represents any object. The default value is the same as that of owner and can be modified

Here you define closures in a class or method,

def scriptClouser = {
    println "scriptClouser this : " + this
    println "scriptClouser owner : " + owner 
    println "scriptClouser delegate : " + delegate 
}
scriptClouser.call()
Copy the code

Output result:

scriptClouser this : pkg.character01@5be067de
scriptClouser owner : pkg.character01@5be067de
scriptClouser delegate : pkg.character01@5be067de
Copy the code

2) Inner class correlation

Class Person{def static classClouser = {println "classClouser this: "+ this println "classClouser owner: " + owner println "classClouser delegate : " + delegate } def static say(){ def methodClouser = { println "methodClouser this : " + this println "methodClouser owner : " + owner println "methodClouser delegate : Call () {person.classclouser.call () {person.say ();Copy the code

Output result:

classClouser this : class pkg.Person
classClouser owner : class pkg.Person
classClouser delegate : class pkg.Person
methodClouser this : class pkg.Person
methodClouser owner : class pkg.Person
methodClouser delegate : class pkg.Person
Copy the code

If the method’s static declaration is removed, the output person points to some concrete object of the current class.

class Person{ def classClouser = {... } def say(){... }} / / 2, non static method call sample Person innerPerson = new Person () innerPerson. ClassClouser. Call () innerPerson. Say ()Copy the code

Output result:

classClouser this : pkg.Person@5df417a7
classClouser owner : pkg.Person@5df417a7
classClouser delegate : pkg.Person@5df417a7
methodClouser this : pkg.Person@5df417a7
methodClouser owner : pkg.Person@5df417a7
methodClouser delegate : pkg.Person@5df417a7
Copy the code

3) Special case: closure of closure

Here, this refers to the class that defines the closure; Owner points to the instance object of nestClouser, and delegate points to the nearest closure object. You can specify innerClouser’s delegate separately, as shown in the following example.

Def nestClouser = {def innerClouser = {println "innerClouser this: " + this println "innerClouser owner : " + owner println "innerClouser delegate : " + delegate } //innerClouser.delegate = innerPerson innerClouser.call() } nestClouser.call()Copy the code

Output result:

innerClouser this : pkg.character01@224b4d61
innerClouser owner : pkg.character01$_run_closure1@5ab14cb9
innerClouser delegate : pkg.character01$_run_closure1@5ab14cb9
Copy the code

If you specify a delegate separately, the output of all three will be different:

innerClouser this : pkg.character01@7c041b41
innerClouser owner : pkg.character01$_run_closure1@361c294e
innerClouser delegate : pkg.Person@7859e786
Copy the code

(4) Delegation strategy

There are four delegate policies in a closure: OWNER_FIRST (default), DELEGATE_FIRST, OWNER_ONLY, and DELEGATE_ONLY. The default policy indicates that variables, methods, and so on in a closure are first sought from the object pointed to by the owner. By changing the delegate pointing to the object and specifying different delegate policies, you can specify which object the closure looks for invoked variables and methods from first.

The following example changes the delegate policy to closing.delegate_first to look first for variable method properties with the same name from the delegate object and return the Owner to the query if the delegate object is not found.

class ClosureOutput{ String name def method = { "The output of this time is ${name}" } String toString(){ method.call() } } class ClosureDelegationOutput{ String name } def output01 = new ClosureOutput(name:'ClosureOutput') def output02 = New ClosureDelegationOutput(name:'ClosureDelegationOutput') println "output01: "+output01.toString() // Modify the delegate object, add the delegate policy, Since the delegate for output01. Method. The delegate = output02 output01. Method. ResolveStrategy = Closure. DELEGATE_FIRST println "Output01:" + output01. ToString ()Copy the code

Output result:

Output01: The output of this time is ClosureOutput Output01: The output of this time is ClosureDelegationOutputCopy the code

Note: If there is no ClosureDelegationOutput method with the same parameter name and the delegate policy is closureDelegate_only, Throws an exception groovy. Lang. MissingPropertyException.

Life cycle

1. Implementation phase

Gradle’s execution process is divided into three phases

  • Initialization: Parses all projects in the whole project and builds all project objects
  • Configuration phase: Tasks of all project objects are parsed and dependency diagrams of all tasks are constructed
  • Execution: Execution of specific tasks and their dependent tasks

2. Listen for examples

In order to facilitate tracking the execution of each stage, log printing is added to each node.

Settings. gradle for global configuration. Add:

Println 'initialization begins... 'Copy the code

Then there is configuration, execution phase listening. In the root directory build. Gradle add:

BeforeEvaluate {project project -> println "$project start configuration phase..." } this. AfterEvaluate {Project evaluate -> println "$Project complete..." } / / configuration phase monitoring (including other project) this. Gradle. BeforeProject {project project - > println "$project preparation configuration..." } this.gradle.afterProject {Project Project -> println "$Project configuration end..." } / / configured gradle. TaskGraph. WhenReady {TaskExecutionGraph taskGraph - > println "configuration phase, TaskExecutionGraph is ready..." If (taskGraph hasTask (taskZ)) {lib1, dependsOn taskZ}} / / execution phase listening in gradle. TaskGraph. BeforeTask {Task Task - > println" $task starts executing..." } gradle. TaskGraph. AfterTask {Task Task, TaskState state - > if (state. Failure) {println "$failed to perform the Task." } else {println "$task completes execution..." }} // Callback listening after the execution phase, Operating this individual files. Gradle. BuildFinished {println 'execution phase end fileTree ('/project01 / build/libs/') {fileTree fileTree - > fileTree.visit { FileTreeElement fileTreeElement -> copy { from fileTreeElement.file into getRootProject().getBuildDir().path + '/testFiletree/' } } } }Copy the code

You can also add other listeners:

//this.gradle.addListener() //this.gradle.addProjectEvaluationListener() this.gradle.addBuildListener(new BuildListener() {@override void buildStarted(Gradle Gradle) {println 'start building'} @override void SettingsEvaluated (Settings Settings) {println 'settings.gradle '} @override void projectsLoaded(gradle gradle) {println 'Initialization phase ends'} @override void projectsEvaluated(Gradle Gradle) {println' TaskExecutionGraph is ready... '} @override void buildFinished(BuildResult BuildResult) {println 'BuildResult'}})Copy the code

Execution Result:

11:57:45: Executing task 'clean'... Initialization begins... > Configure project: root project 'helloGradle' Root project 'helloGradle' > Configure project :buildApp Project ':buildApp' ready to Configure... Project ':buildApp' configuration end... > Configure project :Project01 project ':Project01' Project ':Project01' Configuration end... > Configure project :project02 project ':project02' Project ':project02' configuration end... TaskExecutionGraph is ready... > Task :clean up-to-date Task ':clean' Task ':clean' End > Task :buildApp:clean up-to-date Task ':buildApp:clean' start executing... Task ':buildApp:clean' > Task :Project01:clean up-to-date Task ':Project01:clean' Task ':Project01:clean' End > Task :project02:clean up-to-date Task ':project02:clean' Task ':project02:clean' End BUILD SUCCESSFUL in 42ms 4 Actionable tasks: 4 up-to-date 11:57:45: Task execution Finished 'clean'.Copy the code

Project01 /build/libs/ has been copied to the root directory.

3, develop

Life cycle listening is more about adding custom operations, such as renaming APK, during compilation or at the end. Compared to other build tools, Gradle can’t easily listen to the lifecycle and perform custom operations in a catch-all fashion.

. android { defaultConfig { applicationId "com.game.demo" minSdkVersion rootProject.ext.androidMinSdkVersion 35 versionName targetSdkVersion rootProject. Ext androidTargetSdkVersion versionCode "1.0.1} / / processing apk name... applicationVariants.all { variant -> variant.outputs.all { outputFileName = "${defaultConfig.applicationId}-${defaultConfig.versionName}-${variant.buildType.name}.apk" } } ... }Copy the code

Summarize its execution process:

  • Settings. gradle is the entry to gradle execution. It gets information about the project module and initializes it.
  • Build. Gradle = build.gradle = build.gradle = build.gradle = build.gradle = build.gradle = build.gradle
  • After configuration, call project.Afterevaluate, which indicates that all modules have been configured and are ready for execution.
  • At this point, the so-called directed acyclic graph has been printed, containing the task and its dependent tasks. In gradle. TaskGraph. WhenReady {} can modify the task dependencies, etc.
  • Finally, the specified task and its dependent tasks are executed.

Fourth, the Task

Task and project are both important concepts of Gradle. Tasks are the basic tasks performed by the build process. Android Studio (Windows) can view the task details of the current project using the command “Gradlew Tasks”. When the Gradle API’s built-in tasks do not meet the needs of the project, you can customize tasks to perform specific operations. For example, in different modules of the project gradle files, custom tasks can be called to each other. For example, test1.gradle defines test(), which can be called from test2.gradle. Note that the execution order is different.

1. Define usage

  • Creation method 1: Use the Task function to create the configuration file. Eg: Add task location 37SDK
Task createTask(group: '37SDK ',description:'task study'){doFirst {println 'this group: '+ group println 'Task created successfully. '}}Copy the code

Output :(after execution, you can manage custom tasks in the independent folder 37sdk)

. > Task :createTask Task' :createTask' start executing... This group: 37SDK Task created successfully Task' :createTask' End... .Copy the code

More: There are many assembleXxx tasks under Build, which are automatically created based on buildType and productFlavor.

  • Method 2: Create it using taskContainer and then configure the properties in the closure

      this.tasks.create(name:'helloTask2'){
          setGroup('37sdk')
          setDescription('task study')
          setBuildDir('build/outputs/helloTask3/')
          println 'hello task2'
      }
    Copy the code

For the parameter types that can be specified, see task.class:

Public interface Task extends Task Comparable<Task>, ExtensionAware {String TASK_NAME = "name"; String TASK_DESCRIPTION = "description"; String TASK_GROUP = "group"; String TASK_TYPE = "type"; String TASK_DEPENDS_ON = "dependsOn"; String TASK_OVERWRITE = "overwrite"; String TASK_ACTION = "action"; String TASK_CONSTRUCTOR_ARGS = "constructorArgs"; void setGroup(@Nullable String var1); void setDescription(@Nullable String var1); void setDependsOn(Iterable<? > var1); void setProperty(String var1, Object var2) throws MissingPropertyException; . }Copy the code

Note: If the task output directory is specified, the Project method is called, see project.class:

public interface Project extends Comparable<Project>, ExtensionAware, // Gradle default configuration String DEFAULT_BUILD_FILE = "build.gradle"; String PATH_SEPARATOR = ":"; String DEFAULT_BUILD_DIR_NAME = "build"; String GRADLE_PROPERTIES = "gradle.properties"; String SYSTEM_PROP_PREFIX = "systemProp"; String DEFAULT_VERSION = "unspecified"; String DEFAULT_STATUS = "release"; // More configurable void setBuildDir(Object var1); void setDescription(@Nullable String var1); void setGroup(Object var1); void setVersion(Object var1); void setStatus(Object var1); . // Project getRootProject(); File getRootDir(); File getBuildDir(); . }Copy the code

2, perform

The logic of a task can run in the configuration and execution phases (applying closures doFirst{} and doLast{}); In addition, at the same execution stage, the execution order of different invocation methods will be different.

Sample code:

Task myTask3(group: "MyTask", description: "Task3 ") {doFirst {println "the current order is 2"} println" MyTask3 "doLast {// println "the current order is 3"}} // You can also use taskname. doxxx to add the task mytask3. doFirst {// Println "The current order is 1"}Copy the code

The result is as follows:

16:37:13: Executing task 'myTask3'... Initialization begins... > Configure project: myTask3 root project 'helloGradle' Project ':buildApp' configuration end... Project ':Project01' Configuration end... Project ':project02' configuration end... TaskExecutionGraph is ready... > Task :myTask3 Task ':myTask3' start executing... The current order is 1 the current order is 2 the current order is 3 task ':myTask3' End BUILD SUCCESSFUL in 96ms 1 actionable task: 1 executed 16:37:13: Task Execution Finished 'myTask3'.Copy the code

Example 2: Customize the task to calculate the duration of the execution phase, i.e. calculate the build execution time: prebuildtask.dofirst — buildtask.dolast

// Note 1: why run the this.afterEvaluate listener to calculate build duration? Since this is the end of the configuration phase, the dependency blueprint has been output, you can find each task // note 2: Guarantee to find the task has been configured, prebuild is in the Android Project there is def startBuildTime, endBuildTime enclosing afterEvaluate {Project Project - > def preBuildTask = project.tasks.getByName('preBuild') preBuildTask.doFirst { startBuildTime = System.currentTimeMillis() println 'the start time is ' + startBuildTime } def buildTask = project.tasks.getByName('build') buildTask.doLast { endBuildTime = System.currentTimeMillis() println "the end time is ${endBuildTime - startBuildTime}" } }Copy the code

3. Task dependencies

There are two ways to specify the execution order in the execution phase of a task

  • Specify the order of execution through the Task API (i.e., doFirst, doLast)
  • A: dependsOn
Task taskX{doLast {println 'taskX'}} task taskY{doLast {println 'taskY'}} taskY. DependsOn (taskX) task taskZ(dependsOn:[taskX,taskY]){ // dependsOn this.tasks.findAll { // task ->return task.name.startsWith('lib') // } doLast { println 'taskZ' } }Copy the code

Output result:

17:13:30: Executing task 'taskZ'... Initialization begins... > Configure project: root project 'helloGradle' Project ':buildApp' configuration end... Project ':Project01' Configuration end... Project ':project02' configuration end... TaskExecutionGraph is ready... > Task :taskX Task ':taskX' starts executing... TaskX task ':taskX' End > Task :taskY Task ':taskY' starts executing... TaskY task ':taskY' End > Task :taskZ Task ':taskZ' starts executing... TaskZ task ':taskZ' End End BUILD SUCCESSFUL in 81ms 3 actionable tasks: 3 executed 17:13:30: Task Execution Finished 'taskZ'.Copy the code

TaskX and taskY are executed earlier than taskZ. Similarly, taskY executes taskX first. The dependency effect is executed first by the dependent task and then by the task itself. Compared to Java, if class A depends on class B, class B is compiled first, followed by class A. For example, create lib tasks. When taskZ is executed, the Lib tasks are executed first, and taskZ tasks are executed first.

4. Topology

Gradle-visteg can be used to output task-related dependencies in the form of graphs. By default, visteg. Dot files are generated. Using the command dot-tpng./ visteg.dot-o./visteg.dot.png, it can be converted to image format for viewing. (1) First configure the repository path in the root directory build.gradle

buildscript { repositories { google() jcenter() maven { url "https://plugins.gradle.org/m2/" } } dependencies { The classpath 'com. Android. Tools. Build: gradle: 4.0.2' classpath 'gradle. Plugin. Cz. Malohlava: visteg: 1.0.5'}}Copy the code

(2) Application plug-ins

apply plugin: 'cz.malohlava.visteg'
Copy the code

(3) Visteg attribute configuration, important is enabled (enable plug-in) and destination (output file path)

    visteg {
        enabled        = true  
        colouredNodes  = true
        colouredEdges  = true
        destination    = 'build/reports/visteg.dot'
        exporter       = 'dot'
        colorscheme    = 'spectral11'
        nodeShape      = 'box'
        startNodeShape = 'hexagon'
        endNodeShape   = 'doubleoctagon'
    }
Copy the code

(4) above example taskZ, can view the dot file under the path: / build/reports/visteg. Dot

digraph compile { 
colorscheme=spectral11;
rankdir=TB;
splines=spline;
":app:taskZ" -> ":app:taskX" [colorscheme="spectral11",color=5];
":app:taskZ" -> ":app:taskY" [colorscheme="spectral11",color=5];
":app:taskZ" [shape="hexagon",colorscheme="spectral11",style=filled,color=5];
":app:taskX" [shape="doubleoctagon",colorscheme="spectral11",style=filled,color=5];
":app:taskY" -> ":app:taskX" [colorscheme="spectral11",color=5];
":app:taskY" [shape="box",colorscheme="spectral11",style=filled,color=5];
{ rank=same; ":app:taskZ" }
}

Copy the code

(4) Conversion command: dot-tpng./ visteg.dot-o./visteg.dot.png

5. Application examples

Here through script operation androidmanifest.xml file, to modify APK version number, icon, activity theme and other content, as well as new parameters such as. Once a feasible modification is mastered, other treatments can follow suit.

task replaceManifest(group: "gradleTask", description: "replace") { GPathResult androidManifest = new XmlSlurper().parse("${projectDir}/src/main/AndroidManifest.xml") Beta String versionName = androidManifest['@android:versionName'] Equivalent to androidManifest['@android:versionName']; In addition, if the build.gradle defaultConfig TAB has set version information, the specified version of the configuration file will be built first. if(! versionName.contains('-beta')){ versionName += '-beta' androidManifest.setProperty('@android:versionName', VersionName + "")} / / 2, replace ICONS / / String iconName = androidManifest. Application [' @ the android: icon '] def iconName = "@drawable/logo37" androidManifest.application.setProperty('@android:icon', Def activityTheme = def activityTheme = def activityTheme = def activityTheme = def activityTheme = def activityTheme = def activityTheme = androidManifest.application.'activity'[0]['@android:theme'] println "'activity'[0]['@android:theme']="+activityTheme def  newTheme = "@style/Theme.AppCompat.NoActionBar" AndroidManifest. Application. The activity [0]. SetProperty (' @ android: theme, newTheme + "") / / 3, replace the activity theme def newTheme2 = "@style/Theme.AppCompat.DayNight.DarkActionBar" androidManifest.application.activity.each{ def isReplaceMainActivityTheme = false it.children().each { if(it.name() == "intent-filter"){ it.children().each{ if(it.name()=="action" && it.@"android:name"=="android.intent.action.MAIN"){ isReplaceMainActivityTheme = true return true } } } if(isReplaceMainActivityTheme){ return true } } if (isReplaceMainActivityTheme){ it.@"android:theme" = newTheme2 return true } } new File(("${projectDir}/src/main/AndroidManifest.xml")).write(XmlUtil.serialize(androidManifest)) }Copy the code

In addition to custom tasks, scripts can also be executed in Gradle lifecycle methods, such as adding parameters to androidmanifest.xml. The same applies to supplemental permission declarations, etc

project.afterEvaluate {
    android.applicationVariants.all { ApplicationVariant variant ->
        String variantName = variant.name.capitalize()
        def processManifestTask = project.tasks.getByName("process${variantName}Manifest")
        processManifestTask.doLast { pmt ->
            String manifestPath = "${projectDir}/src/main/AndroidManifest.xml"
            def manifest = file(manifestPath).getText()
            def xml = new XmlParser().parseText(manifest)
            xml.application[0].appendNode("meta-data", ['android:name': 'com.facebook.sdk.ApplicationId', 'android:value': '@string/facebook_app_id'])
            new File(("${projectDir}/src/main/AndroidManifest.xml")).write(XmlUtil.serialize(xml))
        }
    }
}
Copy the code

5. Custom Plugin

The Plugin itself is not much new, but mostly an embodiment of encapsulation. A Gradle plugin is a plugin that encapsulates all the tasks required to perform a specific Task.

1. Plug-in types

Script plug-in: it is a script that can split complex scripts and encapsulate tasks, such as splitting configurations, gradle, and modifying compilation package paths. Examples of import methods:

apply from: ".. /libconfig.gradle"Copy the code

Binary plug-in: script into jar package and other forms, has been published to the warehouse (maven, etc.), common Java plug-in (generate JAR package), Android plug-in (generate APK, AAR) and so on. Examples of import methods:

apply plugin: 'com.android.application'
apply plugin: 'groovy'
...
Copy the code

In the build.gradle file at the root, the tag buildScript builds the relevant path for the project configuration with the argument Closure. Dependencies is the repository for adding compile dependencies, which is repositories for configuring script dependencies. Their configuration is in the form of closures.

Jcenter buildscript {repositories {Google () ()} dependencies {classpath 'com. Android. View the build: gradle: 3.4.1 track' / / is equivalent to: implementation group: 'com.android.tools.build', name: 'gradle', version: '2.4.1'}} AllProjects {repositories {Google () jCenter ()}}Copy the code

More plug-in types:

  • The application plug-in, whose id is com.android.application, generates an APK.
  • Library plug-in, with the id com.android.library, generates an AAR for other application modules.
  • Test plugin, id is com.android.test, used to test other modules.
  • Feature plug-in. The id of the plug-in is com.android.feature, which is used to create an Android Instant App.
  • Instant App plug-in com.android.instantApp is an entry point of Android Instant App.

2. Plug-in creation

Create the Module first. If you name it buildSrc, you can import the plugin directly into your local project. Of course, publishing to the warehouse for use by others does not require this naming restriction.

Create myplugin.groovy and implement a plug-in that doesn’t have any functionality.

  • Apply methods: methods that need to be performed when the plug-in is introduced, and you can customize task actions
  • The Project parameter: Introduces the Project of the current plug-in
Import org.gradle.api.Plugin import org.gradle.api.Project // Custom Plugin class MyPlugin implements Plugin<Project>{@override void apply(Project project){ println 'pluginTest... ' + project.name } }Copy the code

Then, on the resources/meta-inf gradle – plugins/com. Game. The plugin. TestPlugin. Properties

implementation-class=com.game.testPlugin.MyPlugin
Copy the code
// Upload the repository to the local directory uploadArchives {repositories {mavenDeployer {pom.groupId = 'com.game.plugin' pom.artifactid = 'testPlugin' pom.version = '1.0.1' repository(URL: URI ('.. /LocalRepo')) } } }Copy the code

Also, in the root directory build.gradle, provide the plug-in path

Buildscript {repositories {Google () jCenter () maven {url uri('/LocalRepo')// Add repository}} dependencies {classpath "Com. Android. Tools. Build: gradle: 3.4.1 track" / / rely on the plug-in path format classpath '[groupId] : [artifactId] : [version]' the classpath "Com. Game. The plugin: testPlugin:" 1.0.1}}Copy the code

Add a reference to build.gradle in the project module,

apply plugin: 'com.game.plugin.testPlugin'
Copy the code

Six, summarized

It is important to know the gradle life cycle so that you can do the custom operation correctly. The initialization phase of the life cycle, completes the initialization of all projects, determines how many subprojects there are in the whole project, focuses on parsing the build.gradle file; Build. Gradle code runs in the configuration phase and then executes the task logic.

Gradle core module project is the entry of script code. All script code is actually written in the project instance. Each build.gradle corresponds to a project instance. Gradle allows you to locate files, get root projects and manage subprojects, and manage dependencies. Tasks are the role that actually performs the logic, specifying the order of execution and dependencies so that custom tasks can be inserted to complete a particular function, for example tinker hooks his task into the middle of gradle’s life cycle to complete his own function.