版权声明:

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

作者:GavinCT

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

黎明前的黑暗

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

  1. <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文件

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

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

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

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

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

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

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

    1. # 获取当前目录中所有的apk源包
    2. src_apks = []
    3. # python3 : os.listdir()即可,这里使用兼容Python2的os.listdir('.')
    4. for file in os.listdir('.'):
    5. if os.path.isfile(file):
    6. extension = os.path.splitext(file)[1][1:]
    7. if extension in 'apk':
    8. src_apks.append(file)
  4. 遍历渠道号并写入apk。

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

    1. # file name (with extension)
    2. src_apk_file_name = os.path.basename(src_apk)
    3. # 分割文件名与后缀
    4. temp_list = os.path.splitext(src_apk_file_name)
    5. # name without extension
    6. src_apk_name = temp_list[0]
    7. # 后缀名,包含. 例如: ".apk "
    8. src_apk_extension = temp_list[1]
    9. # 创建生成目录,与文件名相关
    10. output_dir = 'output_' + src_apk_name + '/'
    11. # 目录不存在则创建
    12. if not os.path.exists(output_dir):
    13. os.mkdir(output_dir)
    14. # 遍历渠道号并创建对应渠道号的apk文件
    15. for line in lines:
    16. # 获取当前渠道号,因为从渠道文件中获得带有\n,所有strip一下
    17. target_channel = line.strip()
    18. # 拼接对应渠道号的apk
    19. target_apk = output_dir + src_apk_name + "-" + target_channel + src_apk_extension
    20. # 拷贝建立新apk
    21. shutil.copy(src_apk, target_apk)
    22. # zip获取新建立的apk文件
    23. zipped = zipfile.ZipFile(target_apk, 'a', zipfile.ZIP_DEFLATED)
    24. # 初始化渠道信息
    25. empty_channel_file = "META-INF/cztchannel_{channel}".format(channel = target_channel)
    26. # 写入渠道信息
    27. zipped.write(src_empty_file, empty_channel_file)
    28. # 关闭zip流
    29. 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"))

我的代码如下:

  1. /**
  2. * 从apk中获取版本信息
  3. * @param context
  4. * @param channelKey
  5. * @return
  6. */
  7. private static String getChannelFromApk(Context context, String channelKey) {
  8. //从apk包中获取
  9. ApplicationInfo appinfo = context.getApplicationInfo();
  10. String sourceDir = appinfo.sourceDir;
  11. //注意这里:默认放在meta-inf/里, 所以需要再拼接一下
  12. String key = "META-INF/" + channelKey;
  13. String ret = "";
  14. ZipFile zipfile = null;
  15. try {
  16. zipfile = new ZipFile(sourceDir);
  17. Enumeration<?> entries = zipfile.entries();
  18. while (entries.hasMoreElements()) {
  19. ZipEntry entry = ((ZipEntry) entries.nextElement());
  20. String entryName = entry.getName();
  21. if (entryName.startsWith(key)) {
  22. ret = entryName;
  23. break;
  24. }
  25. }
  26. } catch (IOException e) {
  27. e.printStackTrace();
  28. } finally {
  29. if (zipfile != null) {
  30. try {
  31. zipfile.close();
  32. } catch (IOException e) {
  33. e.printStackTrace();
  34. }
  35. }
  36. }
  37. String[] split = ret.split("_");
  38. String channel = "";
  39. if (split != null && split.length >= 2) {
  40. channel = ret.substring(split[0].length() + 1);
  41. }
  42. return channel;
  43. }

总结

使用这种方式打包,打包工作不再需要非得是安卓程序员。需要打包时,只要下载安装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. Opserver 初探三《服务器数据监控》

    用Opserver 怎么像zabbix一样监控服务器呢,查看github官方说明,Opserver可用于连接任何支持Bosun, Orion, or direct WMI监控数据. Opserver ...

  2. Postman—做各种类型的http接口测试

    首先,做接口测试前要有明确的接口文档,假设已经在PC上安装好了Postman. 1. 普通的以key-value传参的get请求 e.g. 获取用户信息 Get请求,写入url拼好参数,发送请求,查看 ...

  3. wap尝试调取app(网易新闻为例)

    <!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <title> ...

  4. libnetwork插件化网络功能

    Docker把网络跟存储这两部分的功能实现都以插件化形式剥离出来,允许用户通过指令来选择不同的后端实现.这也是Docker希望构建围绕着容器的强大生态系统的一些积极的尝试.剥离出来的独立容器网络项目叫 ...

  5. C#中的Dictionary类,默认key是区分大小写的

    在C#中定义一个Dictionary Dictionary<string,string> dictionary = new Dictionary<string,string>( ...

  6. android studio启动和项目编译问题

    第一次安装完成后,不要立刻启动,首先在Android Studio安装目录下的 bin 目录下,找到 idea.properties 文件,在文件最后追加disable.android.first.r ...

  7. Hive学习之Locking

    众所周知,数据库必须要能够支持并发.无论在任何时候,允许同一时刻,多个用户能够同时读取或写入.没有必要给用户提供API显示的获取锁,所以所有的锁都是隐式获取的. 在Hive中有两种类型的锁: 共享锁S ...

  8. [CQOI 2018]异或序列&[Codeforces 617E]XOR and Favorite Number

    Description 题库链接1 题库链接2 已知一个长度为 \(n\) 的整数数列 \(a_1,a_2,\cdots,a_n\) ,给定查询参数 \(l,r\) ,问在 \([l,r]\) 区间内 ...

  9. c#基础学习(0724)之可变参数、ref和out

    params可变参数,无论有几个参数,必须出现在参数列表的最后,可以为可变参数直接传递一个对应类型的数组 #region 可变参数 //1.如果方法有多个参数,可变参数可以作为最后一个参数 //2.可 ...

  10. 微信小程序头部栏实现

    效果如图: 也就是实现红色框的部分. wxml代码 <view class="header {{scrollDown?'scrolled':''}}"> <vie ...