個人原創(chuàng),歡迎指正
場景:
用同一套核心代碼來維護多個訂制的app
現(xiàn)開發(fā)出一套Android app一疯, 在此app基礎上撼玄,復制出另一個app, 兩個app之間略微有一些不同之處墩邀。
將不同環(huán)境的APP安裝在同一設備中掌猛。
使用Gradle構(gòu)建的優(yōu)點:
提高效率,產(chǎn)品未成熟,包括需求上的修改及各種BUG荔茬,用一套代碼來維護废膘,可以減少不必要的重復勞動
免除傳統(tǒng)的重復修改環(huán)境,打包慕蔚,安裝繁瑣的流程丐黄,期間漏改在所難免。而使用Gradle構(gòu)建孔飒,只需選擇環(huán)境-安裝灌闺。快捷坏瞄,方便桂对,不出錯。
實現(xiàn)需求:
兩個APP鸠匀, 用一套代碼來維護蕉斜,不同之處在于應用名稱,圖標缀棍,包名蛛勉,部分跳轉(zhuǎn)邏輯,部分頁面睦柴,部分頁面顯示文字诽凌。
三個環(huán)境: 開發(fā), 測試坦敌, 生產(chǎn)侣诵, 每個環(huán)境的版本,版本號狱窘,圖標不同
命名規(guī)則:ProductFlavor_產(chǎn)品版本號_日期_BuildType.apk
whgc_1.0.0_20190624_Dev.apk
whgc_1.0.0_20190624_Test.apk
whgc_1.0.0_20190624_Release.apk各環(huán)境APP可安裝在同一手機
環(huán)境
AndroidStudio 3.4
Gradle插件:3.4.0
Gradle:5.1.1
三個環(huán)境 實現(xiàn)步驟:
- 新建項目
app: build.gradle
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "com.minicup.test"
minSdkVersion 19
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
...
}
-
app: build目錄結(jié)構(gòu)
image.png 執(zhí)行assemble任務
-
任務執(zhí)行后在build目錄中生成相應文件
image.png
assemble任務會自動創(chuàng)建debug和release兩個目錄
這里的debug和release目錄對應build.gradle中buildTypes的配置
可項目中buildTypes中明明只有release,并沒有debug杜顺,原因是這里的release和debug是系統(tǒng)默認的兩個構(gòu)建類型,即使沒有配置蘸炸,系統(tǒng)依然會生成這兩個目錄
修改一下build.gradle,將release配置也刪除躬络,再次運行assemble任務,發(fā)現(xiàn)同樣生成了release和debug兩個目錄
android {
buildTypes {
}
}
接下來在buildTypes中添加個dev 類型
android {
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
dev{
}
}
}
同步后首先看到變化如下:
再次執(zhí)行assemble任務:
以上符合預期搭儒。
自此在buildTypes中穷当,我們可以配置三種環(huán)境,解決關于三個環(huán)境的需求
android {
...
buildTypes {
appDev{
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
appTest{
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
appRelease {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
也可以這樣配置
android {
...
buildTypes {
// 常用配置:
// signingConfig,
// buildConfigField,
// versionNameSuffix
// multiDexEnabled,
// zipAlignEnabled,
// applicationIdSuffix
debug{
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
appDev{
initWith(debug)
}
appTest{
initWith(debug)
}
appRelease {
initWith(debug)
}
}
}
assemble后的結(jié)果
我們也可以打開ProjectStructure,在這里配置所有可以配置的東西
assemble是構(gòu)建所有buildTypes productFlavors嵌套配置生成的目錄淹禾。
buildTypes中的配置命名不能以test開頭馁菜, //BuildType names cannot start with 'test'
除了debug,其他構(gòu)建類型都是release類型的,因此铃岔,在沒有設置簽名配置時汪疮,會生成含有unsigned的標志。
所有的module必須有同樣的構(gòu)建類型,即使是空的
為三個環(huán)境指定簽名文件智嚷,及不同的BASE_URL
先生成jks文件卖丸,后配置build.gradle
android {
signingConfigs {
release {
storeFile file('app.jks')
storePassword '123456'
keyAlias = 'app'
keyPassword '123456'
}
}
...
buildTypes {
debug{
buildConfigField "String", "BASE_URL", "\"http://192.168.0.1:8080/debug\""
}
appDev{
buildConfigField "String", "BASE_URL", "\"http://192.168.0.1:8080/appDev\""
signingConfig signingConfigs.release
}
appTest{
buildConfigField "String", "BASE_URL", "\"http://192.168.0.1:8080/appDev\""
signingConfig signingConfigs.release
}
appRelease {
buildConfigField "String", "BASE_URL", "\"http://192.168.0.1:8080/appRelease\""
signingConfig signingConfigs.release
}
}
}
編譯后,生成相應的BuildConfig,里面的內(nèi)容與我們配置的一致
運行APP
EventLog
11:07 Executing tasks: [:app:assembleDebug]
11:07 Gradle build finished in 4 s 175 ms
11:07 Install successful
默認運行app時盏道,執(zhí)行的是assembleDebug任務稍浆。
通過設置BuildVariants來切換任務。
切換為appDev構(gòu)建類型后運行app的效果:
EventLog
11:19 Executing tasks: [:app:assembleAppDev]
11:19 Gradle build finished in 6 s 488 ms
11:19 Install successful
對應的構(gòu)建類型BuildConfig中生成了不同的值摇天,運行 appTest, appRelease也得到相應的結(jié)果。
但在運行APP時出現(xiàn)一個問題恐仑,也是上面我們需要解決的需求之一泉坐, 三種BuildTypes運行時,生成的APP會被替換掉裳仆,最后運行三種BuildTypes在手機中只生成一個APP腕让。如何讓開發(fā),測試歧斟,生產(chǎn)環(huán)境的APP存在于同一個手機上纯丸。為了解決這個問題,我們先來看看如何為不同的環(huán)境指定不同的應用名稱及圖標静袖,用以安裝后展示效果
為三個環(huán)境指定不同的應用名稱觉鼻,應用圖標
按照開發(fā)-測試-生產(chǎn)的順序準備三張應用圖標
strings.xml
<resources>
<string name="app_name">test</string>
<string name="app_name_dev">DEV</string>
<string name="app_name_test">TEST</string>
<string name="app_name_release">RELEASE</string>
</resources>
android {
...
buildTypes {
debug{
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
buildConfigField "String", "BASE_URL", "\"http://192.168.0.1:8080/debug\""
manifestPlaceholders = [
app_icon :"@mipmap/ic_launcher",
app_label : "@string/app_name"
]
}
release{
buildConfigField "String", "BASE_URL", "\"http://192.168.0.1:8080/release\""
signingConfig signingConfigs.release
manifestPlaceholders = [
app_icon :"@mipmap/ic_launcher",
app_label : "@string/app_name"
]
}
appDev{
buildConfigField "String", "BASE_URL", "\"http://192.168.0.1:8080/appDev\""
signingConfig signingConfigs.release
manifestPlaceholders = [
app_icon :"@mipmap/ic_launcher_dev",
app_label : "@string/app_name_dev"
]
}
appTest{
buildConfigField "String", "BASE_URL", "\"http://192.168.0.1:8080/appTest\""
signingConfig signingConfigs.release
manifestPlaceholders = [
app_icon :"@mipmap/ic_launcher_test",
app_label : "@string/app_name_test"
]
}
appRelease {
buildConfigField "String", "BASE_URL", "\"http://192.168.0.1:8080/appRelease\""
signingConfig signingConfigs.release
manifestPlaceholders = [
app_icon :"@mipmap/ic_launcher_release",
app_label : "@string/app_name_release"
]
}
}
}
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.minicup.test">
<application
android:allowBackup="true"
android:icon="${app_icon}"
android:label="${app_label}"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
通過設置不同的BuildVariants獲得的結(jié)果:
以上結(jié)果針對BuildType的應用名稱,應用圖標队橙,及打開APP中的BASE_URL都一一對應坠陈。接下來讓三種BuildType運行在一臺機器上。
三種不同的BuildType捐康,安裝在同一設備上仇矾。
android {
...
buildTypes {
...
appDev{
buildConfigField "String", "BASE_URL", "\"http://192.168.0.1:8080/appDev\""
signingConfig signingConfigs.release
manifestPlaceholders = [
app_icon :"@mipmap/ic_launcher_dev",
app_label : "@string/app_name_dev"
]
applicationIdSuffix = ".dev"
}
appTest{
buildConfigField "String", "BASE_URL", "\"http://192.168.0.1:8080/appTest\""
signingConfig signingConfigs.release
manifestPlaceholders = [
app_icon :"@mipmap/ic_launcher_test",
app_label : "@string/app_name_test"
]
applicationIdSuffix = ".test"
}
appRelease {
buildConfigField "String", "BASE_URL", "\"http://192.168.0.1:8080/appRelease\""
signingConfig signingConfigs.release
manifestPlaceholders = [
app_icon :"@mipmap/ic_launcher_release",
app_label : "@string/app_name_release"
]
applicationIdSuffix = ".release"
}
}
}
切換三種BuildType,安裝APP到設備中:
實現(xiàn)一套代碼維護兩個應用解总,每個應用都有dev,test,release三個構(gòu)建類型贮匕,兩個應用的圖標,名稱花枫,包名不同
- app build.gradle 指定不同productFlavor下的包名,及不同buildType下的圖標,名稱及包名后綴
apply plugin: 'com.android.application'
android {
signingConfigs {
release {
storeFile file('appDev.jks')
storePassword '123456'
keyAlias = 'appDev'
keyPassword '123456'
}
}
compileSdkVersion 28
defaultConfig {
minSdkVersion 19
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
flavorDimensions "default"
}
buildTypes {
appDev{
buildConfigField "String", "BASE_URL", "\"http://192.168.0.1:8080/appDev\""
signingConfig signingConfigs.release
manifestPlaceholders = [
app_icon :"@drawable/ic_launcher_dev",
app_label : "@string/app_name_dev"
]
applicationIdSuffix = ".dev"
}
appTest{
buildConfigField "String", "BASE_URL", "\"http://192.168.0.1:8080/appTest\""
signingConfig signingConfigs.release
manifestPlaceholders = [
app_icon :"@drawable/ic_launcher_test",
app_label : "@string/app_name_test"
]
applicationIdSuffix = ".test"
}
appRelease {
buildConfigField "String", "BASE_URL", "\"http://192.168.0.1:8080/appRelease\""
signingConfig signingConfigs.release
manifestPlaceholders = [
app_icon :"@drawable/ic_launcher_release",
app_label : "@string/app_name_release"
]
applicationIdSuffix = ".release"
}
}
productFlavors {
orange {
applicationId "com.minicup.orange"
}
green {
applicationId "com.minicup.green"
}
}
//去掉默認的debug及release兩個buildType
variantFilter { variant ->
if(variant.buildType.name.equals('release') || variant.buildType.name.equals('debug')) {
variant.setIgnore(true)
}
}
}
...
*創(chuàng)建以下的目錄結(jié)構(gòu),不同productFlavor對應相應的圖標,名稱
green目錄下的圖標及名稱:
<resources>
<string name="app_name_dev">DEV_G</string>
<string name="app_name_test">TEST_G</string>
<string name="app_name_release">RELEASE_G</string>
</resources>
orange目錄下的圖標及名稱:
<resources>
<string name="app_name_dev">DEV_O</string>
<string name="app_name_test">TEST_O</string>
<string name="app_name_release">RELEASE_O</string>
</resources>
- MainActivity
public class MainActivity extends AppCompatActivity {
private TextView mTextView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mTextView = (TextView) findViewById(R.id.textView);
mTextView.setText(BuildConfig.FLAVOR + " "+ BuildConfig.BUILD_TYPE);
}
}
-
同步項目刻盐,查看BuildVariant, 生成了兩個應用對應的構(gòu)建類型 2(productFlavor)*3(buildType)=6(buildVariant)
image.png -
執(zhí)行assemble任務:
image.png -
驗證APP,將生成的6個APP安裝到設備中
image.png
- 不同的productFlavor,不同的buildType生成的應用可安裝到同一個設備
- 同一個productFlavor的不同buildType劳翰,根據(jù)之前配置生成不同名稱隙疚,不同圖標,不同包名的app
設置生成APP的文件名稱
productFlavors {
orange {
applicationId "com.minicup.orange"
versionCode 2
versionName "2.2"
}
green {
applicationId "com.minicup.green"
versionCode 3
versionName "3.3"
}
}
applicationVariants.all { variant ->
variant.outputs.all { output ->
//產(chǎn)品名稱_產(chǎn)品版本號_日期_軟件環(huán)境
outputFileName = "${variant.productFlavors[0].name}_" +
"v${variant.versionName}_" +
"${releaseTime()}_" +
"${variant.buildType.name}" +
".apk"
}
}
def releaseTime(){
return new Date().format("yyyy-MM-dd")
}
執(zhí)行assemble任務:
集成JPush時磕道,遇到的問題
按照以上方法配置JPush時供屉,打好的包,不能同時安裝在同一個設備中
目前只能準備兩臺設備,同一設備只能安裝同一buildtype的應用
參考:
https://developer.android.google.cn/studio/build/index.html
http://google.github.io/android-gradle-dsl/current/index.html