安卓-Gradle淺談

做者

你們好,我叫Jack馮; 本人20年碩士畢業於廣東工業大學,於2020年6月加入37手遊安卓團隊;目前主要負責海外遊戲發行安卓相關開發。html

1、Gradle簡介

  • Gradle是什麼,能作什麼?
  • Android Studio的默認構建工具,用來構建應用程序。組成部分見下圖:

  • groovy核心語法:閉包、數據結構等
  • build script block:工程build.gradle的腳本
  • gradle api:project、task、plugin等

2、Groovy基礎

groovy特性:java

- 基於JVM的開發語言,執行:groovy源文件==》class字節碼==》JVM處理執行;或groovy源文件直接解析執行(相似Script) - 無縫集成全部的Java類庫,但腳本寫法比Java更簡潔node

一、字符串

(1)定義使用

String(java.lang.String) + GString(Groovy String),經常使用定義方式有單引號、雙引號、三引號 注意: 單引號和Java的雙引號是同樣的,內容不能改變; 雙引號,支持參數擴展(實現類會變成GString),擴展的字符串能夠是任意表達式,即「 ${ 任意表達式 } 」; 三引號,格式任意,不須要轉義字符、指定輸出。android

示例代碼:正則表達式

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
複製代碼

輸出結果:api

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
複製代碼

二、擴展

字符串擴展的方法衆多,具體來源見下圖: markdown

  • java.lang.String:Java原有的方法。數據結構

  • DefaultGroovyMethods:Groovy對全部對象的一個擴展。閉包

  • StringGroovyMethods:繼承自DefaultGroovyMethods,重寫了適用於String使用的方法。下面是截取的部分源碼:app

    package org.codehaus.groovy.runtime;
    public class StringGroovyMethods extends DefaultGroovyMethodsSupport {
        ...
        //將字符串的第一個字母大寫的簡便方法
    	public static String capitalize(String self){..}
    	
    	//建立一個新的CharSequence,它與這個字符串相反(向後)
    	public static CharSequence reverse(CharSequence self){..}
    	
    	//逐行遍歷此字符串。
    	public static <T> T eachLine(String self, int firstLine, @ClosureParams(value=FromString.class, options={"String","String,Integer"}) Closure<T> closure){..}
    	
    	//返回在字符串中第一次出現已編譯正則表達式時調用閉包的結果。
    	public static String find(String self, Pattern pattern, @ClosureParams(value=SimpleType.class, options="java.lang.String[]") Closure closure) {..}
    	...
    }
    複製代碼

(1) 普通類型參數使用:

def str = "groovy Hello"
//指定長度和填充字符,對已知字符的填充
println str.center(18,'a')
println str.padLeft(18,'a')

//字符串的比較操做符
def str2 = 'Hello'
println str > str2

//獲取字符串的index對應值
println str[0]

//獲取字符串的一段子串
println str[0..1]

//刪掉字串
println str - str2

//字符串反向輸出
println str.reverse()

//字符串首字母大寫
println str.capitalize()

//字符串是否數字的判斷
println str.isNumber()

//字符串轉Integer類型/Double類型等
def str3 = "123"
println str3.toInteger()
println str3.toInteger().class
複製代碼

(2)和閉包組合:下面示例就是在閉包(task類型)傳入一個字符串參數路徑,執行find閉包方法輸出。

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!"
        }
    }
}

//對比常規閉包的定義和使用
def findFile = {...}
findFile.call()
複製代碼

輸出結果:

10:15:29: Executing task 'findFile'...

初始化開始...

> Configure project :
配置階段完成

> 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'.
複製代碼

關於Groovy和Java的異同,除了所述的字符串外,還有自動導入包的方式、方法調用時期差別等,具體可見Groovy文檔《 Differences with Java》 www.groovy-lang.org/differences…

二、閉包基礎

閉包,實質上是一段代碼塊。 這裏介紹閉包基礎部分,主要包括內容:

  • 閉包概念:閉包的定義、調用
  • 閉包參數:普通參數、隱式參數
  • 閉包返回值:老是有返回值的

