Gradle打包工具入門

語言: CN / TW / HK

1、Gradle介紹

Gradle 是一種自動化構建語言,是一種 DSL 。目前是 Android 的預設構建工具,是一個程式設計框架

Gradle 是一個基於 Apache AntApache Maven 概念的專案自動化構建開源工具。它使用一種基於 Groovy 的特定領域語言(DSL)來宣告專案設定,也增加了基於 Kotlin 語言的 kotlin-based DSL ,拋棄了基於 XML 的各種繁瑣配置

特點:

  • 支援區域性構建和增量構建
  • 對多工程的構建支援很出色,工程依賴是 gradle 的第一公民
  • 是第一個構建整合工具,與 antmavenivy 有良好的相容相關性
  • gradle 的整體設計是以作為一種語言為導向的,而非成為一個嚴格死板的框架
  • 支援多方式依賴管理:包括從 maven 遠端倉庫、 nexus 私服、 ivy 倉庫以及本地檔案系統的 jars 或者 dirs
  • 輕鬆遷移: gradle 適用於任何結構的工程,你可以在同一個開發平臺平行構建原工程和 gradle 工程。通常要求寫相關測試,以保證開發的外掛的相似性,這種遷移可以減少破壞性,儘可能的可靠。這也是重構的最佳實踐

2、Gradle配置分析

2.1 根目錄配置

  • settings.gradle

在程式碼編譯時最先找到這個檔案

apply from: 'allconfig.gradle'
include: 'app'  // 包含的工程模組
if(buildType==1){
    include ':mylibrary2'
}else if(buildType==2){
    include ':mylibrary'
}
//在這裡寫一個指令碼,讓編譯速度更快
rootProject.name = 'gradledemo'  // 工程名
  • build.gradle
// 根目錄的構建指令碼
buildscript {
    // 指定了倉庫
    repositories {
        maven {  // 加速地址要放在最上面,從上往下找
            url 'http://maven.aliyun.com/nexus/content/groups/public/'
        }
        google()
        jcenter()
    }
    dependencies {  // 配置外掛
        // gradle 外掛版本
        classpath "com.android.tools.build:gradle:4.0.1"
    }
}

allprojects {
    // 專案本身需要的依賴,配置所有的Module公共依賴
    repositories {
        maven {
            url 'http://maven.aliyun.com/nexus/content/groups/public/'
        }
        google()
        jcenter()
    }
}

// 任務
task Clean(type: Delete) {
    delete rootProject.buildDir // 清理每次編譯生成的檔案
}

2.2 應用目錄配置

  • build.gradle
// 配置當前Module的屬性
// 如果宣告的是com.android.library  表示是一個依賴庫
// 如果宣告的是com.android.plugin   表示是一個外掛
// 如果宣告的是com.android.application   表示是一個app
apply plugin: 'com.android.application'
// 類似引入包一樣,引入外部的gradle配置檔案
apply from: 'config.gradle'

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.2"

    defaultConfig {
        applicationId "com.mn.gradledemo"
        minSdkVersion 16
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndridJunitRunner"
    }

    dependencies {
        implementation fileTree(dir: "libs", include: ["*.jar"])
        implementation 'androidx.appcompat:appcompat:1.2.0'
        implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
        testImplementation 'junit:junit:4.12'
        androidtestInstrumentation 'androidx.test.ext:junit:1.1.2'
        androidtestInstrumentation 'androidx.test.espresso:espresso-core:3.3.0'
    }
}

// 只要聲明瞭一個任務,不用呼叫就會執行
task stringText{
    // 使用def宣告關鍵字
    def str1 = "shuanyinhao";
    def str2 = 'danyinhan';
    println("${str1}---${str2}")
}
  • config.gradle
// ext就表示額外的屬性宣告
ext{
    server = "prod"
    dataSource = "0"
}

3、Gradle基礎語法

3.1 常規語法

// list
task list{
    def list=[1,2,3,4,5,6]
    def weekList = ['one','two','three']
    println(list[0])
    println(weekList[0])
    for(int i in 1..10){
        println i
    }
    // 這裡的it就表示每一個元素, it是一個關鍵字,表示它自己
    weekList.each {
        println it
    }
}

// map
task map{
    def map:['name':'jack','age':19]
    println map['name']
    map.each {
        println "key:${it.key},value:${it.value}"
    }
    println(methodA(2,3)) // 5
}

// 在gradle語法當中,定義一個方法
// 如果在沒有return的情況下,函式預設會返回最後一行非空的值
def methodA(int a,int b){
    a+b
}

// 怎樣定義一個物件
task javaBeanTask{
    Student student = new Student()
    student.name = "Lily"
    student.age = 19
    println student.name + "---${student.age}"
    println student.getName() + "---${student.getAge()}"
}

class Student{
    String name
    int age

    String getName(){
        return name
    }

    void setName(String name){
        this.name = name
    }

    int getAge(){
        return age
    }

    void setAge(int age){
        this.age = age
    }
}

3.2 閉包和it關鍵字

Groovy 中的閉包是一個開放,匿名的程式碼塊,可以接受引數,返回值並賦值給變數

