Gradle 是一款基於Apache Ant 和Apache Maven概念的功能強大的項目自動化構建工具,它使用一種基於Groovy的特定領域語言(DSL)來聲明項目設置,拋棄了基於XML的各類繁瑣配置。Gradle 對多工程的構建支持很出色,還能夠根據特定需求開發自定義插件來解決特定問題,所以掌握Gradle 核心技術能夠提升咱們的開發效率,本文是對Gradle 核心之一Task的相關屬性、執行流程、自定義任務等進行介紹。html
咱們的全部Gradle的構建工做都是由Task組合完成的,它能夠幫助咱們處理不少工做。每次構建(build)至少由一個project構成,一個project由一個至多個task構成。每一個task表明了構建過程中的一個原子性操做,好比編譯、打包、發佈等等這些操做。在Gradle環境下能夠經過命令./gradlew tasks 查看當前工程全部的task。java
Task建立:Gradle 有多種建立task的方式,這裏只介紹常見的兩種方式。android
1. 直接使用task函數建立,實際上是調用Project對象中的task(String name)的方法。 git
2. 經過task容器TaskContainer對象的create()方法進行建立。github
Task 配置:上面兩種建立方式其實都有對應的重載方法,能夠傳入具體的配置參數來進行Task初始化配置。windows
配置項api |
描述閉包 |
默認值函數 |
type工具 |
基於一個存在的Task來建立 |
DefaultTask |
overwrite |
是否替換存在的task,配合type使用 |
false |
dependsOn |
用於配置task的依賴 |
[ ] |
action |
添加到task中的一個Action或者一個閉包 |
null |
description |
用於配置task的描述 |
null |
group |
用於配置task的分組 |
null |
name |
用於配置task的名稱 |
null |
Task 能夠經過重載方法進行參數指定,也能夠經過閉包的形式進行配置。
這裏的group 配置的分組方便task快速定位以及後期維護,如不指定將默認分配到other組。
TaskContainer爲咱們提供了兩個方法,findByPath() 和 getByPath () 。經過查看官方文檔能夠知道,這兩個方法均可以接收task 的名字,相對路徑,絕對路徑做爲參數來查找對應的具體的Task。區別僅在於方法的返回值,findByPath()若是查找不到會直接放回null,而getByPath ()查找不到則會拋出UnknownTaskException異常,因此使用getByPath ()時要用try catch配合使用。注:在gradle低版本還提供過findByName() 、getByName() 方法。不過如今已經不提供這兩個方法了,都經過findByPath() 和 getByPath ()來實現。
當執行一個Task的時候,其實就是執行其擁有的actions列表,這個列表保存在Task對象實例中的actions成員變量中,其類型就是一個List。Task本質上又是由一組被順序執行的Action對象構成,Action實際上是一段代碼塊,相似與Java中的方法。這裏主要介紹Task建立Action的兩個方法,doFirst與doLast。doFirst{} 可使代碼在Gradle 的執行階段中Task以前執行,而doLast{}則偏偏相反,是在Task以後執行。
經過查看Task的源碼能夠知道,doFirst{}就是在Task存放actions的List第一位添加,保證其添加的Action在現有actions List元素的最前面;doLast{}是在actions List末尾添加,從而實現Task中actions順序執行的目的。
Tips:「<<」 操做符在Gradle 的Task上是doLast方法的短標記形式,也就是說「<<」 能夠替代doLast。
4.1 Task的依賴
dependsOn 是task 配置參數之一,主要做用就是爲task 添加依賴task ,保證task 之間的執行順序。經過下面的測試代碼來理解一下:
輸出結果:
咱們執行了./gradlew taskC ,而在上面咱們給taskC指定了依賴taskB, taskB的執行又依賴於taskA 。因此執行結果爲taskA -> taskB -> taskC。
4.2 輸入輸出
TaskInputs:
Task的輸入類,參數能夠接收爲任意對象以及文件、文件夾。
TaskOutputs:
TaskOutputs files ( );
TaskOutputs file ( );
TaskOutputs dir ( );
Task的輸出類,只接收文件類型。
4.3 API指定執行順序
Task的shouldRunAfter 方法和mustRunAfter方法能夠控制一個Task應該或者必定在某個Task以後執行。經過這種方式能夠在某些狀況下控制任務的執行順序,而不是經過強依賴的方式。
taskB.shouldRunAfter(taskA)表示taskB應該在taskA執行以後執行,這裏並非強制的,因此有可能任務順序並不會按預設的執行。
taskB.mustRunAfter(taskA)表示taskB必須在taskA執行以後執行,執行任務的順序是確認的。
注:使用mustRunAfter方法,若是出現Task之間依賴語法矛盾,依賴關係造成閉環,編譯器會報錯。而shouldRunAfter 方法會自動打破閉環,不會報錯。
在此以前先介紹下Gradle 的生命週期:
A. 初始化階段(Initialization)
初始化階段gradle會去解析項目根工程中setting.gradle中的include信息,肯定哪些工程加入構建。
B. 配置階段(Configuration)
配置階段將解析全部工程的build.gradle腳本,配置project對象,建立、配置task等相關信息。
C. 執行階段(Execution)
根據具體的gradle命令,執行對應相關的task以及其依賴的task。
而實際項目中咱們將要掛接的正是gradle的執行階段,由於只有在配置階段完成,執行階段gradle纔會去執行系統默認task 以及自定義task 。project爲咱們提供了這樣的方法project.afterEvaluate{}。
project.afterEvaluate{} 在配置完成後,能夠保證獲取到全部的task,包括系統默認執行的task,這樣就能夠將咱們自定義的task 經過順序執行指定,掛接到構建過程當中。實際開發中,通常咱們都會對系統的assembleRelease任務進行掛載,先來了解一下assembleRelease的內部執行順序:
:preBuild
:preReleaseBuild
:checkReleaseManifest
:prepareReleaseDependencies
:compileReleaseAidl
:compileReleaseNdk
:compileReleaseRenderscript
:generateReleaseBuildConfig
:generateReleaseResValues
:generateReleaseResources
:mergeReleaseResources
:processReleaseManifest
:processReleaseResources
:generateReleaseSources
:incrementalReleaseJavaCompilationSafeguard
:javaPreCompileRelease
:compileReleaseJavaWithJavac
:extractReleaseAnnotations
:mergeReleaseShaders
:compileReleaseShaders
:generateReleaseAssets
:mergeReleaseAssets
:mergeReleaseProguardFiles
:packageReleaseRenderscript
:packageReleaseResources
:processReleaseJavaRes
:transformResourcesWithMergeJavaResForRelease
:transformClassesAndResourcesWithSyncLibJarsForRelease
:mergeReleaseJniLibFolders
:transformNativeLibsWithMergeJniLibsForRelease
:transformNativeLibsWithSyncJniLibsForRelease
:bundleRelease
:compileReleaseSources
:assembleRelease
注:上面代碼基本上列舉了Gradle構建Android項目時執行assembleRelease的全部Task,Lint、Test等非必需Task除外。
下面咱們將結合騰訊開源的熱修復方案Tinker的源碼來分析將自定義task掛接構建過程的原理。
Tinker GitHub連接 https://github.com/Tencent/tinker
上圖爲tinker的工程目錄,與構建相關的代碼都在tinker-build 這個module裏,咱們主要分析的是裏面的TinkerPatchPlugin.groovy文件的代碼。
TinkerPatchPlugin是一個自定義插件類,主要是來實現對熱修復目標工程的配置相關文件的操做。
咱們主要分析的代碼片斷爲下圖,在project.afterEvaluate{} 方法中遍歷獲取到全部的Android變體,而後建立了一個自定義任務TinkerManifestTask ,經過當前的變體拿到AndroidManifest.xml文件的路徑,並傳給了TinkerManifestTask ,接着經過mustRunAfter 指定TinkerManifestTask 在構建過程當中assembleRelease當中:processReleaseManifest任務以後執行,從而達到在AndroidManifest.xml追寫tinker相關的配置信息。
TinkerManifestTask 中具體將tinker配置信息寫入AndroidManifest.xml的核心代碼。
Task中有個enabled屬性,用於啓用和禁用任務,默認是true,表示啓用,設置爲false則禁止該任務,在執行階段該任務會被跳過。在實際項目中是很使用的一個技巧,如提高build編譯速度,禁止一些測試相關的Task,從而縮短執行時間。
斷言就是一個條件表達式。Task有一個onlyIf方法,它接受一個閉包做爲參數,若是該閉包返回爲true則該任務執行,不然跳過。在實際項目中有不少用途,好比控制程序在哪些狀況下打包,何時執行單元測試等。
Gradle 爲咱們提供了不少默認的task來進行使用,下面是Gradle 官網文檔上對copy Task 的使用提供的例子。除了copy,還有delete、upload、zip等許多使用的task。更多具體用法能夠去閱讀Gradle 的官方文檔: https://docs.gradle.org/current/dsl/org.gradle.api.Task.html
ndk編譯,在Android中編譯生成動態庫有兩種方式:
第一種是經過Android.mk腳本執行ndk-build命令來進行手動編譯。這種狀況下,能夠經過自定義task來實現自動化編譯ndk。
這裏咱們自定義的task要指定type 爲Exec類型,Exec task 能夠經過commandLine方法執行windows、Linux環境下的命令,經過預先設置好ndk 編譯的命令,經過task自動執行來達到目的。(上圖爲windows 環境下的執行代碼)
第二種經過cmake 進行編譯。
在android studio 2.2及以上,構建原生庫的默認工具是CMake。CMake是一個跨平臺的構建工具,能夠用簡單的語句來描述全部平臺的安裝(編譯過程)。可以輸出各類各樣的makefile或者project文件。Cmake 並不直接建構出最終的軟件,而是產生其餘工具的腳本(如Makefile ),而後再依這個工具的構建方式使用。它能夠根據不一樣平臺、不一樣的編譯器,生成相應的Makefile或者vcproj項目。從而達到跨平臺的目的。Android Studio利用CMake生成的是ninja,ninja是一個小型的關注速度的構建系統。咱們不須要關心ninja的腳本,知道怎麼配置cmake就能夠了。從而能夠看出cmake實際上是一個跨平臺的支持產出各類不一樣的構建腳本的一個工具。
如今的ndk開發中基本上都是用CMake,而以前經過Android.mk腳本方式基本已經廢棄,這裏簡單介紹下CMake相關的配置。在使用Android Studio 建立工程時,選擇Native C++ ,AS便會自動爲咱們生成ndk 開發相關的文件以及配置。
而生成的這些文件中須要咱們關注主要文件即是CMakeLists.txt。
在配置好CMakeLists.txt,不須要單獨執行編譯命令,由於gradle 已經默認在build task 中預置了NDK編譯相關的操做。因此只要咱們正常對工程編譯,即可在/build/intermediates/cmake/目錄下找到生成的動態庫。
Thanks!
焦俊楠,民生科技有限公司用戶體驗技術部開發工程師。