(1)定義

定義和調用:參數 ->執行體

//(1)閉包定義
def clouser = { String name ->
    println "一、println:clouser ${name}"
}
//(2)調用方式
clouser("test")
clouser.call("test call")


//(3)多個參數
def MyClouser = {String names ,int ages ->
    println "二、println MyClouser:heloo ${names}, my ages is ${ages} "
}
def names = 'my_clouser'
MyClouser(names,100)


//(4)全部閉包的隱式默認參數it,能夠不聲明的
def itClouser = {
    println "三、println itClouser: hello ${it}"
}
itClouser('it_clouser')


//(5)閉包的返回值,沒有return的話,返回null
def returnClouser = {
    println "四、println returnClouser:hello ${it}"
//    return "hello ${it}"
}
def result = returnClouser('return_clouser')
println "五、println returnClouser result:"+result
複製代碼

輸出結果:

一、println:clouser test
一、println:clouser test call
二、println MyClouser:heloo my_clouser, my ages is 100 
三、println itClouser: hello it_clouser
四、println returnClouser:hello return_clouser
五、println returnClouser result:null
複製代碼

(2)使用

舉例:字符串與閉包的結合使用

String str = 'the 2 and 3 is 5'
//一、each的遍歷
str.each {
    String temp -> print temp.multiply(2)//每一個字符拷貝一份,返回值仍是str自己
}
println ""

//二、find來查找符合條件的第一個
println str.find {
    String s -> s.isNumber()
}

//三、查找全部符合條件的
def list =  str.findAll {
    String s -> s.isNumber()
}
println list.toListString()

//四、查找是否有符合條件的
def anyresult = str.any {
    String s -> s.isNumber()
}
println anyresult

//五、查找是否所有都符合條件
def everyresult = str.every {
    String s -> s.isNumber()
}
println everyresult

//六、將小寫字母轉換爲大寫
def list2 = str.collect {
    it.toUpperCase()
}
println list2.toListString()
複製代碼

輸出結果:

tthhee  22  aanndd  33  iiss  55
2
[2, 3, 5]
true
false
[T, H, E,  , 2,  , A, N, D,  , 3,  , I, S,  , 5]
複製代碼

(3)閉包變量

1) 在介紹閉包委託策略以前,這裏先介紹下閉包的三個重要變量。

若是是在類或方法中定義閉包時,三個變量(this、owner、delegate)的值是同樣的;

可是在閉包中嵌套定義了閉包,this和owner、delegate指向的值就會不一樣,若是單獨修改delegate變量指向,則三者值都會不同。

  • this,表明閉包定義處的類,不可修改
  • owner,表明閉包定義處的類或者對象,不可修改
  • delegate,表明任意對象,默認和owner一致,可修改

這裏在類或方法中定義閉包,

def scriptClouser = {
    println "scriptClouser this : " + this
    println "scriptClouser owner : " + owner 
    println "scriptClouser delegate : " + delegate 
}
scriptClouser.call()
複製代碼

輸出結果:

scriptClouser this : pkg.character01@5be067de
scriptClouser owner : pkg.character01@5be067de
scriptClouser delegate : pkg.character01@5be067de
複製代碼

2)內部類相關

//定義內部類
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 : " + delegate
        }
        methodClouser.call()
    }
}
//一、輸出person的static方法調用結果,三者都是指向當前的類
Person.classClouser.call()
Person.say()
複製代碼

輸出結果:

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
複製代碼

若是去掉方法的static聲明,則輸出的person指向會是當前類的某個具體對象。

class Person{
    def classClouser = {...}
    def say(){...}
}
//二、非static方法調用示例
Person innerPerson = new Person()
innerPerson.classClouser.call()
innerPerson.say()
複製代碼

輸出結果:

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
複製代碼

3)特殊情形:閉包中的閉包

這裏,this指向定義閉包的類;owner指向nestClouser的實例對象,delegate指向最近的閉包對象。其中,能夠單獨指定innerClouser的delegate,示例以下。

