Android中的Gradle之玩轉自定義功能
通過上一節 ofollow,noindex">Android中的Gradle之配置及構建優化 ,我們已經瞭解了Gradle的各個配置項的含義,知道了如何優化構建配置,但只會用別人提供好的,無法按自己的意願實現功能。通過本章節,我們將簡單介紹Groovy,瞭解Gradle中的Project與Task,引入gradle指令碼,根據android plugin外掛提供的功能自定義擴充套件,以及寫自己的Task及自己的gradle外掛,相信看完之後能對gradle有進一步的瞭解。
二、Groovy語法簡介
Groovy其實和js、python、kotlin類似,弱型別、閉包等特性,又完全相容java,學習起來較為簡單。具體可以專題進行學習Groovy語法。
1、與java比較
- Groovy完全相容java的語法,也就是說在Groovy可以編寫java程式碼並執行,最終編譯成java位元組碼。
- 句末的分號是可選的
- 類、方法預設是public的
- 編譯器自動新增getter/setter方法
- 屬性可以使用點號獲取
- 方法如果有返回值,最後一個表示式的值即作為返回,省略return。
- ==等同於equals()
- 沒有NullPointerException
2、Groovy高效特性
- assert語句可以在任何位置斷言
- 弱型別變數
- 呼叫方法如果有引數,可以省略括號
- 字串的表示
- 集合類api,如map、list的某些方法
- 閉包
3、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" } 複製程式碼
三、Gradle中的基礎概念
1、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是一個全域性變數的意思。
2、Task
Project又是由一個或者多個Task組成,Task存在著依賴關係,依賴關係又保證了任務的執行順序。比如我們熟知的Task有clean,jar,assemble等。
3、Gradle構建宣告週期
Gradle的宣告週期分為三段:
- 初始化階段:讀取根工程中 settings.gradle 中的 include 資訊,決定有哪幾個工程加入構建,建立Project例項,如
include ':app',':example'
- 配置階段:執行所有工程的 build.gradle 指令碼,配置Project物件,建立、配置Task及相關資訊。該階段會執行build.gradle中的命令,如直接寫在Project中的println命令
- 執行階段:根據gradle命令傳遞過來的task名稱,執行相關依賴任務
四、Gradle指令碼的引入
引入gradle指令碼,可以實現對gradle程式碼的複用,減少維護成本。可以通過 apply from: 'other.gradle'
來對other.gradle進行引入。日常開發中,我們可以通過如下幾點優化我們的專案:
1、將專案中的配置複用項提取出來
在gradle中有很多配置項,如版本號,版本名稱,最小支援SDK版本,是否使用混淆,依賴的第三方庫等。這些配置很有可能在專案中複用,如需修改,可能會導致遺漏,將這些配置提取出來,供各個build.gradle使用。具體如下:
1、在根目錄新建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"]}" ] } 複製程式碼
2、在根目錄build.gradle中增加 apply from:"config.gradle"
3、修改module的gradle檔案
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"] } 複製程式碼
2、對於build.gradle中重複部分進一步提取
1、對於依賴工程或者元件化工程,建議將依賴module中的配置也提取出來。在根目錄新建default.gradle檔案。
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"] } 複製程式碼
2、修改使用預設配置的module中的build.gradle檔案
apply from:"../default.gradle" android{ // 寫入該模組特定的配置 resourcePrefix "example_" //給 Module 內的資源名增加字首, 避免資源名衝突 } dependencies { // 該模組使用的依賴 } 複製程式碼
3、對build.gradle檔案前後做一個對比
修改後的好處在於,比如在工程中多處使用support包中的依賴,一次修改版本號即可對全部工程生效,降低了維護成本。






五、自定義Task任務
1、定義任務
最常用的寫法,執行 ./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' } 複製程式碼
2、依賴關係
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 複製程式碼
3、分組和描述
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 複製程式碼
六、自定義構建功能
1、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 } } 複製程式碼
2、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' ... } 複製程式碼
3、增加自定義處理
將生成的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 } } } } } 複製程式碼
七、總結
到這裡,其實其實剛剛是使用Gradle的開始,我們可以操作Gradle完成一些自己的需求,但想對外提供Gradle外掛還需要一些功夫,接下來還要繼續對android plugin原始碼進行研讀,也找一些有Gradle外掛的開源專案進行學習,如Replugin、Tinker,進一步提高自己對Gradle的認識。