Android中的Gradle之玩轉自定義功能

1、概述

經過上一節Android中的Gradle之配置及構建優化,咱們已經瞭解了Gradle的各個配置項的含義,知道了如何優化構建配置,但只會用別人提供好的,沒法按本身的意願實現功能。經過本章節,咱們將簡單介紹Groovy,瞭解Gradle中的Project與Task,引入gradle腳本,根據android plugin插件提供的功能自定義擴展,以及寫本身的Task及本身的gradle插件,相信看完以後能對gradle有進一步的瞭解。java

2、Groovy語法簡介

Apache Groovy is a powerful, optionally typed and dynamic language, with static-typing and static compilation capabilities, for the Java platform aimed at improving developer productivity thanks to a concise, familiar and easy to learn syntax. It integrates smoothly with any Java program, and immediately delivers to your application powerful features, including scripting capabilities, Domain-Specific Language authoring, runtime and compile-time meta-programming and functional programming.react

上面的意思大體以下:
Apache Groovy是一種功能強大,可選類型和動態語言,與靜態類型和靜態編譯功能,支持Java平臺,因爲其簡潔與易學性提升了開發人員的開發效率。它能夠與任何Java程序平滑集成,併爲您的應用程序提供強大的功能,包括腳本功能,域特定語言創做,運行時和編譯時元編程以及函數編程。
具體能夠專題進行學習Groovy語法。android

一、與java比較

  • Groovy徹底兼容java的語法,也就是說在Groovy能夠編寫java代碼並運行,最終編譯成java字節碼。
  • 句末的分號是可選的
  • 類、方法默認是public的
  • 編譯器自動添加getter/setter方法
  • 屬性可使用點號獲取
  • 方法若是有返回值,最後一個表達式的值即做爲返回,省略return。
  • ==等同於equals()
  • 沒有NullPointerException

二、Groovy高效特性

  • assert語句能夠在任何位置斷言
  • 弱類型變量
  • 調用方法若是有參數,能夠省略括號
  • 字符串的表示
  • 集合類api,如map、list的某些方法
  • 閉包

三、Groovy語法簡單演示

// 1 可選的類型定義
def version = 1

// 2 assert
assert version == 2

// 3 括號是可選的
println version

// 4 字符串
def s1 = 'Groovy'
def s2 = "version is ${version}"
def s3 = '''三個 分號 能夠 換行 '''
println s1 // Groovy
println s2 // version is 1
println s3   // 三個
                // 分號
                // 能夠
                // 換行

// 5 集合api
// list
def buildTools = ['ant','maven']
buildTools << 'gradle'
println buildTools.getClass() // class java.util.ArrayList
assert buildTools.size() == 3 // 沒有異常
// map
def buildYears = ['ant':2000,'maven':2004]
buildYears.gradle = 2009
println buildYears.ant  // 2000
println buildYears['gradle'] // 2009
println buildYears.getClass() // class java.util.LinkedHashMap

// 6 閉包
def c1 = {
    v ->
            println v
}
def method1(Closure closure){
    closure('param')
}
method1(c1) // param

 method1{ 
    c1 "hello"
}



複製代碼

3、Gradle中的基礎概念

一、Project

能夠說每個build.gradle都是一個Project對應項目中就是某一個module,直接在其中定義的屬性或者方法均可以使用project來調用。如def valueTest = 5可使用valueTest 或者project.valueTest來獲取valueTest的值。
根目錄的build.gradle也是一個Project,在其中定義ext{valueTest = 5},在module中的build.gradle中能夠直接使用rootProject.valueTest或者rootProjext.ext.valueTest來引用。這裏的ext是一個全局變量的意思。
編程

二、Task

Project又是由一個或者多個Task組成,Task存在着依賴關係,依賴關係又保證了任務的執行順序。好比咱們熟知的Task有clean,jar,assemble等。
api

三、Gradle構建聲明週期

Gradle的聲明週期分爲三段:
bash

  • 初始化階段:讀取根工程中 settings.gradle 中的 include 信息,決定有哪幾個工程加入構建,建立Project實例,如 include ':app',':example'
  • 配置階段:執行全部工程的 build.gradle 腳本,配置Project對象,建立、配置Task及相關信息。該階段會執行build.gradle中的命令,如直接寫在Project中的println命令
  • 運行階段:根據gradle命令傳遞過來的task名稱,執行相關依賴任務

4、Gradle腳本的引入

引入gradle腳本,能夠實現對gradle代碼的複用,減小維護成本。能夠經過apply from: 'other.gradle'來對other.gradle進行引入。平常開發中,咱們能夠經過以下幾點優化咱們的項目:
微信

