前言
最近遇到了問題,大概是 APPT2 ERROR
錯誤,這個錯誤很常見,說的是 .9圖片
有問題,但是網上的回答都非常的零散和不夠系統。編譯的時候從 LOG終端
中也看不了太多信息。網上的建議是加編譯參數 --stacktrace --debug
,所以就想著把 Gradle 構建系統詳細了解下。
以下說的大多是學習總結,一些概念可能描述的不準確,更多信息請參考文中和文末的資料鏈接。
問題
- Android Studio, Gradle, Groovy 的關系
- Android Studio 生成的項目中
build.gradle
,settings.gradle
,gradle.properties
,gradle-wrapper.properties
這些文件的作用 - 如何構建不同渠道的多個包
- 如何構建服務器地址不同的包,甚至邏輯不同的版本,而不是新開分支
回答
Android Studio, Gradle, Groovy 的關系
-
Groovy
是基于JVM的一門語言,類似于其他基于JVM的語言一樣,如JAVA
,KOTLIN
等,最終生成JAVA字節碼運行在JVM虛擬機上。 我們從 Groovy官網 中下載安裝,并了解語言特性
。println ("Groovy Demo") def num1 = 6 num2 = 3 ppp "你好","Groovy" println "$num1 的平方是 ${sqrt(num1)}" println "$num1 + $num2 = ${ add(num1,num2)}" println "平方求和 ${sqrtSum(num1, num2, {var1, var2->var1 + var2})}" def ppp(m1, m2) { println "$m1 $m2" } def sqrtSum(var1, var2, action) { return sqrt(action(var2, var2) ) } def add(x, y) { x + y } def sqrt(x) { x * x } def mul(var1, var2) { return var1 - var2 } def div = { int var1, int var2-> var1 / var2 }
Groovy
了解個大概,有個概念差不多了。具體的可以參考官網。 -
Android Studio 編譯時就是把整個構造任務委托給
Gradle
來處理,可以構建Android項目還有Ant
,Buck
等。Gradle
可以說是腳本,或者是語言,又或者是一個平臺。Groovy
基于JVM,Gradle
基于Groovy
。Gradle
可以實現自動化打包,自動化測試,項目依賴管理等功能。Gradle
基于約定優先配置
原則,能夠非常智能的知道該如果加載庫,編譯源碼,加載資源,以及生成目標產物的最終位置。而配置的各種的插件,就是定義了一系列的規則。比如,java插件,android插件。apply plugin: 'com.android.application'
Gradle
編譯的時候是執行一系列的task,可以使用下列命令查看所有的taskgradle task --all
常用的有
assemble
,assembleDebug
,assembleRelease
,build
,clean
。
下面的代碼直觀感受下,如何編寫build.gradle
, 以及如何執行的。我們先新建文件夾GradleDemo
,然后新建文件build.gradle
, 基本結構就好了。在build.gradle
寫上下面的代碼:println "Gradle demo" task sayHello { println "hello from task 'sayHello'" }
如果我們執行
gradle task --all
將會看到sayHello
這個task
, 我們執行下gradle sayHello
將會看到如下輸出
Gradle demo
hello from task 'sayHello'
:sayHello UP-TO-DATE
BUILD SUCCESSFUL
Total time: 2.401 secs同理,我們執行
gradle -q assemble
的時候也就是執行plugin
事先定義的復雜的task
assemble
進行構建。 顯然加載不同的插件,Gradle
可以構建java
,ANDROID
,C/C++
等不同的項目。
Android Studio 生成的項目中 build.gradle
, settings.gradle
, gradle.properties
, gradle-wrapper.properties
這些文件的作用
-
我們先看
gradle-wrapper.properties
文件, 位于project/app/gradle/wrapper
中,這個是用于gradle
版本管理的,我們的不同項目現在都可以方便的指定gradle版本
。distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https://services.gradle.org/distributions/gradle-3.3-all.zip如果在指定的目錄中存在
3.3
的版本,就直接使用,如果不存在就去指定的服務器中下載。如果是下載的gradle
,可以使用gradle wrapper
命令來生成wrapper
,它將會生成.gradle
,gradle
,gradlew.bat
,gradlew
等文件和文件夾。現在我們可以使用gradlew.bat --version
查看下運行環境。它將顯示
gradle
,groovy
,jvm
等使用版本。 -
我們接著看
gradle.properties
文件,顧名思義這個是gradle
配置屬性值的地方。比如jdk
(org.gradle.java.home
)位置,編譯參數(org.gradle.jvmargs)
等。具體可以查看 官網說明 build_environment。另外一種使用情況是定義一下統一的配置變量,比如compileSdkVersion
,buildToolsVersion
,supportLibrary
等變量。舉個例子:
在gradle.properties
中添加如下代碼:greetingWord=Hi, groovy
在
build.gradle
中添加一個task
,task sayHi { println rootProject.buildDir println "sayHi word from file $greetingWord" }
它將會打印出 Hi, groovy ,這是我們定義的值。
我們接著看
settings.gradle
文件,這個文件是針對多項目的配置文件,比如這個app
項目引入來了其它庫,對于gradle
來說,就是引入了project
, 這個文件使用include
函數來說明了構建項目的mul-project
。-
我們最后看
app
模塊中的build.gradle
文件,這個文件是單獨對該 模塊/PROJECT 進行配置的。apply plugin: 'com.android.application' //加載android 應用插件 android { //dsl android 配置部分 compileSdkVersion 25 //這兩行必須要有 buildToolsVersion "26.0.2" //這兩行必須要有 defaultConfig { applicationId "com.bbg.gradledemo" minSdkVersion 19 targetSdkVersion 25 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } } dependencies { //依賴 compile fileTree(dir: 'libs', include: ['*.jar']) androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', { exclude group: 'com.android.support', module: 'support-annotations' }) compile 'com.android.support:appcompat-v7:25.3.1' compile 'com.android.support.constraint:constraint-layout:1.0.2' testCompile 'junit:junit:4.12' }
里面的代碼塊是什么意思,更具體的可以參考 官網說明。
如何構建不同渠道的多個包
這個問題和接下來的問題,將涉及到兩個主要概念,build types
和 product flavors
,這兩個概念弄明白了,我們的問題也就差不多解決了。 build types
是什么? build types
是站在程序員的角度來說的,比如 debug
, release
版本,它并不為終端用戶所感知。而 product flavors
則可以理解為面向終端用戶的,比如 免費版
, 付費版
,又比如各大應用市場的不同版本。
我們看個復雜的實例,構建不同渠道的免費和收費的DEBUG、RELEASE兩種版本。這個例子分開來看的話,一共是3個階段。第一階段,構建 release
和 debug
版本;第二階段,構建 channel1
和 channel2
兩個不同渠道的版本;第三階段,構建 free
和 pay
收費模式版本。組合起來的話,一共 2*2*2
最終會構建成 8
版本。
buildTypes
構建apk類型,即 debug
和 release
; productFlavors
構建用戶感知的app變體,即不同的app; flavorDimensions
構建多維度的 flavor
,可以理解為多維度的用戶使用情形。比如該例子說的是,既要區分不同渠道,又要對不同渠道做 free
和 pay
兩種產品的區分。具體的寫法參考下面的 build.gradle
中的代碼。下面的代碼也展示如何在 AndroidManifest.xml
中插入我們配置的值,以及如何在 build.gradle
中配置運行時變量。 在項目代碼中還展示了如何讀取 meta-data
的值和在 build.gradle
中配置的值。
當我們 sync
我們的項目時,IDE中的 Build Variants
選項卡也會出現對應的各種APP變體。更官方的 Build Variants
可以參考這里,而 build.gradle
中 block代碼
塊中的屬性值或方法具體是什么意思或該怎么用,可以參考這里。
下面是我們編譯出來的共 8
個APP變體。 8
種不同的 flavors
。
app-channe2-free-debug.apk
app-channe2-free-release.apk
app-channe2-pay-debug.apk
app-channe2-pay-release.apk
app-channel-free-debug.apk
app-channel-free-release.apk
app-channel-pay-debug.apk
app-channel-pay-release.apk
//默認配置
defaultConfig {
//應用包名,注意和 AndroidManifest.xml 中的 packageName 區別,
//后者影響資源的R類的生成; 前者是唯一包名
applicationId "com.bbg.gradledemo"
minSdkVersion 16
targetSdkVersion 25
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
//manifest 占位符, map類型
//buildConfigField 生成 BuildConfig 類的靜態值,可以代碼訪問
//
manifestPlaceholders = ["KEY_TYPE": "KEY-default"]
buildConfigField("String", "var1", "\"var-default\"")
}
//簽名配置
signingConfigs {
config {
keyAlias 'key0'
keyPassword '123456'
storeFile file("$rootProject.rootDir/keystore.jks")
storePassword '123456'
}
}
//build types
buildTypes {
release {
minifyEnabled true
debuggable true
shrinkResources true
zipAlignEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.config
applicationIdSuffix ".release"
}
debug {
minifyEnabled true
debuggable true
shrinkResources true
zipAlignEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.config
applicationIdSuffix ".debug"
}
}
//多維度flavor解決方法,前后順序影響生成的 variants 類型
//然后在product flavors 指定 deminsion
flavorDimensions "channel", "mode"
productFlavors {
channel {
dimension "channel"
manifestPlaceholders = ["KEY_TYPE": "KEY-channel"]
buildConfigField("String", "var1", "\"var-channel\"")
}
channe2 {
dimension "channel"
manifestPlaceholders = ["KEY_TYPE": "KEY-channe2"]
buildConfigField("String", "var1", "\"var-channe2\"")
}
free {
dimension "mode"
buildConfigField("String", "mode", "\"mode-free\"")
}
pay {
dimension "mode"
buildConfigField("String", "mode", "\"mode-pay\"")
}
}
為不同的Flavor變體加入不同的資源和邏輯
從上面中的回答,我們可以知道,這種方式已經可以滿足我們提出的要求了。一些不同的 Build Variants
, 可以根據變量值,在代碼中進行設置,進行不同的邏輯處理。 假如我們不同的 Variants
需要不一樣的啟動圖標,甚至邏輯部分不同,有沒有另外的解決方案。答案是有的,當我們在 build.gradle
中配置了 buildTyps
和 productFlavors
的時候,Android Studio
就邏輯關系將每個源代碼和資源分組為 源集
Android Studio 按邏輯關系將每個模塊的源代碼和資源分組為源集。模塊的 main/ 源集包括其所有構建變體共用的代碼和資源。其他源集目錄為可選項,在您配置新的構建變體時,Android Studio 不會自動為您創建這些目錄。不過,創建類似于 main/ 的源集有助于讓 Gradle 只應在構建特定應用版本時使用的文件和資源井然有序
如果不同的 源集
包含同一文件的不同版本,Gradle
將按以下優先順序決定使用哪一個:
構建變體 > 構建類型 > 產品風味 > 主源集 > 庫依賴項
我們實踐下,我們把上面的 channe2
變體的應用圖標修改,把第一次進入時候的是否 pay
的邏輯改成 free
。具體的代碼請參考Github。我們創建文件時,可以借助 IDE
來選擇不同的 源集
。這樣我們的 app
目錄結構大概是這樣:
```
app
|
└─src
├─androidTest
│ └─java
│ └─com
│ └─bbg
│ └─gradledemo
├─channe2
│ └─res
│ ├─mipmap-xhdpi
│ └─values
├─free
│ ├─java
│ │ └─com
│ │ └─bbg
│ │ └─gradledemo
│ └─res
│ └─values
├─main
│ ├─java
│ │ └─com
│ │ └─bbg
│ │ └─gradledemo
│ └─res
│ ├─drawable
│ ├─layout
│ ├─mipmap-hdpi
│ ├─mipmap-mdpi
│ ├─mipmap-xhdpi
│ ├─mipmap-xxhdpi
│ ├─mipmap-xxxhdpi
│ └─values
├─pay
│ ├─java
│ │ └─gradledemo
│ └─res
│ └─values
└─test
└─java
└─com
└─bbg
└─gradledemo
```
我們在 chane2
變體中引入 ic_launcher
,在 pay
和 free
變體中處理是否收費邏輯。編譯的時候,可以把我們想要的都一起編譯出來,實現自動化,簡直完美。顯然, gradle
知道該如何合并。我們可以參考這里,了解是 gradle
是如何處理合并的。
結尾
到這里,我們順利的解決了提出的問題。發現 GRADLE
構建系統太出色了。這么好的工具,當然要用起來。期間查閱學習了較多資料,寫成此文,希望對讀者有幫助。文中有較多參考鏈接可以擴展查閱,最后末尾附上一些相關的參考補充資料。
如果文章對你有幫助,請點小心心或贊賞支持。歡迎留言交流。
Gradle 完整指南(Android)
深入理解Android(一):Gradle詳解
構建配置
Groovy File IO Document
gradle 編譯不過,Build可以問題解決方法
gradle實戰
Android SDK構建視頻
Android Gradle
Build Type, Flavour and Build Variant