Gradle Task原理與實踐


前言:

Gradle 是一款基於Apache Ant 和Apache Maven概念的功能強大的項目自動化構建工具,它使用一種基於Groovy的特定領域語言(DSL)來聲明項目設置,拋棄了基於XML的各類繁瑣配置。Gradle 對多工程的構建支持很出色,還能夠根據特定需求開發自定義插件來解決特定問題,所以掌握Gradle 核心技術能夠提升咱們的開發效率,本文是對Gradle 核心之一Task的相關屬性、執行流程、自定義任務等進行介紹。html


Task


咱們的全部Gradle的構建工做都是由Task組合完成的,它能夠幫助咱們處理不少工做。每次構建(build)至少由一個project構成,一個project由一個至多個task構成。每一個task表明了構建過程中的一個原子性操做,好比編譯、打包、發佈等等這些操做。在Gradle環境下能夠經過命令./gradlew tasks 查看當前工程全部的task。java

1. Task的建立和配置

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組。


2. 獲取Task的方式

TaskContainer爲咱們提供了兩個方法,findByPath() 和 getByPath () 。經過查看官方文檔能夠知道,這兩個方法均可以接收task 的名字,相對路徑,絕對路徑做爲參數來查找對應的具體的Task。區別僅在於方法的返回值,findByPath()若是查找不到會直接放回null,而getByPath ()查找不到則會拋出UnknownTaskException異常,因此使用getByPath ()時要用try catch配合使用。注:在gradle低版本還提供過findByName() 、getByName() 方法。不過如今已經不提供這兩個方法了,都經過findByPath() 和 getByPath ()來實現。


3. Task的執行

當執行一個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. Task執行順序


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 方法會自動打破閉環,不會報錯。


5. 掛接自定義Task到構建過程當中

在此以前先介紹下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的核心代碼。


6. Task的啓用與禁用

Task中有個enabled屬性,用於啓用和禁用任務,默認是true,表示啓用,設置爲false則禁止該任務,在執行階段該任務會被跳過。在實際項目中是很使用的一個技巧,如提高build編譯速度,禁止一些測試相關的Task,從而縮短執行時間。


7. Task的onlyIf斷言

斷言就是一個條件表達式。Task有一個onlyIf方法,它接受一個閉包做爲參數,若是該閉包返回爲true則該任務執行,不然跳過。在實際項目中有不少用途,好比控制程序在哪些狀況下打包,何時執行單元測試等。


8. 默認Task

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!


做者介紹

焦俊楠,民生科技有限公司用戶體驗技術部開發工程師。

相關文章
相關標籤/搜索