//閉包中定義一個閉包,三者不一致
def nestClouser = {
    def innerClouser = {
        println "innerClouser this : " + this  
        println "innerClouser owner : " + owner 
        println "innerClouser delegate : " + delegate   
    }
    //innerClouser.delegate = innerPerson
    innerClouser.call()
}
nestClouser.call()
複製代碼

輸出結果:

innerClouser this : pkg.character01@224b4d61
innerClouser owner : pkg.character01$_run_closure1@5ab14cb9
innerClouser delegate : pkg.character01$_run_closure1@5ab14cb9
複製代碼

單獨指定delegate的話,三者的輸出結果都會不同:

innerClouser this : pkg.character01@7c041b41
innerClouser owner : pkg.character01$_run_closure1@361c294e
innerClouser delegate : pkg.Person@7859e786
複製代碼

(4)委託策略

閉包中委託策略分四種:OWNER_FIRST(默認)、DELEGATE_FIRST、OWNER_ONLY、DELEGATE_ONLY,默認策略代表閉包中的變量、方法等,都是首先從owner指向的對象處尋找。經過改變delegate指向對象和不一樣的委託策略指定,能夠指定閉包優先從哪一個對象尋找調用的變量和方法。

下面示例修改委託策略爲Closure.DELEGATE_FIRST,可以使得優先從delegate指向的對象中尋找同名的變量方法屬性,找不到再返回Owner指向對象中查詢。

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()

//修改delegate對象,添加委託策略,從delegate開始尋找
output01.method.delegate = output02
output01.method.resolveStrategy = Closure.DELEGATE_FIRST
println "output01:"+output01.toString()
複製代碼

輸出結果:

output01:The output of this time is ClosureOutput
output01:The output of this time is ClosureDelegationOutput
複製代碼

注意:若是ClosureDelegationOutput方法中沒有ClosureOutput方法的同名參數方法,並且修改的委託策略是Closure.DELEGATE_ONLY,會拋出異常groovy.lang.MissingPropertyException。

3、生命週期

一、執行階段

Gradle的執行流程,主要分爲三個階段

  • Initialization初始化階段:解析整個工程中全部project,構建全部的project對象
  • Configuration配置階段:解析全部project對象的task,構建全部task的依賴圖
  • Execution執行階段:執行具體的task及其依賴的task

二、監聽示例

爲了方便追蹤各個階段的執行狀況,在各節點加了日誌打印。

首先是初始化階段,執行settings.gradle進行全局配置,在文件添加:

println '初始化開始...'
複製代碼

而後是配置、執行階段的監聽。在根目錄build.gradle添加:

//配置階段監聽(本project)
this.beforeEvaluate { Project project ->
    println "$project 配置階段開始 ..."
}
this.afterEvaluate { Project project ->
    println "$project 配置完成 ..."
}

//配置階段監聽(包括其餘project)
this.gradle.beforeProject { Project project ->
    println " $project 準備配置 ..."
}
this.gradle.afterProject  { Project project ->
    println " $project 配置結束 ..."
}

//配置完成
gradle.taskGraph.whenReady { TaskExecutionGraph taskGraph ->
    println "配置階段結束,TaskExecutionGraph is ready ..."
    if(taskGraph.hasTask(taskZ)) {
        lib1.dependsOn taskZ
    }
}

//執行階段的監聽
gradle.taskGraph.beforeTask { Task task ->
    println " $task 開始執行..."
}
gradle.taskGraph.afterTask { Task task, TaskState state ->
    if (state.failure) {
        println " $task 執行失敗..."
    }
    else {
        println " $task 執行結束..."
    }
}

//執行階段結束後的回調監聽,操做個別文件
this.gradle.buildFinished {
    println '執行階段結束'
    fileTree('/project01/build/libs/'){ FileTree fileTree ->
        fileTree.visit { FileTreeElement fileTreeElement ->
            copy {
                from fileTreeElement.file
                into getRootProject().getBuildDir().path + '/testFiletree/'
            }
        }
    }
}
複製代碼

