目錄
一、前言
二、buildTypes
三、buildType
1、buildTypes存在形式
2、buildTypes 中屬性的意義
3、buildTypes 中方法的意義
四、寫在最後
一、前言
繼 上一篇博客 分享了defaultConfig 中可配置參數的含義,今天我們來分享另一個我們也很熟悉的 buildTypes
。
二、buildTypes
buildTypes
也是存在於每個應用級模塊中的 android 下的,即如下所示,是每次構建完項目之後自動生成的結構。
android {
buildTypes{
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
buildTypes 可以配置我們需要的構建類型,例如我們常用到的 “測試類型” 和 “本地類型”,則可以使用如下配置
buildTypes{
// 發佈類型
release {
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
// 測試類型,給測試人員
debug {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
// 本地類型,和後端聯調使用
local {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
增加完這些配置後,我們可以在 android studio 看到多了 “debug” 和 “local” 兩個可以構建的類型,在 “點擊運行” 時,便會使用我們所選擇的構建類型。假設此時選擇的是 “debug” 類型,我們此時運行代碼,則是 debug 下的配置參數。
當然運行編譯成 apk 時,也不例外,各自使用的也是各自類型的配置。
而這裏所說的 “release”,“debug”,“local”,三個構建類型其實便是三個 buildType
,buildType
所能配置的參數便是我們今天要來捋清楚的。
三、buildType
buildType 官方文檔傳送門
1、buildType存在形式
從上一篇博客我們知道,每個配置最終會被映射爲一個類,或是一個屬性、或一個方法。buildType
也不例外,他會被映射爲 com.android.build.gradle.internal.dsl.BuildType
,繼承結構如下
我們重新看一下 DefaultConfig 的繼承結構,可以看到都會繼承到 BaseConfigImpl
類,說明兩者會有一定的交集。這也說明了爲什麼我們看 gradle 文件時,總感覺一個配置參數哪裏都能出現的情況(後面會進行更多的比較,來解除我們這種疑惑)
2、buildType 中屬性的意義
我們先來一個約定,避免使用的代碼過於冗長。
buildTypes{
debug {
// 我們下面的 “使用方法” 代碼都是基於這一塊,除非特殊說明。
}
}
2.1 applicationIdSuffix
- 類型:String
- 描述:會追加在 applicationId 字符串的後面,形成最終的包名。
- 值得一提:在 defaultConfig 中也有這個屬性,但一般不會使用。而會在 buildTypes 中使用,這樣可以讓包名不同,同時安裝多個類型的應用。例如我們上面所提到的 release包、debug包、local包,都可以同時存在而不會覆蓋,方便調試。
- 使用方法:
debug {
applicationIdSuffix '.debug'
}
2.2 consumerProguardFiles
- 類型:List< File >
- 描述:這個屬性只作用於我們創建的
library
中,包括我們以aar形式導入的 library ,或是直接創建的 library。它的作用是,負責該 library 被進行編譯時的混淆規則,我們在 主App 的模塊下則可以不用再管理各個 library 的混淆規則,會直接使用各個 library 的混淆規則文件。 - 值得一提:這個屬性 和 proguardFiles 的區別在於,consumerProguardFiles 會被 主App模塊 作爲混淆文件使用導入,而 proguardFiles 則不會。
- 使用方法:
debug {
consumerProguardFiles 'consumer-rules.pro'
......省略其他配置
}
// 因爲該屬性是一個 List<File> 類型,如果需要多個文件配置,則如下所示
debug {
consumerProguardFiles 'consumer-rules.pro','zincPower-rules.pro'
......省略其他配置
}
2.3 crunchPngs
- 類型:boolean
- 描述:是否對 PNG 圖片進行壓縮處理。設置爲true時,會對未進行最佳壓縮的PNG資源進行壓縮,但也會增加構建時間。
- 值得一提:默認情況下,release 類型是開啓的,debug 類型則爲關閉。
- 使用方法:
debug {
crunchPngs false
......省略其他配置
}
2.4 debuggable
- 類型:boolean
- 描述:是否可以對應用進行調試。
- 值得一提:release 的構建類型默認爲不可調試,即爲false。而 debug 默認爲true,既可以調試。所謂的 無法調試 即下圖的 “蟲子” 標記無法讓應用啓動,而正常的運行是可以的。
- 使用方法:
debug {
debuggable true
}
2.5 javaCompileOptions
- 類型:JavaCompileOptions
- 描述:配置編譯時 java 的一些參數,例如我們使用
annotationProcessor
時所需要的參數。 - 使用方法:
debug {
javaCompileOptions {
annotationProcessorOptions{
arguments = []
classNames ''
....
}
}
......省略其他配置
}
JavaCompileOptions 可以配置的具體參數,請進傳送門
2.6 jniDebuggable
- 類型:boolean
- 描述:是否可以對應用的 native 代碼進行調試
- 值得一提:release 的構建類型默認爲不可調試 native 代碼,即爲false。而 debug 默認爲true,既可以調試 native 代碼。無法對 native 代碼調試,指的是即使在native方法打了斷點,代碼也不會被 “停住”。
- 使用方法:
debug {
jniDebuggable true
}
2.7 manifestPlaceholders
- 類型:Map<String, Object>
- 描述:配置可以在
AndroidManifest.xml
中使用的參數。 - 使用方法:
這裏想配置我們應用的 logo 爲測試版本的logo,方便測試人員區分不同類型的包,則可以在 gradle 中使用下面這段
debug {
manifestPlaceholders = [APP_LOGO_ICON: "@mipmap/ic_logo"]
}
然後在 AndroidManifest.xml
中使用,使用 ${你配置的變量名}
// 在 application 中使用替換,還需要多添加 tools:replace 這一標籤,將我們需要替換的名稱寫上,例如這裏的 android:icon
<application
android:allowBackup="true"
android:icon="${APP_LOGO_ICON}"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme"
tools:replace="android:icon">
......
2.8 minifyEnabled
- 類型:boolean
- 描述:是否開啓混淆,代碼優化。true開始,false則關閉。
- 使用方法:
debug {
minifyEnabled true
}
2.9 proguardFiles
- 類型:List< File >
- 描述:配置混淆規則文件,只有
minifyEnabled
設置爲 true 的時候會使用這個參數,文件中需要申明哪些文件不被優化和混淆。 - 值得一提:因爲被混淆後端代碼,類名和方法名都會有所變化,所以進行反射會失敗,這是我們就需要進行申明他們不被混淆。(這裏只是舉了使用這個參數的一個場景,如果應用本來是正常的,開了混淆後,出現了莫名奇妙的bug,那就思考下是否因爲混淆導致了這一bug)
- 使用方法:
debug {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
2.10 multiDexEnabled
64K 引用限制問題官方文檔 傳送門
- 類型:Boolean
- 描述:是否開啓分包。因爲安卓中方法索引值爲兩個字節,四位十六進制的一個數值,即[0, 0xffff],所以最大方法數爲65536個。一旦超出了,就需要進行分包,所以我們就需要開啓這個參數。
- 使用方法:
android{
buildTypes {
debug {
multiDexEnabled true
...
}
}
}
// 添加依賴
dependencies {
// 如果使用的爲 AndroidX,則使用下面這個導入
// implementation 'androidx.multidex:multidex:2.0.1'
// 如果不使用 AndroidX,則使用下面這段
compile 'com.android.support:multidex:1.0.3'
}
有兩種開啓 MultiDex 方法:
// 第一種:讓你應用的 Application 繼承 MultiDexApplication。
public class MyApplication extends MultiDexApplication {
}
// 第二種:重寫應用的 Application 方法 attachBaseContext
public class MyApplication extends Application {
@Override
protected void attachBaseContext(Context base) {
super.attachBaseContext(base);
MultiDex.install(this);
}
}
最後別忘了在 AndroidManifest.xml
中使用我們在上面的 Application。
2.11 multiDexKeepFile
- 類型:File
- 描述:將我們需要的類打包進主包,即
classs.dex
。我們在第 2.10 小點,分享到使用了多包處理,有時我們需要將一些主要的類打包進主包,則可以使用該屬性。 - 使用方法:
debug {
multiDexKeepFile file('multidex-config.txt')
...
}
multidex-config.txt 中的書寫則如下,每一個文件則爲一行
com/example/MyClass.class
com/example/TestClass.class
2.12 multiDexKeepProguard
- 類型:File
- 描述:將我們需要的類打包進主包,和第 2.11 點的功能相同,區別在於寫法。
- 使用方法:
debug {
multiDexKeepFile file('multidex-config.pro')
...
}
multidex-config.pro 中的寫法如下
// 將會保留所有的在com.example package的類
-keep class com.example.** { *; }
2.13 name
- 類型:String
- 描述:構建類型的名字。
- 使用方法:
// debug 則會被賦值至 name 屬性,而且 name 是final 類型,無法在改動。
debug{
...
}
2.14 pseudoLocalesEnabled
- 類型:boolean
- 描述:這個屬性用於開啓僞語言,用於發現UI中潛在的可本地化問題。
- 使用方法:
第一步:我們在配置中開啓該屬性
debug{
pseudoLocalesEnabled true
}
第二步:安裝我們的應用
第三步:打開設置 -> 系統 -> 語言和輸入法 -> 語言 -> 添加語言
會在 “添加語言” 頁面中看到如下圖的選項,選擇圖中紅色框的內容,各自的不同在圖中也有標明,我們往下走看看具體不同的表現是什麼。
小盆友的測試機是 榮耀8,不同機子會有些許不同
選擇之後,選擇不同的語言會有不同效果,如圖所示
(1)左邊爲未開啓時的樣子;
(2)中間爲選擇了Pseudo locale(相當於en-XA);
(3)右邊爲選擇了Bidirection test locale(相當於en-XB);
2.15 renderscriptDebuggable
- 類型:boolean
- 描述:是否可以對 renderscript 代碼進行調試
- 使用方法:
debug {
renderscriptDebuggable true
}
2.16 renderscriptOptimLevel
- 類型:int
- 描述:設置渲染腳本等級
- 使用方法:
debug {
renderscriptOptimLevel 3
}
2.17 shrinkResources
shrinkResources 官方使用手冊 傳送門
- 類型:boolean
- 描述:是否壓縮資源,如果開啓,gradle在編譯時幫我們把沒有使用的資源給移除。
- 值得一提:shrinkResources 的開啓需要 minifyEnabled 也爲開啓狀態,否則無法運行。
- 使用方法:
debug {
shrinkResources false
}
開啓後編譯完會看到如下日誌
2.18 signingConfig
- 類型:SigningConfig
SigningConfig 的可配置參數 傳送門
- 描述:配置簽名配置。apk包能被安裝是需要被簽名的,我們直接運行的時候,是使用了系統默認的簽名證書,當我們要發佈release包時,則需要使用屬於個人或企業的簽名。
- 使用方法:
buildTypes {
release {
signingConfig {
// 不建議將簽名證書的信息寫在這裏,而應該是寫在 properties 文件中,將其引入使用
config {
keyAlias keystoreProperties['keyAlias']
keyPassword keystoreProperties['keyPassword']
storeFile file(keystoreProperties['storeFile'])
storePassword keystoreProperties['storePassword']
}
}
...
}
}
2.19 testCoverageEnabled
- 類型:boolean
- 描述:測試覆蓋率,可以獲取測試覆蓋率的報告
- 值得一提:記得將 minifyEnabled 置爲 false,否則報告也會是以混淆後的類名出現。
- 使用方法:
第一步:在 build.gradle 中開啓 testCoverageEnabled
debug {
testCoverageEnabled true
}
第二步:連接上一臺可用設備,因爲我們執行的測試代碼是要基於設備的,如果不連接,測試階段會報如下錯
第三步:在 android studio 的 終端(即 Terminal)中輸入下面這行命令,查看所有可運行任務。
小盆友是 mac 環境,所以 ./gradlew 開頭。windows 的環境則直接使用 gradlew 即可。
./gradlew app:tasks
輸入運行後,可以看到圖中高亮部分的可運行task。
我們以駝峯式(即第一個字母加上後面每個單詞的首字母且以大寫的形式)將其運行,代碼如下
// create + 你的構建類型名(這裏爲debug) + CoverReport
./gradlew app:cDCR
運行完之後,就是查看測試報告的階段了。我們進入coverage目錄,具體路徑如下圖所示。
最後用瀏覽器打開 index.html 文件,就可以看報告啦,如下圖所示,因爲小盆友的這個項目沒有寫測試代碼,所以覆蓋率爲0。
2.20 useProguard
- 類型:boolean
- 描述:是否開啓總是開啓 proguard
- 使用方法:
debug {
useProguard true
}
2.21 versionNameSuffix
- 類型:String
- 描述:追加在 versionName 之後
- 使用方法:
debug {
// 如果 versionName "1.0.0" ,則最終的版本名爲 1.0.0.test
versionNameSuffix ".test"
.....
}
2.22 zipAlignEnabled
- 類型:boolean
- 描述:是否開啓zipAlign。會對應用程序進行字節對齊,對齊後會減少了運行應用程序時消耗的內存。
- 使用方法:
debug {
zipAlignEnabled true
.....
}
2.23 matchingFallbacks
- 類型:List< String >
- 描述:用於處理 本地依賴library 間 BuildType 的衝突。
matchingFallbacks 還可以處理 維度風味的 問題,但我們這裏先不討論,後續的文章會介紹。
- 使用方法:
一個東西的出現是爲了處理至少一個問題,所以我們需要先了解下這個問題是怎麼產生的。
如上圖所示,我們的 主Module(一般是App),存在了三個構建類型,即我們一開始所提的 “release”、“debug”、“local” 三種。
此時我們在項目添加了一個 module 名字爲 library,並依賴進我們的app中。當我們構建 “release”、“debug” 兩種版本時,都不會有任何問題,因爲 library 默認也提供了 “release”、“debug” 兩種構建版本。
但是當我們使用 “local” 時,便會出問題了,因爲此時 gradle 的腳本也會默認選擇 “library” 的 “local”,但 “library” 中並沒有。會報下面這樣的錯誤
ERROR: Unable to resolve dependency for ':app@local/compileClasspath': Could not resolve project :lib:library.
Show Details
Affected Modules: app
此時就需要幫 “local” 從 “library” 中選擇一個可用的構建類型,則是通過 matchingFallbacks
進行設置。
鋪墊了這麼多,接下來就是怎麼使用了,在 app 的 build.gradle 中添加如下代碼即可。
local{
matchingFallbacks = ['zinc','release']
...
}
值得一提的是,我們可以配置多個,gradle在編譯的時候,會按照從頭開始匹配的原則,例如這裏的 “zinc” 會匹配不到,則匹配 “release”,因爲 “release” 匹配到了,則會進行使用,中斷後面的匹配。
3、buildTypes 中方法的意義
3.1 buildConfigField(type, name, value)
- 描述:我們可以在 BuildConfig 類中添加值,最終會在 BuildConfig 中添加如下一行代碼。
// 值的注意的是 value 的值是原樣放置,我們通過使用方法一節來了解
<type> <name> = <value>
- 使用方法:
debug {
// 可以通過 BuildConfig 進行獲取
buildConfigField('String', 'name', '"zinc"')
buildConfigField('int', 'age', '26')
.....
}
最終會生成如下圖的配置,我們可以通過下面代碼進行獲取
String name = BuildConfig.name;
int age = BuildConfig.age;
值的一提的是,我們設置 String 類型的參數時,需要加上 “” 雙引號(如例子中的name屬性)。切記!
3.2 consumerProguardFile(proguardFile)
- 描述:和上面分享的 2.2 小點的屬性 consumerProguardFiles 是一樣的作用。只是這裏只能設置一個 混淆文件。
- 使用方法:
debug {
consumerProguardFile('consumer-rules.pro')
}
3.3 consumerProguardFiles(proguardFiles)
- 描述:和上面分享的 2.2 小點的屬性 consumerProguardFiles 是一樣的作用,而且也是多個混淆文件。
- 使用方法:
debug {
consumerProguardFile('consumer-rules.pro', 'zincPower-rules.pro',.....)
}
3.4 externalNativeBuild(action)
- 類型:ExternalNativeBuildOptions
- 描述:這裏我們設置 ndk 編譯過程的一些參數。分爲 cmake 和 ndkBuild 兩個參數。
- 使用方法:
debug {
externalNativeBuild {
ndkBuild {
// Passes an optional argument to ndk-build.
arguments "NDK_MODULE_PATH+=../../third_party/modules"
}
// For ndk-build, instead use the ndkBuild block.
cmake {
// Passes optional arguments to CMake.
arguments "-DANDROID_ARM_NEON=TRUE", "-DANDROID_TOOLCHAIN=clang"
// Sets a flag to enable format macro constants for the C compiler.
cFlags "-D__STDC_FORMAT_MACROS"
// Sets optional flags for the C++ compiler.
cppFlags "-fexceptions", "-frtti"
// Specifies the library and executable targets from your CMake project
// that Gradle should build.
targets "libexample-one", "my-executible-demo"
}
}
}
3.5 initWith(that)
- 描述:會拷貝給定的 buildType(即參數的值)
- 使用方法:
buildTypes {
debug{
}
local{
// 會拷貝 debug 的配置
initWith debug{
// 在這裏進行我們自己的配置
}
}
}
3.6 proguardFile(proguardFile)
- 描述:添加混淆文件,和 2.9小點 的功能一致,只是傳入的是一個文件,這裏就不再贅述
- 使用方法:
debug {
proguardFile 'proguard-rules.pro'
}
3.7 proguardFiles(files)
- 描述:添加混淆文件,和 2.9小點 的功能一致,這裏就不再贅述
- 使用方法:
debug {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
3.8 setProguardFiles(proguardFileIterable)
- 描述:添加混淆文件,和 2.9小點 的功能一致,只是寫法稍微不同,這裏就不再贅述
- 使用方法:
debug {
proguardFiles = [getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro']
}
3.9 resValue(type, name, value)
- 描述:添加 value 資源
- 使用用法:
debug {
// 添加至 res/value,通過 R.string.age 獲取
resValue('string', 'age', '12 years old')
}
四、寫在最後
Gradle 的配置文件看起來好像挺亂,其實是因爲我們沒有進行整體的梳理。很多配置其實不是沒有用,而是我們沒有對他有一個整體的瞭解,正所謂 “無知最可怕”。
如果喜歡的話請給我一個贊,並關注我吧。文章中如有寫的不妥的地方,請評論區或加我微信與我討論吧,共同進步。
歡迎加我微信,進行更多的交流
如果覺得文章有很大的幫助,快來讚賞一次吧😄