版权声明:

欢迎转载,但请保留文章原始出处

作者:GavinCT

出处:http://www.cnblogs.com/ct2011/p/4152323.html

黎明前的黑暗

使用Ant或者Gradle来给程序进行多渠道批量打包,通常都是在manifest文件中写入一个meta标签:

<meta-data android:name="CHANNEL" android:value="xxx" />

meta的key值固定,通过循环改变meta中的value值来实现市场渠道的写入。

Ant批量打包实现相对麻烦,以前写的时候多亏了谦虚的天下-《App自动化之使用Ant编译项目多渠道打包》 。如果没有这篇帖子,真不知道当时Ant要折腾多少回才能写好。

Gradle作为新的安卓官方构建工具,有Google老大撑腰,它的批量打包实现会相对简单些。可以参考《迁移到Android Studio》。当然这里面有些指令过时了,例如:runProguard已经被minifyEnabled替代了。

以上两种都是传统的批量打包方式,他们最大的缺点就是打包时间长。

在前期渠道很少时这种方法还可以接受,但只要渠道稍微增多该方法就不再适用了,原因是每打一个包都要执行一遍构建过程,效率太低。(电脑比较烂,以前一般打包都要花费个30-40分钟。)

打包界的曙光

前几天看到美团的技术分享文档:《美团Android自动化之旅—生成渠道包》,其中第三种方式提到:

如果能直接修改apk的渠道号,而不需要再重新签名能节省不少打包的时间。幸运的是我们找到了这种方法。直接解压apk,解压后的根目录会有一个META-INF目录,如下图所示:



如果在META-INF目录内添加空文件,可以不用重新签名应用。因此,通过为不同渠道的应用添加不同的空文件,可以唯一标识一个渠道。

采用这种方式,每打一个渠道包只需复制一个apk,在META-INF中添加一个使用渠道号命名的空文件即可。

这种打包方式速度非常快,900多个渠道不到一分钟就能打完。

OK,到这里,思路就有了。

  1. 在META-INF中放置一个类似 channel_xxx 的空文件来标识市场。
  2. 在Java代码中解析这个文件名获取市场xxx即可。

由于文档中的代码实现较少,这里我来讲述一下我的实现。

我的实现

基于以上总结的美团思路,实现了一套自己的代码,方便引入到工程后实现这种打包方式。

代码在Github:GavinCT/AndroidMultiChannelBuildTool

Python工具实现

  1. 首先创建一个空文件,等待写入META-INF目录作为channel_xxx文件

    # 空文件 便于写入此空文件到apk包中作为channel文件
    src_empty_file = 'info/czt.txt'
    # 创建一个空文件(不存在则创建)
    f = open(src_empty_file, 'w')
    f.close()
  2. 获取渠道列表。

    考虑到渠道的更新不应该是程序员来做,因此在info文件夹下放置一个channel文件,便于不懂程序的人更新渠道。(每个渠道以换行结束)

    # 获取渠道列表
    channel_file = 'info/channel.txt'
    f = open(channel_file)
    lines = f.readlines()
    f.close()
  3. 找到初始apk

    考虑到现实中为了防止安装包过大,我们通常分为arm和x86两个版本,所以python中支持当前目录下放多个apk来进行打包。

    当然有人会说共用了一个channel文件,多个apk会生成相同市场的对应包。

    你也可以修改一下python,使不同的apk去找不同的channel文件进行打包。

    这里由于我的业务场景这样更方便,我就不修改了。

    # 获取当前目录中所有的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)
  4. 遍历渠道号并写入apk。

    多个apk只是for循环问题,我们来看单个apk生成多市场包的代码

    # file name (with extension)
    src_apk_file_name = os.path.basename(src_apk)
    # 分割文件名与后缀
    temp_list = os.path.splitext(src_apk_file_name)
    # name without extension
    src_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 lines:
    # 获取当前渠道号,因为从渠道文件中获得带有\n,所有strip一下
    target_channel = line.strip()
    # 拼接对应渠道号的apk
    target_apk = output_dir + src_apk_name + "-" + target_channel + src_apk_extension
    # 拷贝建立新apk
    shutil.copy(src_apk, target_apk)
    # zip获取新建立的apk文件
    zipped = zipfile.ZipFile(target_apk, 'a', zipfile.ZIP_DEFLATED)
    # 初始化渠道信息
    empty_channel_file = "META-INF/cztchannel_{channel}".format(channel = target_channel)
    # 写入渠道信息
    zipped.write(src_empty_file, empty_channel_file)
    # 关闭zip流
    zipped.close()

