最近在做一個(gè)通用版的會(huì)員系統(tǒng),給予不同的公司使用,先前是通過切換版本分支來管理的,后面發(fā)現(xiàn)實(shí)在是繁瑣和痛苦管理,僅僅是需要更改不同的常量、主題資源、包名、圖標(biāo)等等,主體代碼邏輯功能基本不變。
先前了解過多渠道包的使用,其實(shí)這里完全可以通過 Gradle 的多渠道打包來這個(gè)痛點(diǎn),期間也踩了坑,在這里做個(gè)記錄
目錄
一、通過 productFlavors 配置不同的渠道/環(huán)境
二、manifestPlaceholders 占位符使用
三、了解 ApplicationId 與 PackageName的區(qū)別
四、替換資源文件
五、打包和調(diào)試編譯安裝不同版本的渠道
以下為完整的實(shí)際項(xiàng)目配置這有兩個(gè)渠道等同于給兩家不同的公司會(huì)員 app 使用的配置
apply plugin: 'com.android.application'
def releaseTime() {
return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
}
//加載本地文件
Properties properties = new Properties()
properties.load(project.rootProject.file('local.properties').newDataInputStream())
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.ablegenius.member"
minSdkVersion 15
targetSdkVersion 28
versionCode 101
versionName "1.0.101"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
multiDexEnabled true
ndk {
//選擇要添加的對(duì)應(yīng)cpu類型的.so庫。
abiFilters "armeabi", "armeabi-v7a", "arm64-v8a", "x86", "arm64-v8a", "x86_64"
//, 'mips', 'mips64'
}
// 渠道配置 gradle 3.0.0 以上需要有這個(gè)
flavorDimensions "app"
}
signingConfigs {
AblegeniusMemberConfig {
//第一種:使用gradle直接簽名打包
/* keyAlias 'dongwang'
keyPassword '123123'
storeFile file('src/main/WineverzhudiStoreFile.jks')
storePassword '123123'*/
//第二種:為了保護(hù)簽名文件,把它放在local.properties中并在版本庫中排除
// ,不把這些信息寫入到版本庫中(注意,此種方式簽名文件中不能有中文)
storeFile file(properties.getProperty("keystroe_storeFile"))
storePassword properties.getProperty("keystroe_storePassword")
keyAlias properties.getProperty("keystroe_keyAlias")
keyPassword properties.getProperty("keystroe_keyPassword")
v2SigningEnabled false
}
}
// 多渠道/多環(huán)境 的不同配置
productFlavors {
SatayKing {
//此處的常量都會(huì)通過Gradle 在 BuildConfig.java 文件中生成 , 你可以直接在Class中使用 BuildConfig.XXXX 進(jìn)行使用
// 每個(gè)環(huán)境包名不同
applicationId "com.ablegenius.member.satayking"
// 動(dòng)態(tài)添加 string.xml 字段;
// 注意,如果在這添加,在 string.xml 不能有這個(gè)字段,會(huì)重名!!!這里使用資源文件覆蓋的方式來處理應(yīng)用名稱
// resValue "string", "app_name", "沙嗲王會(huì)員x"
resValue "bool", "auto_updates", 'false'
// 動(dòng)態(tài)修改 常量 字段
buildConfigField "String", "MAIN_H5_URL", '"https://xxxxxxx22/index.html"'
//服務(wù)器請(qǐng)求地址
buildConfigField "String", "SERVER_URL", '"https://cloudxxxx22/a"'
//一些常量
buildConfigField "String", "company", '"SatayKing"'
buildConfigField "String", "serial", '"xxxxx"'
buildConfigField "int", "ENVIRONMENTInt", '2'
// 修改 AndroidManifest.xml 里渠道變量
manifestPlaceholders = [CHANNEL_VALUE: "SatayKing"
, app_icon : "@mipmap/ic_launcher_shadiewang",
//此方式可直接在 manifest 中通過 ${icon} 進(jìn)行占位引用; 或者在main同級(jí)中創(chuàng)建不同渠道后創(chuàng)建 res 資源文件
icon : "@mipmap/ic_launcher_shadiewang",
//極光相關(guān)
JPUSH_PKGNAME: applicationId,
JPUSH_APPKEY : "xxxxxxx", //JPush上注冊(cè)的包名對(duì)應(yīng)的appkey.
JPUSH_CHANNEL: "developer-default", //暫時(shí)填寫默認(rèn)值即可.
//Google Map 相關(guān)
GoogleMapKey : "AIzaSyCLJ9Gng-xxxxx",
]
}
WineverHK {
dimension "app"
applicationId "com.ablegenius.member.wineverzhudi"
// resValue "string", "app_name", "築地日本料理"
resValue "bool", "auto_updates", 'true'
resValue "drawable", "isrRank", 'true'
buildConfigField "String", "MAIN_H5_URL", '"http://xxxxindex.html"'
buildConfigField "String", "SERVER_URL", '"http://cloud.xxxx/a"'
buildConfigField "String", "company", '"WineverHK"'
buildConfigField "String", "serial", '"xxxx"'
manifestPlaceholders = [CHANNEL_VALUE: "WineverHK"
, app_icon : "@mipmap/ic_launcher_zhudi",
icon : "@mipmap/ic_launcher_zhudi",
JPUSH_PKGNAME: applicationId,
JPUSH_APPKEY : "247aef555a20e8836d1ac361", //JPush上注冊(cè)的包名對(duì)應(yīng)的appkey.
JPUSH_CHANNEL: "developer-default", //暫時(shí)填寫默認(rèn)值即可.
GoogleMapKey : "AIzaSyCtAVjIVmGdnP44W2Nk8DjCT_OJISYUVxA",
]
}
}
buildTypes {
release {
// release模式下,不顯示log
buildConfigField("boolean", "LOG_DEBUG", "false")
// 為版本名添加后綴
// versionNameSuffix "-relase"
// 不開啟混淆
minifyEnabled false
// 移除無用的resource文件
shrinkResources false
// 開啟ZipAlign優(yōu)化
zipAlignEnabled true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.AblegeniusMemberConfig
}
debug {
// debug模式下,顯示log
buildConfigField("boolean", "LOG_DEBUG", "true")
//為已經(jīng)存在的applicationId添加后綴
// applicationIdSuffix ".debug"
// 為版本名添加后綴
versionNameSuffix "-debug"
// 不開啟混淆
minifyEnabled false
// 不開啟ZipAlign優(yōu)化
zipAlignEnabled false
// 不移除無用的resource文件
shrinkResources false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.AblegeniusMemberConfig
}
}
// 3.0 gradle 批量打包
android.applicationVariants.all { variant ->
variant.outputs.all {
//輸出apk名稱為:渠道名_版本名_時(shí)間.apk
outputFileName = "${variant.productFlavors[0].name}Member_v${defaultConfig.versionName}_${releaseTime()}.apk"
}
}
sourceSets {
SatayKing { res.srcDirs = ['src/SatayKing/res', 'src/SatayKing/res/'] }
WineverHK { res.srcDirs = ['src/WineverHK/res', 'src/WineverHK/res/'] }
main { res.srcDirs = ['src/main/res', 'src/main/res/'] }
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
dependencies {
implementation xxxx
}
一、通過 productFlavors 配置不同的渠道/環(huán)境
productFlavors {
SatayKing {
applicationId "com.ablegenius.member.satayking"
}
WineverHK {
applicationId "com.ablegenius.member.wineverzhudi"
}
}
這里注意,在 defaultConfig 中,大家應(yīng)該都是寫了個(gè)默認(rèn)的 applicationId 的。
經(jīng)測(cè)試,productFlavors 設(shè)置的不同環(huán)境包名會(huì)覆蓋 defaultConfig 里面的設(shè)置,
所以我們可以推測(cè),它執(zhí)行的順序應(yīng)該是先執(zhí)行默認(rèn)的,然后在執(zhí)行分渠道的,如果沖突,會(huì)覆蓋處理,這也很符合邏輯。
二、manifestPlaceholders 占位符使用
項(xiàng)目中使用到了極光、GoogleMap 等第三方SDK的配置,大家都知道極光推送需要根據(jù)不同的包名 JPush上注冊(cè)的包名對(duì)應(yīng)的appkey 的才能進(jìn)行推送,如何去修改呢?
使用 manifestPlaceholders 來 定義 【GoogleMapKey 】常量,
在 AndroidManifest.xml 中 使用 "${GoogleMapKey}" 來占位,
<application
android:icon="${icon}"
android:label="${app_name}"
xxxxx>
<!--渠道配置-->
<meta-data
android:name="CHANNEL"
android:value="${CHANNEL_VALUE}" />
<!-- Google Map Key -->
<meta-data
android:name="com.google.android.geo.API_KEY"
android:value="${GoogleMapKey}" />
<!-- 極光推送-->
<!-- User defined. 用戶自定義的廣播接收器-->
<receiver
android:name="com.ablegenius.member.receiver.JpushReceiver"
android:enabled="true">
<!--android:process=":remote"廣播運(yùn)行在遠(yuǎn)端單獨(dú)進(jìn)程中 ,調(diào)試斷點(diǎn)無法執(zhí)行需要關(guān)閉 或者 debug時(shí)候選擇 remote ! -->
<intent-filter>
xxxxx
<!--推送包名必須一致使用Gradle中的常量才是最終的 -->
<category android:name="${applicationId}" />
</intent-filter>
</receiver>
</application>
此處的app名稱和圖標(biāo)都可以使用占位符的方式進(jìn)行引用,
Tpis:如果是這種方式修改應(yīng)用名稱,注意應(yīng)用名稱定義在外層,通過 resValue 定義的常量String 需要 先使用 單引號(hào) 里面再是字符串,'"應(yīng)用名稱"'
resValue "string", "app_name", "築地日本料理"
三、了解 ApplicationId 與 PackageName的區(qū)別
調(diào)試和打包出來的名稱會(huì)以Gradle 中的 applicationId 為最終包名,在 Manifest中的并不是最終的會(huì)被修改,地圖在做key驗(yàn)證的時(shí)候填寫的包名應(yīng)該是ApplicationId ,而不是packageName
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.ablegenius.member">
四、替換資源文件
每個(gè)應(yīng)用資源布局 主題樣式,啟動(dòng)頁圖標(biāo)、應(yīng)用名稱可能 不一樣,這時(shí)怎么做呢? Google 做法:
在 main 的同級(jí)目錄下創(chuàng)建以渠道名命名的文件夾,然后創(chuàng)建資源文件(路徑要與 main 中的一致),然后打包的時(shí)候 gradle 就會(huì)自己替換或者合并資源。 替換圖片和合并顏色的原理也相似。必須名稱統(tǒng)一使用!
在對(duì)應(yīng)的渠道文件夾中創(chuàng)建res 文件, 注意渠道文件夾 目錄為main 同級(jí)中, 創(chuàng)建 res為 : src/渠道名稱/res
五、打包和調(diào)試編譯安裝不同版本的渠道
選取不同的渠道,Gradle 會(huì)自動(dòng)編譯指定渠道,然后再運(yùn)行項(xiàng)目即可
多渠道打包后很多渠道時(shí) 需要默認(rèn) 安裝哪個(gè)渠道, 需要 在Build 中做切換
也可以通過命令打包: ./gradlew assembleRelease
最后如果你有涉及到第三方的Appkey之類的一定要檢查好這塊,以及配置的SHA1值等
參考:
Gradle多渠道打包(動(dòng)態(tài)設(shè)定App名稱,應(yīng)用圖標(biāo),替換常量,更改包名,變更渠道)