这种打包方式优点很明显:打包速度快的飞起,渠道越多优势越大;也有缺点,只适合多渠道包区别只有渠道号的不同,如果多个渠道需要自定义包名、应用名、资源、功能等的,还是需要使用多个productFlavor 的方式。
幸运的是公司的产品不需要这么麻烦,因此研究了下使用gradle实现美团多渠道打包的方法
基本流程
现将流程分解下:
- 调用assemble任务生成apk
- 将apk解压
- 在apk的META-INFO目录下创建空文件:channel_{channelName}
- 解压后的目录重新打包为apk
第一步,没什么好说的,标准as项目生成的gradle脚本就够用了
第二步,apk解压到本地,解压文件可以使用如下自定义task:
copy {
from zipTree(srcFile)
into file(upzipDir)
}
第三步,根据渠道号,在解压后的apk目录下的META-INFO文件夹下创建空文件第四步,重新打包apk:
task("zip_" + variant.name + "_" + channelName, type: Zip)
from upzipDir
archiveName = targetArchiveName
destinationDir targetout
}
基本流程如上,下面给出具体实现:具体实现
首先自定义的构建输出目录在gradle.properties文件中配置:
# app/gradle.properties targetout=targetout然后渠道号采用读取json配置文件的方法,配置文件放在app项目根目录下,名称为,channels.json,示例如下:
{
"channels": [
{
"channelName": "xiaomi"
},
{
"channelName": "yingyongbao"
},
{
"channelName": "taobao"
}
]
}
下面是主要的脚本文件:
apply plugin: 'com.android.application'
android {
.......
buildType {
debug {
minifyEnabled false
}
release {
minifyEnabled true
zipAlignEnabled true
shrinkResources true
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
// 自定义的构建结果输出目录,构建前删除目录并重建
File target = new File(rootDir, project.targetout);
target.deleteDir()
target.mkdir()
// 读取json配置文件,转换为Channels类,类定义在脚本末尾
def channelsMap = new groovy.json.JsonSlurper().parse(file("channels.json"))
Channels channels = new Channels(channelsMap)
applicationVariants.all { variant ->
variant.assemble << {
variant.outputs.each { output ->
// 默认构建apk文件输出的路径,解压目录放在同级目录下
File oriFile = output.outputFile
String oriPath = oriFile.getAbsolutePath();
if (oriPath.endsWith(".apk")) {
oriPath = oriPath.substring(0, oriPath.length() - 4)
}
File upzipDir = new File(oriPath);
// 解压apk文件
copy {
from zipTree(oriFile)
into file(upzipDir)
}
// 挨个处理多渠道
channels.channels.each { channel ->
// 读取渠道名称
def channelName = channel.channelName
// 组装渠道文件名称,并创建空文件
def channelFileName = "channel_" + channelName
def dir = new File(upzipDir, "META-INF")
File channelFile = new File(dir, channelFileName)
channelFile.createNewFile()
// 定义重新压缩的apk的文件名称
String targetArchiveName = applicationId + "-v" + versionName + "-" + channelName + "-" + variant.buildType.name + ".apk"
// 重新压缩渠道包
task("zip_" + variant.name + "_" + channelName, type: Zip) {
from oriPath
archiveName = targetArchiveName
destinationDir target
}.execute()
// 删除之前创建的渠道空文件,便于下一个渠道包的生成
channelFile.delete()
}
}
}
}
}
// 单个渠道属性定义
class Channel {
String channelName;
}
// 渠道列表属性定义
class Channels {
List<channel> channels;
@Override
String toString() {
return "channels:" + channels
}
}
以上为多渠道打包脚本的实现,下面就是渠道号的获取了代码中获取渠道号
获取渠道号的代码片段:
try {
final ZipFile z = new ZipFile(context.getApplicationInfo().sourceDir);
Enumeration e = z.entries();
while (e.hasMoreElements()) {
final ZipEntry entry = e.nextElement();
if (entry.getName().startsWith("META-INF/channel_")) {
channelId = entry.getName().split("_")[1];
Log.e("tag", "渠道号: " + channelId);
break;
}
}
} catch (IOException e) {
e.printStack();
}
以上为整个过程,大家有更好的实现方案、更优雅的代码实现,欢迎留言共享,共同学习
参考:
gradle解压:http://mrhaki.blogspot.com/2012/06/gradle-goodness-unpacking-archive.html
json解析:https://dzone.com/articles/groovy-unmarshalling-json-to-a-specific-object
http://groovy-lang.org/json.html
没有评论:
发表评论