Android APP基于Android Studio多版本构建实战

版权声明:本文为博主原创文章,遵循 CC 4.0 by-sa 版权协议,转载请附上原文出处链接和本声明。
本文链接:https://blog.csdn.net/tyyj90/article/details/72608544

关于多版本构建,我们可以通过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会提示找不到这个命令,无法使用,这需要配置环境变量。
Android Studio的Terminal面板

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

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

(3).配置Gradle环境变量

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

(4).验证

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

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版本,这对于我们来说是多余的。
最后一步

展开阅读全文

没有更多推荐了,返回首页