還能夠添加其餘監聽:

//this.gradle.addListener()
//this.gradle.addProjectEvaluationListener()
this.gradle.addBuildListener(new BuildListener() {
    @Override
    void buildStarted(Gradle gradle) {
        println '開始構建'
    }
    
    @Override
    void settingsEvaluated(Settings settings) {
        println 'settings.gradle 中代碼執行完畢'
    }
    
    @Override
    void projectsLoaded(Gradle gradle) {
        println '初始化階段結束'
    }

    @Override
    void projectsEvaluated(Gradle gradle) {
        println '配置階段結束,TaskExecutionGraph is ready ...'
    }

    @Override
    void buildFinished(BuildResult buildResult) {
        println '執行階段結束 '
    }
})
複製代碼

執行結果:

11:57:45: Executing task 'clean'...

初始化開始...

> Configure project :
root project 'helloGradle' 配置結束 ...
root project 'helloGradle' 配置完成 ...

> Configure project :buildApp
project ':buildApp' 準備配置 ...
project ':buildApp' 配置結束 ...

> Configure project :Project01
project ':Project01' 準備配置 ...
project ':Project01' 配置結束 ...

> Configure project :project02
project ':project02' 準備配置 ...
project ':project02' 配置結束 ...
配置階段結束,TaskExecutionGraph is ready ...

> Task :clean UP-TO-DATE
task ':clean' 開始執行...
task ':clean' 執行結束...

> Task :buildApp:clean UP-TO-DATE
task ':buildApp:clean' 開始執行...
task ':buildApp:clean' 執行結束...

> Task :Project01:clean UP-TO-DATE
task ':Project01:clean' 開始執行...
task ':Project01:clean' 執行結束...

> Task :project02:clean UP-TO-DATE
task ':project02:clean' 開始執行...
task ':project02:clean' 執行結束...
執行階段結束

BUILD SUCCESSFUL in 42ms
4 actionable tasks: 4 up-to-date
11:57:45: Task execution finished 'clean'.
複製代碼

執行完畢,project01/build/libs/下的文件已拷貝到根目錄中。

三、拓展

對於生命週期的監聽,更可能是爲了在編譯過程或者結束階段,添加一些自定義操做,例如重命名APK等。對比其餘構建工具,無法輕易作到像Gradle這樣,見縫插針式地監聽生命週期並執行自定義操做。

...
android {
    defaultConfig {
        applicationId "com.game.demo"
        minSdkVersion rootProject.ext.androidMinSdkVersion
        targetSdkVersion rootProject.ext.androidTargetSdkVersion
        versionCode 35
        versionName "1.0.1"
    }
    //處理apk名稱
    ...
    applicationVariants.all { variant ->
        variant.outputs.all {
            outputFileName = "${defaultConfig.applicationId}-${defaultConfig.versionName}-${variant.buildType.name}.apk"
        }
    }
    ...
}
複製代碼

歸納其執行流程:

  • 首先,settings.gradle是gradle執行的入口,主要獲取項目模塊的相關信息,初始化。
  • 而後,前後配置root.project、子project,配置的過程就是獲取build.gradle的參數、task信息。
  • 接着,配置結束調用 project.afterEvaluate,它表示全部的模塊都已經配置結束,準備進入執行階段。
  • 此時,所謂的有向無環圖已經輸出,包含 task 及其依賴的 task。在gradle.taskGraph.whenReady{}能夠修改task依賴關係等。
  • 最後,執行指定的 task 及其依賴的 task。

4、Task

task和project都是Gradle比較重要的概念,task即任務,是構建過程執行的基本工做。Android Studio(Windows環境)可使用指令「gradlew tasks」查看當前工程的task詳細信息。當Gradle API自帶task沒法知足項目須要時,能夠自定義task執行特定操做。例如,在工程的不一樣模塊gradle文件,自定義task,是能夠相互調用的。例如在test1.gradle定義了test(),可在test2.gradle中調用,注意執行順序會有差異(後面在task執行順序中講解)。

