說gradle之前先扯個笑話,有次我給我同事說,gradle在web裏面管理各種jar也挺好用的,我同事跳起來“納尼,這玩意還可以用在別的地方,不是android特有的麼?” 。可能做某一項工作可能就認爲都是爲自己服務的 ,這個想法很奇怪的。 gradle是一個用來管理的編譯構建流程的軟工具而已,我是這麼理解的。
一、 沒有你的日子裏(手動悲傷)
如果你手動的來完成一個可以執行的java程序要經歷什麼步驟
- 完成一個功能總不能都是自己寫的代碼吧,總會用到一些第三方的包吧, 這個時候你需要下載第三方的包放在本地
- java文件要 通過java自帶的工具編譯成 .class
- 生成.class可能還要順帶生成文檔
- 最後再要用 java自帶的工具將 所有的資源一起打包成 一個.jar
最簡單大概就這這些步驟。如果還要加上一些編譯多個差異包之類的需要,就更復雜。仔細認真的想想,這特麼的也太麻煩了。如果同樣的工程要換個電腦該怎麼辦?這些都問題。 要想辦法解決這些問題,讓問題變的簡單起來,這就是gradle的作用了。幫助我們做了一些需要手動做的事,減輕我們的工作量,把關注點讓在具體的工程代碼上面。
二、 瞅一眼gradle
萬一很簡單,咱瞅一眼就能懂了呢?
gradle的官網是這麼定義的
Gradle is an open-source build automation tool
focused on flexibility and performance.
Gradle build scripts are written using a Groovy or Kotlin DSL.
懵逼…字我都認識,可是 groovy是啥。DSL是啥。
再百度下groovy:
Groovy是一種基於JVM(Java虛擬機)的敏捷開發語言,
它結合了Python、Ruby和Smalltalk的許多強大的特性,
Groovy 代碼能夠與 Java 代碼很好地結合,也能用於擴展現有代碼。
由於其運行在 JVM 上的特性,Groovy 可以使用其他 Java 語言編寫的庫。
再瞅瞅 DSL
領域特定語言(英語:domain-specific language、DSL)
指的是專注於某個應用程序領域的計算機語言。
對於最上面那一串的翻譯過來就是:有一個語言叫groovy ,咱用它制定一些規則,來完成上面所說的操蛋的構建的事。這種新的腳本語言就是gradle
因爲先不回學習自定義,先跳過groovy語法的部分,先瞅瞅gradle的一套規則是怎麼運行的。
三、 純粹的gralde
gradle的是一個單獨的東東,是可以脫離IDE 脫離具體的某個類型的項目(java,android)存在的。我們先瞅瞅它的原始模樣。
-
準備工作
gradle是一個單獨的工具,可以單獨執行。 爲了方便我們把它配置到環境變量中。 如果之前有使用過gradle,一般就是找到C:\Users\xxxxxx\.gradle\wrapper\dists\gradle-4.4-all
這樣的目錄就有了,配置其bin目錄到環境變量中即可。 如果之前沒有使用過,可以單獨下載。 -
目錄結構
在某個你要使用的目錄下調用gradle.bat init
就可以完成初始化。
如圖1。
這個就最基本的結構了。文件 作用 gradle 裏面配置gradle的來源和一個基本配置,默認的是一個網絡連接 .gradle 自動生成的一些記錄,可以不用管它 build.gradle setting.gradle 設置目錄,決定了整個工程包含哪些工程 gradlew.bat 執行任務可以直接調用這個gradlew.bat腳本,只是對命令 做了一封裝 爲了更加的豐富測試,我加了兩個工程, 創建了文件夾projectA projectB裏面分別創建一個空的build.gradle文件,在文件中加入了一句打印的話。然後在setting.gralde中加上
include projcetA, projectB
,在
再來查看工程中的projectgradlew.bat projects
添加一個簡單的任務默認來執行,我擦,居然還需要下載,好吧我本地其實已經下載了的。那我把它拷貝本地wraper中,gralde.wrapper 中的鏈接指向一個本地鏈接就好了。在
//gradle.wrapper
中修改對應的url地址。
修改後就 快了很多,這個時候就可以大大的節省時間,再也不用擔心玩命的等待了。 -
本來想順着官網的文檔來挨着講。可是回想起我自己琢磨gradle的時候,我特麼最關心就是它是怎麼運行的,以及怎麼自定義。那就先說這個事。
- 總的執行順序 初始化->配置->執行階段
- 初始化階段:掃描setting.gradle創建gradle 等對象 官網初始化的解釋
- 配置階段: 掃描 各個 build.gradle 創建對應的project ,獲取項目中有多少個task,因爲task的執行有順序,這裏就爲task構建一個DFA(有向無環圖)
- 執行階段:運行所有的task
因爲是腳本語言,其實每次執行都會把腳本讓進解釋器讀一遍,所以每次執行某個task都會把以上的三個階段走一次。
這裏有幾個概念很關鍵,gradle, project, task。 gradle全局可見,相當於這個gradle工具本身。 project是一個模塊,與android studio中的module是對應的關係,也對應了一個build.gradle的文件, task是最小的執行塊。爲了方便,我們來有兩個project的工程來模擬, 他們的具體執行順序進一步細分爲下面的這個樣子。
看到這幅圖,我們就能知道這個工具的能力邊界在哪裏。通過一步步的配置操作,形成一個任務的DFA來繼續執行,如果我們需要在某些節點插入一些操作,就可以有兩種方式:
- 使用系統的提供的回調監聽 #800a00
- 調整task 或者 創建新的task修改task的執行圖 #802300
3.1 設置監聽插入自定義操作到整個運行過程
所有gradle中能操作的動作,有對應的類文件倆定義它,我們搜索可查看Gradle.java的文件可以查看到這些監聽的事件。
/**
* Adds the given listener to this build. The listener may implement any of the given listener interfaces:
*
* <ul>
* <li>{@link org.gradle.BuildListener}
* <li>{@link org.gradle.api.execution.TaskExecutionGraphListener}
* <li>{@link org.gradle.api.ProjectEvaluationListener}
* <li>{@link org.gradle.api.execution.TaskExecutionListener}
* <li>{@link org.gradle.api.execution.TaskActionListener}
* <li>{@link org.gradle.api.logging.StandardOutputListener}
* <li>{@link org.gradle.api.tasks.testing.TestListener}
* <li>{@link org.gradle.api.tasks.testing.TestOutputListener}
* <li>{@link org.gradle.api.artifacts.DependencyResolutionListener}
* </ul>
*
* @param listener The listener to add. Does nothing if this listener has already been added.
*/
void addListener(Object listener);
一般的我們我們比較關心 projectEvaluation這個對應中掃描build.gradle文件的動作,我們可以知道掃描時候掃描開始,也可以知道什麼時候掃描結束,taskGraph就對應上面的念念叨叨的任務所形成的有向無環圖。
動作 | 對應的類文件 | 動作的能力 |
---|---|---|
projectEvaluate | Project.java | beforeEvaluate文件掃描前 afterEvaluate文件掃描後 |
taskGraph | TaskExecutionGraph.java | whenReady beforeTask afterTask |
在工程projectA 和 projectB的build.gradle中加入掃描的監聽,將其輸出來,還是向最上面一樣,執行taskB
針對taskB來做監聽
println( "I am Project root ")
task testA {
doLast {
println( "testA doLast")
}
}
task testB {
doFirst {
println( "testB doFirst")
}
doLast {
println( "testB doLast")
}
}
testB.dependsOn(testA)
project.gradle.taskGraph.beforeTask {
task ->
if("testB".equals(task.name)) {
println(" before testB ")
}
}
project.gradle.taskGraph.whenReady {
graph ->
println("task graph ready")
}
project.gradle.taskGraph.afterTask {
task ->
if("testB".equals(task.name)) {
println(" afterTask testB ")
}
}
afterEvaluate {
println(" afterEvaluate")
}
beforeEvaluate {
println(" beforeEvaluate")
}
最後執行下testB 看下結果
如圖所示的。看到監聽在不同的階段產生了效果
3.2 修改或者增加task來插入自定義操作到整個運行過程
- 修改task
dofirt dolast可以不斷往上附加,多次設置dolast,都可以執行,
如果原來的task有一個dolast ,可以在後面追求一個dolast的閉包。兩者是累積的關係,不會替換。這樣的話就可以執行自己想要個的操作。
這裏以一個實際有效的例子來說明,在編譯android項目的過程中經常需要打包成一個jar,又需要將操作嵌入到正常的編譯鏈之後,build會生成一系列的文件,這個文件在assemble task執行後一定會執行,我們可以這樣進行操作assemble.doLast { String cmd = "jar -cMf ${buildDir}/aaa.jar ${buildDir}/intermediates/classes/a360/debug" def cmdResult = cmd.execute().text.trim() println "cmdResult: " + cmdResult}
這樣做了之後 每次build都可以自動生成一個jar
- 增加task
task的順序有兩種,一個依賴,一個定義順序,這兩個有啥區別?
舉個例子
規定task A 依賴於 taskB 當執行taskA的時候一定會執行 taskB,再執行taskA
規定taskB 在taskA的前面執行,單獨執行taskA時,taskB不會執行,除非由於其他的任務導致taskB執行了,在執行的時候纔會讓taskB運行在taskA的前面//這樣可以定義依賴關係 testA.dependsOn(testB)
有了上面的依賴方式,則可以增加一個task,通過依賴的方式嵌入到前面的提到的任務執行的有向無環圖中,任務自然就可以執行了。testA.mustRunAfter(testB)
android中的gradle(我已經不單純了)
1.android編譯過程
如果不理解android的編譯過程,你說想寫好自動化構建的腳步,,那就有點幽默了。這個最底層的,我們需要完全的理解之後才能各種操作變化。不想自己畫圖,在網上扣了個圖
2.對應的工具
名稱--------------- | 功能介紹 | 在操作系統中的路徑 |
---|---|---|
aapt | Android資源打包工具 | ${ANDROID_SDK_HOME}/platform-tools/appt或E:\Android\sdk\build-tools\23.0.3\appt |
aidl | Android接口描述語言轉化爲.java文件的工具 | ${ANDROID_SDK_HOME}/platform-tools/aidl或E:\Android\sdk\build-tools\23.0.3\aidl |
javac | Java | Compiler |
dex | 轉化.class文件爲Davik | VM能識別的.dex文件 |
apkbuilder | 生成apk包 | ${ANDROID_SDK_HOME}/tools/opkbuilder |
jarsigner | .jar文件的簽名工具 | ${JDK_HOME}/jarsigner或/usr/bin/jarsigner |
zipalign | 字節碼對齊工具 | ${ANDROID_SDK_HOME}/tools/zipalign |
proguard | 混淆工具 | Android\Sdk\tools |
3. 對應的gradle過程
創建了一個最基本的android項目 隨便執行了一句gradlew.bat build 。 emmm… 58個task。我都沒法截圖了。
執行gradlew.bat tasks --all
可以查看所有的任務。這裏就跳過了,如果有特殊的需求再來找這些。同時
也可以通過 gradlew.bat sourceSets
看到所有默認的資源路徑配置
//部分的結果
main
----
Compile configuration: compile
build.gradle name: android.sourceSets.main
Java sources: [app\src\main\java]
Manifest file: app\src\main\AndroidManifest.xml
Android resources: [app\src\main\res]
Assets: [app\src\main\assets]
AIDL sources: [app\src\main\aidl]
RenderScript sources: [app\src\main\rs]
JNI sources: [app\src\main\jni]
JNI libraries: [app\src\main\jniLibs]
Java-style resources: [app\src\main\resources]
4. android特有的一些類,查看技巧和使用
每次在寫android的buid.gralde我都苦惱於 這些標籤一定不能瞎寫,肯定有地方規定了什麼能夠寫,什麼不能夠寫。那麼這些規定在哪裏可以看到?
最上層的android標籤裏面能夠寫那些你知道?
一般的寫法如下
android {
compileSdkVersion 27
defaultConfig {
applicationId "com.example.a01377123.myapplication"
minSdkVersion 15
targetSdkVersion 27
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}
但是還有哪些呢? 這個時候就可以查看 AppExtension.java
,它以及他的父類的裏面出來的函數都可以寫在
android這個括號裏面的。因爲這個類不好直接搜索,我們可以在android studio中的 build.gradle點擊一個標籤buildTypes可以跳轉到BaseExtension.java
截個圖
/**
* Base extension for all Android plugins.
*
* <p>You don't use this plugin directly. Instead, use one of the following:
*
* <ul>
* <li>{@link AppExtension}: outputs the {@code com.android.application} plugin you use to create
* an Android app module.
* <li>{@link LibraryExtension}: outputs the {@code com.android.library} plugin you use to <a
* href="https://developer.android.com/studio/projects/android-library.html">create an Android
* library module</a>.
* <li>{@link TestExtension}: outputs the {@code com.android.test} plugin you use to create an
* Android test module.
* <li>{@link FeatureExtension}: outputs the {@code com.android.feature} plugin you use to create
* a feature module for your <a href="https://d.android.com/instant-apps">Android Instant
* Apps</a>.
* </ul>
最上面的註釋這些話很重要。可以看到 com.android.applicaiton對應 AppExtension
其函數爲,emm…還是太長了,基本上可以囊括編譯過程中的各種工具的配置,比如aapt,javac,jarsign等。跳過,如果感興趣就可以看到,知道這個標籤下面哪些能夠寫,哪些不能夠寫。
收集了一些常用的做整理。可以方便使用android下的gradle
標籤 | 作用 | 對應的類文件 | 子標籤 |
---|---|---|---|
sourceSets | 制定各種源文件的目錄 | AndroidSourceSet.java | 實例一 |
buildTypes | 編譯的類型 | BuildType.java | 無 |
productFlavors | ProductFlavor.java | 無 |
…我又犯懶了 ,後面還有很多都是類似的道理,查看源文件就知道它的功能邊界在哪裏
實例一
sourceSets {
main {
java.srcDirs('src/test1/java') //定義java 源代碼
res.srcDirs('src/test1/res') //定義資源目錄(layout , drawable,values)
}
}
最後安利一個用來處理打包過程密碼的問題
https://github.com/etiennestuder/gradle-credentials-plugin
//配置
configurations {
baiduDebugImplementation
baiduReleaseImplementation
a360DebugImplementation
a360ReleaseImplementation
}
baiduDebugImplementation 'com.facebook.stetho:stetho:1.3.1'
//可視化查看task依賴的過程
https://github.com/dorongold/gradle-task-tree
//通盤瞭解gradle
https://docs.gradle.org/current/userguide/userguide.html
//關於編寫過程中各種語言語法的api的介紹
https://docs.gradle.org/current/dsl/
//舊的android pulgin用戶指導
http://tools.android.com/tech-docs/new-build-system/user-guide