繼上一篇Gradle自動化項目構建之快速掌握Groovy,咱們繼續深刻Gradle自動化項目構建技術的學習。html
gradle wrapper 就是由gradle 幫咱們生成的gradlew腳本,裏面包含了用到的gradle版本信息,咱們編譯代碼的時候不直接運行gradle命令,而是運行gradlew 命令,他會自動幫咱們下載對應的gradle dist,gradle wrapper被添加到代碼管理系統, 這樣每個開發人員都不用去折騰gradle版本。java
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
複製代碼
// 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}]"
}
複製代碼
附一張不知名大佬的執行流程和聲明週期圖示:android
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的樹狀結構
複製代碼
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)
複製代碼
在gradle腳本文件中使用ext塊擴展屬性(父project中經過ext塊定義的屬性,子project能夠直接訪問使用)git
// 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文件中擴展屬性github
// 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()
...
}
複製代碼
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/'
}
}
}
複製代碼
// 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'
}
}
複製代碼
// 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')
}
複製代碼
// 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任務以及doLast,doFirst方法添加可執行代碼; * 一種是經過 「<<」 快捷建立task任務,閉合執行任務代碼。但不只限於這兩種。編程
TaskContainer:管理全部的Task,如:增長、查找。api
定義(建立)Task數組
// 直接經過task函數去建立
task helloTask {
println 'i am helloTask.'
}
// 經過TaskContainer去建立
this.tasks.create(name: 'helloTask2') {
println 'i am helloTask 2.'
}
複製代碼
配置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。bash
Gradle的執行階段執行的都是Task,即只有Task可在執行階段執行。
// 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 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
複製代碼
// 例子:將每一個版本信息,保存到指定的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
}
複製代碼
經過執行 gradle taskTask 以後,就能夠在工程目錄下看到release.xml文件了。task taskX {
doLast {
println 'taskX'
}
}
task taskY {
// shouldRunAfter taskX
mustRunAfter taskX
doLast {
println 'taskY'
}
}
task taskZ {
mustRunAfter taskY
doLast {
println 'taskZ'
}
}
複製代碼
經過執行 gradle taskY taskZ taskX 以後,能夠看到終端仍是按taskX、taskY、taskZ順序執行的。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()
}
}
複製代碼
TinkerManifestTask manifestTask = project.tasks.create("tinkerProcess${variantName}Manifest", TinkerManifestTask)
...
manifestTask.mustRunAfter variantOutput.processManifest
variantOutput.processResources.dependsOn manifestTask
複製代碼
settings.gradle(對應Settings.java)決定哪些工程須要被gradle處理,佔用了整個gradle生命週期的三分之一,即Initialzation初始化階段。
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是對完成指定功能的Task封裝的體現,只要工程依賴了某個Plugin,就能執行該Plugin中全部的功能,如:使用java插件,就能夠打出jar包,使用Android插件,就能夠生成apk、aar。
建立插件工程
apply plugin: 'groovy'
sourceSets {
main {
groovy {
srcDir 'src/main/groovy'
}
resources {
srcDir 'src/main/resources'
}
}
}
複製代碼
最後,Async一下工程,buildSrc就會被識別出來了,總體目錄如圖:E:\CodeProject\android\Github\JcyDemoList\SourceCodeAnalysis\src\源碼分析\圖示講解\Gradle自定義Plugin.png建立插件類: 與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,並將其傳遞到插件中被插件識別讀取而已。
步驟:
class ReleaseInfoExtension {
String versionCode
String versionName
String versionInfo
String fileName
ReleaseInfoExtension() {}
@Override
String toString() {
return "versionCode = ${versionCode} , versionName = ${versionName} ," +
" versionInfo = ${versionInfo} , fileName = ${fileName}"
}
}
複製代碼
class GradleStudyPlugin implements Plugin<Project> {
/**
* 插件引入時要執行的方法
* @param project 引入當前插件的project
*/
@Override
void apply(Project project) {
// 這樣就能夠在gradle腳本中,經過releaseInfo閉包來完成ReleaseInfoExtension的初始化。
project.extensions.create("releaseInfo", ReleaseInfoExtension)
}
}
複製代碼
apply plugin: 'com.android.gradle'
releaseInfo {
versionCode = '1.0.0'
versionName = '100'
versionInfo = '第一個app信息'
fileName = 'release.xml'
}
複製代碼
def versionCodeMsg = project.extensions.releaseInfo.versionCode
複製代碼
建立擴展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…
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是一個開源的、提供友好操做界面的持續集成(CI)工具,起源於Hudson(Hudson是商用的),主要用於持續、自動的構建/測試軟件項目、監控外部任務的運行(這個比較抽象,暫且寫上,不作解釋)。Jenkins用Java語言編寫,可在Tomcat等流行的servlet容器中運行,也可獨立運行。一般與版本管理工具(SCM)、構建工具結合使用。經常使用的版本控制工具備SVN、GIT,構建工具備Maven、Ant、Gradle。
具體學習請參考:Jenkins詳細教程
...
注:如有什麼地方闡述有誤,敬請指正。期待您的點贊哦!!!