以上Python是属于现学现写,有什么可以优化的地方还请告知。

Java工具实现

Python帮我们向apk包中写入了channel信息,Java端当然也需要对应更改才能使用。

由于解析channel需要去apk也就是zip中去找文件,所以相对耗时一些。

因此在ChannelUtil.java中,会将找到的channel和对应versionCode存储在静态变量和SharedPreference中,保证本次甚至本版本中channel只从zip中获取一次。

在Java代码中读取空渠道文件名

从apk中获取channel,美团留下的代码if (entryName.startsWith("mtchannel"))是有问题的,应该采用if (entryName.startsWith("META-INF/mtchannel"))

我的代码如下:

/**
* 从apk中获取版本信息
* @param context
* @param channelKey
* @return
*/
private static String getChannelFromApk(Context context, String channelKey) {
//从apk包中获取
ApplicationInfo appinfo = context.getApplicationInfo();
String sourceDir = appinfo.sourceDir;
//注意这里:默认放在meta-inf/里, 所以需要再拼接一下
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;
}

总结

使用这种方式打包,打包工作不再需要非得是安卓程序员。需要打包时,只要下载安装Python环境,点击MultiChannelBuildTool.py执行即可。

那Gradle是不是没用了呢?

当然不是,Google老大为他做了这么多,怎么能说不用就不用呢?

他的用处在于实现订制,比如打包出x86和arm的包,或者打出手机包和适应平板的hd包,然后借助上面的工具生成多个市场,即完成了多种适配包多个市场的任务。

Gradle渠道订制的具体内容可以参见:《美团Android自动化之旅—适配渠道包》

还是美团的文档,还是熟悉的味道。在此感谢美团的分享。

常见问题答疑

这部分问题是由美团大神丁志虎在微博上答复的,摘录如下:

  • 这个方案没法解决不同渠道使用渠道自己SDK的问题,友盟的SDK提供了在代码中设置渠道的方式,所以再获取到渠道号后再调用SDK相关设置渠道的方法就可以了
  • apk用的是java那一套签名,放在META-INF文件夹里的文件原则上是不参与签名的。如果Google修改了apk的签名规则,这一套可能就不适用了。

Android批量打包提速 - 1分钟900个市场不是梦的更多相关文章

  1. Ant自动化打多渠道包,Android批量打包提速

    Eclipse用起来虽然方便,但是编译打包android项目还是比较慢,尤其将应用打包发布到各个渠道时,用Eclipse手动打包各种渠道包就有点不切实际了,这时候我们用到Ant帮我们自动编译打包了. ...

  2. Android 批量打包利器

    因为添加了渠道号,对应不同的渠道包,此时,动不动就几十个包,实在让人头疼,此时,需要引入自动打包功能. 首先,列举出援引的博客内容 美团Android自动化之旅—生成渠道包 http://tech.m ...

  3. Android批量打包-如何一秒内打完几百个apk渠道包

    在国内Android常用渠道可能多达几十个,如: 谷歌市场.腾讯应用宝.百度手机助手.91手机商城.360应用平台.豌豆荚.安卓市场.小米.魅族商店.oppo手机.联想乐商.中兴汇天地.华为.安智.应 ...

  4. 基于apktool项目的android批量打包工具,多平台支持

    好久木有写博客了,今天有点兴致就写一下,献上一个没怎么用的批量打包工具,python实现的,虽然说现在android的批量打包有一个很好的工具可以使用gradle,这个灰常牛叉的工具和android ...

  5. android批量打包

    http://blog.csdn.net/johnny901114/article/details/48714849

  6. Android几种常见的多渠道(批量)打包方式介绍

    多渠道打包,主要是为了统计不同的渠道上包的下载数量,渠道越多,我们需要打的包数量越多,这个时候,我们没法去使用单纯的手动打包去一个一个的生成不同的渠道包,我们需要更高效的打包方式. 声明渠道方式一: ...

  7. android ant 多渠道批量打包

    注:本文转载于:http://blog.csdn.net/zz7zz7zz/article/details/8915701 前言: 利用ant 可实现多渠道,批量打包. 正文: 思想:通过循环更改An ...

  8. 【Android开发经验】使用Ant批量打包Android应用全然指南

    本文章由Socks完毕.博客地址:http://blog.csdn.net/zhaokaiqiang1992 转载请说明. 折腾了一下午.百度了一下午,最终实现了使用Ant对Android应用的批量打 ...

  9. Android 自动编译、打包生成apk文件 4 - 多渠道批量打包

    相关文章列表: < Android 自动编译.打包生成apk文件 1 - 命令行方式> < Android 自动编译.打包生成apk文件 2 - 使用原生Ant方式 > < ...