一、將項目中的配置複用項提取出來

在gradle中有不少配置項,如版本號,版本名稱,最小支持SDK版本,是否使用混淆,依賴的第三方庫等。這些配置頗有可能在項目中複用,如需修改,可能會致使遺漏,將這些配置提取出來,供各個build.gradle使用。具體以下:
一、在根目錄新建config.gradle
閉包

ext {

    android = [
            compileSdkVersion: 27,
            minSdkVersion    : 16,
            targetSdkVersion : 27,
            versionCode      : 1,
            versionName      : "1.0.0",
            multiDexEnabled  : true
    ]

    version = [
            kotlin_version       : "1.2.50",
            support_version      : "27.1.1",
            constraint_version   : "1.1.3",
            junit_version        : "4.12",
            runner_version       : "1.0.2",
            espresso_core_version: "3.0.2"
    ]

    dependencies = [
            "androidJUnitRunner": "android.support.test.runner.AndroidJUnitRunner",
            "kotlin"            : "org.jetbrains.kotlin:kotlin-stdlib-jdk7:${version["kotlin_version"]}",
            "appcompat_v7"      : "com.android.support:appcompat-v7:${version["support_version"]}",
            "constraint_layout" : "com.android.support.constraint:constraint-layout:${version["constraint_version"]}",
            "junit"             : "junit:junit:${version["junit_version"]}",
            "runner"            : "com.android.support.test:runner:${version["runner_version"]}",
            "espresso_core"     : "com.android.support.test.espresso:espresso-core:${version["espresso_core_version"]}"
    ]
    
}
複製代碼

二、在根目錄build.gradle中增長apply from:"config.gradle"
三、修改module的gradle文件
app

android {
    compileSdkVersion rootProject.ext.android["compileSdkVersion"]
    defaultConfig {
        applicationId "net.loosash.learngradle"
        minSdkVersion rootProject.ext.android["minSdkVersion"]
        targetSdkVersion rootProject.ext.android["targetSdkVersion"]
        versionCode rootProject.ext.android["versionCode"]
        versionName rootProject.ext.android["versionName"]
        testInstrumentationRunner rootProject.ext.dependencies["androidJUnitRunner"]
    }
    ...
}
...
dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    implementation rootProject.ext.dependencies["kotlin"]
    implementation rootProject.ext.dependencies["appcompat_v7"]
    implementation rootProject.ext.dependencies["constraint_layout"]
    testImplementation rootProject.ext.dependencies["junit"]
    androidTestImplementation rootProject.ext.dependencies["runner"]
    androidTestImplementation rootProject.ext.dependencies["espresso_core"]
}
複製代碼

二、對於build.gradle中重複部分進一步提取

一、對於依賴工程或者組件化工程,建議將依賴module中的配置也提取出來。在根目錄新建default.gradle文件。
maven

apply plugin: 'com.android.library'
android {
    compileSdkVersion rootProject.ext.android["compileSdkVersion"]
    
    defaultConfig {
        minSdkVersion rootProject.ext.android["minSdkVersion"]
        targetSdkVersion rootProject.ext.android["targetSdkVersion"]
        versionCode rootProject.ext.android["versionCode"]
        versionName rootProject.ext.android["versionName"]
        testInstrumentationRunner rootProject.ext.dependencies["androidJUnitRunner"]
    }
    
    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
    }
}

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    // support
    implementation rootProject.ext.dependencies["appcompat_v7"]
    // test
    testImplementation rootProject.ext.dependencies["junit"]
    androidTestImplementation rootProject.ext.dependencies["runner"]
    androidTestImplementation rootProject.ext.dependencies["espresso_core"]
}

複製代碼

二、修改使用默認配置的module中的build.gradle文件

apply from:"../default.gradle"

android{
    // 寫入該模塊特定的配置
    resourcePrefix "example_" //給 Module 內的資源名增長前綴, 避免資源名衝突

}

dependencies {
    // 該模塊使用的依賴

}

複製代碼

三、對build.gradle文件先後作一個對比

修改後的好處在於,好比在工程中多處使用support包中的依賴,一次修改版本號便可對所有工程生效,下降了維護成本。

修改前
修改後
修改前
修改後
修改前
修改後

5、自定義Task任務

一、定義任務

最經常使用的寫法,執行./gradlew hello打印出hello world

task hello{
	println 'hello world'
}
複製代碼

在android studio建立項目後,會在根目錄的build.gradle中建立clean任務,就是刪除build文件夾下文件

task clean(type: Delete) {
    delete rootProject.buildDir
}
複製代碼

