关于多版本构建,我们可以通过buildTypes来新增构建类型,一般而言这里也不需要自行定义,默认会生成debug和release两种类型。

重点在于使用productFlavors生成不同“风味”的版本,我们可以构建标准版和中性版APP,这在企业应用中非常普遍,中性版本不含有logo信息,可再次贴牌等。同时应该在release版本中关闭log输出。

我这里的区别为中性版assets/defaultLogo/logo_default.png和标准版的资源是不一样的,所以需要创建对应的productFlavor文件夹,放入需要替换的logo_default.png资源。同时不论是中性版还是标准版还有四种机型需要适配(F600P、F600、F200P、F200),也就是说我们需要生成8个版本的app。

1.目标


生成多个不同“风味”的版本很容易,只要定义productFlavors即可。但目标应该更进一步,通过只敲一次命令行生成所有的标准版和中性版release app,并且按照我们的命令格式重命名apk文件,最后只需要到build/outputs/apk拿出对应的安装包。

2.使用Gradle命令行


(1).查看gradle命令是否可用

默认情况下打开Android Studio的Terminal面板,键入gradle -version会提示找不到这个命令,无法使用,这需要配置环境变量。

(2).查看Android Studio自带Gradle安装位置

在File-Settings中查看Gradle home(Android Studio安装后会自带一个Gradle,就在其安装目录gradle下)所在位置。

(3).配置Gradle环境变量

建议使用用户变量,只有当前用户可用。添加一个变量名为GRADLE_HOME、变量值为E:/as/gradle/gradle-2.14.1的环境变量。然后编辑path环境变量,新建一行,并填入%GRADLE_HOME%/bin/即可。

(4).验证

再次在Terminal中输入gradle –version命令后回车。出现下面的gradle版本信息,就说明gradle构建工具在命令行方式下已经正常工作。

3.关闭log开关


当然前提是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);

4.配置正式签名


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下引用即可。

5.配置productFlavors


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中定义如下:

<?xml version="1.0" encoding="utf-8"?><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);

6.定制重命名逻辑


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"))}

7.最后一步


以上步骤配置好了生成签名版程序的所有要素,最后在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版本,这对于我们来说是多余的。

更多相关文章

  1. GitHub 标星 2.5K+!教你通过玩游戏的方式学习 VIM!
  2. 如何在后台运行Linux命令?
  3. No.11 使用firewall配置的防火墙策略的生效模式
  4. 理解Android(安卓)Support Library
  5. 编译WebRTC之Android版本(AppRTC工程编译)
  6. 【Android】ImageView设置背景图片报错:Error inflating class Im
  7. Linux下Android手机刷机指南
  8. Android中对sd卡的读写权限问题
  9. Android(安卓)编译命令

随机推荐

  1. Android(安卓)手机共享笔记本Wifi
  2. Android中滑屏初探 ---- scrollTo 以及 s
  3. JDK8的安装及win10配置JDK8的环境变量
  4. Android Paging组件Demo
  5. android感应器Senor
  6. 【Android】Android 相关下载
  7. android上一些方法的区别和用法的注意事
  8. IOS 5编程 -2 -准备工作。
  9. 2011年Android Camera学习笔记之一
  10. 使用sencha cmd创建android应用