? ? ? ? 我們知道Android開發中,Android studio開發平臺是通過Gradle構建項目的,那Gradle到底是如何構建生成我們想要的apk文件的呢,而對于一個Android開發人員,關于Gradle相關的知識點,我們應該需要了解那些方面的知識呢,今天我就帶大家來了解一下Android開發應該知道的關于Gradle相關知識點吧。
Gradle知識點匯總:
一:Android Studio apk生成的過程簡介
二:為什么使用Gradle,Gradle有什么優點
三:Andorid構建項目時,Gradle有哪些相關的文件
四:Grovvy常見基本語法介紹
五:Gradle插件講解
六:Gradle的生命周期
七:Android中Gradle的常見配置關鍵字與常見問題匯總(不斷更新)
八:如何提高Gradle編譯效率
九:擴展閱讀
一:Android Studio apk生成的過程簡介
Apk生成流程圖:
簡述:Andorid Studio開發平臺通過讀取Gradle相關文件,從而開始構建并整合項目中的各種類型的文件,從而一步步的生成Apk文件,而我們可以根據項目的需要,通過配置Gradle文件的內容,從而構建出符合我們自己需求的apk文件。
例如:
1、生成多渠道的apk包(應用包,各大手機應用商店)
2、生成不同開發環境的apk包(dev,sit,uat,prod)
二:為什么使用Gradle,Gradle有什么優點
? ? ? ? Gradle是一個構建工具,那么為什么要用構建工具,這就需要先從項目自動化開始講起。
? ? ? ? 在我們開發軟件時,會面臨相似的情況就是,我們需要去用IDE來進行編碼,當完成一些功能時會進行編譯、單元測試、打包等工作,這些工作都需要開發人員手動來實現。而一般的軟件都是迭代式開發的,一個版本接著一個版本,每個版本又可能有很多的功能,如果開發每次實現功能時都需要手動的進行編譯、單元測試和打包等工作,那顯然會非常耗時而且也容易出現問題,因此項目自動化應運而生。
它有以下優點:
一、它可以盡量防止開發手動介入從而節省了開發的時間并減少錯誤的發生。
二、自動化可以自定義有序的步驟來完成代碼的編譯、測試和打包等工作,讓重復的步驟變得簡單。
三、IDE可能受到不同操作系統的限制,而自動化構建是不會依賴于特定的操作系統和IDE的,具有平臺無關性。
四:Gradle是一款基于JVM的專注于靈活性和性能的開源構建工具,Gradle的特性與優點如下圖:
特性與優點詳解:
1、輕松的可拓展性
? ? ? ? Gradle有非常良好的拓展性。如果你想要在多個構建或者項目中分享可重用代碼,Gradle的插件會幫助你實現。將Gradle插件應用于你的項目中,它會在你的項目構建過程中提供很多幫助:為你的添加項目的依賴的第三方庫、為你的項目添加有用的默認設置和約定(源代碼位置、單元測試代碼位置)。
2、采用了Groovy
? ? ? ? Ant和Maven的構建腳本是由XML來編寫的,如果XML邏輯復雜內容太多就不容易維護。Gradle可以使用Groovy來實現構建腳本,Groovy是基于Jvm一種動態語言,它的語法和Java非常相似并兼容Java,因此你無需擔心學習Groovy的成本。Groovy在Java的基礎上增加了很多動態類型和靈活的特性,比起XML,Gradle更具有表達性和可讀性。
3、強大的依賴管理
? ? ? ?Gradle提供了可配置的可靠的依賴管理方案。一旦依賴的庫被下載并存儲到本地緩存中,我們的項目就可以使用了。依賴管理很好的實現了在不同的平臺和機器上產生相同的構建結果。
4、靈活的約定
? ? ? ? Gradle可以為構建你的項目提供引導和默認值,如果你使用這種約定,你的Gradle構建腳本不會有幾行。比起Ant,Gradle不僅僅提供了約定,還可以讓你輕松的打破約定。
5、GradleWrapper
? ? ? ?GradleWrapper是對Gradle的包裝,它的作用是簡化Gradle本身的下載、安裝和構建,比如它會在我們沒有安裝Gradle的情況下,去下載指定版本的Gradle并進行構建。Gradle的版本很多,所以有可能出現版本兼容的問題,這時就需要GradleWrapper去統一Gradle的版本,避免開發團隊因為Gradle版本不一致而產生問題。
6、可以和其他構建工具集成
? ? ? ? Gradle可以和Ant、Maven和Ivy進行集成,比如我們可以把Ant的構建腳本導入到Gradle的構建中。
7、底層API
? ? ? ? Gradle顯然無法滿足所有企業級構建的所有要求,但是可以通過HookGradle的生命周期,來監控和配置構建腳本。
8、社區的支持和推動
? ? ? ? Gradle是一個開源的項目,它遵循了Apache License 2.0協議。Gradle的優良特性吸引了很多開發者并形成了Gradle社區,很多開源軟件開發者為Gradle的核心代碼做出了共享。
三:Andorid構建項目時,Gradle有哪些相關的文件
1、build.gradle(項目)
2、build.gradle(module)
3、gradle-wrapper.properties
4、config.gradle
5、setting.gradle
6、gradle.properties
GradleWrapper文件:
? ? ? ?GradleWrapper稱為Gradle包裝器,是對Gradle的一層包裝。為什么需要GradleWrapper呢?比如在一個開發團隊中,如果每進來一個成員,都需要在計算機中安裝Gradle,這個時候運行Gradle的環境和版本就會對構建結果帶來不確定性。針對這個問題,Gradle提供了一個解決方案,那就是GradleWrapper,它是一個腳本,可以在計算機沒有安裝Gradle的情況下運行Gradle構建,并且能夠指定Gradle的版本,開發人員可以快速啟動并運行Gradle項目,而不必手動安裝,這樣就標準化了項目,從而提高了開發效率。AS在新建項目時會自帶GradleWrapper,這也是我們很少去單獨去下載安裝Gradle的原因。
???GradleWrapper的工作流程如下圖所示:
???gradle-wrapper.properties是GradleWrapper的屬性文件,用來配置GradleWrapper,Gradle4.2.1版本對應的gradle-wrapper.properties如下所示。
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-4.2.1-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
字段的含義解析:
distributionBase:Gradle解包后存儲的主目錄。
distributionPath:distributionBase指定目錄的子目錄。distributionBase+distributionPath就是Gradle解包后的存放位置。
distributionUrl:Gradle發行版壓縮包的下載地址。
zipStoreBase:Gradle壓縮包存儲主目錄。
zipStorePath:zipStoreBase指定目錄的子目錄。zipStoreBase+zipStorePath就是Gradle壓縮包的存放位置。
? ? ? ? 這里我們最需要關注的是distributionUrl這個字段,如果官方的地址下載不了或者緩慢,可以將這個地址換為其他的鏡像地址,或者干脆把Gradle發行版壓縮包放在服務器上以供下載。
四:grovvy常見基本語法介紹
簡述:Groovy是Apache旗下的一種基于JVM的面向對象編程語言,既可以用于面向對象編程,也可以用作純粹的腳本語言,在語言的設計上它吸納了Python、Ruby和Smalltalk語言的優秀特性,比如動態類型轉換、閉包和元編程支持。
? ? ? ? Groovy與Java可以很好的互相調用并結合編程 ,比如在寫Groovy的時候忘記了語法可以直接按Java的語法繼續寫,也可以在Java中調用Groovy腳本。比起Java,Groovy語法更加的靈活和簡潔,可以用更少的代碼來實現Java實現的同樣功能。
? ? ? ?項目構建比較復雜,為了使用各種開發語言的開發者都能夠快速的構建項目,專家們開發出了Gradle這個基于Groovy的DSL,DSL(DomainSpecifcLanguage)意為領域特定語言,只用于某個特定的領域。
? ? ? ? 我們只要按照Groovy的DSL語法來寫,就可以輕松構建項目。
語法一:變量
???Groovy中用def關鍵字來定義變量,可以不指定變量的類型,默認訪問修飾符是public。
???def a = 1;
???def int b = 1;
???def c = "hello world";
語法二:方法
? ? ? ? 方法使用返回類型或def關鍵字定義,方法可以接收任意數量的參數,這些參數可以不申明類型,如果不提供可見性修飾符,則該方法為public。
???用def關鍵字定義方法。
???task method <<{
???????add (1,2)
???????minus 1,2 //1
???}
???def add(int a,int b) {
???????println a+b? //3
???}
???def minus(a,b) {? ?//2
???????println? a-b
???}
如果指定了方法返回類型,可以不需要def關鍵字來定義方法。
???task method <<{
???????def number=minus 1,2
???????println number
???}
???int? minus(a,b) {
???????return a-b
???}
???如果不使用return,方法的返回值為最后一行代碼的執行結果。
???int minus(a,b) {
???????a-b? //4
???}
從上面兩段代碼中可以發現Groovy中有很多省略的地方:
1、語句后面的分號可以省略。
2、方法的括號可以省略,比如注釋1和注釋3處。
3、參數類型可以省略,比如注釋2處。
4、return可以省略掉,比如注釋4處。
語法三:類
???Groovy類非常類似于Java類。
???task method <<{
???????def p = new Person()
???????p.increaseAge 5
???????printlnp.age
???}
???class Person {
???????String name
???????Integer age =10
???????def increaseAge(Integer years) {
???????????this.age+= years
???????}
???}
???運行gradlemethod打印結果為:15
Groovy類與Java類有以下的區別:
1、默認類的修飾符為public。
2、沒有可見性修飾符的字段會自動生成對應的setter和getter方法。
3、類不需要與它的源文件有相同的名稱,但還是建議采用相同的名稱。
語法四:語句
4.1、斷言
? ? ?Groovy斷言和Java斷言不同,它一直處于開啟狀態,是進行單元測試的首選方式。
???task method <<{
???????assert 1+2 == 6
???}
???輸出結果為:
???Execution failed for task ':method'.
???????????> assert 1+2 == 6
???????????|?|
???????????3?false
解析:當斷言的條件為false時,程序會拋出異常,不再執行下面的代碼,從輸出可以很清晰的看到發生錯誤的地方。
4.2、for循環
? ? ? Groovy支持Java的for(int i=0;i<N;i++)和for(int i:array)形式的循環語句,另外還支持for in loop形式,支持遍歷范圍、列表、Map、數組和字符串等多種類型。
//遍歷范圍
???def x = 0
for (iin 0..3 ) {
???????x += I }
assert x == 6
//遍歷列表
???def x = 0
for (iin [0, 1, 2, 3] ) {
???????x += I }
assert x == 6
//遍歷Map中的值
???def map = ['a':1, 'b':2, 'c':3]
???x = 0
???for ( v inmap.values() ) {
???????x += v }
assert x == 6
4.3、switch語句
???Groovy中的Switch語句不僅兼容Java代碼,還可以處理更多類型的case表達式。
???task method <<{
???????def x = 16
???????def result = ""
???????switch ( x ) {
???????????case "ok":
???????????????result = "found ok"
???case [1, 2, 4, 'list']:
???????????????result = "list"
???????????????break
???????????case 10..19:
???????????????result = "range"
???????????????break
???????????case Integer:
???????????????result = "integer"
???????????????break
???????????default:
???????????????result = "default"
???????}
???????assert result == "range"
???}
解析:case表達式可以是字符串、列表、范圍、Integer等等,因為篇幅原因,這里只列出了一小部分。
語法五:數據類型
Groovy中的數據類型主要有以下幾種:
1、Java中的基本數據類型
2、Groovy中的容器類
3、閉包
5.1、字符串
? ? ? Groovy中的基本數據類型和Java大同小異,這里主要介紹下字符串類型。
? ? ? 在Groovy中有兩種字符串類型,普通字符串String(java.lang.String)和插值字符串GString(groovy.lang.GString)。
5.1.1:單引號字符串
? ? ? ?在Groovy中單引號字符串和雙引號字符串都可以定義一個字符串常量,只不過單引號字符串不支持插值。( 'Android進階解密‘)
5.1.2:雙引號字符串
?????要想插值可以使用雙引號字符串,插值指的是替換字符串中的占位符,占位符表達式為${}或者以$為前綴。
???def name = 'Android進階之光'
???println "hello ${name}"
???println "hello $name"
5.1.3:三引號字符串
???三引號字符串可以保留文本的換行和縮進格式,不支持插值。
???task method <<{
???????def name = '''Android進階之光
???????Android進階解密
???????Android進階?'''
???????println name
???}
???打印結果為:
???Android進階之光
???????????Android進階解密
???Android進階?
5.1. 4:GString
? ? ? ? String是不可變的,GString卻是可變的,GString和String即使有相同的字面量,它們的hashCodes的值也可能不同,因此應該避免使用GString作為Map的key。
???assert "one: ${1}".hashCode() != "one: 1".hashCode()
? ? ? ?當雙引號字符串中包含插值表達式時,字符串類型為GString,因此上面的斷言為true。
5.2、List
? ? ? ? Groovy沒有定義自己的集合類,它在Java集合類的基礎上進行了增強和簡化。Groovy的List對應Java中的List接口,默認的實現類為Java中的ArrayList。
???def number = [1, 2, 3]
???assert number instance of List
???def linkedList= [1, 2, 3]? as LinkedList???
???assert linkedList instanceof java.util.LinkedList
???可以使用as操作符來顯式指定List的實現類為java.util.LinkedList。
???獲取元素同樣要比Java要簡潔些,使用[]來獲取List中具有正索引或負索引的元素。
???task method <<{
???????def number?= [1, 2, 3, 4]
???????assert number [1] == 2
???????assert number [-1] == 4? //1?
???????number << 5????//2????????????
???????assert number [4] == 5
???????assert number [-1] == 5
???}
???注釋1處的索引-1是列表末尾的第一個元素。注釋2處使用<<運算符在列表末尾追加一個元素。
5.3、Map
? ? ? 創建Map同樣使用[],需要同時指定鍵和值,默認的實現類為java.util.LinkedHashMap。
???def name = [one: '魏無羨', two: '楊影楓', three: '張無忌']
???????????assert name['one']?== '魏無羨'
???????????assertname.two?== '楊影楓‘
???Map還有一個鍵關聯的問題:
???def key = 'name'
???def person = [key: '魏無羨']???//1
???????????assert person.containsKey('key')
???person = [(key): '魏無羨']?????//2??????
???????????assertperson.containsKey('name')
???注釋1處魏無羨的鍵值是key這個字符串,而不是key變量的值name。如果想要以key變量的值為鍵值,需要像注釋2處一樣使用(key),用來告訴解析器我們傳遞的是一個變量,而不是定義一個字符串鍵值。
5.4 閉包(Closure)
? ? ? ?Groovy中的閉包是一個開放的、匿名的、可以接受參數和返回值的代碼塊。
???閉包的定義遵循以下語法:
{ [closureParameters-> ] statements }
???閉包分為兩個部分,分別是參數列表部分[closureParameters-> ]和語句部分statements。
???參數列表部分是可選的,如果閉包只有一個參數,參數名是可選的,Groovy會隱式指定it作為參數名,如下所示。
{println it }????//使用隱式參數it的閉包
當需要指定參數列表時,需要->將參數列表和閉包體相分離。
{ it ->println it }? ?//it是一個顯示參數
{ String a, String b ->???????????????????????????????
???println "${a} is a $"
}
???閉包是groovy.lang.Cloush類的一個實例,這使得閉包可以賦值給變量或字段,如下所示。
//將閉包賦值給一個變量
def println={ it ->println it }????
assert printlnin stanceofClosure
//將閉包賦值給Closure類型變量
Closure do= {println 'do!' }
調用閉包
閉包既可以當做方法來調用,也可以顯示調用call方法。
def code = { 123 }
assert code() == 123 //閉包當做方法調用
assert code.call() == 123 //顯示調用call方法
def isOddNumber= {inti-> i%2 != 0 }??????????????????????????
assert isOddNumber(3) == true?//調用帶參數的閉包
6、I/O操作
? ? ? Groovy的I/O操作要比Java的更為的簡潔。
6.1 文件讀取
???我們可以在PC上新建一個name.txt,在里面輸入一些內容,然后用Groovy來讀取該文件的內容:
def filePath= "D:/Android/name.txt"
def file = new File(filePath) ;
file.eachLine{
???printlnit
}
可以看出Groovy的文件讀取是很簡潔的,還可以更簡潔些:
def filePath= "D:/Android/name.txt"
def file = new File(filePath) ;
println file.text
6.2 文件寫入
文件寫入同樣十分簡潔:
def filePath= "D:/Android/name.txt"
def file = new File(filePath);
file.withPrintWriter{
???it.println("三井壽")
???it.println("仙道彰")
}
7. 其他
7.1 asType
asType可以用于數據類型轉換:
String a = '23'
int b = a as int
def c =a.asType(Integer)
assert c instanceof java.lang.Integer
7.2 判斷是否為真
if (name != null &&name.length> 0) {}
可以替換為
if (name) {}
7.3 安全取值
? ? ?在Java中,要安全獲取某個對象的值可能需要大量的if語句來判空:
if (school != null) {
???if (school.getStudent() != null) {
???????if (school.getStudent().getName() != null) {
???????????System.out.println(school.getStudent().getName());
???????}
???}
}
Groovy中可以使用?.來安全的取值:
println school?.student?.name
7.4? with操作符
???對同一個對象的屬性進行賦值時,可以這么做:
task method <<{
Person p = new Person()
p.name = "楊影楓"
p.age= 19
p.sex= "男"
println p.name
}
class Person {??????????????????????
???String name?????????????????????
???Integer age
???String sex
}
使用with來進行簡化:
Person p = new Person()
p.with{
??name = "楊影楓"
??age= 19
??sex= "男"
?}??
println p.name
8. 總結
? ? ? 大概的介紹了Groovy的一些語法,包括:變量、方法、數據類型等等,比起Groovy官方文檔來說,介紹的并不多,但不要忘了本系列的目標是學習與Android相關的Gradle,Groovy并不是重點,我們只需要了解本文所介紹的內容就夠了,如果碰到哪里不會再去查找Groovy官方文檔和Groovy API文檔。
例子:
?task hello {
???????doLast{
???????????println 'Hello world!'
???????}
???}
簡潔版:
?task hello << {
???????println 'Hello world!'
???}
解析:
? ? ?task(任務)和action(動作)是Gradle的重要元素。上面的代碼中,task代表一個獨立的原子性操作,比如復制一個文件,編譯一次Java代碼,這里我們簡單的定義一個名為hello的任務。doLast 代表task執行的最后一個action,通俗來講就是task執行完畢后會回調doLast中的代碼,在上面這個Gradle的任務,Gradle的任務包括創建任務、任務依賴、動態定義任務和任務的分組和描述。
創建任務
方法一:直接用任務名稱創建
???def Task hello=task(hello)
???hello.doLast{
???????println"hello world"
???}
方法二:任務名稱+任務配置創建
???def Task hello=task(hello,group:BasePlugin.BUILD_GROUP)
???hello.doLast{
???????println"hello world"
???}
? ? 其中group為任務配置項,它代表了分組,關于分組具體見3.4小節。
方法三:TaskContainer的create方法創建
???tasks.create(name: 'hello') << {
???????println"hello world"
???}
? ? ?此前創建任務的方式最終都會調用tasks的create方法,其中tasks類型為TaskContainer。
任務依賴:任務依賴會決定任務運行的先后順序,被依賴的任務會在定義依賴的任務之前執行。創建任務間的依賴關系如下所示。
???task hello << {
???????println'Hello world!'
???}
???task go(dependsOn: hello) << {
???????println"go for it"
???}
? ? ? 在hello任務的基礎上增加了一個名為go的任務,通過dependsOn來指定依賴的任務為hello,因此go任務運行在hello之后。
???運行gradle -q go構建腳本,打印結果如下:
???Hello world!
???go for it
動態定義任務:動態定義任務指的是在運行時來定義任務的名稱,如下所示。
? ? ? ? ? ?3.times {number ->
???????????task "task$number" << {
???????????println "task $number"
???}
???}
解析:這里用到了Groovy語法,關于Groovy語法會在本系列后續的文章進行介紹。times是Groovy在java.lang.Number中拓展的方法,是一個定時器。3.times中循環創建了三個新任務,隱式變量number的值為0,1,2,任務的名稱由task加上number的值組成,達到了動態定義任務的目的。
???運行gradle -q task0構建腳本,打印結果如下:
???task 0
任務的分組和描述
? ? ? Gradle有任務組的概念,可以為任務配置分組和描述,以便于更好的管理任務,擁有良好的可讀性。改造上面的例子,為hello任務添加分組和描述。
???task hello {
???????group = 'build'
???????description = 'hello world'
???????doLast{
???????????println"任務分組: ${group}"
???????????println"任務描述: ${description}"
???????}
???}
???task go(dependsOn: hello) << {
???????println"go for it"
???}
也可以采用3.1小節中其他的創建任務方式來為任務添加分組和描述,如下所示。
???def Task hello=task(hello)
???hello.description='hello world'
???hello.group=BasePlugin.BUILD_GROUP
???hello.doLast{
???????println"任務分組: ${group}"
???????println"任務描述: ${description}"
???}
???task go(dependsOn: hello) << {
???????println"go for it"
???}
五:Gradle插件講解
5.1、應用Gradle插件
? ? ? ? 要想應用插件,主要有兩個步驟,一是解析插件,二是把插件應用到項目中,應用插件通過 Project.apply()方法來完成。
? ? ? ? 在Gradle中一般有兩種類型的插件,分別叫做腳本插件和對象插件。腳本插件是額外的構建腳本,它會進一步配置構建,可以把它理解為一個普通的build.gradle。對象插件又叫做二進制插件,是實現了Plugin接口的類。
2.1腳本插件
文件other.gradle:
ext{
verson='1.0'
url='http://liuwangshu.cn'
}
這實際上不算是一個真正的腳本插件,就是一個簡單的腳本,主要是用于演示腳本插件是如何被應用的。
? ? 我們在build.gradle中來應用這個插件:
build.gradle
apply from: 'other.gradle'
task test {
???doLast{
???????println"版本為:${verson},地址為:${url}"
???}
}
? ? ? apply是Gradle project中提供的方法,用于配置項目中的插件。執行gradlew.bat test,會打印出想要的結果。
對象插件:我們知道對象插件就是實現了org.gradle.api.plugins<Project>接口的插件,對象插件可以分為內部插件和第三方插件。
2.2.1 內部插件
? ? ? 如果我們想要應用Java插件可以這么寫:
build.gradle文件:
apply plugin:org.gradle.api.plugins.JavaPlugin
Gradle默認就導入了org.gradle.api.plugins包,因此我們也可以去掉包名:
apply plugin:JavaPlugin
? ? ? ?實現了org.gradle.api.plugins接口的插件會有pulginid,使用pulginid是最簡潔、最常用的方式:apply plugin: 'java‘
? ? ? ?Gradle的發行包中有大量的插件,這些插件有很多類型,比如語言插件、集成插件、軟件開發插件等等,如果我們想向項目添加c++源代碼編譯功能,可以這么寫:
????????apply plugin: 'cpp'
第三方插件:
???第三方的對象插件通常是jar文件,要想讓構建腳本知道第三方插件的存在,需要使用buildscript來設置。
buildscript{
?repositories {
???maven {
?????url"https://plugins.gradle.org/m2/"
???}
?}
?dependencies {
???classpath"com.jfrog.bintray.gradle:gradle-bintray-plugin:1.8.4"
?}
}
apply plugin: "com.jfrog.bintray"
? ? ? 在buildscript中來定義插件所在的原始倉庫和插件的依賴 ,再通過apply方法配置就可以了。Android Gradle插件也屬于第三方插件,如果我們想引入Android Gradle插件,可以這么寫:
buildscript{
???repositories {
???????jcenter()
???}
???dependencies {
???????classpath'com.android.tools.build:gradle:2.3.2'
???}
}
apply plugin: 'com.android.application'
? ? ? 這樣我們就可以使用Android Gradle插件,通過apply方法來使用App工程插件,這樣項目會編譯成為一個apk。
自定義對象插件:
? ? ? ? 對象插件是實現了org.gradle.api.plugins<Project>接口的插件,這個接口中只定義個一個簡單的apply方法,想要自定義插件就需要去實現org.gradle.api.plugins<Project>接口。
? ? ? ? 來實現一個簡單的自定義插件,為了方便測試,不再采用文本編輯,而是使用IntelliJ來編輯(AS也可以),用IntelliJ來打開2.1小節的例子,改寫build.gradle文件:
build.gradle
applyplugin:CustomPlugin
classCustomPluginimplements Plugin<Project> {
???@Override
???void apply(Project project) {
???????project.task('CustomPluginTask') {
???????????doLast{
???????????????println"自定義插件"
???????????}
???????}
???}
}
? ? ? ?在build.gradle中自定義了一個插件CustomPlugin,在apply方法中創建一個名稱為CustomPluginTask的任務。在IntelliJ的Terminal中輸入gradlew.bat CustomPluginTask來執行CustomPluginTask任務。
Gradle插件的作用和好處:
Gradle插件可以做什么呢?主要有以下的幾點
1、為項目配置依賴。
2、為項目配置約定,比如約定源代碼的存放位置。
3、為項目添加任務,完成測試、編譯、打包等任務。
4、為項目中的核心對象和其他插件的對象添加拓展類型。
使用Gradle插件主要有以下的好處:
1、重用和減少維護在多個項目類似的邏輯的開銷。
2、更高程度的模塊化。
3、封裝必要的邏輯,并允許構建腳本盡可能是聲明性。
六:Gradle的生命周期
Gradle構建生命周期分三個階段:
???初始化階段:負責判斷有多少個Projects參與構建。
???配置階段:負責對初始化階段創建的Projects完成配置。
???執行階段:根據配置階段的配置執行任務。
初始化階段:
? ? ? ?構建初始化階段首先尋找一個叫settings.gradle的文件,檢查是否當前構建是否是多項目構建,并負責創建項目樹。在多項目構建中,settings.gradle是必需的,因為這個文件定義了參與構建的項目,通過settings.gradle判斷有哪些項目需要初始化,加載所有需要初始化的項目的build.gradle文件并為每個項目創建project對象。
? ? ? ? 可以看出,settings.gradle中的代碼最先執行,所以理論上還可以做其他事情。
??而在沒有settings.gradle文件的項目中,如果執行構建,則Gradle按這個順序查找settings.gradle:
1、從當前目錄的master文件夾內尋找。
2、如果master目錄中也沒有,則搜索父目錄。
3、如果父目錄也沒找到,則把構建當成單個項目構建。
4、如果找到了,并且發現當前項目是多項目構建的一部分,則執行多項目構建。沒找到,則執行單項目構建。
? ? ? ? 從第二步中可以看出,Gradle支持從子項目中觸發構建父項目。如果不想從子項目觸發父項目,而只是做單項目構建,則應該在gradle命令后加上-u命令行選項。
配置階段:
? ? ? ? 配置階段負責對初始化階段創建的projects做一些配置,比如添加Task,修改Task的行為等,執行各項目下的build.gradle腳本,完成project的配置,并且構造Task任務依賴關系圖以便在執行階段按照依賴關系執行Task.執行task中的配置代碼。
執行階段:
? ? ? ?通過配置階段的Task圖,按順序執行需要執行的任務中的動作代碼,就是執行任務中寫在doFirst或doLast中的代碼。
? ? ? ?以上就是Gradle構建項目時的生命周期,由于Gradle的強大和易配性,如果想在構建的過程中去做一些額外的操作的話,可以使用Gradle自帶的鉤子方法,Gradle提供了很多生命周期監聽方法,可以在各個階段Hook指定的任務。
執行流程圖:
備注:這里有幾個概念很關鍵,gradle, project, task。
1、gradle全局可見,相當于這個gradle工具本身。
2、project是一個模塊,與android studio中的module是對應的關系,也對應了一個build.gradle的文件。
3、task是最小的執行塊。為了方便,我們來有兩個project的工程來模擬,他們的具體執行順序進一步細分為下面的這個樣子。
圖解:
Project
???Project提供的生命周期回調方法有:
???//在Project進行配置前調用
???void beforeEvaluate(Closure closure)
???//在Project配置結束后調用
???void afterEvaluate(Closure closure)
? ? ? beforeEvaluate必須在父模塊的build.gradle對子模塊進行配置才能生效,因為在當前模塊的build.gradle中配置,它自己本身都沒配置好,所以不會監聽到。
Gradle
???Gradle提供的生命周期回調方法很多,部分與Project里的功能雷同:
//在project進行配置前調用,child project必須在root project中設置才會生效,root project必須在settings.gradle中設置才會生效。
???void beforeProject(Closure closure)
//在project配置后調用
???afterProject(Closure closure)
//構建開始前調用
???void buildStarted(Closure closure)
//構建結束后調用
???void buildFinished(Closure closure)
//所有project配置完成后調用
???void projectsEvaluated(Closure closure)
//當settings.gradle中引入的所有project都被創建好后調用,只在該文件設置才會生效
???void projectsLoaded(Closure closure)
//settings.gradle配置完后調用,只對settings.gradle設置生效
???void settingsEvaluated(Closure closure)
我們修改setting.gradle的代碼如下:
???gradle.settingsEvaluated{
???????println "settings:執行settingsEvaluated..."}
???gradle.projectsLoaded{
???????println"settings:執行projectsLoaded..."}
???gradle.projectsEvaluated{
???????println"settings:執行projectsEvaluated..."}
???gradle.beforeProject{proj->
???????????println"settings:執行${proj.name}beforeProject"}
???gradle.afterProject{proj->
???????????println"settings:執行${proj.name}afterProject"}
???gradle.buildStarted{
???????println"構建開始..."}
???gradle.buildFinished{
???????println"構建結束..."}
???include? ":app"
這個時候的執行結果如下:
???settings:執行settingsEvaluated...
???settings:執行projectsLoaded...
???settings:執行testbeforeProject
???根項目配置開始---
???根項目里任務配置---
???根項目配置結束---
???settings:執行test afterProject
???settings:執行app beforeProject
???子項目beforeEvaluate回調...
???APP子項目配置開始---
???APP子項目里任務配置---
???APP子項目配置結束---
???settings:執行app afterProject
???APP子項目after Evaluate回調...
???settings:執行projects Evaluated...
???構建結束...
Gradle構建周期中的Hook點:
七:Android中Gradle的常見配置關鍵字與常見問題匯總(不斷更新)
一、Gradle是什么
1、Gradle是一個自動化構建工具
2、兼容Maven等倉庫
3、基于Groovy的特定領域語言來聲明設置
二、GradleWraper的作用是什么
1、GradleWrapper是一個腳本文件
2、它會在沒有安裝Gradle的情況下為我們下載Gradle,之后我們就可以使用gradlew命令,像使用gradle一樣來使用Gradle了
3、GradleWraper簡化了gradle的安裝部署
三:Gradle命令
1、gradlew clean:清除app目錄下的build文件夾
2、gradlew check:執行lint檢查
3、gradlew assemble:打release和debug包
4、gradlew build: 執行check和assemble
5、gradlew assembleRelease/gradlewassembleDebug:打全部渠道的Release或者debug包
四:設置編譯android項目的參數
???????android {
???????//編譯SDK的版本
???????compileSdkVersion22
???????// build tools的版本
???????buildToolsVersion"23.0.1"
???????//aapt配置
???????aaptOptions{
???????//不用壓縮的文件
???????noCompress'pak', 'dat', 'bin', 'notice'
???????//打包時候要忽略的文件
???????ignoreAssetsPattern"!.svn:!.git"
???????//分包
???????multiDexEnabled true
???????//--extra-packages是為資源文件設置別名:意思是通過該應用包名+R,com.android.test1.R和com.android.test2.R都可以訪問到資源
???????additionalParameters'--extra-packages', 'com.android.test1','--extra-packages','com.android.test2'
???????}
???????//默認配置
???????defaultConfig{
???????//應用的包名
???????applicationId "com.example.heqiang.androiddemo"
???????minSdkVersion 21
???????targetSdkVersion 22
???????versionCode 1
???????versionName "1.0"
???????}
???????//編譯配置
???????compileOptions{
???????// java版本
???????sourceCompatibilityJavaVersion.VERSION_1_7
???????targetCompatibilityJavaVersion.VERSION_1_7
???????}
???????//源文件目錄設置
???????sourceSets{
???????main {
???????//jnilib的位置
???????jniLibs.srcDirs=jniLibs.srcDirs<< 'src/jniLibs'
???????//定義多個資源文件夾,這種情況下,兩個資源文件夾具有相同優先級,即如果一個資源在兩個文件夾都聲明了,合并會報錯。
???????res.srcDirs= ['src/main/res', 'src/main/res2']
???????//指定多個源文件目錄
???????java.srcDirs= ['src/main/java', 'src/main/aidl']
???????}
???????}
???????//簽名配置
???????signingConfigs{
???????debug {
???????keyAlias 'androiddebugkey'
???????keyPassword 'android'
???????storeFilefile('keystore/debug.keystore')
???????storePassword 'android'
???????}
???????}
buildTypes{
???????//release版本配置
???????release {
???????debuggable false
???????//是否進行混淆
???????minifyEnabled true
???????//去除沒有用到的資源文件,要求minifyEnabled為true才生效
???????shrinkResources true
???????//混淆文件的位置
???????proguardFilesgetDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
???????signingConfigsigningConfigs.debug
???????//ndk的一些相關配置,也可以放到defaultConfig里面。
???????//指定要ndk需要兼容的架構(這樣其他依賴包里mips,x86,arm-v8之類的so會被過濾掉)
???????ndk{
???????abiFilter "armeabi"
???????}
???????}
//debug版本配置
???????debug {
???????debuggable true
???????//是否進行混淆
???????minifyEnabled false
???????//去除沒有用到的資源文件,要求minifyEnabled為true才生效
???????shrinkResources true
???????//混淆文件的位置
???????proguardFilesgetDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
???????signingConfig signingConfigs.debug
???????//ndk的一些相關配置,也可以放到defaultConfig里面。
???????//指定要ndk需要兼容的架構(這樣其他依賴包里mips,x86,arm-v8之類的so會被過濾掉)
???????ndk{
???????abiFilter"armeabi"
???????}
???????}
???????}
???????// lint配置
???????lintOptions{
???????//移除lint檢查的error
???????abortOnErrorfalse
???????//禁止掉某些lint檢查
???????disable 'NewApi'
???????}
???????}
備注:android中還可以有以下配置:productFlavors{ }產品風格配置,ProductFlavor類型;testOptions{ }測試配置,TestOptions類型;dexOptions{ }dex配置,DexOptions類型;packagingOptions{ }PackagingOptions類型;jacoco{ }JacocoExtension類型。 用于設定jacoco版本;splits{ } Splits類型。
五:幾種依賴的區別
六:排除依賴傳遞,解決依賴沖突
1、exclude:設置不編譯指定的模塊,排除指定模塊的依賴
2、transitive:用于自動處理子依賴項,默認為true,gradle自動添加子依賴項。設置為false排除所有的傳遞依賴
3、force:強制設置某個模塊的版本。
常見代碼實現:
implementation ('io.reactivex.rxjava2:rxandroid:2.1.0')?{
???????exclude group: 'io.reactivex.rxjava2', module: 'rxjava'
???}
依賴替換規則
??依賴替換規則的適用場景分為以下幾種:
1.根據某些條件對依賴進行替換;
2.將本地依賴替換為外部依賴;
3.將外部依賴替換為本地依賴;
外部依賴和本地依賴是什么。
外部依賴:外部依賴,顧名思義,就是從遠程倉庫拉取的依賴,也被稱為常用的三方庫:
//從遠程倉庫拉取的開源代碼庫
implementation 'com.facebook.stetho:stetho:1.5.1'
implementation 'io.reactivex.rxjava3:rxjava:3.0.0-RC0‘
本地依賴:也就是我們項目中常見的module,按照**《阿里Java開發手冊》**中來描述,也叫做一方庫:
implementation project(':library')
? ? ? ?根據某些條件對依賴進行替換舉個例子,很多UI三方庫都會依賴RecyclerView,但這么多的依賴庫,我們不可避免遇到版本不同導致依賴沖突的情況,一般情況下,我們是這么解決的:
???????????//將RecyclerView的依賴從這個三方庫中排除掉
???????????implementation "xxx.xxx:xxx:1.0.0",{
???????????exclude group: 'com.android.support', module: 'recyclerview-v7'
???????}
? ? ? ? ?將RecyclerView的依賴從這個三方庫中排除掉,令其使用項目本身的RecyclerView版本,這樣項目就可以正常運行了,看起來并沒有什么問題。
試想,如果項目的依賴比較復雜,也許我們要面對的將是這樣的依賴配置:
???????implementation "libraryA:xxx:1.0.0",{
???????exclude group: 'com.android.support', module: 'recyclerview-v7'
???????}
???????implementation "libraryB:xxx:2.2.0",{
???????exclude group: 'com.android.support', module: 'recyclerview-v7'
???????}
???????implementation "libraryC:xxx:0.0.8",{
???????exclude group: 'com.android.support', module: 'recyclerview-v7'
???????}
將外部依賴替換為本地依賴
???????該規則和2非常相似,只不過將依賴替換的雙方調換了而已,下面是官方的示例代碼:
???????configurations.all{
???????resolutionStrategy.dependencySubstitution{
???????substitute module("org.utils:api") because "we work with the unreleased development version" with project(":api")
???????substitute module("org.utils:util:2.5") with project(":util")
???????}
???????}
???????1.因為group不同,所以需要先將2.x的rxjava全局exclude掉;
???????2.將所有3.x的rxjava的依賴版本都統一(文中是3.0.0-RC0)
七:Gradle打包時的Proguard
1、通過在buildTypes中配置minifyEnable來開啟和關閉proguard
2、通過proguardFiles來配置混淆參數與keep的內容
混淆的作用:
1、壓縮(Shrink):檢測并移除代碼中無用的類、字段、方法和特性(Attribute)。
2、優化(Optimize):對字節碼進行優化,移除無用的指令。
3、混淆(Obfuscate):使用a,b,c,d這樣簡短而無意義的名稱,對類、字段和方法進行重命名。
4、預檢(Preveirfy):在Java平臺上對處理后的代碼進行預檢,確保加載的class文件是可執行的。
哪些不做混淆:
1、Android系統組件
2、JNI
3、反射
4、WebView的JS調用
5、內部類
6、Annottation
7、enum
8、范型
9、序列化
10、第三方
八:多渠道打包
原理:在AndroidManifest.xml配置mete-data
<meta-data
???????android:name="UMENG_CHANNEL"
???????android:value="Channel_ID" />
配置Flavors:
android {
productFlavors{
???????xiaomi{
???????manifestPlaceholders= [UMENG_CHANNEL_VALUE: "xiaomi"]}
???????_360 {
???????manifestPlaceholders= [UMENG_CHANNEL_VALUE: "_360"]}
???????baidu{
???????manifestPlaceholders= [UMENG_CHANNEL_VALUE: "baidu"]}
???????wandoujia{
???????manifestPlaceholders= [UMENG_CHANNEL_VALUE: "wandoujia"]}
???????}
}
批量修改:
android {
??productFlavors{
???????xiaomi{}
???????_360 {}
???????baidu{}
???????wandoujia{}
}
productFlavors.all{
???????flavor ->flavor.manifestPlaceholders= [UMENG_CHANNEL_VALUE: name]
???????}}
? ? ?在APP內讀取mete-data配置確定渠道
? ? ? 然后用./gradlewassembleRelease這條命令會把Product Flavor下的所有渠道的Release版本都打出來。
? ? ? ?因為以上方法需要多次編譯,速度較慢,當渠道變多之后不適合多渠道打包
改進的方法1:apk反編譯后重寫AndroidManifest文件,再重新編譯簽名
改進的方法2:如果在META-INF目錄內添加空文件,可以不用重新簽名應用。因此,通過為不同渠道的應用添加不同的空文件,可以唯一標識一個渠道,因為v1簽名可以在不改變簽名情況下二次打包,我們可以在gradle中對dex文件進行自己的簽名。
???在采用V2簽名后,以上方法不再適用,考慮到V2簽名的特點(對APK Signing Block是不進行驗證的),我們向V2簽名后的APK簽名區塊寫入渠道號,實現多渠道打包。
備注:在多渠道批量打包方面,可以參考使用:騰訊VasDolly,美團Walle。
九:如何提高Gradle編譯效率
備注:上面是谷歌官方推薦的提升Gradle編譯速度的建議,具體詳情可以去擴展閱讀中查看。
十:擴展閱讀
1、https://blog.csdn.net/itachi85/article/details/81906139(劉望舒系列)
2、http://www.lxweimin.com/p/2e19268bf387(AndroidGradle學習(七):Gradle構建生命周期)
3、https://blog.csdn.net/woxueliuyun/article/details/54602701(Gradle生命周期)
4、https://blog.csdn.net/u011486491/article/details/79001186(AndroidGradle學習2——gradle生命周期和重要的gradle)
5、https://blog.csdn.net/coloriy/article/details/52807393(AndroidGradle看這一篇就夠了)
6、http://www.lxweimin.com/p/c11862136abf(史上最全Androidbuild.gradle配置詳解)
7、http://www.lxweimin.com/p/1274c1f1b6a4(要點提煉|Gradle指南)
8、http://www.lxweimin.com/p/2bd920314012(AndroidGradle干貨)
9、http://www.lxweimin.com/p/dcf14b220a19(不一樣的Gradle多渠道配置總結)
10、http://www.lxweimin.com/p/d84032b46b56(如何開發一款高性能的gradletransform)
11、https://juejin.im/post/5d2dee0851882569755f5494(JakeWharton評價我的代碼像是在打地鼠)
12、http://www.lxweimin.com/p/b7a6ee994a52(Android應用構建速度提升的十個小技巧)
13、https://blog.csdn.net/weixin_34290352/article/details/88009764(Android面試題之Gradle配置篇)