8 全局變量 ext
我們前面講解了gradle的生命週期,在配置的過程中,整個項目會生成一個gradle 對象,每個build.gradle的文檔都會生成一個project對象。這兩個對象都有一個ext,這個ext的屬性就類似於我們的錢包一樣,獨立屬於gradle與project對象。我們可以往這個ext對象裏面放置屬性。
8.1 gradle的ext對象
我們可以使用這樣的方法存儲一個變量,這個變量屬於gradle,整個工程都能使用
gradle.ext.myName = 'helloKay'
讀取方式如下
task A{
doLast{
println(gradle.ext.myName)
}
}
8.2 project 的ext對象
保存值
ext{
myName ='abc'
myName1 = 'abc'
}
獲取值,可以直接獲取
println(myN)
上面這個代碼println(myN)就等於println(project.ext.myN)
我們一般在ext內存儲一些通用的變量,除此以外,我們也使用這個ext來做一些很酷的功能,比如說我們的gradle文件很大了,我們可以好像代碼一下,進行抽取。
8.3 gradle的複用
新建一個other.gradle,放置在app目錄下面,在other內輸入下面的內容
println "configuring $project"
task hello{
doLast{
println 'hello from other script'
}
}
def showMyName(){
'i am a boy'
}
ext{
showName = showMyName()
}
接着我們可以在build.gradle文件內使用這個些內容
apply from:'other.gradle'
這句話是加載other.grale,apply from是加載其他插件或者腳本的意思,我們在這裏加載了本地的腳本,之後,我們可以直接調用腳本里面的task,如果需要調用方法的話,需要通過ext對象來調用。我們常常使用公用的方法來獲取應用的信息。
如果抽取的文件在根目錄怎麼處理呢?
apply from:new File(project.getProjectDir().parent,"util.gradle")
9 BuildConfig文件
android提供了一種機制,能夠讓我們在build.gradle定義一些字段,在代碼裏面使用。假設一個場景,我們在公司開發的時候,爲了安全,我們常常提供兩個服務器,一個測試服務器,一個是線上服務器。常見的方法是我們在一個常量的類裏面提供一個boolean來做開關,如果是測試版就設置爲false,如果是線上版本就設置爲true。這樣開發確實很方便,但是可能出現一個比較嚴重的問題,就是開發人員不小心在打包測試版本的時候,沒有吧boolean設置爲ture。導致線上包出現問題。
實際gradle提供了一個工具類給我們來使用,這個類就是BuildConfig。這個類會把gradle.build的。
buildConfigField "boolean", "isdebug", "false"
這句話是定義在定製版本內部的,我們可以在不同的版本里獲取到這個值,這裏有三個參數,
- 參數一 “boolean” 代表這個參數的類型
- 參數二 “isdebug” 參數的名稱
- 參數三 “false” 參數的值
注意不管你定義的什麼類型的參數
我們在代碼的時候讀取的時候按照下面的來讀取。
if(BuildConfig.isdebug){
Toast.makeText(this,"isDebug",0).show();
}else{
Toast.makeText(this,"isNotDebug",0).show();
}
10 Task 任務
10.1 什麼是Task
我們在前面的學習了gradle的很多知識,特別是學習到構建不同版本的時候,我們在命令行中輸入了./gradlew assembleRelease 命令就能夠得到一個安裝包。但是我們沒有和大家講解這個命令是什麼。其實這個就是一個Task(任務),Task實際就是一連串的操作,最後得到我們需要的內容。
我們可以這麼理解,Gradle是一個大的舞臺,這個舞臺提供了基礎的能力。不同插件(java Pluging、android pluging)是不同的表演團隊,提供不同的表演。這個表演就是Task。不同的Task完成不同的任務,我們根據不同的需求選擇不同的Task。如果在開發中,發現系統插件給我的Task不能滿足需求怎麼辦呢?我們也可以根據規則,編寫一個屬於我們自己的Task。
所以綜合一下,Task分成了兩種,一種是Pluging提供的,另外一種是開發者自定義的。
10.2 查看項目的Tasks
我們打開android studio,再最右邊的便籤欄可以打開當前項目的所有的task。我們接着打開build標籤,
在上面的圖裏面,我們發現了之前的任務。我們可以嘗試點擊一下執行相關的任務。發現和我們在命令行執行的效果是一樣的。除了使用IDE查看任務外,我們也可以使用命令來查看當前項目的任務。
./gradlew tasks
如果想看個個任務的詳細信息
./gradlew tasks --all
詳細信息裏面多了很多內容,比如說項目的依賴等信息。
我們發現這些Task很多類似名稱的Task,這個是插件爲我們提供的任務。
- assemble 組合項目的輸出,在java中多用於生成jar、war包,而在android中用於生成apk
- check 用戶項目的檢查任務,比如說lint
- connectedCheck 用於連接設備的檢查
- build 這種任務會執行assemble與check的任務
- clean 這個task清空項目的所有輸出。
10.3 定義Task
我們學會了查看當前項目的任務,我們來看看如何定義一個Task,及task的使用。
- task myTask { configure closure }
- task myTask(type: SomeType) { configure closure }
上面是task的兩種定義方式,第一種是直接定義一個task,第二種是讓這個task繼承於某類的task,可以理解爲繼承。
直接定義Task
我們可以在腳本的任意位置添加以下的代碼
task showMeTheMoney{
println("show me the money");
}
接着我們在終端的界面輸入下面的命令
./gradlew showMeTheMoney
使用某種type來定義Task
task myCopy(type:Copy){
from './build/outputs/apk/app-noLog-free-release.apk'
into './test/'
}
接着我們在終端的界面輸入下面的命令(注意你的目錄可能和我的不一樣,替換成你自己的目錄)
./gradlew myCopy
上面的這個任務繼承於Copy。
這個是Copy的所有屬性,https://docs.gradle.org/current/dsl/org.gradle.api.tasks.Copy.html。
除了Copy以外我們還有什麼類型呢?
https://docs.gradle.org/current/userguide/java_plugin.html,我們可以參考Java插件的類型
上面是支持的所有類型,我們還可以按照文檔實現一個delete類型的task
task myCheck(type:Delete){
delete 'test'
}
10.4 Task的關係與執行順序
我們在實際開發中會有這樣的需要,很多任務實際上都是有關聯的。比如說任務A執行的時候需要任務B執行完成,我們就會說A依賴於B,或者任務A裏面裏面的命令執行需要按照一定的順序執行,這個時候我們就需要給這個Task的指令提供一定的順序標準,簡單的來說,就是我們在運行某個task時,我們需要先執行某些命令,再執行其他命令,最後再執行特定的命令。
10.4.1 Gradle 生命週期加深
根據之前我們學習的內容,我知道Gradle的生命週期有以下幾個
- Initialization 讀取setting.gradle文件,分辨項目是單項目結構還是多項目結構。在這個階段會生成一個全局Gradle對象
- Configuration 讀取每個參與編譯的項目的build.gradle文件,執行所有項目的task的內容(除了doLast、doFrist以外)。並會根據task的關係組成一個有向圖。
- Execution 根據有向圖執行Task的內容。
下面我們來看看這個例子。
settings.gradle
println 'This is executed during the initialization phase.'
build.gradle
println 'This is executed during the configuration phase.'
task configured {
println 'This is also executed during the configuration phase.'
}
task test {
doLast {
println 'This is executed during the execution phase.'
}
}
task testBoth {
doFirst {
println 'This is executed first during the execution phase.'
}
doLast {
println 'This is executed last during the execution phase.'
}
println 'This is executed during the configuration phase as well.'
}
執行的結果就跟我們之前提到的內容一樣
最先在Initialization執行settings.gradle的內容,接着在Configuration執行各Task的內容,最後在
Execution中執行doFirst與doLast的內容。
10.4.2 doFirst 與 doLast
前面我們講了生命週期的問題,我們知道doFirst 與 doLast 都是在Execution階段執行的,Task的各項操作都是類似鏈條一樣執行的,doFirst會把內部的closure插入到這個鏈條的頂部,doLast會把內部的鏈條的closure插入到鏈條的底部,所以doFirst會比doLast的執行提前。
task testBoth {
doFirst {
println 'This is executed first during the execution phase.'
}
doLast {
println 'This is executed last during the execution phase.'
}
println 'This is executed during the configuration phase as well.'
}
注意如果有比較舊的項目,你會看到這樣的寫法
task showArgs << {
println "Hello World"
}
在這裏<<就是doLast的縮寫,注意新的版本gradle已經建議不要這麼寫了。
10.4.3 任務之前的關係
我們之前講過了,項目之間可能存在互相依賴的關係,比如說A需要B執行完成後再執行,這個時候我們可以使用任務的關係運算符,讓任務按指定的順序執行。
dependsOn 依賴關係
task A(dependsOn:'B'){
doFirst{
println("this is A")
}
}
task B{
doFirst{
println("this is B")
}
}
如果執行A,會在A的執行前執行B的內容,也就是說A的執行依賴於B,dependsOn 可以多個依賴
mustRunAfter 依賴的弱前後關係
task A{
doFirst{
println("this is A")
}
}
task B{
doFirst{
println("this is B")
}
}
task C{
doFirst{
println("this is C")
}
}
我們有三個任務,A,B,C。它們的依賴關係是
A.dependsOn(B)
A.dependsOn(C)
上面這個意思就是告訴gradle B,C會在A前面執行。但是執行的順序一般是按照這個任務的名稱。但是我們在開發中可能會有這樣的需求,C要先執行,再執行B。好那我們再改改
A.dependsOn(B)
A.dependsOn(C)
B.dependsOn(C)
經過我們的改造,好像是可以了,但是有一個很嚴重的問題。因爲我們的任務都是一個個獨立的個體。也就是能夠單獨執行的,但是上面的關係,如果我們單獨執行B就必須執行C。怎麼做呢?我們要引進一個弱關鍵關係操作符。mustRunAfter
A.dependsOn(B)
A.dependsOn(C)
C.mustRunAfter(B)
finalizeBy 最後執行的依賴
task UITest{
doFirst{
println("this is UITest")
}
}
task Test{
doFirst{
println("this is Test")
}
}
task CopyReport{
doFirst{
println("this is CopyReport")
}
}
他們的關係是
UITest->Test->CopyReport,按照我們之前的學習的內容,可能有同學覺得要這樣寫
CopyReport.dependsOn(Test)
Test.dependsOn(UITest)
編寫完成成後,我們把命令提供給運維的哥們,我們告訴他如果要測試應用的話,需要運行CopyReport這個任務。運維的哥們看到就覺得很奇怪了,明明是測試的任務,爲啥要運行 CopyReport。爲了更好使用的話,我們引入一個新的關鍵字finalizedBy。這個關鍵字會在任務運行完成後再運行指定的任務。
Test.dependsOn(UITest)
Test.finalizedBy(CopyReport)
10.4.4 動態修改Task
在項目中,我們可能會需要對某些Task的關係、內容進行修改。
方法1
gradle在配置的階段會把所有的Task進行彙總,我們可以在這個階段對特定的task修改
tasks.whenTaskAdded { task ->
if (task.name == 'assembleWithLogFreeRelease') {
task.dependsOn(A)
}
}
tasks.whenTaskAdded { task ->
if (task.name == 'assembleWithLogFreeRelease') {
task.enabled = false
}
}
方法2
上面的方法是對特定的Task進行修改,其實我們是可以對特定的流程進修改的。gradle提供了一個對象applicationVariants。這個對象封裝了android 所有的流程,我們可以對指定的流程進行修改,這個對象的參數如下圖
android.applicationVariants.all { variant ->
variant.assemble.finalizedBy(A)
}
10.4.5 task 帶參數輸入
在實際開發中,會有這樣的需求,會根據我們的輸入
// File: build.gradle
task showArgs << {
println "$word1 $word2"
}
在終端輸入
$ gradle showArgs -Pword1=hello -pword2=world
10.5 安全打包
我們知道,我們的應用發佈的時候需要使用一個keyStroe簽名文件和簽名密碼,對於一個app來說,這個就是app的身份證,如果別人拿到了這些資料就可以重新打包應用,爲了安全,我們應該把線上包的簽名和密碼與代碼分開來保存。現在的一般做法就是把這些資源寫在配置文件,再通過腳本來修改打包。
我們一般都使用這兩個文件來保存密碼,使用的方式有點不同。
如果使用的是gradle.properties文件,直接在文件內定義變量即可
signname = xmg
signpasss = 123456
讀取的時候
signingConfigs {
debug {
}
realse {
keyAlias signname
keyPassword signpasss
storeFile file('/Users/kay/Desktop/release_key.jks')
storePassword signpasss
}
}
如果是其他的properties文件的話,按照下面的來讀取
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
def myname = properties.getProperty('name')
def pass = properties.getProperty('pass')
最後將編寫好build.gradle腳本進行打包,就可以使用。
11相關資料
11.1 android 構建的完整流程
11.2 Gradle文檔資料地址
11.3 Gradle android pluging dsl 文檔地址
https://google.github.io/android-gradle-dsl/current/index.html
11.4 Gradle android pluging使用手冊
https://google.github.io/android-gradle-dsl/current/index.html