Android 多渠道打包
Android 其实并没有多渠道的概念,所谓的渠道号是人为添加以识别应用市场渠道的。
Android Gradle 原生多渠道打包
原理
虽然 Android 本身不支持渠道号,但是 Android-Gradle 插件有维度的概念,通过维度可以实现模拟多渠道打包。
如下面的例子:
flavorDimensions "channel", "version"
productFlavors {
dji {
dimension 'channel'
buildConfigField 'String','channel_key','"dji_sign"'
}
'360' {
dimension 'channel'
buildConfigField 'String','channel_key','"360_sign"'
}
free {
dimension 'version'
}
vip {
dimension 'version'
}
}
通过 productFlavors
与 dimension
的组合关系,上面的例子可以打出如下的多种组合包:
djifreedebug、djivipdebug、djifreerelease、djiviprelease、360...
buildConfigField
属性可以给特殊维度设置独有变量,如上述例子中,dji 维度的包会生成一个值为 "dji_sign" 的 channel_key 变量,而 360 维度的包则会生成一个值为 "360_sign" 的 channel_key 变量。
buildConfigField
顾名思义是给 BuildConfig 添加不同维度下的变量,与之作用类似的还有 manifestPlaceholders
,从名字可以看出它是给 AndroidManifest 添加不同维度下的占位符。关于这俩个 Gradle 属性的详细使用规则这里不做详细介绍,各位可以自行 Google。
实现
Module - build.gradle - android 模块下,添加如下配置:
flavorDimensions "channel"
productFlavors {
dji {
dimension "channel"
}
baidu {
dimension "channel"
}
xiaomi {
dimension "channel"
}
}
productFlavors.all {
flavor -> manifestPlaceholders.put("channel", name)
}
manifestPlaceholders
创建了一个名为 'channel',值为维度名的占位符,然后在 AndroidManifest 配置(引用方式:${占位符}
)该占位符,以表达渠道的概念:
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
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>
<meta-data
android:name="channel"
android:value="${channel}" />
</application>
然后控制台执行 ./gradlew build
,就会打出所有维度(渠道)的包。
用下面的方式获取 channel 的值上报。
public String parseChannel() {
String channel = null;
try {
ApplicationInfo appInfo = getPackageManager().getApplicationInfo(getPackageName(), GET_META_DATA);
Bundle metaData = appInfo.metaData;
channel = metaData.getString("channel");
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}
return channel;
}
总结
原生 Gradle 支持,缺点是工程量大、速度慢。
第三方 Gradle 插件多渠道打包
这里推荐使用 packer-ng-plugin
原理
APK 文件就是一个带签名信息的 ZIP 文件,根据 ZIP 文件格式规范,每个 ZIP 文件的最后都必须有一个叫 Central Directory Record 的部分,这个 CDR 的最后部分叫"end of central directory record",这一部分包含一些元数据,它的末尾是 ZIP 文件的注释。注释包含 Comment Length 和 File Comment 两个字段,前者表示注释内容的长度,后者是注释的内容,正确修改这一部分不会对 ZIP 文件造成破坏,利用这个字段,我们可以添加一些自定义的数据,PackerNg 项目就是在这里添加和读取渠道信息。
实现
根目录 build.gradle 添加:
buildscript {
......
dependencies{
// add packer-ng
classpath 'com.mcxiaoke.gradle:packer-ng:1.0.9'
}
}
module 模块 build.gradle 添加:
apply plugin: 'com.android.application'
apply plugin: 'packer' //1.
android {
...
defaultConfig {
...
}
signingConfigs {
release {
storeFile file("你的签名地址")
storePassword "你的签名密码"
keyAlias "你的签名别名"
keyPassword "你的签名密码"
v2SigningEnabled false //2.一定要把v2签名关掉
}
debug {
storeFile file("你的签名地址")
storePassword "你的签名密码"
keyAlias "你的签名别名"
keyPassword "你的签名密码"
v2SigningEnabled false //一定要把v2签名关掉
}
}
buildTypes {
release {
...
signingConfig signingConfigs.release //3.
}
debug {
...
signingConfig signingConfigs.debug
}
}
}
dependencies {
...
implementation 'com.mcxiaoke.gradle:packer-helper:1.0.9' //4.
}
点击同步,代码中获取渠道号:
String market = PackerNg.getMarket(Context);
配置完毕,开始打包。
打包方式有俩种,这里只介绍控制台打包,其余参见 Github。
在项目根目录下创建 markets.txt 文件,文件内容如下:
google#谷歌渠道
xiaomi#小米渠道
huawei#华为渠道
每行一个渠道,#
号表示注释。
然后控制台输入如下命令打包(执行命令前建议运行一次项目,确认无误再执行):
./gradlew -Pmarket=markets.txt clean apkRelease
打包成功后,可在 根目录/build/archives
看到渠道包文件(注意不是 module 下的 build 文件夹)。
总结
packer-ng-plugin 插件通过修改复制 zip(apk)实现了快速打包,缺点是兼容性没有原生好,可能存在适配问题。
其它多渠道打包方式
除此之外还有其余快速打包方式,如通过 python 快速修改 apk -- AndroidManifest -- meta_data 属性,然后重签名对齐;如通过美团网批量打包工具 walle,这里仅提供思路和方向,具体项目选择合适的打包方式即可。