一、定義使用

  • 建立方式1:直接經過task函數構建,建立時填充基本配置。eg:添加task的存放位置37sdk
task createTask(group: '37sdk',description:'task study'){
     //在執行階段輸出
     doFirst {
         println 'this group : ' + group
         println 'Task created successfully。'
     }
 }
複製代碼

輸出:(執行完畢能夠在獨立文件夾37sdk管理自定義task)

...
> Task :createTask
task ':createTask' 開始執行...
this group : 37sdk
Task created successfully
task ':createTask' 執行結束...
...
複製代碼

更多:build下面有不少assembleXxx任務,是根據buildType和productFlavor的不一樣自動建立多個。

  • 建立方式2:經過taskContainer去建立,而後在閉包中配置屬性

    this.tasks.create(name:'helloTask2'){
          setGroup('37sdk')
          setDescription('task study')
          setBuildDir('build/outputs/helloTask3/')
          println 'hello task2'
      }
    複製代碼

可指定的參數類型,見Task.class:

public interface Task extends 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;
        	   	...
         }
複製代碼

注意:若指定task輸出目錄,調用的是Project的方法,見Project.class:

public interface Project extends Comparable<Project>, ExtensionAware, PluginAware {
   //gradle默認配置
       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";
     
   //更多的可指定配置
       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();    
       ... 
    }
     
複製代碼

二、執行

task的邏輯可運行在配置階段和執行階段(應用閉包 doFirst{ } 和 doLast{ } );另外,同是執行階段,不一樣調用方式的執行順序會有差異。

示例代碼:

// 使用 Task 在執行階段進行操做
task myTask3(group: "MyTask", description: "task3") {
    doFirst {
        // 次執行
        println "the current order is 2"
    }
    
	println "這是一條運行在配置階段的,myTask3"
	
    doLast {
        // 最後執行
        println "the current order is 3"
    }
}

// 也可使用 taskName.doxxx 的方式添加執行任務
myTask3.doFirst {
    // 這種方式的最早執行
    println "the current order is 1"
}
複製代碼

執行結果以下:

16:37:13: Executing task 'myTask3'...

初始化開始...

> Configure project :
這是一條運行在配置階段的,myTask3

root project 'helloGradle' 配置完成 ...
project ':buildApp' 配置結束 ...
project ':Project01' 配置結束 ...
project ':project02' 配置結束 ...
配置階段結束,TaskExecutionGraph is ready ...

> Task :myTask3
task ':myTask3' 開始執行...
the current order is 1
the current order is 2
the current order is 3
task ':myTask3' 執行結束...
執行階段結束 

BUILD SUCCESSFUL in 96ms
1 actionable task: 1 executed
16:37:13: Task execution finished 'myTask3'.
複製代碼

示例2:自定義task去計算執行階段的耗時,即計算build執行時長,區間:preBuildTask.doFirst--buildTask.doLast

//注意1:爲何運行在this.afterEvaluate 監聽去計算build時長?由於是配置結束階段,依賴藍圖已經輸出,能夠查找到每個task
//注意2:保證要找的task已經配置完畢,prebuild是在Android工程裏面有

def startBuildTime,endBuildTime
this.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}"
    }
}
複製代碼

三、task依賴

task的執行階段,指定執行順序有兩種方式

  • 經過Task的API指定執行順序(即doFirst、doLast)
  • 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'
    }
}
複製代碼

輸出結果:

17:13:30: Executing task 'taskZ'...

初始化開始...

> Configure project :
root project 'helloGradle' 配置完成 ...
project ':buildApp' 配置結束 ...
project ':Project01' 配置結束 ...
project ':project02' 配置結束 ...
配置階段結束,TaskExecutionGraph is ready ...

> Task :taskX
task ':taskX' 開始執行...
taskX
task ':taskX' 執行結束...

> Task :taskY
task ':taskY' 開始執行...
taskY
task ':taskY' 執行結束...

