当一个APP成熟起来,功能会越来越多,业务会越来越复杂,面向的用户群越来越大。这个时候为了更进一步的发展和扩大业务,我们可以对APP进行拆分,做成两个甚至更多个的APP,每个APP都服务特定的用户群。为了缩短开发时间,降低维护成本,肯定是不能再单独新建一个项目工程的。那怎么在原有的项目工程来进行开发,从而实现一套代码能够打出不同的APP呢?这就是本篇文章要介绍的“差异化打包”。
gradle配置如下:
apply plugin: 'com.android.application'
android {
compileSdkVersion 29
defaultConfig {
applicationId "com.github.productflavors"
minSdkVersion 21
targetSdkVersion 29
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
}
//差异化打包,名字任意起
flavorDimensions 'normal'
/*多渠道的一些配置 */
productFlavors {
normala {
dimension = 'normal'
// 设置applicationId(这里很重要,两个相同applicationId的apk不同同时安装在同一台Android手机中)
applicationId = 'com.github.productflavorsnormala'
targetSdkVersion 29
maxSdkVersion 29
minSdkVersion 21
// signingConfig signingConfigs.release
buildConfigField "String", "BASE_URL", "\"http://www.baidu.com\""
// resValue "string", "app_name", "DemoA"//注释:须删除main下 res/values/Strings.xml 中 app_name
manifestPlaceholders = [
app_name : "DemoA",
app_icon : "@mipmap/ic_launcher",
app_round_icon: "@mipmap/ic_launcher_round",
app_theme : "@style/AAAA"//配置normalA的差异化主题
]
versionCode 1
versionName "1.0"
}
normalb {
dimension = 'normal'
applicationId = 'com.github.productflavorsnormalb'
targetSdkVersion 29
maxSdkVersion 29
minSdkVersion 21
// signingConfig signingConfigs.release
buildConfigField "String", "BASE_URL", "\"http://www.qq.com\""
// resValue "string", "app_name", "DemoB"//注释:须删除main下 res/values/Strings.xml 中 app_name
manifestPlaceholders = [
app_name : "DemoB",
app_icon : "@mipmap/ic_launcher",
app_round_icon: "@mipmap/ic_launcher_round",
app_theme : "@style/BBBB"//配置normalB的差异化主题
]
versionCode 1
versionName "1.0"
}
}
//指定版本加载对应的代码or配置
sourceSets {
normala {
manifest.srcFile '/src/normala/AndroidManifest.xml'//其中区分了主题和图标
}
normalb {
manifest.srcFile '/src/normalb/AndroidManifest.xml'//其中区分了主题和图标
}
}
buildTypes {
debug {
// signingConfig signingConfigs.config
}
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
//签名文件配置: 这是第一种写法:
/* signingConfigs {
debug {
storeFile file('../sign.jks')
storePassword "123456"
keyAlias "key0"
keyPassword "123456"
v1SigningEnabled true
v2SigningEnabled true
}
release {
storeFile file('../sign.jks')
storePassword "123456"
keyAlias "key0"
keyPassword "123456"
v1SigningEnabled true
v2SigningEnabled true
}
}*/
// 这是签名的第二种写法 调用getSigningProperties方法里面的函数,
// 通过读取里面的配置文件进行操作:
//
signingConfigs {
debug {
v1SigningEnabled true
v2SigningEnabled true
}
release {
storeFile
storePassword
keyAlias
keyPassword
v1SigningEnabled true
v2SigningEnabled true
}
}
getSigningProperties()
//自定义输出包名的设置
applicationVariants.all { variant ->
//获取当前渠道
def flavor = variant.productFlavors[0]
//获取当前build版本
def buildType = variant.buildType.name
//获取当前时间的"YYYY-MM-dd"格式。
def createTime = new Date().format("YYYY-MM-dd", TimeZone.getTimeZone("GMT+08:00"))
variant.outputs.all {
if (buildType == "release") {
//输出apk名称为:渠道名_版本名_时间.apk
def fileName = " ${flavor.name}-${variant.buildType.name}_v${flavor.versionName}_${createTime}.apk"
outputFileName = fileName
}
}
}
}
//读取签名配置文件
def getSigningProperties() {
def propFile = file('sign.properties')
if (propFile.canRead()) {
def Properties props = new Properties()
props.load(new FileInputStream(propFile))
if (props != null && props.containsKey('STORE_FILE') && props.containsKey('STORE_PASSWORD') &&
props.containsKey('KEY_ALIAS') && props.containsKey('KEY_PASSWORD')) {
android.signingConfigs.release.storeFile = file(props['STORE_FILE'])
android.signingConfigs.release.storePassword = props['STORE_PASSWORD']
android.signingConfigs.release.keyAlias = props['KEY_ALIAS']
android.signingConfigs.release.keyPassword = props['KEY_PASSWORD']
} else {
println 'signing.properties found but some entries are missing'
android.buildTypes.release.signingConfig = null
}
} else {
println 'sign.properties not found'
android.buildTypes.release.signingConfig = null
}
}
//获取版本号:
def getVersionCode() {
def versionFile = file('version.properties')
if (versionFile.canRead()) {
def Properties versionProps = new Properties()
versionProps.load(new FileInputStream(versionFile))
def versionCode = versionProps['VERSION_CODE'].toInteger()
def runTasks = gradle.startParameter.taskNames
//仅在assembleRelease任务是增加版本号
if ('assembleRelease' in runTasks) {
versionProps['VERSION_CODE'] = (++versionCode).toString()
versionProps.store(versionFile.newWriter(), null)
}
return versionCode
} else {
throw new GradleException("Could not find version.properties!")
}
}
//获取当前时间
def getCurrentTime() {
return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.1.0'
implementation 'androidx.constraintlayout:constraintlayout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'androidx.test:runner:1.2.0'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0'
}
目录介绍
再看下每个版本中的代码:
公共部分:
package com.github.productflavors;
import android.content.Context;
import android.widget.Toast;
public class Toa {
public static void toast(Context context,String msg){
Toast.makeText(context,msg,Toast.LENGTH_LONG).show();
}
}
差异化normalA部分:
package com.github.productflavors;
import androidx.appcompat.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toa.toast(this, "A");
}
}
差异化normalB部分
package com.github.productflavors;
import android.os.Bundle;
import androidx.appcompat.app.AppCompatActivity;
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Toa.toast(this, "B");
}
}
其实把MainActivity分在两个差异化的版本里是因为可能这两个类中所有逻辑都不一样, 所以把它抽出来放在差异化部分,假如MainActivity的主题配置每个版本都不一样,那么就可以在对应版本的 AndroidManifest.xml中进行配置
再看下AndroidManifest.xml 文件配置:
公共部分:
差异化normalA部分:
差异化normalB部分:
其中application节点中接收了gradle中多渠道差异化配置的 manifestPlaceholders 中定制的参数,其中normalA和normalB共同配置了不同的icon图标及MainActivity的差异化主题, 差异化的图标及主题要放在对应的差异化的部分中,这样在运行时就可以显示出gradle中配置的样式了。
可以看下差异化部分的主题配置:
normalA部分:
normalB部分:
可以看到主题的颜色并不一样。
在选择版本build时先按照下方图片中的方法切换到对应的版本:
最后看下效果:
差异化normalA部分:
差异化normalB部分:
点击HELLO WOLD!按钮跳转到共同的SecondActivity页面:
以上所演示的功能的相关代码已提交到 :ProductFlavors