如果本文幫助到你,本人不勝榮幸,如果浪費了你的時間,本人深感抱歉。 希望用最簡單的大白話來幫助那些像我一樣的人。如果有什麼錯誤,請一定指出,以免誤導大家、也誤導我。 本文來自:www.jianshu.com/users/320f9… 感謝您的關注。
最近有個需求一次要打包9個型別的App,而且常量和String.xml都有變數。雖然之前也是一直存在變數,但是每次也僅僅只打包一個。這讓我每次改變數,打包9個。要是以後每次都打包9次,我得瘋了。 根據之前的瞭解,gradle 應該是可以解決這個問題的。所以就仔細研究了一番。
先放一個完整的 多渠道/多環境 打包的配置,然後再來講解。
實現了:
- 不同環境,不同包名;
- 不同環境,修改不同的 string.xml 資原始檔;
- 不同環境,修改指定的常量;
- 不同環境,修改 AndroidManifest.xml 裡渠道變數;
- 不同環境,引用不同的 module。
先放一個完整的配置,可以參考:
apply plugin: 'com.android.application'
android {
compileSdkVersion 22
buildToolsVersion '22.0.1'
// 簽名檔案
signingConfigs {
config {
keyAlias 'lyl'
keyPassword '123456'
storeFile file('../lyl.jks')
storePassword '123456'
}
}
// 預設配置
defaultConfig {
//applicationId "com.lyl.app"
minSdkVersion 16
targetSdkVersion 22
versionCode 1
versionName "1.0.0"
signingConfig signingConfigs.config
multiDexEnabled true
// gradle 3.0.0 以上需要有這個
// flavorDimensions "app"
}
// 多渠道/多環境 的不同配置
productFlavors {
dev {
// gradle 3.0.0 以上需要有這個
// dimension "app"
// 每個環境包名不同
applicationId "com.lyl.dev"
// 動態新增 string.xml 欄位;
// 注意,這裡是新增,在 string.xml 不能有這個欄位,會重名!!!
resValue "string", "app_name", "dev_myapp"
resValue "bool", "isrRank", 'false'
// 動態修改 常量 欄位
buildConfigField "String", "ENVIRONMENT", '"dev"'
// 修改 AndroidManifest.xml 裡渠道變數
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "dev"]
}
stage {
// gradle 3.0.0 以上需要有這個
// dimension "app"
applicationId "com.lyl.stage"
resValue "string", "app_name", "stage_myapp"
resValue "bool", "isrRank", 'true'
buildConfigField "String", "ENVIRONMENT", '"stage"'
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "stage"]
}
prod {
// gradle 3.0.0 以上需要有這個
// dimension "app"
applicationId "com.lyl.prod"
resValue "string", "app_name", "myapp"
resValue "bool", "isrRank", 'true'
buildConfigField "String", "ENVIRONMENT", '"prod"'
manifestPlaceholders = [UMENG_CHANNEL_VALUE: "prod"]
}
}
dexOptions {
incremental true
// javaMaxHeapSize "4g"
}
//移除lint檢測的error
lintOptions {
abortOnError false
}
def releaseTime() {
return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
}
buildTypes {
debug {
signingConfig signingConfigs.config
}
release {
buildConfigField("boolean", "LOG_DEBUG", "false")
minifyEnabled false
zipAlignEnabled true
//移除無用的resource檔案
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.config
// 批量打包(gradle 3.0.0 以下)
applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith('.apk')) {
//輸出apk名稱為:渠道名_版本名_時間.apk
def fileName = "${variant.productFlavors[0].name}_v${defaultConfig.versionName}_${releaseTime()}.apk"
output.outputFile = new File(outputFile.parent, fileName)
}
}
}
// 批量打包(gradle 3.0.0 以上)
// android.applicationVariants.all { variant ->
// variant.outputs.all {
// outputFileName = // "${variant.productFlavors[0].name}_v${defaultConfig.versionName}_${releaseTime()}.apk"
// }
}
}
}
}
repositories {
mavenCentral()
}
dependencies {
compile 'com.facebook.android:facebook-android-sdk:4.0.0'
compile project(':qrscan')
compile 'com.android.support:appcompat-v7:22.0.0'
compile 'com.google.code.gson:gson:2.3'
compile files('libs/android-async-http-1.4.6.jar')
compile 'com.google.android.gms:play-services:7.5.0'
compile 'com.android.support:support-annotations:22.1.1'
compile 'com.github.bumptech.glide:glide:3.7.0'
compile 'de.hdodenhof:circleimageview:2.1.0'
}
複製程式碼
接下來我們來詳細看看修改特定的欄位。
不同環境的設定基本都是在 productFlavors 裡設定的, 而且在裡面你想新增多少個環境都可以。
1. 不同環境,不同包名;
productFlavors {
dev {
applicationId "com.lyl.dev"
}
stage {
applicationId "com.lyl.stage"
}
prod {
applicationId "com.lyl.prod"
}
}
複製程式碼
這裡注意,在 defaultConfig 中,大家應該都是寫了個預設的 applicationId 的。 經測試,productFlavors 設定的不同環境包名會覆蓋 defaultConfig 裡面的設定, 所以我們可以推測,它執行的順序應該是先執行預設的,然後在執行分渠道的,如果衝突,會覆蓋處理,這也很符合邏輯。
2. 不同環境,新增 string.xml 資原始檔;
利用 resValue 來定義資源的值,顧名思義 res 底下的內容應該都可以建立,最後用 R.xxx.xxx 來引用。 如下就根據不同的型別,新增了不同的 app_name 欄位,以及定義了 布林值,可以通過 R.string.app_name 來引用。
注意,這裡是新增,是在 string.xml 裡面新增了一個欄位app_name,所以在現有的 string.xml 中不能有這個欄位,否則會報錯!!!
productFlavors {
dev {
resValue "string", "app_name", "dev_myapp"
resValue "bool", "isrRank", 'false'
}
stage {
resValue "string", "app_name", "stage_myapp"
resValue "bool", "isrRank", 'true'
}
prod {
resValue "string", "app_name", "myapp"
resValue "bool", "isrRank", 'true'
}
}
複製程式碼
通過以上我們大概可以推測出 color、dimen 也可以通過類似的方法新增。
3. 不同環境,動態修改指定的常量;
使用 BuildConfig 的變數。
①定義欄位
當我們定義如下欄位之後,編譯後自動生成檔案,在 app/build/source/BuildConfig/dev/com.lyl.dev/BuildConfig 目錄, 開啟這個檔案,我們就能看到我們所定義的欄位了。
productFlavors {
dev {
buildConfigField "String", "ENVIRONMENT", '"dev"'
}
stage {
buildConfigField "String", "ENVIRONMENT", '"stage"'
}
prod {
buildConfigField "String", "ENVIRONMENT", '"prod"'
}
}
複製程式碼
②引用欄位
在我們自己的任意的類中,來直接通過 BuildConfig 就可以呼叫我們定義的欄位。
public class Constants {
public static final String ENVIRONMENT = BuildConfig.ENVIRONMENT;
}
複製程式碼
注意: 這裡有個小細節,看其中第三個引數,是先用了“'”,然後在用了“"”,這種語法在 Java 裡可能比較陌生,但是在很多其他語言中,這種用法是很常見的。 它的意思是 "dev" 這個整體是屬於一個字串,至於為什麼要這麼寫,你把單引號去掉,然後去 app/build/source/BuildConfig/dev/com.lyl.dev/BuildConfig 這個檔案看一看就知道了。
4. 不同環境,修改 AndroidManifest.xml 裡渠道變數
①在 AndroidManifest.xml 裡新增渠道變數
<application
android:icon="${app_icon}"
android:label="@string/app_name"
android:theme="@style/AppTheme">
...
<meta-data
android:name="UMENG_CHANNEL"
android:value="${ENVIRONMENT}" />
...
</application>
複製程式碼
②在 build.gradle 設定 productFlavors
productFlavors {
dev {
manifestPlaceholders = [ENVIRONMENT: "dev",
app_icon : "@drawable/icon_dev"]
}
stage {
manifestPlaceholders = [ENVIRONMENT: "stage",
app_icon : "@drawable/icon_stage"]
}
prod {
manifestPlaceholders = [ENVIRONMENT: "prod",
app_icon : "@drawable/icon_prod"]
}
}
複製程式碼
這樣我們可以在不同環境使用不同的 key 值。
5. 不同環境,引用不同的 module
這個就很強大了,根據不同的環境,引用對應的 module。 你可以替換大量的圖片,string,color,vaule等等。
首先,要建立跟渠道對應的 module,然後再引用。 引用方式如下:
dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
// 引用本的專案
devCompile project(':devModule')
stageCompile project(':stageModule')
prodCompile project(':prodModule')
// 也可以分渠道引用網路的。因為這裡都相同,所以地址也就都一樣了
devCompile 'com.roughike:bottom-bar:2.0.2'
stageCompile 'com.roughike:bottom-bar:2.0.2'
prodCompile 'com.roughike:bottom-bar:2.0.2'
}
複製程式碼
xxxCompile 代表 各個渠道的名稱。 然後把需要分渠道的檔案,放到不同的 module 裡面,把主專案的檔案刪掉。 **千萬注意:**如果這樣做了,每次需要引用的時候,在各個渠道的 module 裡面都必須要放置檔案哦,不然會找不到資源。 通過這種方式可以替換整套素材資源,具體如何使用還得看專案需求。
通過以上方式,我們基本可以 通過 gradle 動態設定應用標題,應用圖示,替換常量,設定不同包名,更改渠道等等。
打包編譯
最後,做完所有的配置之後,然後就是打包操作了。
打包某一個(日常編譯)
因為 buildTypes 裡面有兩種,所以每個渠道都會有兩種模式。
打包所有的,就是正常打包流程
如圖所示:
打包完成之後,然後就可以在我們指定的目錄下,看到我們所生成的apk包。
使用 local.properties 存放私密配置
以上就可以基本實現 gradle 的設定,但是如果我們要將我們的專案上傳到 Github ,或者要將專案傳送給別人。上面有些私密的東西就會被別人看到。比如:.jks 檔案的密碼。 在專案跟目錄下,有個 local.properties 檔案,我們可以使用它來存放一些私密的屬性,然後在 gradle 中讀取,而 local.properties 檔案不需要上傳。 local.properties 檔案裡設定如下:
sdk.dir=D\:\\Android\\android-sdk
gaodeKey=e348025dd034d1c347dd0345e34802
keyPassword=123456
複製程式碼
在 build.gradle 讀取 local.properties 欄位資訊
// 載入 local.properties 資源
Properties properties = new Properties()
InputStream inputStream = project.rootProject.file('local.properties').newDataInputStream() ;
properties.load( inputStream )
android {
...
// 簽名檔案
signingConfigs {
config {
keyAlias 'lyl'
// 獲取 local.properties 欄位資訊
keyPassword properties.getProperty( 'keyPassword' )
storeFile file('../lyl.jks')
storePassword properties.getProperty( 'keyPassword' )
}
}
...
}
複製程式碼
這樣就可以將自己想要隱藏的一些資料隱藏起來。
可能加快 Android Studio 編譯的辦法
1. 在根目錄的 build.gradle 里加上如下程式碼:
allprojects {
// 加上這個
tasks.withType(JavaCompile) {
//使在一個單獨的守護程式編譯
options.fork = true
//增量編譯
options.incremental = true
}
repositories {
jcenter()
}
}
複製程式碼
2. 在 app 級別下 build.gradle 裡 加上
android {
dexOptions {
incremental true
}
}
複製程式碼
最後放上一個多渠道的專案地址,可以參考: github.com/Wing-Li/boo…
OK。 如果本文有什麼問題,請一定指出。 O(∩_∩)O