一、概述
通過上一節Android中的Gradle之配置及構建優化,我們已經瞭解了Gradle的各個配置項的含義,知道了如何優化構建配置,但只會用別人提供好的,無法按自己的意願實現功能。通過本章節,我們將簡單介紹Groovy,瞭解Gradle中的Project與Task,引入gradle指令碼,根據android plugin外掛提供的功能自定義擴充套件,以及寫自己的Task及自己的gradle外掛,相信看完之後能對gradle有進一步的瞭解。
二、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.
上面的意思大致如下:
Apache Groovy是一種功能強大,可選型別和動態語言,與靜態型別和靜態編譯功能,支援Java平臺,由於其簡潔與易學性提高了開發人員的開發效率。它可以與任何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的認識。