閉包,是一個程式碼塊,或可以理解成一個匿名函式,在外部方法呼叫時,可以將其作為方法的實參傳遞給方法的形參,並在方法內部回撥此匿名函式,且回撥此匿名函式時可以傳遞實參給到匿名函式的內部去接收,並執行此匿名函式

同時,此程式碼塊或匿名函式也可以賦值給一個變數,使其具有自執行的能力,且最後一行的執行語句作為匿名函式的返回

// 閉包,自定義閉包
def mEach(closure){
    for(int i in 1..5){
        closure(i)
    }
}

def mEachWithParams(closure){
    def map = ['name':'groovy','age':10]
    map.each{
        closure(it.key,it.value)
    }
}

// 呼叫閉包
task closureTask{
    // 回撥一個引數的時候,it就是指這個引數,就能用it,多個就不行了
    mEach({
        println it
        // a->println a
    })
    mEachWithParams{
        m,n—>println "${m} is ${n}"
    }
}

4、環境區分

主要目的是不需要修改程式碼就能區分測試環境和生產環境

例如有這樣的程式碼目錄(不同環境的配置檔案)

app/src/main/filters/debug/config.properties
app/src/main/filters/release/config.properties

通過讀取檔案流實現按不同環境區分

  • build.gradle
// 配置當前Module的屬性
// 如果宣告的是com.android.library  表示是一個依賴庫
// 如果宣告的是com.android.plugin   表示是一個外掛
// 如果宣告的是com.android.application   表示是一個app
apply plugin: 'com.android.application'
// 類似引入包一樣,引入外部的gradle配置檔案
apply from: 'config.gradle'

android {
...

    // 構建型別
    buildTypes{
        // 測試環境
        debug{
            // 引數: 宣告的型別、名字、屬性值
            buildConfigField 'String','SERVER2',getServer2('debug')
        }
        release{
            buildConfigField 'String','SERVER2',getServer2('release')
        }
    }
}

// 讀取檔案流,str代表debug還是release
def getServer2(String str){
    def SERVER2
    Properties properties = new Properties();
    // 相對路徑
    def proFile = file("src/main/filters/"+str+"/config.properties")
    if(proFile.canRead()){
        properties.load(new FileInputStream(proFile))
        if(properties!=null){
            SERVER2 = properties['SERVER2']
        }
    }
    return SERVER2
}

5、多渠道打包

多渠道打包常用於安卓 app ,例如統計不同渠道的資料(投放到多個應用市場)

5.1 核心邏輯

主要核心實現如下

apply plugin: 'com.android.application'
apply from: 'config.gradle'

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.2"

    defaultConfig {
        applicationId "com.mn.gradledemo"
        minSdkVersion 16
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"
        testInstrumentationRunner "androidx.test.runner.AndridJunitRunner"
        // 多渠道打包
        flavorDimensions "versionCode"
    }
...

    // 構建型別
    buildTypes{
        // 測試環境
        debug{
            // 引數: 宣告的型別、名字、屬性值
            buildConfigField 'String','SERVER2',getServer2('debug')
            android.applicationVariants.all{
                variant ->
                    variant.outputs.all{
                        def fileName = "${getCurrentTime()}_V{defaultConfig.versionName}_debug.apk"
                        outputFileName = fileName
                    }
            }
        }
        release{
            buildConfigField 'String','SERVER2',getServer2('release')
        }
    }

    // 多渠道打包
    productFlavors{
        xiaomi{
            buildConfigField 'String','PLATE_FORM',"\"xiaomi\""
            manifestPlaceholders = [UMENG_CHANNEL_VALUE: "xiaomi"]
        }
        yinyongbao{
            buildConfigField 'String','PLATE_FORM',"\"yingyongbao\""
            manifestPlaceholders = [UMENG_CHANNEL_VALUE: "yingyongbao"]
        }
    }
}

static def getCurrentTime(){
    return new Date().format("yyyy-MM-dd",timeZone.getTimeZone("UTC"))
}

5.2 一鍵化配置多渠道打包

// 一鍵化多渠道打包
    productFlavors{
        xiaomi{}
        yingyongbao{}
    }
    productFlavors.all{
        flavor ->
            flavor.manifestPlaceholders = [UMENG_CHANNEL_VALUE: name]
            buildConfigField 'String','PLATE_FORM',"\"${name}\""
    }

6、gradle打包加速

maven 打包一樣, gradle 會在編譯時的使用者家目錄,例如 /root/.gradle 目錄下生成一個快取目錄,除此之外,在應用的目錄下也會生成一個 build 目錄,這個目錄下也有相應的 build cache

可以在全域性配置 gradle ,使其拉取外掛時走國內的源

配置檔案為 /root/.gradle/init.gradle ,內容如下

allprojects {
    repositories {
        mavenLocal()
		maven { name "Aliyun" ; url "http://maven.aliyun.com/repository/public" }
		maven { name "Bstek" ; url "http://nexus.bsdn.org/content/groups/public/" }
    }

	buildscript { 
		repositories { 
			maven { name "Aliyun" ; url 'http://maven.aliyun.com/repository/public' }
			maven { name "Bstek" ; url 'http://nexus.bsdn.org/content/groups/public/' }
			maven { name "M2" ; url 'http://plugins.gradle.org/m2/' }
		}
	}
}