美团多渠道打包 总结

一.为什么用?

Android 7.0(Nougat)引入一项新的应用签名方案APK Signature Scheme v2,它是一个对全文件进行签名的方案,能提供更快的应用安装时间、对未授权APK文件的更改提供更多保护,在默认情况下,Android Gradle 2.2.0插件会使用APK Signature Scheme v2和传统签名方案来签署你的应用。

下面以 新的应用签名方案 来指APK Signature Scheme v2。

目前该方案不是强制性的,在 build.gradle 添加 v2SigningEnabled false ,就能使用传统签名方案来签署我们的应用(见下面的代码片段)。

  android {
    ...
    defaultConfig { ... }
    signingConfigs {
      release {
        storeFile file("myreleasekey.keystore")
        storePassword "password"
        keyAlias "MyReleaseKey"
        keyPassword "password"
        v2SigningEnabled false
      }
    }
  }

但新的应用签名方案有着良好的向后兼容性,能完全兼容低于Android 7.0(Nougat)的版本。对比旧签名方案,它有更快的验证速度和更安全的保护,因此新的应用签名方案可能会被采纳成一个强制配置,笔者认为现在有必要对现有的渠道包生成方式进行检查、升级或改造来支持新的应用签名方案。

新的签名方案对已有的渠道生成方案有什么影响呢?下图是新的应用签名方案和旧的签名方案的一个对比:

新的签名方案会在ZIP文件格式的 Central Directory 区块所在文件位置的前面添加一个APK Signing Block区块,下面按照ZIP文件的格式来分析新应用签名方案签名后的APK包。

整个APK(ZIP文件格式)会被分为以下四个区块:

  1. Contents of ZIP entries(from offset 0 until the start of APK Signing Block)
  2. APK Signing Block
  3. ZIP Central Directory
  4. ZIP End of Central Directory

新应用签名方案的签名信息会被保存在区块2(APK Signing Block)中, 而区块1(Contents of ZIP entries)、区块3(ZIP Central Directory)、区块4(ZIP End of Central Directory)是受保护的,在签名后任何对区块1、3、4的修改都逃不过新的应用签名方案的检查。

之前的渠道包生成方案是通过在META-INF目录下添加空文件,用空文件的名称来作为渠道的唯一标识,之前在META-INF下添加文件是不需要重新签名应用的,这样会节省不少打包的时间,从而提高打渠道包的速度。但在新的应用签名方案下META-INF已经被列入了保护区了,向META-INF添加空文件的方案会对区块1、3、4都会有影响,新应用签名方案签署的应用经过我们旧的生成渠道包方案处理后,在安装时会报以下错误:

Failure [INSTALL_PARSE_FAILED_NO_CERTIFICATES: 
Failed to collect certificates from base.apk: META-INF/CERT.SF indicates base.apk is signed using APK Signature Scheme v2, 
but no such signature was found. Signature stripped?]

目前另外一种比较流行的渠道包快速生成方案(往APK中添加ZIP Comment)也因为上述原因,无法在新的应用签名方案下进行正常工作。如果只有少量渠道包的场景下,这种耗时时长还能够勉强接受。但是目前我们有将近900个渠道,如果采用传统方式打完所有的渠道包需要近3个小时,这是不能接受的。


二.原理

新一代渠道包生成工具

到这里为止一个新的渠道包生成方案逐步清晰了起来,下面是新一代渠道包生成工具的描述:

  1. 对新的应用签名方案生成的APK包中的ID-value进行扩展,提供自定义ID-value(渠道信息),并保存在APK中
  2. 而APK在安装过程中进行的签名校验,是忽略我们添加的这个ID-value的,这样就能正常安装了
  3. 在App运行阶段,可以通过ZIP的EOCD(End of central directory)Central directory等结构中的信息(会涉及ZIP格式的相关知识,这里不做展开描述)找到我们自己添加的ID-value,从而实现获取渠道信息的功能

新一代渠道包生成工具完全是基于ZIP文件格式和APK Signing Block存储格式而构建,基于文件的二进制流进行处理,有着良好的处理速度和兼容性,能够满足不同的语言编写的要求,目前笔者采用的是Java+Groovy开发, 该工具主要有四部分组成:

  1. 用于写入ID-value信息的Java类库
  2. Gradle构建插件用来和Android的打包流程进行结合
  3. 用于读取ID-value信息的Java类库
  4. 用于供com.android.application使用的读取渠道信息的AAR

这样,每打一个渠道包只需复制一个APK,然后在APK中添加一个ID-value即可,这种打包方式速度非常快,对一个30M大小的APK包只需要100多毫秒(包含文件复制时间)就能生成一个渠道包,而在运行时获取渠道信息只需要大约几毫秒的时间。

这个项目我们取名为Walle(瓦力),已经开源,项目的Github地址是: https://github.com/Meituan-Dianping/walle (求Issue、PR、Star)。希望业内有类似需求的团队能够在APK Signature Scheme V2签名下愉快地生成渠道包,同时也期待大家一起对该项目进行完善和优化。


三.使用

Gradle插件使用方式