> Task :taskZ
task ':taskZ' 開始執行...
taskZ
task ':taskZ' 執行結束...
執行階段結束

BUILD SUCCESSFUL in 81ms
3 actionable tasks: 3 executed
17:13:30: Task execution finished 'taskZ'.
複製代碼

執行taskZ,就會提早執行taskX、taskY;相似的,執行taskY也會先執行taskX。依賴的效果,首先執行所依賴的task,再到本task。同比Java,若是類A依賴類B,類B會先被編譯,而後纔是類A。依賴的目的,在執行階段添加本身的操做,例如建立lib系列的task任務,當執行到taskZ時,先把lib系列任務先執行,而後纔是taskZ自身任務。

四、拓撲圖

關於task依賴拓撲圖,能夠引入插件gradle-visteg,以圖的形式輸出Task相關依賴,默認生成visteg.dot文件;使用指令dot -Tpng ./visteg.dot -o ./visteg.dot.png,可將其轉換圖片格式查看。 (1)首先在根目錄build.gradle配置倉庫路徑

buildscript {
    repositories {
        google()
        jcenter()
        maven {
            url "https://plugins.gradle.org/m2/"
        }
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:4.0.2'
        classpath 'gradle.plugin.cz.malohlava:visteg:1.0.5'
    }
}
複製代碼

(2)應用插件

apply plugin: 'cz.malohlava.visteg'
複製代碼

(3)visteg屬性配置,重要是enabled(啓用插件)和destination(輸出文件路徑)

visteg {
        enabled        = true  
        colouredNodes  = true
        colouredEdges  = true
        destination    = 'build/reports/visteg.dot'
        exporter       = 'dot'
        colorscheme    = 'spectral11'
        nodeShape      = 'box'
        startNodeShape = 'hexagon'
        endNodeShape   = 'doubleoctagon'
    }
複製代碼

(4)執行上方示例taskZ,在路徑下能夠查看dot文件:/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" }
}

複製代碼

(4)轉換命令:dot -Tpng ./visteg.dot -o ./visteg.dot.png

五、應用實例

這裏經過腳本操做AndroidManifest.xml文件,去修改APK的版本號、圖標、活動主題等內容,以及新增參數如等。掌握了一種可行修改方式後,其餘處理也能夠依樣進行。

task replaceManifest(group: "gradleTask", description: "replace") {

    GPathResult androidManifest = new XmlSlurper().parse("${projectDir}/src/main/AndroidManifest.xml")
    //一、修改版本號beta
    String versionName = androidManifest['@android:versionName']
    //注意:等同於androidManifest['@android:versionName'];另外,若是build.gradle的defaultConfig標籤有設定version信息,最後構建優先選擇配置文件的指定版本。
    if(!versionName.contains('-beta')){
        versionName += '-beta'
        androidManifest.setProperty('@android:versionName', versionName + "")
    }

    //二、替換圖標
    //String iconName = androidManifest.application['@android:icon']
    def iconName = "@drawable/logo37"
    androidManifest.application.setProperty('@android:icon', iconName + "")

    //三、替換活動主題:按聲明activity的順序ID修改
    def activityTheme = androidManifest.application.'activity'[0]['@android:theme']
    println "'activity'[0]['@android:theme']="+activityTheme
    def newTheme = "@style/Theme.AppCompat.NoActionBar"
    androidManifest.application.activity[0].setProperty('@android:theme',newTheme + "")

    //三、替換主活動主題
    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))
}

複製代碼

除了自定義task這種方式之外,也能夠在Gradle生命週期的方法中執行腳本,示例:在AndroidManifest.xml中添加參數。同理,補充權限聲明也是一樣的方式等

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))
        }
    }
}
複製代碼

5、自定義plugin

plugin自己的新東西並很少,主要是封裝的一個體現。Gradle plugin插件,就是將完成特定任務的全部Task都封裝到一個插件中,當別人引用這個插件,就能夠完成特定的功能。

一、插件類型

