Gradle自動化項目構建之Gradle學習及實戰

繼上一篇Gradle自動化項目構建之快速掌握Groovy,咱們繼續深刻Gradle自動化項目構建技術的學習。html

Gradle概念

什麼是gradle wrapper?

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
複製代碼

Gradle執行流程

  1. 初始化階段:執行settings.gradle腳本,解析整個工程中全部Project,構建全部Project對應的project對象。
  2. 配置階段:解析全部project對象中的task對象,構建好全部task的拓撲圖
  3. 執行階段:執行具體的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產生效應。

附一張不知名大佬的執行流程和聲明週期圖示:android

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

  1. 在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
       ...
     }
    複製代碼
  2. 在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()
       ...
     }
    複製代碼
    1. hasProperty('xxx'):判斷是否有在gradle.properties文件定義xxx屬性。
    2. 在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')
}
複製代碼
  1. implementation和api: 編譯依賴包並將依賴包中的類打包進apk。
  2. provided: 只提供編譯支持,但打包時依賴包中的類不會寫入apk。
    1. 依賴包只在編譯期起做用。(如:tinker的tinker-android-anno只用於在編譯期生成Application,並不須要把該庫中類打包進apk,這樣能夠減少apk包體積)
    2. 被依賴的工程中已經有了相同版本的第三方庫,爲了不重複引用,可使用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,如:增長、查找。api

  1. 定義(建立)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
  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

    • 注1:給Task分組以後,該task會被放到指定組中,方便歸類查找。(默認被分組到other中)
    • 注2:給Task添加描述,至關於給方法添加註釋。

Task的執行詳情

Gradle的執行階段執行的都是Task,即只有Task可在執行階段執行。

  1. 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}"
        }
    }
    複製代碼
  2. 總結
    1. Task閉包中直接編寫的代碼,會在配置階段執行。能夠經過doFirst、doLast塊將代碼邏輯放到執行階段中執行。
    2. doFirst、doLast能夠指定多個。
    3. 外部指定的doFirst、doLast會比內部指定的先執行。
    4. doFirst、doLast能夠對gradle中提供的已有的task進行擴展。

Task的執行順序

  1. Task執行順序指定的三種方式:
    1. dependsOn強依賴方式
    2. 經過Task輸入輸出指定(與第1種等效)
    3. 經過API指定執行順序
  2. 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沒有關係,它們的執行順序是隨機的。
  3. Task的輸入輸出 流程:Task Inputs --> Task One ——> Task Outputs --> 經過輸入輸出關聯Task間的關閉 --> Task Inputs --> Task Two ——> Task Outputs --> .....
    1. 流程分析:
      1. inputs和outputs是Task的屬性。
      2. inputs能夠是任意數據類型對象,而outputs只能是文件(或文件夾)。
      3. TaskA的outputs能夠做爲TaskB的inputs。
    2. 代碼實戰
      // 例子:將每一個版本信息,保存到指定的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文件了。
  4. Task API指定順序
    • mustRunAfter : 強行指定在某個或某些task執行以後才執行。
    • shouldRunAfter : 與mustRunAfter同樣,但不強制。
    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順序執行的。
  5. 掛接到構建生命週期
    1. 例子: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()
          }
      }
      複製代碼
    2. 例子:Tinker將自定義的manifestTask插入到了gradle腳本中processManifest與processResources這兩個任務之間
      TinkerManifestTask manifestTask = project.tasks.create("tinkerProcess${variantName}Manifest", TinkerManifestTask)
      ...
      manifestTask.mustRunAfter variantOutput.processManifest
      variantOutput.processResources.dependsOn manifestTask
      複製代碼
  6. Task類型
    1. Gradle DSL Version 5.1
    2. 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

  1. 建立插件工程

    • 在工程目錄下建立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文件中輸入以下腳本:
      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
  2. 建立插件類: 與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
        }
    }
    複製代碼
  3. 指定插件入口: 在編寫完插件類的邏輯以後,須要在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
    複製代碼
  4. 使用自定義插件: 打開app工程的build.gradle,應用上面的自定義gradle插件,並Async。

    apply plugin: 'com.android.application'
    apply plugin: 'com.android.gradle'
    
    android {
      ...
    }
    複製代碼

    在Terminal中能夠看到,在gradle的配置階段,就輸出了前面自定義插件的apply方法中的日誌。

  5. 建立擴展屬性: 插件每每會在gradle腳本中進行參數配置,如在android{}中,能夠配置compileSdkVersion等參數,其實本質上,就是在gradle腳本中使用閉包方式建立了一個javaBean,並將其傳遞到插件中被插件識別讀取而已。

    步驟:

    1. 建立一個實體類,聲明成員變量,用於接收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}"
          }
      }
      複製代碼
    2. 在自定義插件中,對當前project進行擴展。
      class GradleStudyPlugin implements Plugin<Project> {
      
          /**
           * 插件引入時要執行的方法
           * @param project 引入當前插件的project
           */
          @Override
          void apply(Project project) {
              // 這樣就能夠在gradle腳本中,經過releaseInfo閉包來完成ReleaseInfoExtension的初始化。
              project.extensions.create("releaseInfo", ReleaseInfoExtension)
          }
      }
      複製代碼
    3. 打開在app工程的build.gradle,經過擴展key值命名閉包的方式,就能夠配置指定參數了。
      apply plugin: 'com.android.gradle'
      
      releaseInfo {
          versionCode = '1.0.0'
          versionName = '100'
          versionInfo = '第一個app信息'
          fileName = 'release.xml'
      }
      複製代碼
    4. 接收參數
      def versionCodeMsg = project.extensions.releaseInfo.versionCode
      複製代碼
  6. 建立擴展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倉庫便可

  7. Demo請參考:github.com/Endless5F/J…

android插件對gradle擴展

  1. 譯者序 | Gradle Android插件用戶指南翻譯
  2. Manipulation tasks(操做task) | Gradle Android插件用戶指南翻譯
  3. 自定義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詳細教程

參考連接

www.bilibili.com/video/av415…

www.jianshu.com/p/498ae3fab…

www.jianshu.com/u/f9de25923…

...

注:如有什麼地方闡述有誤,敬請指正。期待您的點贊哦!!!

相關文章
相關標籤/搜索