关于多版本构建,我们可以通过buildTypes来新增构建类型,一般而言这里也不需要自行定义,默认会生成debug和release两种类型。
重点在于使用productFlavors生成不同“风味”的版本,我们可以构建标准版和中性版APP,这在企业应用中非常普遍,中性版本不含有logo信息,可再次贴牌等。同时应该在release版本中关闭log输出。
我这里的区别为中性版assets/defaultLogo/logo_default.png和标准版的资源是不一样的,所以需要创建对应的productFlavor文件夹,放入需要替换的logo_default.png资源。同时不论是中性版还是标准版还有四种机型需要适配(F600P、F600、F200P、F200),也就是说我们需要生成8个版本的app。
生成多个不同“风味”的版本很容易,只要定义productFlavors即可。但目标应该更进一步,通过只敲一次命令行生成所有的标准版和中性版release app,并且按照我们的命令格式重命名apk文件,最后只需要到build/outputs/apk拿出对应的安装包。
默认情况下打开Android Studio的Terminal面板,键入gradle -version会提示找不到这个命令,无法使用,这需要配置环境变量。
在File-Settings中查看Gradle home(Android Studio安装后会自带一个Gradle,就在其安装目录gradle下)所在位置。
建议使用用户变量,只有当前用户可用。添加一个变量名为GRADLE_HOME、变量值为E:/as/gradle/gradle-2.14.1的环境变量。然后编辑path环境变量,新建一行,并填入%GRADLE_HOME%/bin/即可。
再次在Terminal中输入gradle –version命令后回车。出现下面的gradle版本信息,就说明gradle构建工具在命令行方式下已经正常工作。
当然前提是log可以全局通过一个boolean值控制。
使用以下函数可以判断是否debug版本,非debug版本也就是正式版关闭log即可。
/**
* 判断当前应用是否debug状态
*
* @param context
* @return
*/
public static boolean isApkDebug(Context context) {
ApplicationInfo info = context.getApplicationInfo();
return (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
}
我在自定义的Application(MainApp.java)中根据判断是否开启log开关。
// Log开关
Logger.DEBUG = AppUtils.isApkDebug(this);
android{
…
signingConfigs {
releaseSign {
storeFile file("E:\\xx\\xx\\xx.keystore")
storePassword "xxxxx"
keyAlias "xx"
keyPassword "xxxxx"
}
}
buildTypes {
release {
signingConfig signingConfigs.releaseSign
…
}
}
…
}
这段代码很好理解,通过在android闭包下配置signingConfigs,并在buildTypes的release下引用即可。
productFlavors {
// 中性版本F600P
neutralF600P {
// 这里替换了assets内logo_default.png资源
manifestPlaceholders = getVersionNameMap('F600P')
}
// 标准版本F600P
standardF600P {
manifestPlaceholders = getVersionNameMap("F600P")
}
// 中性版本F600
neutralF600 {
manifestPlaceholders = getVersionNameMap("F600")
}
// 标准版本F600
standardF600 {
manifestPlaceholders = getVersionNameMap("F600")
}
// 中性版本F200P
neutralF200P {
manifestPlaceholders = getVersionNameMap("F200P")
}
// 标准版本F200P
standardF200P {
manifestPlaceholders = getVersionNameMap("F200P")
}
// 中性版本F200
neutralF200 {
manifestPlaceholders = getVersionNameMap("F200")
}
// 标准版本F200
standardF200 {
manifestPlaceholders = getVersionNameMap("F200")
}
}
通过在android闭包下配置8个版本的flavor,并给中性版创建对应的文件夹,放入需要替换的logo_default.png资源。Flavor对应的文件夹位于src下,和main平级。
src->
main
neutralF600P
neutralF600
neutralF200P
neutralF200
再来看getVersionNameMap(key)函数。
/**
* 根据key获取version Map
* @param key
* @return
*/
def getVersionNameMap(key) {
def versionNameMap = [:]
if (key == "F600P") {
versionNameMap.D_VERSION_NAME = "F600P"
} else if (key == "F600") {
versionNameMap.D_VERSION_NAME = "F600"
} else if (key == "F200P") {
versionNameMap.D_VERSION_NAME = "F200P"
} else if (key == "F200") {
versionNameMap.D_VERSION_NAME = "F200"
} else {
versionNameMap.D_VERSION_NAME = "F600P"
}
return versionNameMap
}
解释一下:manifestPlaceholders是android gradle dsl中定义的属性,用来替换其中的一些字段,使用groovy GString在AndroidMainifest中占位,通过给manifestPlaceholders赋值Map,将Map中的值替换到AndroidMainifest中。
AndroidMainfest.xml中定义如下:
<manifest
xmlns:android="http://schemas.android.com/apk/res/android"
package="xxx" >
<application
android:name="xx.MainApp"
android:allowBackup="true"
android:hardwareAccelerated="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@android:style/Theme.Black.NoTitleBar.Fullscreen">
…
<meta-data
android:name="D_VERSION"
android:value="${D_VERSION_NAME}" />
…
application>
manifest>
Java代码中通过meta-data的对应value去配置为不同的版本,如F600P或F600,当然这部分代码需要在Java中实现对应的逻辑。
/**
* 通过key获取Application标签下meta-data的值
*
* @param context
* @param key
* @return
*/
public static String getApplicationMetaDataVal(Context context, String key) {
ApplicationInfo appInfo = null;
String msg = null;
try {
appInfo = context.getPackageManager().getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
msg = appInfo.metaData.getString(key);
} catch (NameNotFoundException e) {
e.printStackTrace();
}
return msg;
}
接下来再看转化为版本Version的代码,则部分代码并非必须的,相信看到这里已经明白了,我们是间接的用AndroidMainfest中的meta-data标签传递在build.gradle配置的值,从而达到让java代码加载不同版本的目的。
// 获取不同版本的对应配置参数
Version version = Version.F600P;
String versionName = AppUtils.getApplicationMetaDataVal(this, "D_VERSION");
if (versionName.equals(Version.F600P.getName())) {
version = Version.F600P;
} else if (versionName.equals(Version.F600.getName())) {
version = Version.F600;
} else if (versionName.equals(Version.F200P.getName())) {
version = Version.F200P;
} else if (versionName.equals(Version.F200.getName())) {
version = Version.F200;
}
Log.d("version", "v:" + version.getName());
versionConfig = VersionFactory.getVersionConfig(version);
android {
…
android.applicationVariants.all { variant ->
variant.outputs.each { output ->
def outputFile = output.outputFile
if (outputFile != null && outputFile.name.endsWith('.apk')) {
String flavorName = variant.productFlavors[0].name
//修改apk文件名
def fileName = "xxx-${flavorName}-${defaultConfig.versionName}-${defaultConfig.versionCode}-signed-${releaseTime()}.apk"
def fileParentDir
if (flavorName.startsWith("standard")) {
fileParentDir = flavorName.substring("standard".length(), flavorName.length());
} else if (flavorName.startsWith("neutral")) {
fileParentDir = flavorName.substring("neutral".length(), flavorName.length());
}
File dir = new File(outputFile.parent + File.separator + fileParentDir + File.separator)
if (!dir.exists()) {
dir.mkdirs()
}
output.outputFile = new File(dir, fileName)
}
}
}
…
}
xxx-${flavorName}-${defaultConfig.versionName}-${defaultConfig.versionCode}-signed-${releaseTime()}.apk
这一大段就是重命名后的apk文件名,最终生成的文件名为:
xxx-neutralF600P-v1.0.5-2455-signed-20170502.apk
很好懂,用GString的写法占位。其中后面还有一段的逻辑为把同一型号下标准版和中性版的app放到同一目录下,如F600P文件夹下,放入neutralF600P 和standardF600P app。
最后来看一下releaseTime()函数,作用为按照yyyyMMdd格式化当前时间,即格式化为年月日的形式。
def releaseTime() {
return new Date().format("yyyyMMdd",TimeZone.getTimeZone("UTC"))
}
以上步骤配置好了生成签名版程序的所有要素,最后在android studio命令行中敲入:
gradle aR
回车后任务开始进行,全部finish后到xx工程\xx\build\outputs\apk下取出apk即可。
详解:
之所以可以使用gradle aR命令是因为gradle支持这种简短的驼峰式写法,实际上和gradle assembleRelease命令是等价的。
另外,我们使用这个命令生成的apk全部为release版本,虽然通过gradle build和gradle assemble都可以生成我们想要的release版本,但gradle build同时生成了debug版本,这对于我们来说是多余的。