这种打包方式优点很明显:打包速度快的飞起,渠道越多优势越大;也有缺点,只适合多渠道包区别只有渠道号的不同,如果多个渠道需要自定义包名、应用名、资源、功能等的,还是需要使用多个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
没有评论:
发表评论