配置build.gradle

在位于项目的根目录 build.gradle 文件中添加Walle Gradle插件的依赖, 如下:

buildscript {
    dependencies {
        classpath 'com.meituan.android.walle:plugin:1.1.6'
    }
}

并在当前App的 build.gradle 文件中apply这个插件,并添加上用于读取渠道号的AAR

apply plugin: 'walle'

dependencies {
    compile 'com.meituan.android.walle:library:1.1.6'
}

配置插件

walle {
    // 指定渠道包的输出路径
    apkOutputFolder = new File("${project.buildDir}/outputs/channels");
    // 定制渠道包的APK的文件名称
    apkFileNameFormat = '${appName}-${packageName}-${channel}-${buildType}-v${versionName}-${versionCode}-${buildTime}.apk';
    // 渠道配置文件
    channelFile = new File("${project.getProjectDir()}/channel")
}

配置项具体解释:

  • apkOutputFolder:指定渠道包的输出路径, 默认值为new File("${project.buildDir}/outputs/apk")

  • apkFileNameFormat:定制渠道包的APK的文件名称, 默认值为'${appName}-${buildType}-${channel}.apk'
    可使用以下变量:

         projectName - 项目名字
         appName - App模块名字
         packageName - applicationId (App包名packageName)
         buildType - buildType (release/debug等)
         channel - channel名称 (对应渠道打包中的渠道名字)
         versionName - versionName (显示用的版本号)
         versionCode - versionCode (内部版本号)
         buildTime - buildTime (编译构建日期时间)
         fileSHA1 - fileSHA1 (最终APK文件的SHA1哈希值)
         flavorName - 编译构建 productFlavors 名
    
  • channelFile:包含渠道配置信息的文件路径。 具体内容格式详见:渠道配置文件示例,支持使用#号添加注释。

如何获取渠道信息

在需要渠道等信息时可以通过下面代码进行获取

String channel = WalleChannelReader.getChannel(this.getApplicationContext());

如何生成渠道包

生成渠道包的方式是和assemble${variantName}Channels指令结合,渠道包的生成目录默认存放在 build/outputs/apk/,也可以通过walle闭包中的apkOutputFolder参数来指定输出目录

用法示例:

  • 生成渠道包 ./gradlew clean assembleReleaseChannels
  • 支持 productFlavors ./gradlew clean assembleMeituanReleaseChannels

更多用法

插入额外信息

channelFile只支持渠道写入,如果想插入除渠道以外的其他信息,请在walle配置中使用configFile

walle {
    // 渠道&额外信息配置文件,与channelFile互斥
	configFile = new File("${project.getProjectDir()}/config.json")
}

configFile是包含渠道信息和额外信息的配置文件路径。
配置文件采用json格式,支持为每个channel单独配置额外的写入信息。具体内容格式详见:渠道&额外信息配置文件示例 。

注意:

  • 此配置项与channelFile功能互斥,开发者在使用时选择其一即可,两者都存在时configFile优先执行。
  • extraInfo 不要出现以channel为key的情况

而对应的渠道信息获取方式如下:

ChannelInfo channelInfo= WalleChannelReader.getChannelInfo(this.getApplicationContext());
if (channelInfo != null) {
   String channel = channelInfo.getChannel();
   Map<String, String> extraInfo = channelInfo.getExtraInfo();
}
// 或者也可以直接根据key获取
String value = WalleChannelReader.get(context, "buildtime");
临时生成某渠道包

我们推荐使用channelFile/configFile配置来生成渠道包,但有时也可能有临时生成渠道包需求,这时可以使用:

  • 生成单个渠道包: ./gradlew clean assembleReleaseChannels -PchannelList=meituan

  • 生成多个渠道包: ./gradlew clean assembleReleaseChannels -PchannelList=meituan,dianping

  • 生成渠道包&写入额外信息:

    ./gradlew clean assembleReleaseChannels -PchannelList=meituan -PextraInfo=buildtime:20161212,hash:xxxxxxx

    注意: 这里的extraInfo以key:value形式提供,多个以,分隔。

  • 使用临时channelFile生成渠道包: ./gradlew clean assembleReleaseChannels -PchannelFile=/Users/xx/Documents/channel

  • 使用临时configFile生成渠道包: ./gradlew clean assembleReleaseChannels -PconfigFile=/Users/xx/Documents/config.json

使用上述-P参数后,本次打包channelFile/configFile配置将会失效,其他配置仍然有效。 -PchannelList,-PchannelFile-PconfigFile三者不可同时使用。

四.我的例子

https://github.com/amsterly/JsWatcher

外部工具常用代码:

外部工具形式
使用外部工具查看apk的channel
java -jar walle-cli-all.jar show C:\Project\JsWatcher\app\release\app-alyyyyyyy-release_3gcn.apk

使用工具 根据channel配置文件 打包好的release APK 生成对应渠道的apk 目录与源apk相同
java -jar walle-cli-all.jar batch -f C:\Project\walle\app\channel  C:\Project\JsWatcher\app\release\app-alyyyyyyy-release.apk

你可能感兴趣的:(IDE)