腳本插件:實爲腳本,做用是可拆分複雜腳本、封裝任務,例如拆分配置.gradle、修改編譯打包路徑等。引入方式示例:

apply from: "../libconfig.gradle"
複製代碼

二進制插件:腳本打成jar包等形式,已發佈到倉庫(maven等),常見的Java插件(生成jar包)、Android插件(生成apk、aar)等。引入方式示例:

apply plugin: 'com.android.application'
apply plugin: 'groovy'
...
複製代碼

根目錄build.gradle文件中,標籤buildscript可爲該項目配置構建相關路徑,參數是Closure。dependencies是添加編譯依賴項的,repositories是爲腳本依賴項配置存儲庫。他們的配置都是用閉包的形式。

buildscript {
    repositories {
        google()
        jcenter()   
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.4.1' 
        //等價於:implementation group: 'com.android.tools.build', name: 'gradle', version: '3.4.1'
    }
}
allprojects {
    repositories {
        google()
        jcenter()  
    }
}
複製代碼

更多插件類型:

  • 應用程序插件,插件id爲com.android.application,會生成一個APK。
  • 庫插件,插件id爲com.android.library,會生成一個AAR,提供給其餘應用程序模塊用。
  • 測試插件,插件id爲com.android.test,用於測試其餘的模塊。
  • feature插件,插件id爲com.android.feature,建立Android Instant App時須要用到的插件。
  • Instant App插件,插件id爲com.android.instantapp,是Android Instant App的入口。

二、插件建立

首先建立module,若是命名爲buildSrc,在本地工程中能夠直接引入使用自建立的plugin;固然,發佈到倉庫供給他人使用的話就不用考慮這個命名限制。

這裏建立MyPlugin.groovy,實現一個沒有任何功能的插件。

  • apply方法:插件被引入時須要執行的方法,能夠自定義task操做
  • Project參數:引入當前插件的project
import org.gradle.api.Plugin
import org.gradle.api.Project

//自定義插件
class MyPlugin implements Plugin<Project>{
	@Override
	void apply(Project project){
		println 'pluginTest...' + project.name
	}
}
複製代碼

而後在resources/META-INF.gradle-plugins/com.game.plugin.testPlugin.properties

implementation-class=com.game.testPlugin.MyPlugin
複製代碼
//提交倉庫到本地目錄
uploadArchives {
    repositories {
        mavenDeployer {
            pom.groupId = 'com.game.plugin'
            pom.artifactId = 'testPlugin'
            pom.version = '1.0.1'
            repository(url: uri('../LocalRepo'))
        }
    }
}
複製代碼

再者,在根目錄build.gradle,提供插件路徑

buildscript {
    repositories {
        google()
        jcenter()
        maven {
            url uri('/LocalRepo')//添加依賴倉庫
        }
    }
    dependencies {
        classpath "com.android.tools.build:gradle:3.4.1"

        //依賴插件路徑格式classpath '[groupId]:[artifactId]:[version]'
        classpath "com.game.plugin:testPlugin:1.0.1"
    }
}
複製代碼

在項目模塊的build.gradle添加引用,

apply plugin: 'com.game.plugin.testPlugin'
複製代碼

6、總結

一、編寫gradle插件,比較重要的是對gradle生命週期的掌握,才能正確地去作自定義操做。生命週期的初始化階段,完成全部工程的初始化,決定整個項目有多少子項目,重點是解析build.gradle文件;而後是配置階段,build.gradle的代碼基本都是運行在配置階段,配置結束就開始真正執行task任務邏輯。

二、gradle核心模塊的project,是腳本代碼的入口,全部腳本代碼實際都編寫在project的實例中,每個build.gradle對應一個project的實例,在build.gradle能夠定位文件、獲取root工程和管理子工程以及管理依賴;task纔是真正執行邏輯的角色,可指定執行順序和依賴,以插入自定義的task來完成特定功能,例如tinker將本身的task掛接到gradle生命週期的中間,去完成本身的功能。

相關文章
相關標籤/搜索