繼上一篇Gradle自動化專案構建之快速掌握Groovy,我們繼續深入Gradle自動化專案構建技術的學習。
Gradle概念
什麼是gradle wrapper?
gradle wrapper 就是由gradle 幫我們生成的gradlew指令碼,裡面包含了用到的gradle版本資訊,我們編譯程式碼的時候不直接執行gradle命令,而是執行gradlew 命令,他會自動幫我們下載對應的gradle dist,gradle wrapper被新增到程式碼管理系統, 這樣每一個開發人員都不用去折騰gradle版本。
gradle命令(Linux執行需要使用 ./)
常用任務指令:
gradlew build。生成所有的輸出,並執行所有的檢查。
gradlew run。生成應用程式並執行某些指令碼或二進位制檔案
gradlew check。執行所有檢測類任務如tests、linting等
gradlew clean。刪除build檔案目錄。
gradlew projects。檢視專案結構。
gradlew tasks。檢視任務列表。檢視某個任務詳細資訊,可用gradle help --task someTask
gradlew dependencies。檢視依賴列表。
gradlew assembleDebug(或者gradlew aD) 編譯並打Debug包
gradlew assembleRelease(或者gradlew aR) 編譯並打Release的包
除錯類:
gradlew -?, -h, --help。檢視幫助資訊。
gradlew -v,--version。檢視版本資訊。
gradlew -s,--stacktrace。執行任務時,列印棧資訊。如gradle build --s
日誌類:
-q, --quiet。只列印errors類資訊。
-i, --info。列印詳細的資訊。
效能類:
--configure-on-demand,--no-configure-on-demand。是否開啟按需配置模式。
--build-cache, --no-build-cache。是否使用快取。
其它的詳見其官方文件:https://docs.gradle.org/current/userguide/command_line_interface.html
複製程式碼
Gradle執行流程
- 初始化階段:執行settings.gradle指令碼,解析整個工程中所有Project,構建所有Project對應的project物件。
- 配置階段:解析所有project物件中的task物件,構建好所有task的拓撲圖
- 執行階段:執行具體的task以及依賴的task
Gradle生命週期
// setting.gradle檔案
println '初始化階段執行完畢'
// settings.gradle配置完後呼叫,只對settings.gradle設定生效
gradle.settingsEvaluated {
println "settings:執行settingsEvaluated..."
}
// 當settings.gradle中引入的所有project都被建立好後呼叫,只在該檔案設定才會生效
gradle.projectsLoaded {
println "settings:執行projectsLoaded..."
}
// 在每個project進行配置前呼叫,child project必須在root project中設定才會生效,root project必須在settings.gradle中設定才會生效
gradle.beforeProject { proj ->
println "settings:執行${proj.name} beforeProject"
}
// 在每個project配置後呼叫
gradle.afterProject { proj ->
println "settings:執行${proj.name} afterProject"
}
// 所有project配置完成後呼叫
gradle.projectsEvaluated {
println "settings: 執行projectsEvaluated..."
}
//構建開始前呼叫
gradle.buildStarted {
println "構建開始..."
}
//構建結束後呼叫
gradle.buildFinished {
println "構建結束..."
}
// build.gradle檔案中
/**
* 配置本Project階段開始前的監聽回撥
*/
this.beforeEvaluate {
println '配置階段執行之前'
}
/**
* 配置本Project階段完成以後的回撥
*/
this.afterEvaluate {
println '配置階段執行完畢'
}
/**
* gradle執行本Project完畢後的回撥監聽
*/
this.gradle.buildFinished {
println '執行階段執行完畢'
}
/**
* 所有project配置完成後呼叫,可直接在setting.gradle中監聽
*/
gradle.projectsEvaluated {
gradle ->
println "所有的project都配置完畢了,準備生成Task依賴關係"
}
/**
* 表示本Project "task 依賴關係已經生成"
*/
gradle.taskGraph.whenReady {
TaskExecutionGraph graph ->
println "task 依賴關係已經生成"
}
/**
* 每一個 Task 任務執行之前回撥
*/
gradle.taskGraph.beforeTask {
Task task ->
println "Project[${task.project.name}]--->Task[${task.name}] 在執行之前被回撥"
}
/**
* 每一個 task 執行之後被回撥
*/
gradle.taskGraph.afterTask {
task, TaskState taskState ->
//第二個參數列示 task 的狀態,是可選的引數
println "Project[${task.project.name}]--->Task[${task.name}] 在執行完畢,taskState[upToDate:${taskState.upToDate},skipped:${taskState.skipped},executed:${taskState.executed},didWork:${taskState.didWork}]"
}
複製程式碼
- 注1:上述例子中setting.gradle和build.gradle中存在重複的Gradle生命週期
- 注2:有一些生命週期只在setting.gradle中配置有效,比如settingsEvaluated
- 注3:根據Gradle執行流程,第一步初始化setting.gradle檔案,第二步配置各個project。而配置各個project的順序是按照projectName首字母a-z的順序執行,因此若某一生命週期在所有project的中間的位置宣告,則會在宣告處以及後面的project產生效應。
附一張不知名大佬的執行流程和宣告週期圖示:
Project
Peoject定義:
1. 從Gradle的角度看,Gradle的管理是樹狀結構的,最外層的是根project,裡層module是子project。
2. 每一個子project都會對應輸出,比如:apk,war,aar等等這個依賴配置完成,
3. 每個project的配置和管理都是依靠自己的build.gradle完成的,並且build.gradle檔案也是是否為project的標識。
4. 雖然Gradle的管理是樹狀結構,也可以在裡層module中再建立module,但是實際開發中絕對不會在子project中再建立子project,因此此樹狀結構只有兩層。
注:通過命令:gradlew projects,可以驗證Project的樹狀結構
複製程式碼
Project相關api
api 作用
getAllprojects() 獲取工程中所有的project(包括根project與子project)
getSubProjects() 獲取當前project下,所有的子project(在不同的project下呼叫,結果會不一樣,可能返回null)
getParent() 獲取當前project的父project(若在rooProject的build.gradle呼叫,則返回null)
getRootProject() 獲取專案的根project(一定不會為null)
project(String path, Closure configureClosure) 根據path找到project,通過閉包進行配置(閉包的引數是path對應的Project物件)
allprojects(Closure configureClosure) 配置當前project和其子project的所有project
subprojects(Closure configureClosure) 配置子project的所有project(不包含當前project)
複製程式碼
屬性相關api
-
在gradle指令碼檔案中使用ext塊擴充套件屬性(父project中通過ext塊定義的屬性,子project可以直接訪問使用)
// rootProject : build.gradle ext { // 定義擴充套件屬性 compileSdkVersion = 28 libAndroidDesign = 'com.android.support:design:28.0.0' } // app : build.gradle android { compileSdkVersion = this.compileSdkVersion // 父project中的屬性,子project可以直接訪問使用 ... } dependencies { compile this.libAndroidDesign // 也可以使用:this.rootProject.libAndroidDesign ... } 複製程式碼
-
在gradle.properties檔案中擴充套件屬性
// gradle.properties isLoadTest=true // 定義擴充套件屬性 mCompileSdkVersion=28 // 定義擴充套件屬性 // setting.gradle // 判斷是否需要引入Test這個Module if(hasProperty('isLoadTest') ? isLoadTest.toBoolean() : false) { include ':Test' } // app : build.gradle android { compileSdkVersion = mCompileSdkVersion.toInteger() ... } 複製程式碼
- hasProperty('xxx'):判斷是否有在gradle.properties檔案定義xxx屬性。
- 在gradle.properties中定義的屬性,可以直接訪問,但得到的型別為Object,一般需要通過toXXX()方法轉型。
檔案相關API
api 作用
getRootDir() 獲取rootProject目錄
getBuildDir() 獲取當前project的build目錄(每個project都有自己的build目錄)
getProjectDir() 獲取當前project目錄
File file(Object path) 定位一個檔案,相對於當前project開始查詢
ConfigurableFileCollection files(Object... paths) 定位多個檔案,與file類似
copy(Closure closure) 拷貝檔案
fileTree(Object baseDir, Closure configureClosure) 定位一個檔案樹(目錄+檔案),可對檔案樹進行遍歷
例子:
// 列印common.gradle檔案內容
println getContent('common.gradle')
def getContent(String path){
try{
def file = file(path)
return file.text
}catch(GradleException e){
println 'file not found..'
}
return null
}
// 拷貝檔案、資料夾
copy {
from file('build/outputs/apk/')
into getRootProject().getBuildDir().path + '/apk/'
exclude {} // 排除檔案
rename {} // 檔案重新命名
}
// 對檔案樹進行遍歷並拷貝
fileTree('build/outputs/apk/') { FileTree fileTree ->
// 訪問樹結構的每個結點
fileTree.visit { FileTreeElement element ->
println 'the file name is: '+element.file.name
copy {
from element.file
into getRootProject().getBuildDir().path + '/test/'
}
}
}
複製程式碼
依賴相關API
1. 配置工程倉庫及gradle外掛依賴
// rootProject : build.gradle
buildscript { ScriptHandler scriptHandler ->
// 配置工程倉庫地址
scriptHandler.repositories { RepositoryHandler repositoryHandler ->
repositoryHandler.jcenter()
repositoryHandler.mavenCentral()
repositoryHandler.mavenLocal()
repositoryHandler.ivy {}
repositoryHandler.maven { MavenArtifactRepository mavenArtifactRepository ->
mavenArtifactRepository.name 'personal'
mavenArtifactRepository.url 'http://localhost:8081/nexus/repositories/'
mavenArtifactRepository.credentials {
username = 'admin'
password = 'admin123'
}
}
}
// 配置工程的"外掛"(編寫gradle指令碼使用的第三方庫)依賴地址
scriptHandler.dependencies {
classpath 'com.android.tools.build:gradle:2.2.2'
classpath 'com.tencent.tinker-patch-gradle-plugin:1.7.7'
}
}
// =========================== 上述簡化後 ============================
buildscript {
/**
* 配置工程倉庫地址
* 由於repositories這個閉包中的delegate是repositoryHandler,
* 因此可以省略repositoryHandler的引用,直接使用其屬性和方法。
*/
repositories {
jcenter()
mavenCentral()
mavenLocal()
ivy {}
maven {
name 'personal'
url 'http://localhost:8081/nexus/repositories/'
credentials {
username = 'admin'
password = 'admin123'
}
}
}
// 配置工程的"外掛"(編寫gradle指令碼使用的第三方庫)依賴地址
dependencies {
classpath 'com.android.tools.build:gradle:2.2.2'
classpath 'com.tencent.tinker-patch-gradle-plugin:1.7.7'
}
}
複製程式碼
2. 配置應用程式第三方庫依賴
// app : build.gradle
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) // 依賴檔案樹
// compile file() // 依賴單個檔案
// compile files() // 依賴多個檔案
implementation 'com.android.support:appcompat-v7:28.0.0' // 依賴倉庫中的第三方庫(即:遠端庫)
implementation project('CommonSDK') { // 依賴工程下其他Module(即:原始碼庫工程)
exclude module: 'support-v4' // 排除依賴:排除指定module
exclude group: 'com.android.support' // 排除依賴:排除指定group下所有的module
transitive false // 禁止傳遞依賴,預設值為false
}
implementation('xxx') {
changing true // 每次都從服務端拉取
}
// 棧內編譯
provided('com.tencent.tinker:tinker-android-anno:1.9.1')
}
複製程式碼
- implementation和api: 編譯依賴包並將依賴包中的類打包進apk。
- provided: 只提供編譯支援,但打包時依賴包中的類不會寫入apk。
- 依賴包只在編譯期起作用。(如:tinker的tinker-android-anno只用於在編譯期生成Application,並不需要把該庫中類打包進apk,這樣可以減小apk包體積)
- 被依賴的工程中已經有了相同版本的第三方庫,為了避免重複引用,可以使用provided。
外部命令API
// copyApk任務:用於將app工程生成出來apk目錄及檔案拷貝到本地下載目錄
task('copyApk') {
// doLast中會在gradle執行階段執行
doLast {
// gradle的執行階段去執行
def sourcePath = this.buildDir.path + '/outputs/apk'
def destinationPath = '/Users/xxx/Downloads'
def command = "mv -f ${sourcePath} ${destinationPath}"
// exec塊程式碼基本是固定的
exec {
try {
executable 'bash'
args '-c', command
println 'the command is executed success.'
}catch (GradleException e){
println 'the command is executed failed.'
}
}
}
}
複製程式碼
Task
Task定義及配置
Task定義的方法很簡單,建立的方式主要為兩種: * 一種迭代宣告task任務以及doLast,doFirst方法新增可執行程式碼; * 一種是通過 “<<” 快捷建立task任務,閉合執行任務程式碼。但不僅限於這兩種。
TaskContainer:管理所有的Task,如:增加、查詢。
-
定義(建立)Task
// 直接通過task函式去建立 task helloTask { println 'i am helloTask.' } // 通過TaskContainer去建立 this.tasks.create(name: 'helloTask2') { println 'i am helloTask 2.' } 複製程式碼
- 檢視所有Task命令:gradlew task
- 執行某一Task命令:gradlew taskName
-
配置Task
// 給Task指定分組與描述 task helloTask(group: 'study', description: 'task study'){ // 語法糖 ... } task helloTask { group 'study' // 或者setGroup('study') description 'task study' // 或者setDescription('task study') ... } 複製程式碼
Task除了可以配置group、description外,還可以配置name、type、dependsOn、overwrite、action。
- 注1:給Task分組之後,該task會被放到指定組中,方便歸類查詢。(預設被分組到other中)
- 注2:給Task新增描述,相當於給方法新增註釋。
Task的執行詳情
Gradle的執行階段執行的都是Task,即只有Task可在執行階段執行。
- Task中doFirst與doLast的使用:
// 1. task程式碼塊內部使用 task helloTask { println 'i am helloTask.' doFirst { println 'the task group is: ' + group } // doFirst、doLast可以定義多個 doFirst {} } // 2. 外部指定doFirst(會比在閉包內部指定的doFirst先執行) helloTask.doFirst { println 'the task description is: ' + description } // 統計build執行時長 def startBuildTime, endBuildTime this.afterEvaluate { Project project -> // 通過taskName找到指定的Task def preBuildTask = project.tasks.getByName('preBuild') // 執行build任務時,第一個被執行的Task // 在preBuildTask這個task執行前執行 preBuildTask.doFirst { startBuildTime = System.currentTimeMillis() } def buildTask = project.tasks.getByName('build') // 執行build任務時,最後一個被執行的Task // 在buildTask這個task執行後執行 buildTask.doLast { endBuildTime = System.currentTimeMillis() println "the build time is: ${endBuildTime - startBuildTime}" } } 複製程式碼
- 總結
- Task閉包中直接編寫的程式碼,會在配置階段執行。可以通過doFirst、doLast塊將程式碼邏輯放到執行階段中執行。
- doFirst、doLast可以指定多個。
- 外部指定的doFirst、doLast會比內部指定的先執行。
- doFirst、doLast可以對gradle中提供的已有的task進行擴充套件。
Task的執行順序
- Task執行順序指定的三種方式:
- dependsOn強依賴方式
- 通過Task輸入輸出指定(與第1種等效)
- 通過API指定執行順序
- Task的依賴
task taskX { doLast { println 'taskX' } } task taskY { doLast { println 'taskY' } } // 方式一:靜態依賴 // task taskZ(dependsOn: taskY) // 依賴一個task task taskZ(dependsOn: [taskX, taskY]) { // 依賴多個task,需要用陣列[]表示 doLast { println 'taskZ' } } // 方式二:靜態依賴 taskZ.dependsOn(taskX, taskY) // 方式三:動態依賴 task taskZ() { dependsOn this.tasks.findAll { // 依賴所有以lib開頭的task task -> return task.name.startsWith('lib') } doLast { println 'taskZ' } } // lib開頭task task lib1 << { println 'lib1' } task lib2 << { println 'lib2' } task lib3 << { println 'lib3' } 注:此處 << 為快捷建立task,閉包裡程式碼等同於在doLast閉包中執行一樣,但此寫法目前已被標記為deprecated 複製程式碼
- taskZ依賴了taskX與taskY,所以在執行taskZ時,會先執行taskX、taskY。
- taskZ依賴了taskX與taskY,但taskX與taskY沒有關係,它們的執行順序是隨機的。
- Task的輸入輸出
流程:Task Inputs --> Task One ——> Task Outputs --> 通過輸入輸出關聯Task間的關閉 --> Task Inputs --> Task Two ——> Task Outputs --> .....
- 流程分析:
- inputs和outputs是Task的屬性。
- inputs可以是任意資料型別物件,而outputs只能是檔案(或資料夾)。
- TaskA的outputs可以作為TaskB的inputs。
- 程式碼實戰
通過執行 gradle taskTask 之後,就可以在工程目錄下看到release.xml檔案了。// 例子:將每個版本資訊,儲存到指定的release.xml中 ext { versionCode = '1.0.0' versionName = '100' versionInfo = 'App的第1個版本,完成聊天功能' destFile = file('release.xml') if (destFile != null && !destFile.exists()) { destFile.createNewFile() } } // writeTask輸入擴充套件屬性,輸出檔案 task writeTask { // 為task指定輸入 inputs.property('versionCode', this.versionCode) inputs.property('versionName', this.versionName) inputs.property('versionInfo', this.versionInfo) // 為task指定輸出 outputs.file this.destFile doLast { def data = inputs.getProperties() // 返回一個map File file = outputs.getFiles().getSingleFile() // 將map轉為實體物件 def versionMsg = new VersionMsg(data) def sw = new StringWriter() def xmlBuilder = new groovy.xml.MarkupBuilder(sw) if (file.text != null && file.text.size() <= 0) { // 檔案中沒有內容 // 實際上,xmlBuilder將xml資料寫入到sw中 xmlBuilder.releases { // <releases> release { // <releases>的子節點<release> versionCode(versionMsg.versionCode) // <release>的子節點<versionCode>1.0.0<versionCode> versionName(versionMsg.versionName) versionInfo(versionMsg.versionInfo) } } // 將sw裡的內容寫到檔案中 file.withWriter { writer -> writer.append(sw.toString()) } } else { // 已經有其它版本資訊了 xmlBuilder.release { versionCode(versionMsg.versionCode) versionName(versionMsg.versionName) versionInfo(versionMsg.versionInfo) } def lines = file.readLines() def lengths = lines.size() - 1 file.withWriter { writer -> lines.eachWithIndex { String line, int index -> if (index != lengths) { writer.append(line + '\r\n') } else if (index == lengths) { writer.append(sw.toString() + '\r\n') writer.append(line + '\r\n') } } } } } } // readTask輸入writeTask的輸出檔案 task readTask { inputs.file destFile doLast { def file = inputs.files.singleFile println file.text } } task taskTest(dependsOn: [writeTask, readTask]) { doLast { println '任務執行完畢' } } class VersionMsg { String versionCode String versionName String versionInfo } 複製程式碼
- 流程分析:
- Task API指定順序
- mustRunAfter : 強行指定在某個或某些task執行之後才執行。
- shouldRunAfter : 與mustRunAfter一樣,但不強制。
通過執行 gradle taskY taskZ taskX 之後,可以看到終端還是按taskX、taskY、taskZ順序執行的。task taskX { doLast { println 'taskX' } } task taskY { // shouldRunAfter taskX mustRunAfter taskX doLast { println 'taskY' } } task taskZ { mustRunAfter taskY doLast { println 'taskZ' } } 複製程式碼
- 掛接到構建生命週期
- 例子:build任務執行完成後,執行一個自定義task
this.afterEvaluate { Project project -> def buildTask = project.tasks.getByName('build') if (buildTask == null) throw GradleException('the build task is not found') buildTask.doLast { taskZ.execute() } } 複製程式碼
- 例子:Tinker將自定義的manifestTask插入到了gradle指令碼中processManifest與processResources這兩個任務之間
TinkerManifestTask manifestTask = project.tasks.create("tinkerProcess${variantName}Manifest", TinkerManifestTask) ... manifestTask.mustRunAfter variantOutput.processManifest variantOutput.processResources.dependsOn manifestTask 複製程式碼
- 例子:build任務執行完成後,執行一個自定義task
- Task型別
- Gradle DSL Version 5.1
- Copy - Gradle DSL Version 5.1--> Task types
Gradle其它模組
Settings類
settings.gradle(對應Settings.java)決定哪些工程需要被gradle處理,佔用了整個gradle生命週期的三分之一,即Initialzation初始化階段。
SourceSet類
Gradle有一個約定的目錄結構,格式和maven的結構一樣。但不同的是,gradle的目錄結構是可以改的。對預設的檔案位置進行修改,從而讓gradle知道哪種資源要從哪些資料夾中去查詢。
// 1. sourceSets是可以呼叫多次的
android {
sourceSets {
main {
// 配置jni so庫存放位置
jniLibs.srcDirs = ['libs']
}
}
sourceSets {
main {
// 根據模組配置不同的資源位置
res.srcDirs = ['src/main/res', // 普通資源目錄
'src/main/res-ad', // 廣告資源目錄
'src/main/res-player'] // 播放器相關資源目錄
}
}
}
// 2. sourceSets一般情況下是一次性配置
android {
sourceSets {
main {
jniLibs.srcDirs = ['libs']
res.srcDirs = ['src/main/res',
'src/main/res-ad',
'src/main/res-player']
}
}
}
// 3. 使用程式設計的思想,配置sourceSets
this.android.sourceSets{
main {
jniLibs.srcDirs = ['libs']
res.srcDirs = ['src/main/res',
'src/main/res-ad',
'src/main/res-player']
}
}
複製程式碼
Gradle Plugin
Gradle外掛(Plugin)是什麼?
Gradle中的Plugin是對完成指定功能的Task封裝的體現,只要工程依賴了某個Plugin,就能執行該Plugin中所有的功能,如:使用java外掛,就可以打出jar包,使用Android外掛,就可以生成apk、aar。
自定義Plugin
-
建立外掛工程
- 在工程目錄下建立buildSrc資料夾。
- 在buildSrc目錄下,建立src資料夾、build.gradle檔案。
- 在buildSrc/src目錄下,再建立main資料夾。
- 在buildSrc/src/main目錄下,再分別建立groovy、resources資料夾。
- 在buildSrc/src/main/resources再建立一個META-INF資料夾,再在META-INF下建立一個gradle-plugins資料夾。
- 在build.gradel檔案中輸入如下指令碼:
最後,Async一下工程,buildSrc就會被識別出來了,整體目錄如圖:E:\CodeProject\android\Github\JcyDemoList\SourceCodeAnalysis\src\原始碼分析\圖示講解\Gradle自定義Plugin.pngapply plugin: 'groovy' sourceSets { main { groovy { srcDir 'src/main/groovy' } resources { srcDir 'src/main/resources' } } } 複製程式碼
-
建立外掛類: 與Java一樣,在groovy目錄下,建立一個包,再建立一個外掛類(如:com.android.gradle.GradleStudyPlugin),該外掛類必須實現Plugin介面。
注意:gradle外掛類是.groovy檔案,不是.java檔案
import org.gradle.api.Plugin import org.gradle.api.Project /** * 自定義Gradle外掛 */ class GradleStudyPlugin implements Plugin<Project> { /** * 外掛引入時要執行的方法 * @param project 引入當前外掛的project */ @Override void apply(Project project) { println 'hello gradle study plugin. current project name is ' + project.name } } 複製程式碼
-
指定外掛入口: 在編寫完外掛類的邏輯之後,需要在META-INF.gradle-plugins目錄下建立一個properties檔案(建議以外掛類包名來命名,如:com.android.gradle.properties),在該properties中宣告外掛類,以此來指定外掛入口。
該properties檔案的名字將作為當前gradle外掛被app工程引用的依據。
implementation-class=com.android.gradle.GradleStudyPlugin // 如果報錯 Could not find implementation class 'xxx' 的話, // 一般是類全路徑有問題,預設包不需要寫包路徑,修改如下即可:implementation-class=GradleStudyPlugin 複製程式碼
-
使用自定義外掛: 開啟app工程的build.gradle,應用上面的自定義gradle外掛,並Async。
apply plugin: 'com.android.application' apply plugin: 'com.android.gradle' android { ... } 複製程式碼
在Terminal中可以看到,在gradle的配置階段,就輸出了前面自定義外掛的apply方法中的日誌。
-
建立擴充套件屬性: 外掛往往會在gradle指令碼中進行引數配置,如在android{}中,可以配置compileSdkVersion等引數,其實本質上,就是在gradle指令碼中使用閉包方式建立了一個javaBean,並將其傳遞到外掛中被外掛識別讀取而已。
步驟:
- 建立一個實體類,宣告成員變數,用於接收gradle中配置的引數。(可以理解為就是javaBean,不過要注意,該檔案字尾是.groovy,不是.java)
class ReleaseInfoExtension { String versionCode String versionName String versionInfo String fileName ReleaseInfoExtension() {} @Override String toString() { return "versionCode = ${versionCode} , versionName = ${versionName} ," + " versionInfo = ${versionInfo} , fileName = ${fileName}" } } 複製程式碼
- 在自定義外掛中,對當前project進行擴充套件。
class GradleStudyPlugin implements Plugin<Project> { /** * 外掛引入時要執行的方法 * @param project 引入當前外掛的project */ @Override void apply(Project project) { // 這樣就可以在gradle指令碼中,通過releaseInfo閉包來完成ReleaseInfoExtension的初始化。 project.extensions.create("releaseInfo", ReleaseInfoExtension) } } 複製程式碼
- 開啟在app工程的build.gradle,通過擴充套件key值命名閉包的方式,就可以配置指定引數了。
apply plugin: 'com.android.gradle' releaseInfo { versionCode = '1.0.0' versionName = '100' versionInfo = '第一個app資訊' fileName = 'release.xml' } 複製程式碼
- 接收引數
def versionCodeMsg = project.extensions.releaseInfo.versionCode 複製程式碼
- 建立一個實體類,宣告成員變數,用於接收gradle中配置的引數。(可以理解為就是javaBean,不過要注意,該檔案字尾是.groovy,不是.java)
-
建立擴充套件Task: 自定義外掛無非就是封裝一些常用Task,所以,擴充套件Task才是自定義外掛的最重要的一部分。擴充套件Task也很簡單,繼承DefaultTask,編寫TaskAction註解方法。
// 例子:把app版本資訊寫入到xml檔案中 import groovy.xml.MarkupBuilder import org.gradle.api.DefaultTask import org.gradle.api.tasks.TaskAction class ReleaseInfoTask extends DefaultTask { ReleaseInfoTask() { group 'android' // 指定分組 description 'update the release info' // 新增說明資訊 } /** * 使用TaskAction註解,可以讓方法在gradle的執行階段去執行。 * doFirst其實就是在外部為@TaskAction的最前面新增執行邏輯。 * 而doLast則是在外部為@TaskAction的最後面新增執行邏輯。 */ @TaskAction void doAction() { updateInfo() } private void updateInfo() { // 獲取gradle指令碼中配置的引數 def versionCodeMsg = project.extensions.releaseInfo.versionCode def versionNameMsg = project.extensions.releaseInfo.versionName def versionInfoMsg = project.extensions.releaseInfo.versionInfo def fileName = project.extensions.releaseInfo.fileName // 建立xml檔案 def file = project.file(fileName) if (file != null && !file.exists()) { file.createNewFile() } // 建立寫入xml資料所需要的類。 def sw = new StringWriter(); def xmlBuilder = new groovy.xml.MarkupBuilder(sw) // 若xml檔案中沒有內容,就多建立一個realease節點,並寫入xml資料 if (file.text != null && file.text.size() <= 0) { xmlBuilder.releases { release { versionCode(versionCodeMsg) versionName(versionNameMsg) versionInfo(versionInfoMsg) } } file.withWriter { writer -> writer.append(sw.toString()) } } else { // 若xml檔案中已經有內容,則在原來的內容上追加。 xmlBuilder.release { versionCode(versionCodeMsg) versionName(versionNameMsg) versionInfo(versionInfoMsg) } def lines = file.readLines() def lengths = lines.size() - 1 file.withWriter { writer -> lines.eachWithIndex { String line, int index -> if (index != lengths) { writer.append(line + '\r\n') } else if (index == lengths) { writer.append(sw.toString() + '\r\n') writer.append(line + '\r\n') } } } } } } 複製程式碼
與建立擴充套件屬性一樣,擴充套件Task也需要在project中建立注入。
/** * 自定義Gradle外掛 */ class GradleStudyPlugin implements Plugin<Project> { /** * 外掛引入時要執行的方法 * @param project 引入當前外掛的project */ @Override void apply(Project project) { // 建立擴充套件屬性 // 這樣就可以在gradle指令碼中,通過releaseInfo閉包來完成ReleaseInfoExtension的初始化。 project.extensions.create("releaseInfo", ReleaseInfoExtension) // 建立Task project.tasks.create("updateReleaseInfo", ReleaseInfoTask) } } 複製程式碼
再次Async工程之後,就可以在Idea的gradle標籤裡android分組中看到自定義好的Task了。
注:這種在工程下直接建立buildSrc目錄編寫的外掛,只能對當前工程可見,所以,如果需要將我們自定義好的grdle外掛被其他工程所使用,則需要單獨建立一個庫工程,並建立如buildSrc目錄下所有的檔案,最後上傳maven倉庫即可
-
Demo請參考:github.com/Endless5F/J…
android外掛對gradle擴充套件
- 譯者序 | Gradle Android外掛使用者指南翻譯
- Manipulation tasks(操作task) | Gradle Android外掛使用者指南翻譯
- 自定義Apk輸出位置:
this.afterEvaluate { this.android.applicationVariants.all { variant -> def output = variant.outpus.first() // 獲取變體輸出檔案(outputs返回是一個集合,但只有一個元素,即輸出apk的file) def apkName = "app-${variant.baseName}-${variant.versionName}.apk" output.outputFile = new File(output.outputFile.parent, apkName) } } 複製程式碼
Jenkins
Jenkins是一個開源的、提供友好操作介面的持續整合(CI)工具,起源於Hudson(Hudson是商用的),主要用於持續、自動的構建/測試軟體專案、監控外部任務的執行(這個比較抽象,暫且寫上,不做解釋)。Jenkins用Java語言編寫,可在Tomcat等流行的servlet容器中執行,也可獨立執行。通常與版本管理工具(SCM)、構建工具結合使用。常用的版本控制工具有SVN、GIT,構建工具有Maven、Ant、Gradle。
具體學習請參考:Jenkins詳細教程
參考連結
...
注:若有什麼地方闡述有誤,敬請指正。期待您的點贊哦!!!