随机推荐

  1. CS231n学习笔记-图像分类笔记(上篇)

    原文地址:智能单元 图像分类:所谓图像分类问题,就是已有固定的分类标签集合,然后对于输入的图像按照标签类别,将其打上标签. 下面先介绍一下一个简单的图像如何利用计算机进行分类: 例子:以下图为例,图像 ...

  2. js前端日期格式化处理

    js前端日期格式化处理 1.项目中时间返回值,很过时候为毫秒值,我们需要转换成 能够看懂的时间的格式: 例如: ​ yyyy-MM-dd HH:mm:ss 2.处理方法(处理方法有多种,可以传值到前端 ...

  3. [转] 使用 MVC 5 的 EF6 Code First 入门 系列

    译文:http://www.cnblogs.com/Bce-/category/573301.html 原文:http://www.asp.net/mvc/overview/getting-start ...

  4. Okhttp3上传多张图片同时传递参数

    之前上传图片都是直接将图片转化为io流传给服务器,没有用框架传图片. 最近做项目,打算换个方法上传图片. Android发展到现在,Okhttp显得越来越重要,所以,这次我选择用Okhttp上传图片. ...

  5. OpenGL11-绘制汉字最高效方法(使用Freetype)(代码已更新)

    最新版本,之前的版本有些文件没有打包 视频教程请关注 http://edu.csdn.net/lecturer/lecturer_detail?lecturer_id=440 OpenGL本身并没有绘 ...

  6. C/C++练习题(一)

    1. volatile 关键字在 C++ 中的性能和 C 的一样? 作用是一样的,但是其内部实现原理可能不同. 2. scanf 格式化输入是怎么赋值的? 由于scanf输入的数据个数是不定的,从键盘 ...

  7. JQCloud: 一个前端生成美化标签云的简单JQuery插件

    本文原文地址:https://jiang-hao.com/articles/2018/blog-JQCloud.html 因为博客需要,发现了一个生成美化简约风格的标签云的JQuery插件. 官网地址 ...

  8. lucene源码分析(8)MergeScheduler

    1.使用IndexWriter.java mergeScheduler.merge(this, MergeTrigger.EXPLICIT, newMergesFound); 2.定义MergeSch ...

  9. 开始使用Newbe.Pct-Web自动化测试

    前篇介绍了,使用 Newbe.Pct 之前的准备工作.本篇将开始介绍如何使用本项目运行第一个测试用例. 阅前语 从本篇开始,读者将会接触到使用一些代码.希望读者不必纠结于语法本身.出现代码的地方都会伴 ...

  10. SVN服务器搭建和使用以及冲突解决、用户密码修改

    Subversion是优秀的版本控制工具,其具体的的优点和详细介绍,这里就不再多说. 首先来下载和搭建SVN服务器. 现在Subversion已经迁移到apache网站上了,下载地址: http:// ...