如果您已經達到上面的程度,那麼可以不用再看下文了,直接看最後的總結即可
本文將從下面幾個部分進行講解:
官方解釋是: Gradle is an open-source build automation tool focused on flexibility and performance. Gradle build scripts are written using a Groovy or Kotlin DSL.
可以從三個角度來理解
gradle 可以類比做一條流水線,task 可以比作流水線上的機器人,每個機器人負責不同的事情,最終生成完整的構建產物
2. gradle 腳本使用了 groovy 或者 kotlin DSL
與 GPL 相比起來,DSL 使用簡單,定義比較簡潔,比起配置文件,DSL 又可以實現語言邏輯
3. gradle 基於 groovy 編寫,而 groovy 是基於 jvm 語言
關於 gradle 的項目層次,我們新建一個項目看一下,項目地址在EasyGradle
settings.gradle 是負責配置項目的腳本
對應的可調用的方法在文檔里可以查找
一般在項目里見到的引用子模塊的方法,就是使用 include,這樣引用,子模塊位於根項目的下一級
include :app
如果想指定子模塊的位置,可以使用 project 方法獲取 Project 對象,設置其 projectDir 參數
include :app project(:app).projectDir = new File(./app)
build.gradle 負責整體項目的一些配置,對應的是 Project 類
以 EasyGradle 項目來看
buildscript { // 配置項目的 classpath repositories { // 項目的倉庫地址,會按順序依次查找 google() jcenter() mavenLocal() } dependencies { // 項目的依賴 classpath com.android.tools.build:gradle:3.0.1 classpath com.zy.plugin:myplugin:0.0.1 } }
allprojects { // 子項目的配置 repositories { google() jcenter() mavenLocal() } }
build.gradle 是子項目的配置,對應的也是 Project 類
子項目和根項目的配置是差不多的,不過在子項目里可以看到有一個明顯的區別,就是引用了一個插件 apply plugin "com.android.application",後面的 android dsl 就是 application 插件的 extension,關於 android plugin dsl 可以看 android-gradle-dsl
以 app 模塊的 build.gradle 來看
apply plugin: com.android.application // 引入 android gradle 插件
android { // 配置 android gradle plugin 需要的內容 compileSdkVersion 26 defaultConfig { // 版本,applicationId 等配置 applicationId "com.zy.easygradle" minSdkVersion 19 targetSdkVersion 26 versionCode 1 versionName "1.0" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile(proguard-android.txt), proguard-rules.pro } } compileOptions { // 指定 java 版本 sourceCompatibility 1.8 targetCompatibility 1.8 }
// flavor 相關配置 flavorDimensions "size", "color" productFlavors { big { dimension "size" } small { dimension "size" } blue { dimension "color" } red { dimension "color" } } }
// 項目需要的依賴 dependencies { implementation fileTree(dir: libs, include: [*.jar]) // jar 包依賴 implementation com.android.support:appcompat-v7:26.1.0 // 遠程倉庫依賴 implementation com.android.support.constraint:constraint-layout:1.1.3 implementation project(:module1) // 項目依賴 }
在 gradle 3.4 里引入了新的依賴配置,如下:
還是以 EasyGradle 為例,看一下各個依賴的不同: 項目里有三個模塊:app,module1, module2
模塊 app 中有一個類 ModuleApi
模塊 module1 中有一個類 Module1Api
模塊 module2 中有一個類 Module2Api
其依賴關係如下:
implementation 依賴
當 module1 使用 implementation 依賴 module2 時,在 app 模塊中無法引用到 Module2Api 類
api 依賴
當 module1 使用 api 依賴 module2 時,在 app 模塊中可以正常引用到 Module2Api 類,如下圖
compileOnly 依賴
當 module1 使用 compileOnly 依賴 module2 時,在編譯階段 app 模塊無法引用到 Module2Api 類,module1 中正常引用,但是在運行時會報錯
反編譯打包好的 apk,可以看到 Module2Api 是沒有被打包到 apk 里的
runtimeOnly 依賴
當 module1 使用 runtimeOnly 依賴 module2 時,在編譯階段,module1 也無法引用到 Module2Api
在介紹下面的流程之前,先明確幾個概念,flavor,dimension,variant
flavorDimensions "size", "color"
productFlavors { big { dimension "size" } small { dimension "size" } blue { dimension "color" } red { dimension "color" } }
那麼生成的 variant 對應的就是 bigBlue,bigRed,smallBlue,smallRed
每個 variant 可以對應的使用 variantImplementation 來引入特定的依賴,比如:bigBlueImplementation,只有在 編譯 bigBlue variant的時候才會引入
gradlew / gradlew.bat 這個文件用來下載特定版本的 gradle 然後執行的,就不需要開發者在本地再安裝 gradle 了。這樣做有什麼好處呢?開發者在本地安裝 gradle,會碰到的問題是不同項目使用不同版本的 gradle 怎麼處理,用 wrapper 就很好的解決了這個問題,可以在不同項目里使用不同的 gradle 版本。gradle wrapper 一般下載在 GRADLE_CACHE/wrapper/dists 目錄下
gradle/wrapper/gradle-wrapper.properties 是一些 gradlewrapper 的配置,其中用的比較多的就是 distributionUrl,可以執行 gradle 的下載地址和版本
在 gradle 里,有一種 init.gradle 比較特殊,這種腳本會在每個項目 build 之前先被調用,可以在其中做一些整體的初始化操作,比如配置 log 輸出等等
gradle 構建分為三個階段
初始化階段主要做的事情是有哪些項目需要被構建,然後為對應的項目創建 Project 對象
配置階段
執行階段
gradle 在構建過程中,會提供一些列回調介面,方便在不同的階段做一些事情,主要的介面有下面幾個
gradle.addBuildListener(new BuildListener() { @Override void buildStarted(Gradle gradle) { println(構建開始) // 這個回調一般不會調用,因為我們註冊的時機太晚,註冊的時候構建已經開始了,是 gradle 內部使用的 }
@Override void settingsEvaluated(Settings settings) { println(settings 文件解析完成) }
@Override void projectsLoaded(Gradle gradle) { println(項目載入完成) gradle.rootProject.subprojects.each { pro -> pro.beforeEvaluate { println("${pro.name} 項目配置之前調用") } pro.afterEvaluate{ println("${pro.name} 項目配置之後調用") } } }
@Override void projectsEvaluated(Gradle gradle) { println(項目解析完成) }
@Override void buildFinished(BuildResult result) { println(構建完成) } })
gradle.taskGraph.whenReady { println("task 圖構建完成") } gradle.taskGraph.beforeTask { println("每個 task 執行前會調這個介面") } gradle.taskGraph.afterTask { println("每個 task 執行完成會調這個介面") }
默認創建的 task 繼承自 DefaultTask 如何聲明一個 task
task myTask { println myTask in configuration doLast { println myTask in run } }
class MyTask extends DefaultTask { @Input Boolean myInputs @Output @TaskAction void start() { } }
tasks.create("mytask").doLast { }
Task 的一些重要方法分類如下:
Task.doFirst
比如要指定 Task 之間的依賴順序,寫法如下:
task task1 { doLast { println(task2) } } task task2 { doLast { println(task2) } } task1.finalizedBy(task2) task1.dependsOn(task2) task1.mustRunAfter(task2) task1.shouldRunAfter(task2) task1.finalizedBy(task2)
android gradle plugin 提供了 transform api 用來在 .class to dex 過程中對 class 進行處理,可以理解為一種特殊的 Task,因為 transform 最終也會轉化為 Task 去執行
要實現 transform 需要繼承 com.android.build.api.transform.Transform 並實現其方法,實現了 Transform 以後,要想應用,就調用 project.android.registerTransform()
public class MyTransform extends Transform { @Override public String getName() { // 返回 transform 的名稱,最終的名稱會是 transformClassesWithMyTransformForDebug 這種形式 return "MyTransform"; }
@Override public Set<QualifiedContent.ContentType> getInputTypes() { /** 返回需要處理的數據類型 有 下面幾種類型可選 public static final Set<ContentType> CONTENT_CLASS = ImmutableSet.of(CLASSES); public static final Set<ContentType> CONTENT_JARS = ImmutableSet.of(CLASSES, RESOURCES); public static final Set<ContentType> CONTENT_RESOURCES = ImmutableSet.of(RESOURCES); public static final Set<ContentType> CONTENT_NATIVE_LIBS = ImmutableSet.of(NATIVE_LIBS); public static final Set<ContentType> CONTENT_DEX = ImmutableSet.of(ExtendedContentType.DEX); public static final Set<ContentType> DATA_BINDING_ARTIFACT = ImmutableSet.of(ExtendedContentType.DATA_BINDING); */ return TransformManager.CONTENT_CLASS; }
@Override public Set<? super QualifiedContent.Scope> getScopes() { /** 返回需要處理內容的範圍,有下面幾種類型 PROJECT(1), 只處理項目的內容 SUB_PROJECTS(4), 只處理子項目 EXTERNAL_LIBRARIES(16), 只處理外部庫 TESTED_CODE(32), 只處理當前 variant 對應的測試代碼 PROVIDED_ONLY(64), 處理依賴 @Deprecated PROJECT_LOCAL_DEPS(2), @Deprecated SUB_PROJECTS_LOCAL_DEPS(8); */ return Sets.immutableEnumSet(QualifiedContent.Scope.PROJECT); }
@Override public boolean isIncremental() { // 是否增量,如果返回 true,TransformInput 會包括一份修改的文件列表,返回 false,會進行全量編譯,刪除上一次的輸出內容 return false; }
@Override void transform(TransformInvocation transformInvocation) throws TransformException, InterruptedException, IOException { // 在這裡處理 class super.transform(transformInvocation) // 在 transform 里,如果沒有任何修改,也要把 input 的內容輸出到 output,否則會報錯 for (TransformInput input : transformInvocation.inputs) { input.directoryInputs.each { dir -> // 獲取對應的輸出目錄 File output = transformInvocation.outputProvider.getContentLocation(dir.name, dir.contentTypes, dir.scopes, Format.DIRECTORY) dir.changedFiles // 增量模式下修改的文件 dir.file // 獲取輸入的目錄 FileUtils.copyDirectory(dir.file, output) // input 內容輸出到 output } input.jarInputs.each { jar -> // 獲取對應的輸出 jar File output = transformInvocation.outputProvider.getContentLocation(jar.name, jar.contentTypes, jar.scopes, Format.JAR) jar.file // 獲取輸入的 jar 文件 FileUtils.copyFile(jar.file, output) // input 內容輸出到 output } } } }
// 註冊 transform android.registerTransform(new MyTransform())
在 transform 中的處理,一般會涉及到 class 文件的修改,操縱位元組碼的工具一般是 javasist 和 asm 居多,這兩個工具在這裡先不介紹了。後面有機會會展開說一下
gradle 的插件可以看作是一系列 task 的集合
2. 在 src/main 目錄下創建 groovy 目錄,然後創建自己的包名和插件類
3. 在 src/main 目錄下創建 resources/META-INFO/gradle-plugins 目錄,創建 ,myplugin.properties 文件,文件里內容是
implementation-class=com.zy.plugin.MyPlugin // 這裡是自己的插件類
4. 修改 build.gradle 文件
// 引入 groovy 和 java 插件 apply plugin: groovy apply plugin: java
buildscript { repositories { mavenLocal() maven { url http://depot.sankuai.com/nexus/content/groups/public/ } maven { url https://maven.google.com } jcenter() } }
repositories { mavenLocal() maven { url "http://mvn.dianpingoa.com/android-nova" } maven { url http://depot.sankuai.com/nexus/content/groups/public/ } maven { url https://maven.google.com } }
dependencies { compile gradleApi() compile localGroovy() compile com.android.tools.build:gradle:3.0.1 }
現在為止,項目結構是這個樣子的
在剛才創建的插件類里,就可以寫插件的代碼了。插件類繼承 Plugin,並實現 apply 介面,apply 就是在 build.gradle 里 apply plugin xxx 的時候要調用的介面了
package com.zy.plugin
import org.gradle.api.Plugin import org.gradle.api.Project
class MyPlugin implements Plugin<Project> {
@Override void apply(Project project) { println("apply my plugin") } }
我們再定義一個 task 類 MyTask,繼承自 DefaultTask,簡單的輸出一些信息
import org.gradle.api.DefaultTask import org.gradle.api.tasks.TaskAction
class MyTask extends DefaultTask {
@TaskAction void action() { println(my task run) } }
然後在 plugin 中註冊這個 task
@Override void apply(Project project) { println("apply my plugin") project.tasks.create("mytask", MyTask.class) } }
這樣一個簡單的插件就開發好了,如何使用呢
apply plugin: maven
install { repositories.mavenInstaller { pom.version = 0.0.1 // 配置插件版本號 pom.artifactId = myplugin // 配置插件標識 pom.groupId = com.zy.plugin // 配置插件組織 } }
之後執行 ./gradlew install 便會把插件安裝在本地 maven 倉庫
之後在使用的地方引入我們插件的 classpath
classpath com.zy.plugin:myplugin:0.0.1
之後載入插件
apply plugin; myplugin // 這裡的 myplugin 是前面說的 myplugin.properties 的名字
然後運行 ./gradlew tasks --all | grep mytask,就可以看到我們在 plugin 里新增的 task 了
在插件 build.gradle 里新增上傳的配置如下
uploadArchives { repositories { mavenDeployer { repository(url: "mavenUrl") pom.version = 0.0.1 pom.artifactId = myplugin } } }
運行 ./gradlew uploadArchives 就可以了
那麼開發插件的時候如何調試呢?
2.之後在執行 task 的時候增加下面的參數
./gradlew app:mytask -Dorg.gradle.debug=true
此時可以看到 gradle 在等待 debug 進程連接
3.之後在插件代碼中打好斷點,在 as 中點擊 debug 按鈕,就可以調試插件代碼了
其中一定要掌握的如下:
推薦閱讀: