Gradle工作原理全面瞭解

說gradle之前先扯個笑話,有次我給我同事說,gradle在web裏面管理各種jar也挺好用的,我同事跳起來“納尼,這玩意還可以用在別的地方,不是android特有的麼?” 。可能做某一項工作可能就認爲都是爲自己服務的 ,這個想法很奇怪的。 gradle是一個用來管理的編譯構建流程的軟工具而已,我是這麼理解的。

一、 沒有你的日子裏(手動悲傷)

如果你手動的來完成一個可以執行的java程序要經歷什麼步驟

  1. 完成一個功能總不能都是自己寫的代碼吧,總會用到一些第三方的包吧, 這個時候你需要下載第三方的包放在本地
  2. java文件要 通過java自帶的工具編譯成 .class
  3. 生成.class可能還要順帶生成文檔
  4. 最後再要用 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)存在的。我們先瞅瞅它的原始模樣。

  1. 準備工作
    gradle是一個單獨的工具,可以單獨執行。 爲了方便我們把它配置到環境變量中。 如果之前有使用過gradle,一般就是找到C:\Users\xxxxxx\.gradle\wrapper\dists\gradle-4.4-all這樣的目錄就有了,配置其bin目錄到環境變量中即可。 如果之前沒有使用過,可以單獨下載。

  2. 目錄結構
    在某個你要使用的目錄下調用gradle.bat init就可以完成初始化。
    如圖1。
    這個就最基本的結構了。

    文件 作用
    gradle 裏面配置gradle的來源和一個基本配置,默認的是一個網絡連接
    .gradle 自動生成的一些記錄,可以不用管它
    build.gradle
    setting.gradle 設置目錄,決定了整個工程包含哪些工程
    gradlew.bat 執行任務可以直接調用這個gradlew.bat腳本,只是對命令 做了一封裝

    爲了更加的豐富測試,我加了兩個工程, 創建了文件夾projectA projectB裏面分別創建一個空的build.gradle文件,在文件中加入了一句打印的話。然後在setting.gralde中加上include projcetA, projectB,在
    再來查看工程中的project gradlew.bat projects

    添加一個簡單的任務默認來執行,我擦,居然還需要下載,好吧我本地其實已經下載了的。那我把它拷貝本地wraper中,gralde.wrapper 中的鏈接指向一個本地鏈接就好了。在 //gradle.wrapper中修改對應的url地址。
    修改後就 快了很多,這個時候就可以大大的節省時間,再也不用擔心玩命的等待了。

  3. 本來想順着官網的文檔來挨着講。可是回想起我自己琢磨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來繼續執行,如果我們需要在某些節點插入一些操作,就可以有兩種方式:

  1. 使用系統的提供的回調監聽 #800a00
  2. 調整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來插入自定義操作到整個運行過程

  1. 修改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

  1. 增加task
    task的順序有兩種,一個依賴,一個定義順序,這兩個有啥區別?
    舉個例子
    規定task A 依賴於 taskB 當執行taskA的時候一定會執行 taskB,再執行taskA
    //這樣可以定義依賴關係
    testA.dependsOn(testB)
    
    規定taskB 在taskA的前面執行,單獨執行taskA時,taskB不會執行,除非由於其他的任務導致taskB執行了,在執行的時候纔會讓taskB運行在taskA的前面
    testA.mustRunAfter(testB)
    
    有了上面的依賴方式,則可以增加一個task,通過依賴的方式嵌入到前面的提到的任務執行的有向無環圖中,任務自然就可以執行了。

推薦官網關於task 操作,大家可以看看

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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章