成為一名優(yōu)秀的Android開發(fā),需要一份完備的知識體系阔蛉,在這里瓣喊,讓我們一起成長為自己所想的那樣~。
前言
從明面上看芽世,Gradle 是一款強大的構建工具挚赊,而且許多文章也僅僅都把 Gradle 當做一款工具對待。但是济瓢,Gradle 不僅僅是一款強大的構建工具荠割,它看起來更像是一個編程框架。Gradle 的組成可以細分為如下三個方面:
- 1)旺矾、
groovy 核心語法
:包括 groovy 基本語法蔑鹦、閉包、數(shù)據(jù)結構箕宙、面向對象等等嚎朽。 - 2)、
Android DSL(build scrpit block)
:Android 插件在 Gradle 所特有的東西柬帕,我們可以在不同的 build scrpit block 中去做不同的事情哟忍。 - 3)、
Gradle API
:包含 Project陷寝、Task锅很、Setting 等等(本文重點)。
可以看到凤跑,Gradle 的語法是以 groovy 為基礎的爆安,而且,它還有自己獨有的 API仔引,所以我們可以把 Gradle 認作是一款編程框架扔仓,利用 Gradle 我們可以在編程中去實現(xiàn)項目構建過程中的所有需求致扯。需要注意的是,想要隨心所欲地使用 Gradle当辐,我們必須提前掌握好 groovy,如果對 groovy 還不是很熟悉的建議看看 《深入探索Gradle自動化構建技術(二鲤看、Groovy 筑基篇)》 一文缘揪。
需要注意的是,Groovy 是一門語言义桂,而 DSL 一種特定領域的配置文件找筝,Gradle 是基于 Groovy 的一種框架工具,而 gradlew 則是 gradle 的一個兼容包裝工具慷吊。
一袖裕、Gradle 優(yōu)勢
1、更好的靈活性
在靈活性上溉瓶,Gradle 相對于 Maven急鳄、Ant 等構建工具, 其 提供了一系列的 API 讓我們有能力去修改或定制項目的構建過程堰酿。例如我們可以 利用 Gradle 去動態(tài)修改生成的 APK 包名疾宏,但是如果是使用的 Maven、Ant 等工具触创,我們就必須等生成 APK 后坎藐,再手動去修改 APK 的名稱。
2哼绑、更細的粒度
在粒度性上岩馍,使用 Maven、Ant 等構建工具時抖韩,我們的源代碼和構建腳本是獨立的蛀恩,而且我們也不知道其內(nèi)部的處理是怎樣的。但是帽蝶,我們的 Gradle 則不同赦肋,它 從源代碼的編譯、資源的編譯励稳、再到生成 APK 的過程中都是一個接一個來執(zhí)行的佃乘。
此外,Gradle 構建的粒度細化到了每一個 task 之中驹尼。并且它所有的 Task 源碼都是開源的趣避,在我們掌握了這一整套打包流程后,我們就可以通過去修改它的 Task 去動態(tài)改變其執(zhí)行流程新翎。例如 Tinker 框架的實現(xiàn)過程中程帕,它通過動態(tài)地修改 Gradle 的打包過程生成 APK 的同時住练,也生成了各種補丁文件。
3愁拭、更好的擴展性
在擴展性上讲逛,Gradle 支持插件機制,所以我們可以復用這些插件岭埠,就如同復用庫一樣簡單方便盏混。
4、更強的兼容性
Gradle 不僅自身功能強大惜论,而且它還能 兼容所有的 Maven许赃、Ant 功能,也就是說馆类,Gradle 吸取了所有構建工具的長處混聊。
可以看到,Gradle 相比于其它構建工具乾巧,其好處不言而喻句喜,而其 最核心的原因就是因為 Gradle 是一套編程框架。
二卧抗、Gradle 構建生命周期
Gradle 的構建過程分為 三部分:初始化階段藤滥、配置階段和執(zhí)行階段。下面分別來詳細了解下它們社裆。
1拙绊、初始化階段
首先,在這個階段中泳秀,會讀取根工程中的 setting.gradle 中的 include 信息标沪,確定有多少工程加入構建,然后嗜傅,會為每一個項目(build.gradle 腳本文件)創(chuàng)建一個個與之對應的 Project 實例金句,最終形成一個項目的層次結構。 與初始化階段相關的腳本文件是 settings.gradle吕嘀,而一個 settings.gradle 腳本對應一個 Settings 對象违寞,我們最常用來聲明項目的層次結構的 include 就是 Settings 對象下的一個方法,在 Gradle 初始化的時候會構造一個 Settings 實例對象偶房,以執(zhí)行各個 Project 的初始化配置趁曼。
settings.gradle
在 settings.gradle 文件中,我們可以 在 Gradle 的構建過程中添加各個生命周期節(jié)點監(jiān)聽棕洋,其代碼如下所示:
include ':app'
gradle.addBuildListener(new BuildListener() {
void buildStarted(Gradle var1) {
println '開始構建'
}
void settingsEvaluated(Settings var1) {
// var1.gradle.rootProject 這里訪問 Project 對象時會報錯挡闰,
// 因為還未完成 Project 的初始化。
println 'settings 評估完成(settings.gradle 中代碼執(zhí)行完畢)'
}
void projectsLoaded(Gradle var1) {
println '項目結構加載完成(初始化階段結束)'
println '初始化結束,可訪問根項目:' + var1.gradle.rootProject
}
void projectsEvaluated(Gradle var1) {
println '所有項目評估完成(配置階段結束)'
}
void buildFinished(BuildResult var1) {
println '構建結束 '
}
})
復制代碼
編寫完相應的 Gradle 生命周期監(jiān)聽代碼之后摄悯,我們就可以在 Build 輸出界面看到如下信息:
Executing tasks: [clean, :app:assembleSpeedDebug] in project
/Users/quchao/Documents/main-open-project/Awesome-WanAndroid
settings評估完成(settins.gradle中代碼執(zhí)行完畢)
項目結構加載完成(初始化階段結束)
初始化結束赞季,可訪問根項目:root project 'Awesome-WanAndroid'
Configuration on demand is an incubating feature.
> Configure project :app
gradlew version > 4.0
WARNING: API 'variant.getJavaCompiler()' is obsolete and has been
replaced with 'variant.getJavaCompileProvider()'.
It will be removed at the end of 2019.
For more information, see
https://d.android.com/r/tools/task-configuration-avoidance.
To determine what is calling variant.getJavaCompiler(), use
-Pandroid.debug.obsoleteApi=true on the command line to display more
information.
skip tinyPicPlugin Task!!!!!!
skip tinyPicPlugin Task!!!!!!
所有項目評估完成(配置階段結束)
> Task :clean UP-TO-DATE
:clean spend 1ms
...
> Task :app:clean
:app:clean spend 2ms
> Task :app:packageSpeedDebug
:app:packageSpeedDebug spend 825ms
> Task :app:assembleSpeedDebug
:app:assembleSpeedDebug spend 1ms
構建結束
Tasks spend time > 50ms:
...
復制代碼
此外,在 settings.gradle 文件中奢驯,我們可以指定其它 project 的位置申钩,這樣就可以將其它外部工程中的 moudle 導入到當前的工程之中了。示例代碼如下所示:
if (useSpeechMoudle) {
// 導入其它 App 的 speech 語音模塊
include "speech"
project(":speech").projectDir = new File("../OtherApp/speech")
}
復制代碼
2瘪阁、配置階段
配置階段的任務是 執(zhí)行各項目下的 build.gradle 腳本典蜕,完成 Project 的配置,與此同時罗洗,會構造 Task 任務依賴關系圖以便在執(zhí)行階段按照依賴關系執(zhí)行 Task。而在配置階段執(zhí)行的代碼通常來說都會包括以下三個部分的內(nèi)容钢猛,如下所示:
- 1)伙菜、build.gralde 中的各種語句。
- 2)命迈、閉包贩绕。
- 3)、Task 中的配置段語句壶愤。
需要注意的是淑倾,執(zhí)行任何 Gradle 命令,在初始化階段和配置階段的代碼都會被執(zhí)行征椒。
3娇哆、執(zhí)行階段
在配置階段結束后,Gradle 會根據(jù)各個任務 Task 的依賴關系來創(chuàng)建一個有向無環(huán)圖勃救,我們可以通過 Gradle 對象的 getTaskGraph 方法來得到該有向無環(huán)圖 => TaskExecutionGraph碍讨,并且,當有向無環(huán)圖構建完成之后蒙秒,所有 Task 執(zhí)行之前勃黍,我們可以通過 whenReady(groovy.lang.Closure) 或者 addTaskExecutionGraphListener(TaskExecutionGraphListener) 來接收相應的通知,其代碼如下所示:
gradle.getTaskGraph().addTaskExecutionGraphListener(new
TaskExecutionGraphListener() {
@Override
void graphPopulated(TaskExecutionGraph graph) {
}
})
復制代碼
然后晕讲,Gradle 構建系統(tǒng)會通過調(diào)用 gradle <任務名> 來執(zhí)行相應的各個任務覆获。
4、Hook Gradle 各個生命周期節(jié)點
整個 Gradle 生命周期的流程包含如下 四個部分:
- 1)瓢省、首先弄息,解析 settings.gradle 來獲取模塊信息,這是初始化階段净捅。
- 2)疑枯、然后,配置每個模塊蛔六,配置的時候并不會執(zhí)行 task荆永。
- 3)废亭、接著,配置完了以后具钥,有一個重要的回調(diào) project.afterEvaluate豆村,它表示所有的模塊都已經(jīng)配置完了,可以準備執(zhí)行 task 了骂删。
- 4)掌动、最后,執(zhí)行指定的 task 及其依賴的 task宁玫。
在 Gradle 構建命令中粗恢,最為復雜的命令可以說是 gradle build
這個命令了,因為項目的構建過程中需要依賴很多其它的 task欧瘪。
注意事項
- 1)眷射、每一個 Hook 點對應的監(jiān)聽器一定要在回調(diào)的生命周期之前添加。
- 2)佛掖、如果注冊了多個 project.afterEvaluate 回調(diào)妖碉,那么執(zhí)行順序將與注冊順序保持一致。
5芥被、獲取構建各個階段欧宜、任務的耗時情況
了解了 Gradle 生命周期中的各個 Hook 方法之后,我們就可以 利用它們來獲取項目構建各個階段拴魄、任務的耗時情況冗茸,在 settings.gradle 中加入如下代碼即可:
long beginOfSetting = System.currentTimeMillis()
def beginOfConfig
def configHasBegin = false
def beginOfProjectConfig = new HashMap()
def beginOfProjectExcute
gradle.projectsLoaded {
println '初始化階段,耗時:' + (System.currentTimeMillis() -
beginOfSetting) + 'ms'
}
gradle.beforeProject { project ->
if (!configHasBegin) {
configHasBegin = true
beginOfConfig = System.currentTimeMillis()
}
beginOfProjectConfig.put(project, System.currentTimeMillis())
}
gradle.afterProject { project ->
def begin = beginOfProjectConfig.get(project)
println '配置階段匹中,' + project + '耗時:' +
(System.currentTimeMillis() - begin) + 'ms'
}
gradle.taskGraph.whenReady {
println '配置階段蚀狰,總共耗時:' + (System.currentTimeMillis() -
beginOfConfig) + 'ms'
beginOfProjectExcute = System.currentTimeMillis()
}
gradle.taskGraph.beforeTask { task ->
task.doFirst {
task.ext.beginOfTask = System.currentTimeMillis()
}
task.doLast {
println '執(zhí)行階段,' + task + '耗時:' +
(System.currentTimeMillis() - task.beginOfTask) + 'ms'
}
}
gradle.buildFinished {
println '執(zhí)行階段职员,耗時:' + (System.currentTimeMillis() -
beginOfProjectExcute) + 'ms'
}
復制代碼
在 Gradle 中麻蹋,執(zhí)行每一種類型的配置腳本就會創(chuàng)建與之對應的實例,而在 Gradle 中如 三種類型的配置腳本焊切,如下所示:
- 1)扮授、
Build Scrpit
:對應一個 Project 實例,即每個 build.gradle 都會轉換成一個 Project 實例专肪。 - 2)刹勃、
Init Scrpit
:對應一個 Gradle 實例,它在構建初始化時創(chuàng)建嚎尤,整個構建執(zhí)行過程中以單例形式存在荔仁。 - 3)次洼、
Settings Scrpit
:對應一個 Settings 實例遇骑,即每個 settings.gradle 都會轉換成一個 Settings 實例落萎。
可以看到,一個 Gradle 構建流程中會由一至多個 project 實例構成练链,而每一個 project 實例又是由一至多個 task 構成翔脱。下面,我們就來認識下 Project媒鼓。
三碍侦、Project
Project 是 Gradle 構建整個應用程序的入口,所以它非常重要隶糕,我們必須對其有深刻地了解。不幸的是站玄,網(wǎng)上幾乎沒有關于 project 講解的比較好的文章枚驻,不過沒關系,下面株旷,我們將會一起來深入學習 project api 這部分再登。
由前可知,每一個 build.gradle 都有一個與之對應的 Project 實例晾剖,而在 build.gradle 中锉矢,我們通常都會配置一系列的項目依賴,如下面這個依賴:
implementation 'com.github.bumptech.glide:glide:4.8.0'
復制代碼
類似于 implementation齿尽、api 這種依賴關鍵字沽损,在本質上它就是一個方法調(diào)用,在上面循头,我們使用 implementation() 方法傳入了一個 map 參數(shù)卡骂,參數(shù)里面有三對 key-value缝左,完整寫法如下所示:
implementation group: 'com.github.bumptech.glide' name:'glide' version:'4.8.0'
復制代碼
當我們使用 implementation、api 依賴對應的 aar 文件時苞慢,Gradle 會在 repository 倉庫 里面找到與之對應的依賴文件,你的倉庫中可能包含 jcenter辑畦、maven 等一系列倉庫,而每一個倉庫其實就是很多依賴文件的集合服務器, 而他們就是通過上述的 group、name焕襟、version 來進行歸類存儲的。
1它褪、Project 核心 API 分解
在 Project 中有很多的 API,但是根據(jù)它們的 屬性和用途 我們可以將其分解為 六大部分包吝,如下圖所示:
對于 Project 中各個部分的作用息堂,我們可以先來大致了解下,以便為 Project 的 API 體系建立一個整體的感知能力,如下所示:
- 1)斋扰、
Project API
:讓當前的 Project 擁有了操作它的父 Project 以及管理它的子 Project 的能力。 - 2)问裕、
Task 相關 API
:為當前 Project 提供了新增 Task 以及管理已有 Task 的能力。由于 task 非常重要巍杈,我們將放到第四章來進行講解筷畦。 - 3)作媚、
Project 屬性相關的 Api
:Gradle 會預先為我們提供一些 Project 屬性,而屬性相關的 api 讓我們擁有了為 Project 添加額外屬性的能力女揭。 - 4)、
File 相關 Api
:Project File 相關的 API 主要用來操作我們當前 Project 下的一些文件處理境蔼。 - 5)逢享、
Gradle 生命周期 API
:即我們在第二章講解過的生命周期 API。 - 6)、
其它 API
:添加依賴俊犯、添加配置、引入外部文件等等零散 API 的聚合绢彤。
2、Project API
每一個 Groovy 腳本都會被編譯器編譯成 Script 字節(jié)碼饶氏,而每一個 build.gradle 腳本都會被編譯器編譯成 Project 字節(jié)碼,所以我們在 build.gradle 中所寫的一切邏輯都是在 Project 類內(nèi)進行書寫的喊崖。下面,我們將按照由易到難的套路來介紹 Project 的一系列重要的 API。
需要提前說明的是粟耻,默認情況下我們選定根工程的 build.gradle 這個腳本文件中來學習 Project 的一系列用法霜威,關于 getAllProject 的用法如下所示:
1、getAllprojects
getAllprojects 表示 獲取所有 project 的實例,示例代碼如下所示:
/**
* getAllProjects 使用示例
*/
this.getProjects()
def getProjects() {
println "<================>"
println " Root Project Start "
println "<================>"
// 1挽绩、getAllprojects 方法返回一個包含根 project 與其子 project 的 Set 集合
// eachWithIndex 方法用于遍歷集合肩民、數(shù)組等可迭代的容器,
// 并同時返回下標割卖,不同于 each 方法僅返回 project
this.getAllprojects().eachWithIndex { Project project, int index ->
// 2、下標為 0剿涮,表明當前遍歷的是 rootProject
if (index == 0) {
println "Root Project is $project"
} else {
println "child Project is $project"
}
}
}
復制代碼
首先悬槽,我們使用了 def 關鍵字定義了一個 getProjects 方法猿棉。然后,在注釋1處杖爽,我們調(diào)用了 getAllprojects 方法返回一個包含根 project 與其子 project 的 Set 集合聪铺,并鏈式調(diào)用了 eachWithIndex 遍歷 Set 集合铃剔。接著撒桨,在注釋2處,我們會判斷當前的下標 index 是否是0番宁,如果是元莫,則表明當前遍歷的是 rootProject,則輸出 rootProject 的名字蝶押,否則茎截,輸出 child project 的名字抖坪。
下面,我們在命令行執(zhí)行 ./gradlew clean
坦喘,其運行結果如下所示:
quchao@quchaodeMacBook-Pro Awesome-WanAndroid % ./gradlew clean
settings 評估完成(settings.gradle 中代碼執(zhí)行完畢)
項目結構加載完成(初始化階段結束)
初始化結束,可訪問根項目:root project 'Awesome-WanAndroid'
初始化階段从橘,耗時:5ms
Configuration on demand is an incubating feature.
> Configure project :
<================>
Root Project Start
<================>
Root Project is root project 'Awesome-WanAndroid'
child Project is project ':app'
配置階段,root project 'Awesome-WanAndroid'耗時:284ms
> Configure project :app
...
配置階段叹洲,總共耗時:428ms
> Task :app:clean
執(zhí)行階段胁编,task ':app:clean'耗時:1ms
:app:clean spend 2ms
構建結束
Tasks spend time > 50ms:
執(zhí)行階段,耗時:9ms
復制代碼
可以看到,執(zhí)行了初始化之后伟件,就會先配置我們的 rootProject,并輸出了對應的工程信息放航。接著狈涮,便會執(zhí)行子工程 app 的配置躁锁。最后命雀,執(zhí)行了 clean 這個 task匈织。
需要注意的是胳喷,rootProject 與其旗下的各個子工程組成了一個樹形結構,但是這顆樹的高度也僅僅被限定為了兩層。
2安券、getSubprojects
getSubprojects 表示獲取當前工程下所有子工程的實例墩崩,示例代碼如下所示:
/**
* getAllsubproject 使用示例
*/
this.getSubProjects()
def getSubProjects() {
println "<================>"
println " Sub Project Start "
println "<================>"
// getSubprojects 方法返回一個包含子 project 的 Set 集合
this.getSubprojects().each { Project project ->
println "child Project is $project"
}
}
復制代碼
同 getAllprojects 的用法一樣,getSubprojects 方法返回了一個包含子 project 的 Set 集合侯勉,這里我們直接使用 each 方法將各個子 project 的名字打印出來鹦筹。其運行結果如下所示:
quchao@quchaodeMacBook-Pro Awesome-WanAndroid % ./gradlew clean
settings 評估完成(settings.gradle 中代碼執(zhí)行完畢)
...
> Configure project :
<================>
Sub Project Start
<================>
child Project is project ':app'
配置階段,root project 'Awesome-WanAndroid'耗時:289ms
> Configure project :app
...
所有項目評估完成(配置階段結束)
配置階段址貌,總共耗時:425ms
> Task :app:clean
執(zhí)行階段铐拐,task ':app:clean'耗時:1ms
:app:clean spend 2ms
構建結束
Tasks spend time > 50ms:
執(zhí)行階段,耗時:9ms
復制代碼
可以看到练对,同樣在 Gradle 的配置階段輸出了子工程的名字遍蟋。
3、getParent
getParent 表示 獲取當前 project 的父類螟凭,需要注意的是匿值,如果我們在根工程中使用它,獲取的父類會為 null赂摆,因為根工程沒有父類挟憔,所以這里我們直接在 app 的 build.gradle 下編寫下面的示例代碼:
...
> Configure project :
配置階段,root project 'Awesome-WanAndroid'耗時:104ms
> Configure project :app
gradlew version > 4.0
my parent project is Awesome-WanAndroid
配置階段烟号,project ':app'耗時:282ms
...
所有項目評估完成(配置階段結束)
配置階段绊谭,總共耗時:443ms
...
復制代碼
可以看到,這里輸出了 app project 當前的父類汪拥,即 Awesome-WanAndroid project达传。
4、getRootProject
如果我們想在根工程僅僅獲取當前的 project 實例該怎么辦呢迫筑?直接使用 getRootProject 即可在任意 build.gradle 文件獲取當前根工程的 project 實例宪赶,示例代碼如下所示:
/**
* 4、getRootProject 使用示例
*/
this.getRootPro()
def getRootPro() {
def rootProjectName = this.getRootProject().name
println "root project is $rootProjectName"
}
復制代碼
5脯燃、project
project 表示的是 指定工程的實例搂妻,然后在閉包中對其進行操作。在使用之前辕棚,我們有必要看看 project 方法的源碼欲主,如下所示:
/**
* <p>Locates a project by path and configures it using the given closure. If the path is relative, it is
* interpreted relative to this project. The target project is passed to the closure as the closure's delegate.</p>
*
* @param path The path.
* @param configureClosure The closure to use to configure the project.
* @return The project with the given path. Never returns null.
* @throws UnknownProjectException If no project with the given path exists.
*/
Project project(String path, Closure configureClosure);
復制代碼
可以看到邓厕,在 project 方法中兩個參數(shù),一個是指定工程的路徑扁瓢,另一個是用來配置該工程的閉包详恼。下面我們看看如何靈活地使用 project,示例代碼如下所示:
/**
* 5引几、project 使用示例
*/
// 1昧互、閉包參數(shù)可以放在括號外面
project("app") { Project project ->
apply plugin: 'com.android.application'
}
// 2、更簡潔的寫法是這樣的:省略參數(shù)
project("app") {
apply plugin: 'com.android.application'
}
復制代碼
使用熟練之后伟桅,我們通常會采用注釋2處的寫法敞掘。
6、allprojects
allprojects 表示 用于配置當前 project 及其旗下的每一個子 project贿讹,如下所示:
/**
* 6渐逃、allprojects 使用示例
*/
// 同 project 一樣的更簡潔寫法
allprojects {
repositories {
google()
jcenter()
mavenCentral()
maven {
url "https://jitpack.io"
}
maven { url "https://plugins.gradle.org/m2/" }
}
}
復制代碼
在 allprojects 中我們一般用來配置一些通用的配置够掠,比如上面最常見的全局倉庫配置民褂。
7、subprojects
subprojects 可以 統(tǒng)一配置當前 project 下的所有子 project疯潭,示例代碼如下所示:
/**
* 7赊堪、subprojects 使用示例:
* 給所有的子工程引入 將 aar 文件上傳置 Maven 服務器的配置腳本
*/
subprojects {
if (project.plugins.hasPlugin("com.android.library")) {
apply from: '../publishToMaven.gradle'
}
}
復制代碼
在上述示例代碼中,我們會先判斷當前 project 旗下的子 project 是不是庫竖哩,如果是庫才有必要引入 publishToMaven 腳本哭廉。
3、project 屬性
目前相叁,在 project 接口里遵绰,僅僅預先定義了 七個 屬性,其源碼如下所示:
public interface Project extends Comparable<Project>, ExtensionAware, PluginAware {
/**
* 默認的工程構建文件名稱
*/
String DEFAULT_BUILD_FILE = "build.gradle";
/**
* 區(qū)分開 project 名字與 task 名字的符號
*/
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";
...
}
復制代碼
幸運的是增淹,Gradle 提供了 ext 關鍵字讓我們有能力去定義自身所需要的擴展屬性椿访。有了它便可以對我們工程中的依賴進行全局配置。下面虑润,我們先從配置的遠古時代講起成玫,以便讓我們對 gradle 的 全局依賴配置有更深入的理解。
ext 擴展屬性
1拳喻、遠古時代
在 AS 剛出現(xiàn)的時候哭当,我們的依賴配置代碼是這樣的:
android {
compileSdkVersion 27
buildToolsVersion "28.0.3"
...
}
復制代碼
2、刀耕火種
但是這種直接寫值的方式顯示是不規(guī)范的冗澈,因此钦勘,后面我們使用了這種方式:
def mCompileSdkVersion = 27
def mBuildToolsVersion = "28.0.3"
android {
compileSdkVersion mCompileSdkVersion
buildToolsVersion mBuildToolsVersion
...
}
復制代碼
3、鐵犁牛耕
如果每一個子 project 都需要配置相同的 Version亚亲,我們就需要多寫很多的重復代碼个盆,因此脖岛,我們可以利用上面我們學過的 subproject 和 ext 來進行簡化:
// 在根目錄下的 build.gradle 中
subprojects {
ext {
compileSdkVersion = 27
buildToolsVersion = "28.0.3"
}
}
// 在 app moudle 下的 build.gradle 中
android {
compileSdkVersion this.compileSdkVersion
buildToolsVersion this.buildToolsVersion
...
}
復制代碼
4、工業(yè)時代
使用 subprojects 方法來定義通用的擴展屬性還是存在著很嚴重的問題颊亮,它跟之前的方式一樣柴梆,還是會在每一個子 project 去定義這些被擴展的屬性,此時终惑,我們可以將 subprojects 去除绍在,直接使用 ext 進行全局定義即可:
// 在根目錄下的 build.gradle 中
ext {
compileSdkVersion = 27
buildToolsVersion = "28.0.3"
}
復制代碼
5、電器時代
當項目越來越大的時候雹有,在根項目下定義的 ext 擴展屬性越來越多偿渡,因此,我們可以將這一套全局屬性配置在另一個 gradle 腳本中進行定義霸奕,這里我們通常會將其命名為 config.gradle溜宽,通用的模板如下所示:
ext {
android = [
compileSdkVersion : 27,
buildToolsVersion : "28.0.3",
...
]
version = [
supportLibraryVersion : "28.0.0",
...
]
dependencies = [
// base
"appcompat-v7" : "com.android.support:appcompat-v7:${version["supportLibraryVersion"]}",
...
]
annotationProcessor = [
"glide_compiler" : "com.github.bumptech.glide:compiler:${version["glideVersion"]}",
...
]
apiFileDependencies = [
"launchstarter" : "libs/launchstarter-release-1.0.0.aar",
...
]
debugImplementationDependencies = [
"MethodTraceMan" : "com.github.zhengcx:MethodTraceMan:1.0.7"
]
releaseImplementationDependencies = [
"MethodTraceMan" : "com.github.zhengcx:MethodTraceMan:1.0.5-noop"
]
...
}
復制代碼
6、更加智能化的現(xiàn)在
盡管有了很全面的全局依賴配置文件质帅,但是适揉,在我們的各個模塊之中,還是不得不寫一大長串的依賴代碼煤惩,因此嫉嘀,我們可以 使用遍歷的方式去進行依賴,其模板代碼如下所示:
// 在各個 moulde 下的 build.gradle 腳本下
def implementationDependencies = rootProject.ext.dependencies
def processors = rootProject.ext.annotationProcessor
def apiFileDependencies = rootProject.ext.apiFileDependencies
// 在各個 moulde 下的 build.gradle 腳本的 dependencies 閉包中
// 處理所有的 aar 依賴
apiFileDependencies.each { k, v -> api files(v)}
// 處理所有的 xxximplementation 依賴
implementationDependencies.each { k, v -> implementation v }
debugImplementationDependencies.each { k, v -> debugImplementation v }
...
// 處理 annotationProcessor 依賴
processors.each { k, v -> annotationProcessor v }
// 處理所有包含 exclude 的依賴
debugImplementationExcludes.each { entry ->
debugImplementation(entry.key) {
entry.value.each { childEntry ->
exclude(group: childEntry.key, module: childEntry.value)
}
}
}
復制代碼
也許未來隨著 Gradle 的不斷優(yōu)化會有更加簡潔的方式魄揉,如果你有更好地方式剪侮,我們可以來探討一番。
在 gradle.properties 下定義擴展屬性
除了使用 ext 擴展屬性定義額外的屬性之外洛退,我們也可以在 gradle.properties 下定義擴展屬性瓣俯,其示例代碼如下所示:
// 在 gradle.properties 中
mCompileVersion = 27
// 在 app moudle 下的 build.gradle 中
compileSdkVersion mCompileVersion.toInteger()
復制代碼
4、文件相關 API
在 gradle 中兵怯,文件相關的 API 可以總結為如下 兩大類:
- 1)彩匕、路徑獲取 API
getRootDir()
getProjectDir()
getBuildDir()
- 2)、文件操作相關 API
文件定位
文件拷貝
文件樹遍歷
1)摇零、路徑獲取 API
關于路徑獲取的 API 常用的有 三種推掸,其示例代碼如下所示:
/**
* 1、路徑獲取 API
*/
println "the root file path is:" + getRootDir().absolutePath
println "this build file path is:" + getBuildDir().absolutePath
println "this Project file path is:" + getProjectDir().absolutePath
復制代碼
然后驻仅,我們執(zhí)行 ./gradlew clean
谅畅,輸出結果如下所示:
> Configure project :
the root file path is:/Users/quchao/Documents/main-open-project/Awesome-WanAndroid
this build file path is:/Users/quchao/Documents/main-open-project/Awesome-WanAndroid/build
this Project file path is:/Users/quchao/Documents/main-open-project/Awesome-WanAndroid
配置階段,root project 'Awesome-WanAndroid'耗時:538ms
復制代碼
2)噪服、文件操作相關 API
1毡泻、文件定位
常用的文件定位 API 有 file/files
,其示例代碼如下所示:
// 在 rootProject 下的 build.gradle 中
/**
* 1粘优、文件定位之 file
*/
this.getContent("config.gradle")
def getContent(String path) {
try {
// 不同與 new file 的需要傳入 絕對路徑 的方式仇味,
// file 從相對于當前的 project 工程開始查找
def mFile = file(path)
println mFile.text
} catch (GradleException e) {
println e.toString()
return null
}
}
/**
* 1呻顽、文件定位之 files
*/
this.getContent("config.gradle", "build.gradle")
def getContent(String path1, String path2) {
try {
// 不同與 new file 的需要傳入 絕對路徑 的方式,
// file 從相對于當前的 project 工程開始查找
def mFiles = files(path1, path2)
println mFiles[0].text + mFiles[1].text
} catch (GradleException e) {
println e.toString()
return null
}
}
復制代碼
2丹墨、文件拷貝
常用的文件拷貝 API 為 copy
廊遍,其示例代碼如下所示:
/**
* 2、文件拷貝
*/
copy {
// 既可以拷貝文件贩挣,也可以拷貝文件夾
// 這里是將 app moudle 下生成的 apk 目錄拷貝到
// 根工程下的 build 目錄
from file("build/outputs/apk")
into getRootProject().getBuildDir().path + "/apk/"
exclude {
// 排除不需要拷貝的文件
}
rename {
// 對拷貝過來的文件進行重命名
}
}
復制代碼
3喉前、文件樹遍歷
我們可以 使用 fileTree 將當前目錄轉換為文件數(shù)的形式,然后便可以獲取到每一個樹元素(節(jié)點)進行相應的操作王财,其示例代碼如下所示:
/**
* 3卵迂、文件樹遍歷
*/
fileTree("build/outputs/apk") { FileTree fileTree ->
fileTree.visit { FileTreeElement fileTreeElement ->
println "The file is $fileTreeElement.file.name"
copy {
from fileTreeElement.file
into getRootProject().getBuildDir().path + "/apkTree/"
}
}
}
復制代碼
5、其它 API
1绒净、依賴相關 API
根項目下的 buildscript
buildscript 中 用于配置項目核心的依賴见咒。其原始的使用示例與簡化后的使用示例分別如下所示:
原始的使用示例
buildscript { ScriptHandler scriptHandler ->
// 配置我們工程的倉庫地址
scriptHandler.repositories { RepositoryHandler repositoryHandler ->
repositoryHandler.google()
repositoryHandler.jcenter()
repositoryHandler.mavenCentral()
repositoryHandler.maven { url 'https://maven.google.com' }
repositoryHandler.maven { url "https://plugins.gradle.org/m2/" }
repositoryHandler.maven {
url uri('../PAGradlePlugin/repo')
}
// 訪問本地私有 Maven 服務器
repositoryHandler.maven {
name "personal"
url "http://localhost:8081:/JsonChao/repositories"
credentials {
username = "JsonChao"
password = "123456"
}
}
}
// 配置我們工程的插件依賴
dependencies { DependencyHandler dependencyHandler ->
dependencyHandler.classpath 'com.android.tools.build:gradle:3.1.4'
...
}
復制代碼
簡化后的使用示例
buildscript {
// 配置我們工程的倉庫地址
repositories {
google()
jcenter()
mavenCentral()
maven { url 'https://maven.google.com' }
maven { url "https://plugins.gradle.org/m2/" }
maven {
url uri('../PAGradlePlugin/repo')
}
}
// 配置我們工程的插件依賴
dependencies {
classpath 'com.android.tools.build:gradle:3.1.4'
...
}
復制代碼
app moudle 下的 dependencies
不同于 根項目 buildscript 中的 dependencies 是用來配置我們 Gradle 工程的插件依賴的,而 app moudle 下的 dependencies 是用來為應用程序添加第三方依賴的挂疆。關于 app moudle 下的依賴使用這里我們 需要注意下 exclude 與 transitive 的使用 即可改览,示例代碼如下所示:
implementation(rootProject.ext.dependencies.glide) {
// 排除依賴:一般用于解決資源、代碼沖突相關的問題
exclude module: 'support-v4'
// 傳遞依賴:A => B => C 囱嫩,B 中使用到了 C 中的依賴恃疯,
// 且 A 依賴于 B漏设,如果打開傳遞依賴墨闲,則 A 能使用到 B
// 中所使用的 C 中的依賴,默認都是不打開郑口,即 false
transitive false
}
復制代碼
2鸳碧、外部命令執(zhí)行
我們一般是 使用 Gradle 提供的 exec 來執(zhí)行外部命令,下面我們就使用 exec 命令來 將當前工程下新生產(chǎn)的 APK 文件拷貝到 電腦下的 Downloads 目錄中犬性,示例代碼如下所示:
/**
* 使用 exec 執(zhí)行外部命令
*/
task apkMove() {
doLast {
// 在 gradle 的執(zhí)行階段去執(zhí)行
def sourcePath = this.buildDir.path + "/outputs/apk/speed/release/"
def destinationPath = "/Users/quchao/Downloads/"
def command = "mv -f $sourcePath $destinationPath"
exec {
try {
executable "bash"
args "-c", command
println "The command execute is success"
} catch (GradleException e) {
println "The command execute is failed"
}
}
}
}
復制代碼
四瞻离、Task
只有 Task 才可以在 Gradle 的執(zhí)行階段去執(zhí)行(其實質是執(zhí)行的 Task 中的一系列 Action),所以 Task 的重要性不言而喻乒裆。
1套利、從一個例子 ?? 出發(fā)
首先,我們可以在任意一個 build.gradle 文件中可以去定義一個 Task鹤耍,下面是一個完整的示例代碼:
// 1肉迫、聲明一個名為 JsonChao 的 gradle task
task JsonChao
JsonChao {
// 2、在 JsonChao task 閉包內(nèi)輸出 hello~稿黄,
// 執(zhí)行在 gradle 生命周期的第二個階段喊衫,即配置階段。
println("hello~")
// 3杆怕、給 task 附帶一些 執(zhí)行動作(Action)族购,執(zhí)行在
// gradle 生命周期的第三個階段壳贪,即執(zhí)行階段。
doFirst {
println("start")
}
doLast {
println("end")
}
}
// 4寝杖、除了上述這種將聲明與配置违施、Action 分別定義
// 的方式之外,也可以直接將它們結合起來瑟幕。
// 這里我們又定義了一個 Android task醉拓,它依賴于 JsonChao
// task,也就是說收苏,必須先執(zhí)行完 JsonChao task亿卤,才能
// 去執(zhí)行 Android task,由此鹿霸,它們之間便組成了一個
// 有向無環(huán)圖:JsonChao task => Android task
task Andorid(dependsOn:"JsonChao") {
doLast {
println("end?")
}
}
復制代碼
首先排吴,在注釋1處,我們聲明了一個名為 JsonChao 的 gradle task懦鼠。接著钻哩,在注釋2處,在 JsonChao task 閉包內(nèi)輸出了 hello~肛冶,這里的代碼將會執(zhí)行在 gradle 生命周期的第二個階段街氢,即配置階段。然后睦袖,在注釋3處珊肃,這里 給 task 附帶一些了一些執(zhí)行動作(Action),即 doFirst 與 doLast馅笙,它們閉包內(nèi)的代碼將執(zhí)行在 gradle 生命周期的第三個階段伦乔,即執(zhí)行階段。
對于 doFirst 與 doLast 這兩個 Action董习,它們的作用分別如下所示:
-
doFirst
:表示 task 執(zhí)行最開始的時候被調(diào)用的 Action烈和。 -
doLast
:表示 task 將執(zhí)行完的時候被調(diào)用的 Action。
需要注意的是皿淋,doFirst 和 doLast 是可以被執(zhí)行多次的招刹。
最后,注釋4處窝趣,我們可以看到疯暑,除了注釋1、2高帖、3處這種將聲明與配置缰儿、Action 分別定義的方式之外,也可以直接將它們結合起來散址。在這里我們又定義了一個 Android task乖阵,它依賴于 JsonChao task宣赔,也就是說,必須先執(zhí)行完 JsonChao task瞪浸,才能 去執(zhí)行 Android task儒将,由此,它們之間便組成了一個 有向無環(huán)圖:JsonChao task => Android task对蒲。
執(zhí)行 Android 這個 gradle task 可以看到如下輸出結果:
> Task :JsonChao
start
end
執(zhí)行階段钩蚊,task ':JsonChao'耗時:1ms
:JsonChao spend 4ms
> Task :Andorid
end?
執(zhí)行階段,task ':Andorid'耗時:1ms
:Andorid spend 2ms
構建結束
Tasks spend time > 50ms:
執(zhí)行階段蹈矮,耗時:15ms
復制代碼
2砰逻、Task 的定義及配置
Task 常見的定義方式有 兩種,示例代碼如下所示:
// Task 定義方式1:直接通過 task 函數(shù)去創(chuàng)建(在 "()" 可以不指定 group 與 description 屬性)
task myTask1(group: "MyTask", description: "task1") {
println "This is myTask1"
}
// Task 定義方式2:通過 TaskContainer 去創(chuàng)建 task
this.tasks.create(name: "myTask2") {
setGroup("MyTask")
setDescription("task2")
println "This is myTask2"
}
復制代碼
定義完上述 Task 之后再同步項目泛鸟,即可看到對應的 Task Group 及其旗下的 Tasks蝠咆。
Task 的屬性
需要注意的是,不管是哪一種 task 的定義方式北滥,在 "()" 內(nèi)我們都可以配置它的一系列屬性刚操,如下:
project.task('JsonChao3', group: "JsonChao", description: "my tasks",
dependsOn: ["JsonChao1", "JsonChao2"] ).doLast {
println "execute JsonChao3 Task"
}
復制代碼
目前 官方所支持的屬性 可以總結為如下表格:
選型 | 描述 | 默認值 |
---|---|---|
"name" | task 名字 | 無,必須指定 |
"type" | 需要創(chuàng)建的 task Class | DefaultTask |
"action" | 當 task 執(zhí)行的時候再芋,需要執(zhí)行的閉包 closure 或 行為 Action | null |
"overwrite" | 替換一個已存在的 task | false |
"dependsOn" | 該 task 所依賴的 task 集合 | [] |
"group" | 該 task 所屬組 | null |
"description" | task 的描述信息 | null |
"constructorArgs" | 傳遞到 task Class 構造器中的參數(shù) | null |
使用 "$" 來引用另一個 task 的屬性
在這里菊霜,我們可以 在當前 task 中使用 "$" 來引用另一個 task 的屬性,示例代碼如下所示:
task Gradle_First() {
}
task Gradle_Last() {
doLast {
println "I am not $Gradle_First.name"
}
}
復制代碼
使用 ext 給 task 自定義需要的屬性
當然济赎,除了使用已有的屬性之外鉴逞,我們也可以 使用 ext 給 task 自定義需要的屬性,代碼如下所示:
task Gradle_First() {
ext.good = true
}
task Gradle_Last() {
doFirst {
println Gradle_First.good
}
doLast {
println "I am not $Gradle_First.name"
}
}
復制代碼
使用 defaultTasks 關鍵字標識默認執(zhí)行任務
此外联喘,我們也可以 使用 defaultTasks 關鍵字 來將一些任務標識為默認的執(zhí)行任務华蜒,代碼如下所示:
defaultTasks "Gradle_First", "Gradle_Last"
task Gradle_First() {
ext.good = true
}
task Gradle_Last() {
doFirst {
println Gradle_First.goodg
}
doLast {
println "I am not $Gradle_First.name"
}
}
復制代碼
注意事項
每個 task 都會經(jīng)歷 初始化辙纬、配置豁遭、執(zhí)行 這一套完整的生命周期流程。
3蓖谢、Task 的執(zhí)行詳解
Task 通常使用 doFirst 與 doLast 兩個方式用于在執(zhí)行期間進行操作。其示例代碼如下所示:
// 使用 Task 在執(zhí)行階段進行操作
task myTask3(group: "MyTask", description: "task3") {
println "This is myTask3"
doFirst {
// 老二
println "This group is 2"
}
doLast {
// 老三
println "This description is 3"
}
}
// 也可以使用 taskName.doxxx 的方式添加執(zhí)行任務
myTask3.doFirst {
// 這種方式的最先執(zhí)行 => 老大
println "This group is 1"
}
復制代碼
Task 執(zhí)行實戰(zhàn)
接下來,我們就使用 doFirst 與 doLast 來進行一下實戰(zhàn)腕够,來實現(xiàn) 計算 build 執(zhí)行期間的耗時,其完整代碼如下所示:
// Task 執(zhí)行實戰(zhàn):計算 build 執(zhí)行期間的耗時
def startBuildTime, endBuildTime
// 1捅厂、在 Gradle 配置階段完成之后進行操作,
// 以此保證要執(zhí)行的 task 配置完畢
this.afterEvaluate { Project project ->
// 2盈厘、找到當前 project 下第一個執(zhí)行的 task,即 preBuild task
def preBuildTask = project.tasks.getByName("preBuild")
preBuildTask.doFirst {
// 3契吉、獲取第一個 task 開始執(zhí)行時刻的時間戳
startBuildTime = System.currentTimeMillis()
}
// 4捐晶、找到當前 project 下最后一個執(zhí)行的 task眼耀,即 build task
def buildTask = project.tasks.getByName("build")
buildTask.doLast {
// 5、獲取最后一個 task 執(zhí)行完成前一瞬間的時間戳
endBuildTime = System.currentTimeMillis()
// 6、輸出 build 執(zhí)行期間的耗時
println "Current project execute time is ${endBuildTime - startBuildTime}"
}
}
復制代碼
4、Task 的依賴和執(zhí)行順序
指定 Task 的執(zhí)行順序有 三種 方式尤慰,如下圖所示:
1)割择、dependsOn 強依賴方式
dependsOn 強依賴的方式可以細分為 靜態(tài)依賴和動態(tài)依賴,示例代碼如下所示:
靜態(tài)依賴
task task1 {
doLast {
println "This is task1"
}
}
task task2 {
doLast {
println "This is task2"
}
}
// Task 靜態(tài)依賴方式1 (常用)
task task3(dependsOn: [task1, task2]) {
doLast {
println "This is task3"
}
}
// Task 靜態(tài)依賴方式2
task3.dependsOn(task1, task2)
復制代碼
動態(tài)依賴
// Task 動態(tài)依賴方式
task dytask4 {
dependsOn this.tasks.findAll { task ->
return task.name.startsWith("task")
}
doLast {
println "This is task4"
}
}
復制代碼
2)蕉饼、通過 Task 指定輸入輸出
我們也可以通過 Task 來指定輸入輸出玛歌,使用這種方式我們可以 高效地實現(xiàn)一個 自動維護版本發(fā)布文檔的 gradle 腳本昧港,其中輸入輸出相關的代碼如下所示:
task writeTask {
inputs.property('versionCode', this.versionCode)
inputs.property('versionName', this.versionName)
inputs.property('versionInfo', this.versionInfo)
// 1支子、指定輸出文件為 destFile
outputs.file this.destFile
doLast {
//將輸入的內(nèi)容寫入到輸出文件中去
def data = inputs.getProperties()
File file = outputs.getFiles().getSingleFile()
// 寫入版本信息到 XML 文件
...
}
task readTask {
// 2、指定輸入文件為上一個 task(writeTask) 的輸出文件 destFile
inputs.file this.destFile
doLast {
//讀取輸入文件的內(nèi)容并顯示
def file = inputs.files.singleFile
println file.text
}
}
task outputwithinputTask {
// 3、先執(zhí)行寫入丰辣,再執(zhí)行讀取
dependsOn writeTask, readTask
doLast {
println '輸入輸出任務結束'
}
}
復制代碼
首先飘哨,我們定義了一個 WirteTask,然后琐凭,在注釋1處芽隆,指定了輸出文件為 destFile, 并寫入版本信息到 XML 文件淘正。接著摆马,定義了一個 readTask,并在注釋2處鸿吆,指定輸入文件為上一個 task(即 writeTask) 的輸出文件。最后述呐,在注釋3處惩淳,使用 dependsOn 將這兩個 task 關聯(lián)起來,此時輸入與輸出的順序是會先執(zhí)行寫入,再執(zhí)行讀取思犁。這樣代虾,一個輸入輸出的實際案例就實現(xiàn)了。如果想要查看完整的實現(xiàn)代碼激蹲,請查看 Awesome-WanAndroid 的 releaseinfo.gradle 腳本棉磨。
此外,在 McImage 中就利用了 dependsOn 的方式將自身的 task 插入到了 Gradle 的構建流程之中学辱,關鍵代碼如下所示:
// inject task
(project.tasks.findByName(chmodTask.name) as Task).dependsOn(mergeResourcesTask.taskDependencies.getDependencies(mergeResourcesTask))
(project.tasks.findByName(mcPicTask.name) as Task).dependsOn(project.tasks.findByName(chmodTask.name) as Task)
mergeResourcesTask.dependsOn(project.tasks.findByName(mcPicTask.name))
復制代碼
通過 API 指定依賴順序
除了 dependsOn 的方式乘瓤,我們還可以在 task 閉包中通過 mustRunAfter 方法指定 task 的依賴順序,需要注意的是策泣,在最新的 gradle api 中衙傀,mustRunAfter 必須結合 dependsOn 強依賴進行配套使用,其示例代碼如下所示:
// 通過 API 指定依賴順序
task taskX {
mustRunAfter "taskY"
doFirst {
println "this is taskX"
}
}
task taskY {
// 使用 mustRunAfter 指定依賴的(一至多個)前置 task
// 也可以使用 shouldRunAfter 的方式萨咕,但是是非強制的依賴
// shouldRunAfter taskA
doFirst {
println "this is taskY"
}
}
task taskZ(dependsOn: [taskX, taskY]) {
mustRunAfter "taskY"
doFirst {
println "this is taskZ"
}
}
復制代碼
5统抬、Task 類型
除了定義一個新的 task 之外,我們也可以使用 type 屬性來直接使用一個已有的 task 類型(很多文章都說的是繼承一個已有的類危队,不是很準確)聪建,比如 Gradle 自帶的 Copy、Delete茫陆、Sync task
等等妆偏。示例代碼如下所示:
// 1、刪除根目錄下的 build 文件
task clean(type: Delete) {
delete rootProject.buildDir
}
// 2盅弛、將 doc 復制到 build/target 目錄下
task copyDocs(type: Copy) {
from 'src/main/doc'
into 'build/target/doc'
}
// 3钱骂、執(zhí)行時會復制源文件到目標目錄,然后從目標目錄刪除所有非復制文件
task syncFile(type:Sync) {
from 'src/main/doc'
into 'build/target/doc'
}
復制代碼
6挪鹏、掛接到構建生命周期
我們可以使用 gradle 提供的一系列生命周期 API 去掛接我們自己的 task 到構建生命周期之中见秽,比如使用 afterEvaluate 方法 將我們第三小節(jié)定義的 writeTask 掛接到 gradle 配置完所有的 task 之后的時刻,示例代碼如下所示:
// 在配置階段執(zhí)行完之后執(zhí)行 writeTask
this.project.afterEvaluate { project ->
def buildTask = project.tasks.findByName("build")
doLast {
buildTask.doLast {
// 5.x 上使用 finalizedBy
writeTask.execute()
}
}
}
復制代碼
需要注意的是讨盒,配置完成之后解取,我們需要在 app moudle 下引入我們定義的 releaseinfo 腳本,引入方式如下:
apply from: this.project.file("releaseinfo.gradle")
復制代碼
五返顺、SourceSet
SourceSet 主要是 用來設置我們項目中源碼或資源的位置的禀苦,目前它最常見的兩個使用案例就是如下 兩類:
- 1)、修改 so 庫存放位置遂鹊。
- 2)振乏、資源文件分包存放。
1秉扑、修改 so 庫存放位置
我們僅需在 app moudle 下的 android 閉包下配置如下代碼即可修改 so 庫存放位置:
android {
...
sourceSets {
main {
// 修改 so 庫存放位置
jniLibs.srcDirs = ["libs"]
}
}
}
復制代碼
2慧邮、資源文件分包存放
同樣,在 app moudle 下的 android 閉包下配置如下代碼即可將資源文件進行分包存放:
android {
sourceSets {
main {
res.srcDirs = ["src/main/res",
"src/main/res-play",
"src/main/res-shop"
...
]
}
}
}
復制代碼
此外,我們也可以使用如下代碼 將 sourceSets 在 android 閉包的外部進行定義:
this.android.sourceSets {
...
}
復制代碼
六误澳、Gradle 命令
Gradle 的命令有很多耻矮,但是我們通常只會使用如下兩種類型的命令:
- 1)、獲取構建信息的命令忆谓。
- 2)裆装、執(zhí)行 task 的命令。
1倡缠、獲取構建信息的命令
// 1哨免、按自頂向下的結構列出子項目的名稱列表
./gradlew projects
// 2、分類列出項目中所有的任務
./gradlew tasks
// 3毡琉、列出項目的依賴列表
./gradlew dependencies
復制代碼
2铁瞒、執(zhí)行 task 的命令
常規(guī)的用于執(zhí)行 task 的命令有 四種,如下所示:
// 1桅滋、用于執(zhí)行多個 task 任務
./gradlew JsonChao Gradle_Last
// 2慧耍、使用 -x 排除單個 task 任務
./gradlew -x JsonChao
// 3、使用 -continue 可以在構建失敗后繼續(xù)執(zhí)行下面的構建命令
./gradlew -continue JsonChao
// 4丐谋、建議使用簡化的 task name 去執(zhí)行 task芍碧,下面的命令用于執(zhí)行
// Gradle_Last 這個 task
./gradlew G_Last
復制代碼
而對于子目錄下定義的 task,我們通常會使用如下的命令來執(zhí)行它:
// 1号俐、使用 -b 執(zhí)行 app 目錄下定義的 task
./gradlew -b app/build.gradle MyTask
// 2泌豆、在大型項目中我們一般使用更加智能的 -p 來替代 -b
./gradlew -p app MyTask
復制代碼
七、總結
至此吏饿,我們就將 Gradle 的核心 API 部分講解完畢了踪危,這里我們再來回顧一下本文的要點虐呻,如下所示:
- 一驾讲、Gradle 優(yōu)勢
- 1昨凡、更好的靈活性
- 2钠怯、更細的粒度
- 3、更好的擴展性
- 4睛驳、更強的兼容性
- 二辜梳、Gradle 構建生命周期
- 1泡态、初始化階段
- 2官疲、配置階段
- 3袱结、執(zhí)行階段
- 4、Hook Gradle 各個生命周期節(jié)點
- 5途凫、獲取構建各個階段垢夹、任務的耗時情況
- 三、Project
- 1颖榜、Project 核心 API 分解
- 2棚饵、Project API
- 3煤裙、project 屬性
- 4掩完、文件相關 API
- 5噪漾、其它 API
- 四、Task
- 1且蓬、從一個例子 ?? 出發(fā)
- 2欣硼、Task 的定義及配置
- 3、Task 的執(zhí)行詳解
- 4恶阴、Task 的依賴和執(zhí)行順序
- 5诈胜、Task 類型
- 6、掛接到構建生命周期
- 五冯事、SourceSet
- 1焦匈、修改 so 庫存放位置
- 2、資源文件分包存放
- 六昵仅、Gradle 命令
- 1缓熟、獲取構建信息的命令
- 2、執(zhí)行 task 的命令
Gradle 的核心 API 非常重要摔笤,這對我們高效實現(xiàn)一個 Gradle 插件無疑是必不可少的够滑。因為 只有扎實基礎才能走的更遠,愿我們能一同前行吕世。
作者:jsonchao
鏈接:https://juejin.cn/post/6844904132092903437
來源:掘金
著作權歸作者所有彰触。商業(yè)轉載請聯(lián)系作者獲得授權,非商業(yè)轉載請注明出處命辖。