【转载】900多个渠道包1分钟打包完成!

原文链接:美团Android自动化之旅—生成渠道包

概述

  • 每当发新版本时,美团团购Android客户端会被分发到各个应用市场,比如豌豆荚,360手机助手等。为了统计这些市场的效果(活跃数,下单数等),需要有一种方法来唯一标识它们。

  • 团购客户端目前通过渠道号(channel)来区分不同的市场,代码中使用Config.channel变量记录该渠道号。比如,豌豆荚市场中美团应用的渠道号是wandoujia,360手机助手中美团应用的渠道号为qihu360。客户端访问API时会在请求参数中带上渠道号,以便后台接下来计算不同渠道的效果。

  • 每次发版时,市场部会提供一个渠道列表,Android RD会根据这些渠道相应地生成等量的渠道包。随着渠道越来越多(截止本文写作时已有900多个渠道),客户端打渠道包的方式也一直在演进,本文接下来就详细介绍美团应用的打包之旅。

Maven

Maven是一个软件项目管理和自动构建工具,配合使用android-maven-plugin插件,以及maven-resources-plugin插件可以很方便的生成渠道包,下面简要介绍下打包过程,更多Maven以及插件的使用方法请参考相关文档。

  • 首先,在AndroidManifest.xml的<application>节点中添加如下<meta-data>元素,用来定义渠道的来源:

      <!-- 使用Maven打包时会用具体的渠道号替换掉${channel} -->
      <meta-data
      android:name="channel"
      android:value="${channel}" />
      定义好渠道来源后,接下来就可以在程序启动时读取渠道号了:
    
      private String getChannel(Context context) {
          try {
              PackageManager pm = context.getPackageManager();
              ApplicationInfo appInfo = pm.getApplicationInfo(context.getPackageName(), PackageManager.GET_META_DATA);
              return appInfo.metaData.getString("channel");
          } catch (PackageManager.NameNotFoundException ignored) {
      }
      return "";
    
      }
    
  • 要替换AndroidManifest.xml文件定义的渠道号,还需要在pom.xml文件中配置Resources插件:

      <resources>           
          <resource>
              <directory>${project.basedir}</directory>
              <filtering>true</filtering>
              <targetPath>${project.build.directory}/filtered-manifest</targetPath>
              <includes>
                  <include>AndroidManifest.xml</include>
              </includes>
          </resource>
      </resources>
    
  • 准备工作已经完成,现在需要的就是实际的渠道号了。下面的脚本会遍历渠道列表,逐个替换并打包:

      #!/bin/bash
    
      package(){
          while read line
          do
              mvn clean
              mvn  -Dchannel=$line package
          done < $1
      }
    
      package $1
    

在前期渠道很少时这种方法还可以接受,但只要渠道稍微增多该方法就不再适用了,原因是每打一个包都要执行一遍构建过程,效率太低。

apktool

apktool是一个逆向工程工具,可以用它解码(decode)并修改apk中的资源。接下来详细介绍如何使用apktool生成渠道包。

  • 前期工作和用Maven打包一样,也需要在AndroidManifest.xml文件中定义<meta-data>元素,并在应用启动的时候读取清单文件中的渠道号。具体请参考上面的代码。

  • 和Maven不一样的是,每次打包时不再需要重新构建项目。打包时,只需生成一个apk,然后在该apk的基础上生成其他渠道包即可。

  • 首先,使用apktool decode应用程序,在终端中输入如下命令:

      apktool d your_original_apk build
    
  • 上面的命令会在build目录中decode应用文件,decode完成后的目录如下:

目录图片.jpg
  • 接下来,替换AndroidManifest.xml文件中定义的渠道号,下面是一段python脚本:

      import re
    
      def replace_channel(channel, manifest):
      pattern = r'(<meta-data\s+android:name="channel"\s+android:value=")(\S+)("\s+/>)'
      replacement = r"\g<1>{channel}\g<3>".format(channel=channel)
      return re.sub(pattern, replacement, manifest)
    
  • 然后,使用apktool构建未签名的apk:

      apktool b build your_unsigned_apk
    
  • 最后,使用jarsigner重新签名apk:

      jarsigner -sigalg MD5withRSA -digestalg SHA1 -keystore your_keystore_path -storepass your_storepass -signedjar your_signed_apk, your_unsigned_apk, your_alias
    

上面就是使用apktool打包的方法,通过使用脚本可以批量地生成渠道包。不像Maven,每打一个包都需要执行一次构建过程,该方法只需构建一次,大大节省了时间。

但是好景不长,我们的渠道包越来越多,目前已有近900个渠道,打完所有的渠道包需要近3个小时。有没有更快的打包方式呢?且看下节。

META-INF

  • 如果能直接修改apk的渠道号,而不需要再重新签名能节省不少打包的时间。幸运的是我们找到了这种方法。
    直接解压apk,解压后的根目录会有一个META-INF目录,如下图所示:
  • 如果在META-INF目录内添加空文件,可以不用重新签名应用。因此,通过为不同渠道的应用添加不同的空文件,可以唯一标识一个渠道。

  • 下面的python代码用来给apk添加空的渠道文件,渠道名的前缀为mtchannel_:

      import zipfile
      zipped = zipfile.ZipFile(your_apk, 'a', zipfile.ZIP_DEFLATED) 
      empty_channel_file = "META-INF/mtchannel_{channel}".format(channel=your_channel)
      zipped.write(your_empty_file, empty_channel_file)
    
  • 添加完空渠道文件后的目录,META-INFO目录多了一个名为mtchannel_meituan的空文件:

  • 接下来就可以在Java代码中读取空渠道文件名了:

      public static String getChannel(Context context) {
          ApplicationInfo appinfo = context.getApplicationInfo();
          String sourceDir = appinfo.sourceDir;
          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("mtchannel")) {
                      ret = entryName;
                      break;
                  }
              }
          } catch (IOException e) {
              e.printStackTrace();
          } finally {
              if (zipfile != null) {
                  try {
                      zipfile.close();
                  } catch (IOException e) {
                      e.printStackTrace();
                  }
              }
          }
    
          String[] split = ret.split("_");
          if (split != null && split.length >= 2) {
              return ret.substring(split[0].length() + 1);
    
          } else {
              return "";
          }
      }
    

这样,每打一个渠道包只需复制一个apk,在META-INF中添加一个使用渠道号命名的空文件即可。这种打包方式速度非常快,900多个渠道不到一分钟就能打完。

总结

  • **上面总共介绍了三种打渠道包的方式。目前,Android团队打包基本使用第三种方式,完成了打包的自动化,解放了工程师的生产力,善哉善哉。 **

  • **打包的问题解决了,但有时候还需要为不同的渠道定制不同的APK。下一讲会介绍Android构建利器Gradle,以及如何使用Gradle定制渠道包,敬请期待。 **

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容

  • 原文链接 每当发新版本时,美团团购Android客户端会被分发到各个应用市场,比如豌豆荚,360手机助手等。为了统...
    高伟同学阅读 693评论 0 9
  • Spring Cloud为开发人员提供了快速构建分布式系统中一些常见模式的工具(例如配置管理,服务发现,断路器,智...
    卡卡罗2017阅读 134,579评论 18 139
  • Android市场的渠道分散已不是什么新鲜事,但如何高效打包仍是令许多开发者头疼的问题。本篇文章着重介绍了目前最新...
    _曾胖子阅读 1,912评论 1 10
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,368评论 25 707
  • 今年注定是一个独特的春节。 因为春节那天我正好在赶回家的火车上。 在中国最南方工作的我,要穿越大半个中国去大西北过...
    苏小昨阅读 349评论 1 2