个人原创,欢迎指正
场景:
用同一套核心代码来维护多个订制的app
现开发出一套Android app, 在此app基础上,复制出另一个app, 两个app之间略微有一些不同之处。
将不同环境的APP安装在同一设备中。
使用Gradle构建的优点:
提高效率,产品未成熟,包括需求上的修改及各种BUG,用一套代码来维护,可以减少不必要的重复劳动
免除传统的重复修改环境,打包,安装繁琐的流程,期间漏改在所难免。而使用Gradle构建,只需选择环境-安装。快捷,方便,不出错。
实现需求:
-
两个APP, 用一套代码来维护,不同之处在于应用名称,图标,包名,部分跳转逻辑,部分页面,部分页面显示文字。
-
三个环境: 开发, 测试, 生产, 每个环境的版本,版本号,图标不同
-
命名规则:ProductFlavor_产品版本号_日期_BuildType.apk
whgc_1.0.0_20190624_Dev.apk
whgc_1.0.0_20190624_Test.apk
whgc_1.0.0_20190624_Release.apk -
各环境APP可安装在同一手机
环境
AndroidStudio 3.4
Gradle插件:3.4.0
Gradle:5.1.1
三个环境 实现步骤:
- 新建项目
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目录结构
image.png -
执行assemble任务
-
任务执行后在build目录中生成相应文件
image.png
assemble任务会自动创建debug和release两个目录
这里的debug和release目录对应build.gradle中buildTypes的配置
可项目中buildTypes中明明只有release,并没有debug,原因是这里的release和debug是系统默认的两个构建类型,即使没有配置,系统依然会生成这两个目录
修改一下build.gradle,将release配置也删除,再次运行assemble任务,发现同样生成了release和debug两个目录
android {
buildTypes {
}
}
接下来在buildTypes中添加个dev 类型
android {
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
dev{
}
}
}
同步后首先看到变化如下:
image.png
image.png
再次执行assemble任务:
image.png
以上符合预期。
自此在buildTypes中,我们可以配置三种环境,解决关于三个环境的需求
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后的结果
image.png
我们也可以打开ProjectStructure,在这里配置所有可以配置的东西
image.png
assemble是构建所有buildTypes productFlavors嵌套配置生成的目录。
buildTypes中的配置命名不能以test开头, //BuildType names cannot start with 'test'
除了debug,其他构建类型都是release类型的,因此,在没有设置签名配置时,会生成含有unsigned的标志。
所有的module必须有同样的构建类型,即使是空的
为三个环境指定签名文件,及不同的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,里面的内容与我们配置的一致
image.png
运行APP
image.png
EventLog
11:07 Executing tasks: [:app:assembleDebug]
11:07 Gradle build finished in 4 s 175 ms
11:07 Install successful
默认运行app时,执行的是assembleDebug任务。
通过设置BuildVariants来切换任务。
image.png
切换为appDev构建类型后运行app的效果:
image.png
EventLog
11:19 Executing tasks: [:app:assembleAppDev]
11:19 Gradle build finished in 6 s 488 ms
11:19 Install successful
对应的构建类型BuildConfig中生成了不同的值,运行 appTest, appRelease也得到相应的结果。
但在运行APP时出现一个问题,也是上面我们需要解决的需求之一, 三种BuildTypes运行时,生成的APP会被替换掉,最后运行三种BuildTypes在手机中只生成一个APP。如何让开发,测试,生产环境的APP存在于同一个手机上。为了解决这个问题,我们先来看看如何为不同的环境指定不同的应用名称及图标,用以安装后展示效果
为三个环境指定不同的应用名称,应用图标
按照开发-测试-生产的顺序准备三张应用图标
ic_launcher_dev.png ic_launcher_test.png ic_launcher_release.png
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获得的结果:
BuildType.appDev BuildType.appTest BuildType.appRelease以上结果针对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到设备中:
image.png
实现一套代码维护两个应用,每个应用都有dev,test,release三个构建类型,两个应用的图标,名称,包名不同
- 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)
}
}
}
...
*创建以下的目录结构,不同productFlavor对应相应的图标,名称
image.png
green目录下的图标及名称:
ic_launcher_dev.xml ic_launcher_test.xml ic_launcher_release.xml
<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目录下的图标及名称:
ic_launcher_dev.xml ic_launcher_test.xml ic_launcher_release.xml
<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, 生成了两个应用对应的构建类型 2(productFlavor)*3(buildType)=6(buildVariant)
image.png -
执行assemble任务:
image.png -
验证APP,将生成的6个APP安装到设备中
image.png
image.png image.png image.png image.png image.png image.png
- 不同的productFlavor,不同的buildType生成的应用可安装到同一个设备
- 同一个productFlavor的不同buildType,根据之前配置生成不同名称,不同图标,不同包名的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 ->
//产品名称_产品版本号_日期_软件环境
outputFileName = "${variant.productFlavors[0].name}_" +
"v${variant.versionName}_" +
"${releaseTime()}_" +
"${variant.buildType.name}" +
".apk"
}
}
def releaseTime(){
return new Date().format("yyyy-MM-dd")
}
执行assemble任务:
image.png集成JPush时,遇到的问题
按照以上方法配置JPush时,打好的包,不能同时安装在同一个设备中
image.png
目前只能准备两台设备,同一设备只能安装同一buildtype的应用
参考:
https://developer.android.google.cn/studio/build/index.html
http://google.github.io/android-gradle-dsl/current/index.html
网友评论