說明:Task建立的時候能夠經過 type: SomeType 指定Type,Type其實就是告訴Gradle,這個新建的Task對象會從哪一個基類Task派生。好比,Gradle自己提供了一些通用的Task,最多見的有Copy 任務。Copy是Gradle中的一個類。當咱們:task myTask(type:Copy)的時候,建立的Task就是一個Copy Task。相似的,有以下寫法:

task copyDocs(type: Copy) {
    from 'src/main/doc'
    into 'build/target/doc'
}
複製代碼

二、依賴關係

Gradle中Task存在着依賴關係,在執行過程當中會先執行所依賴的任務,再執行目標任務。

task hello {
    doLast {
        println 'Hello world!'
    }
}
Task intro(dependsOn: hello) {
    doLast {
        println "I'm Gradle"
    }
}
複製代碼

執行結果

> Task :hello
Hello world!

> Task :intro
I'm Gradle 複製代碼

三、分組和描述

task能夠增長分組和說明

task hello {
    group 'Custom Group 1'
    description 'This is the Hello Task'
    doLast {
        println 'Hello world!'
    }
}
task intro(dependsOn: hello) {
    group 'Custom Group 2'
    description 'This is the intro Task'
    doLast {
        println "I'm Gradle"
    }
}
複製代碼

輸入./gradlew tasks

Custom Group 1 tasks
--------------------
hello - This is the Hello Task

Custom Group 2 tasks
--------------------
intro - This is the intro Task
複製代碼

4.獲取已經定義的任務,並增長執行處理

在上面已有的Task hello中增長執行處理。

task hello {
    group 'Custom Group 1'
    description 'This is the Hello Task'
    doLast {
        println 'Hello world!'
    }
}
task intro(dependsOn: hello) {
    group 'Custom Group 2'
    description 'This is the intro Task'
    doLast {
        println "I'm Gradle"
    }
}
tasks.hello{
    doFirst{
        println "prepare to say"
    }
}

複製代碼

執行./gradlew intro獲得以下輸出

> Task :hello
prepare to say
Hello world!

> Task :intro
I'm Gradle 複製代碼

6、自定義構建功能

一、buildTypes

能夠利用這裏的屬性,針對debug版本和release版本對包名進行修改。具體使用以下:
1)對applicationId進行修改,使其能同時安裝debug版本和release版本

android {
    ...
    buildTypes {
        debug {
			// bebug版本包名爲xxx.xxx.xxx.debug
			applicationIdSuffix ".debug"
        }
        ...
    }
}
複製代碼

2)對release版本進行簽名配置

signingConfigs {
        release {
            keyAlias 'xxxx'
            keyPassword 'xxxxxx'
            storeFile file('your-keystore-path')
            storePassword 'xxxxxx'
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.release

        }
    }
複製代碼

二、productFlavors

能夠根據該屬性進行生成不一樣的APK版本,版本能夠攜帶不一樣的特性,最經常使用的就是多渠道打包、根據不一樣環境生成不一樣APK。

productFlavors{
		productA{
        	// 定義特定版本號
        	// applicationIdSuffix ".a"
			applicationId "xxx.xxx.xxx.a"
			// 定義特定版本名稱
			versionName "version-a-1.0"
			// 定義特定的BuildConfig
			buildConfigField("String","CUSTUMER_CONFIG","xaxaxaxa")
		}
		productB{
			applicationId "xxx.xxx.xxx.b"
			versionName "version-b-1.0"
			buildConfigField("String","CUSTUMER_CONFIG","xbxbxbxb")
		}
	}
	
dependencies{
	...
	// 特定版本依賴
	productACompile 'io.reactivex.rxjava2:rxjava:2.0.1'
	...
}

複製代碼

三、增長自定義處理

將生成的apk以自定義命名複製到自定義文件夾

applicationVariants.all { variant ->
        tasks.all {
            if ("assemble${variant.name.capitalize()}".equalsIgnoreCase(it.name)) {
                it.doLast {
                    copy {
                        rename { String fileName ->
                            println "------------${fileName}--------------"
                            fileName.replace(".apk", "-${defaultConfig.versionCode}.apk")
                        }
                        def destPath = file("/Users/solie_h/Desktop/abc/")
                        from variant.outputs.first().outputFile
                        into destPath
                    }
                }
            }
        }
    }
複製代碼

7、總結

到這裏,其實其實剛剛是使用Gradle的開始,咱們能夠操做Gradle完成一些本身的需求,但想對外提供Gradle插件還須要一些功夫,接下來還要繼續對android plugin源碼進行研讀,也找一些有Gradle插件的開源項目進行學習,如Replugin、Tinker,進一步提升本身對Gradle的認識。

關注微信公衆號,最新技術乾貨實時推送

相關文章
相關標籤/搜索