若是您已經達到上面的程度,那麼能夠不用再看下文了,直接看最後的總結便可html
本文將從下面幾個部分進行講解:
java
1. gradle 是一個自動化構建工具
gradle 是經過組織一系列 task 來最終完成自動化構建的,因此 task 是 gradle 裏最重要的概念
咱們以生成一個可用的 apk 爲例,整個過程要通過 資源的處理,javac 編譯,dex 打包,apk 打包,簽名等等步驟,每一個步驟就對應到 gradle 裏的一個 taskandroid
gradle 能夠類比作一條流水線,task 能夠比做流水線上的機器人,每一個機器人負責不一樣的事情,最終生成完整的構建產物 git
2. gradle 腳本使用了 groovy 或者 kotlin DSL
gradle 使用 groovy 或者 kotlin 編寫,不過目前仍是 groovy 居多
那什麼是 DSL 呢?DSL 也就是 Domain Specific Language 的簡稱,是爲了解決某一類任務專門設計的計算機語言
DSL 相對應的是 GPL (General-Purpose Language),好比 java
與 GPL 相比起來,DSL 使用簡單,定義比較簡潔,比起配置文件,DSL 又能夠實現語言邏輯
對 gradle 腳原本說,他實現了簡潔的定義,又有充分的語言邏輯,以 android {} 爲例,這自己是一個函數調用,參數是一個閉包,可是這種定義方式明顯要簡潔不少github
3. gradle 基於 groovy 編寫,而 groovy 是基於 jvm 語言
gradle 使用 groovy 編寫,groovy 是基於 jvm 的語言,因此本質上是面向對象的語言,面嚮對象語言的特色就是一切皆對象,因此,在 gradle 裏,.gradle 腳本的本質就是類的定義,一些配置項的本質都是方法調用,參數是後面的 {} 閉包
好比 build.gradle 對應 Project 類,buildScript 對應 Project.buildScript 方法api
關於 gradle 的項目層次,咱們新建一個項目看一下,項目地址在 EasyGradle
bash
settings.gradle 是負責配置項目的腳本
對應 Settings 類,gradle 構建過程當中,會根據 settings.gradle 生成 Settings 的對象
對應的可調用的方法在文檔裏能夠查找
其中幾個主要的方法有:閉包
通常在項目裏見到的引用子模塊的方法,就是使用 include,這樣引用,子模塊位於根項目的下一級app
include ':app'
複製代碼
若是想指定子模塊的位置,可使用 project 方法獲取 Project 對象,設置其 projectDir 參數jvm
include ':app'
project(':app').projectDir = new File('./app')
複製代碼
build.gradle 負責總體項目的一些配置,對應的是 Project 類
gradle 構建的時候,會根據 build.gradle 生成 Project 對象,因此在 build.gradle 裏寫的 dsl,其實都是 Project 接口的一些方法,Project 實際上是一個接口,真正的實現類是 DefaultProject
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 裏引入了新的依賴配置,以下:
新配置 | 棄用配置 | 行爲 | 做用 |
---|---|---|---|
implementation | compile | 依賴項在編譯時對模塊可用,而且僅在運行時對模塊的消費者可用。 對於大型多項目構建,使用 implementation 而不是 api/compile 能夠顯著縮短構建時間,由於它能夠減小構建系統須要從新編譯的項目量。 大多數應用和測試模塊都應使用此配置。 | implementation 只會暴露給直接依賴的模塊,使用此配置,在模塊修改之後,只會從新編譯直接依賴的模塊,間接依賴的模塊不須要改動 |
api | compile | 依賴項在編譯時對模塊可用,而且在編譯時和運行時還對模塊的消費者可用。 此配置的行爲相似於 compile(如今已棄用),通常狀況下,您應當僅在庫模塊中使用它。 應用模塊應使用 implementation,除非您想要將其 API 公開給單獨的測試模塊。 | api 會暴露給間接依賴的模塊,使用此配置,在模塊修改之後,模塊的直接依賴和間接依賴的模塊都須要從新編譯 |
compileOnly | provided | 依賴項僅在編譯時對模塊可用,而且在編譯或運行時對其消費者不可用。 此配置的行爲相似於 provided(如今已棄用)。 | 只在編譯期間依賴模塊,打包之後運行時不會依賴,能夠用來解決一些庫衝突的問題 |
runtimeOnly | apk | 依賴項僅在運行時對模塊及其消費者可用。 此配置的行爲相似於 apk(如今已棄用)。 | 只在運行時依賴模塊,編譯時不依賴 |
仍是以 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
在 android gradle plugin 3.x 以後,每一個 flavor 必須對應一個 dimension,能夠理解爲 flavor 的分組,而後不一樣 dimension 裏的 flavor 兩兩組合造成一個 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/wrapper/gradle-wrapper.jar 是 gradlewrapper 運行須要的依賴包
在 gradle 裏,有一種 init.gradle 比較特殊,這種腳本會在每一個項目 build 以前先被調用,能夠在其中作一些總體的初始化操做,好比配置 log 輸出等等
使用 init.gradle 的方法:
gradle 構建分爲三個階段
初始化階段
初始化階段主要作的事情是有哪些項目須要被構建,而後爲對應的項目建立 Project 對象
配置階段
配置階段主要作的事情是對上一步建立的項目進行配置,這時候會執行 build.gradle 腳本,而且會生成要執行的 task
執行階段
執行階段主要作的事情就是執行 task,進行主要的構建工做
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 行爲
Task.doFirst
Task.doLast
Task 依賴順序
Task.dependsOn
Task.mustRunAfter
Task.shouldRunAfter
Task.finalizedBy
Task 的分組描述
Task.group
Task.description
Task 是否可用
Task.enabled
Task 輸入輸出
gradle 會比較 task 的 inputs 和 outputs 來決定 task 是不是最新的,若是 inputs 和 outputs 沒有變化,則認爲 task 是最新的,task 就會跳過不執行
Task.inputs
Task.outputs
Task 是否執行
能夠經過指定 Task.upToDateWhen = false 來強制 task 執行 Task.upToDateWhen
好比要指定 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 的集合
在 android 工程的 build.gradle 腳本里,第一行就是 apply plugin: 'com.android.application',這個就是引入 android gradle 插件,插件裏有 android 打包相關的 task
關於 android gradle plugin 的源碼分析,在後面會講到,如今先看看如何實現一個本身的 plugin
implementation-class=com.zy.plugin.MyPlugin // 這裏是本身的插件類
複製代碼
// 引入 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' 的時候要調用的接口了
插件開發可使用 groovy 和 java,使用 groovy 的話能夠有更多的語法糖,開發起來更方便一些
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,簡單的輸出一些信息
package com.zy.plugin
import org.gradle.api.DefaultTask
import org.gradle.api.tasks.TaskAction
class MyTask extends DefaultTask {
@TaskAction
void action() {
println('my task run')
}
}
複製代碼
而後在 plugin 中註冊這個 task
class MyPlugin implements Plugin<Project> {
@Override
void apply(Project project) {
println("apply my plugin")
project.tasks.create("mytask", MyTask.class)
}
}
複製代碼
這樣一個簡單的插件就開發好了,如何使用呢
咱們首先須要在 build.gradle 中引入 maven 插件,而且配置 install 相關的屬性
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 了
./gradlew mytasks 就能夠執行 task 了
在插件 build.gradle 裏新增上傳的配置以下
uploadArchives {
repositories {
mavenDeployer {
repository(url: "mavenUrl")
pom.version = '0.0.1'
pom.artifactId = 'myplugin'
}
}
}
複製代碼
運行 ./gradlew uploadArchives 就能夠了
那麼開發插件的時候如何調試呢?
1.首先在 as 中新增一個 remote 配置
2.以後在執行 task 的時候增長下面的參數
./gradlew app:mytask -Dorg.gradle.debug=true
複製代碼
此時能夠看到 gradle 在等待 debug 進程鏈接
3.以後在插件代碼中打好斷點,在 as 中點擊 debug 按鈕,就能夠調試插件代碼了
主要要點以下圖:
其中必定要掌握的以下: