@linux1s1s
2016-06-17T01:19:16.000000Z
字数 3765
阅读 3678
AndroidBuild
我们先来回顾一下通过Ant或者Gradle进行多渠道批量打包,通常是在AndroidManifest中配置:
<meta-data android:name="CHANNEL" android:value="xxx" />
meta-data通过配置value来动态改变渠道名称,然后我们可以在代码中这样去获取Channel
private String getChannelNameFromManifest(){try {return (String) mContext.getPackageManager().getApplicationInfo(mContext.getPackageName(), PackageManager.GET_META_DATA).metaData.get("CHANNEL");} catch (NameNotFoundException e) {e.printStackTrace();}}
Ant 在多渠道包上配置起来相对麻烦一点,如果你感兴趣的话可以参考这个博客 谦虚的天下-《App自动化之使用Ant编译项目多渠道打包》。
而Gradle作为Android官方构建工具,批量打包自然会相对简单一点,就像下面这样简单配置一下即可:
/*** -------------------------------------------------------* Flavors* --------------------------------------------------------*/productFlavors {intl {applicationId "com.xxx.x1"versionName createVersionName(versionNameInit)manifestPlaceholders = [deeplinkScheme: "x1"]}gloav {applicationId "com.xxx.x2"versionName createVersionName(versionNameInit)manifestPlaceholders = [deeplinkScheme: "x2"]}}
配置可以如此简单,但是批量打包还是绕不过从新构建这个过程,用一句通俗的话说就是,如果我需要1000个渠道包,每个渠道包需要5分钟,那么批量完成这些包需要5000分钟,Android在开发Gradle的时候可能完全想不到这个问题,因为除了天朝有这么多渠道市场外,其他国家都是望其项背,啰嗦几句后,咱们还是得面对现实,解决这个耗时的打包问题。
这里从最简单的情况说起,如果这么多渠道仅仅是区分不同的Channel,其他都相同。如果还有别的细微区别,这里暂且不考虑,话说回来,即使是这些细微的区别可能也仅仅是个别渠道,所以最笨的解决方法是从新构建这些个别渠道,其他都可以通过捷径来解决。
这个捷径是什么呢,当然是避免从新构建Build,如果不从新构建那么如果做到区分不同的渠道呢?
我们先来看一下APK的构造

这个压缩目录中META-INF目录中的文件原则上是不参与签名的,所以我们可以通过在该文件中放入Channel标示而逃过从新构建,这样我们就可以直接通过文件操作来完成批量打包,而文件的复制解压压缩操作所消耗的时间和从新构建消耗的时间相比,简直可以忽略不计,上面就是整个打包方案的核心思想,接下来我们来实施上面的核心思想。
在META-INF这个目录中添加一个以xxx_channelName_channelId命名的空文件,以xiaomi渠道举个例子如下所示:

然后再将解压后并添加相应文件的目录重新压缩为.apk格式的文件即可,上面的工作完成了,接下来就是批量添加文件过程了,这里给出Python脚本以供参考
#!/usr/bin/python# coding=utf-8import zipfileimport shutilimport os# 空文件 便于写入此空文件到apk包中作为channel文件src_empty_file = 'xxxxx.txt'#创建一个空文件(不存在则创建)with open(src_empty_file, 'w') as f:pass# 获取当前目录中所有的apk源包src_apks = []# python3 : os.listdir()即可,这里使用兼容Python2的os.listdir('.')for file in os.listdir('.'):if os.path.isfile(file):extension = os.path.splitext(file)[1][1:]if extension in 'apk':src_apks.append(file)# 获取渠道列表channel_file = 'channelConfig.txt'with open(channel_file) as f:for src_apk in src_apks:# file name (with extension)src_apk_file_name = os.path.basename(src_apk)# 分割文件名与后缀temp_list = os.path.splitext(src_apk_file_name)# name without extensionsrc_apk_name = temp_list[0]# 后缀名,包含. 例如: ".apk "src_apk_extension = temp_list[1]# 创建生成目录,与文件名相关output_dir = 'output_' + src_apk_name + '/'# 目录不存在则创建if not os.path.exists(output_dir):os.mkdir(output_dir)# 遍历渠道号并创建对应渠道号的apk文件for line in f.readlines():# 获取当前渠道号,因为从渠道文件中获得带有\n,所有strip一下target_channel = line.strip()# 获取渠道号,从中分离出渠道name和渠道idtarget_channel_split = target_channel.split('_')target_channel_name = target_channel_split[0]#target_channel_id = target_channel_split[1]# 拼接对应渠道号的apktarget_apk = output_dir + src_apk_name + '-release-' + target_channel_name + src_apk_extension# 拷贝建立新apkshutil.copy(src_apk, target_apk)# zip获取新建立的apk文件zipped = zipfile.ZipFile(target_apk, 'a', zipfile.ZIP_DEFLATED)# 初始化渠道信息empty_channel_file = "META-INF/xxxx_{channel}".format(channel = target_channel)# 写入渠道信息zipped.write(src_empty_file, empty_channel_file)# 关闭zip流zipped.close()
最后在代码中获取这个channel标示即可。
private String getChannelFromApk(String channelKey) {ApplicationInfo appinfo = mContext.getApplicationInfo();String sourceDir = appinfo.sourceDir;String key = "META-INF/" + channelKey;String ret = "";ZipFile zipfile = null;try {zipfile = new ZipFile(sourceDir);Enumeration<?> entries = zipfile.entries();while (entries.hasMoreElements()) {ZipEntry entry = ((ZipEntry) entries.nextElement());String entryName = entry.getName();if (entryName.startsWith(key)) {ret = entryName;break;}}} catch (IOException e) {e.printStackTrace();} finally {if (zipfile != null) {try {zipfile.close();} catch (IOException e) {e.printStackTrace();}}}String[] split = ret.split("_");String channel = "";if (split != null && split.length >= 2) {channel = ret.substring(split[0].length() + 1);}return channel;}
基本上到这里就OK了,如果问题,欢迎讨论。
