Android多渠道打包方案

Posted by Haiden on April 14, 2019

Android多渠道打包方案对比

1 packer-ng-plugin

打包原理:因为APK文件也是一个带签名信息的ZIP文件,根据 ZIP文件格式规范,每个ZIP文件的最后都必须有一个叫 Central Directory Record 的部分,这一部分包含一些元数据,它的末尾是ZIP文件的注释。注释包含Comment Length和File Comment两个字段,前者表示注释内容的长度,后者是注释的内容,正确修改这一部分不会对ZIP文件造成破坏,利用这个字段可以添加一些自定义的数据。

打包过程不涉及代码的重新编译,仅对文件末尾数据进行操作,速度比在Gradle配置productFlavors快。

1) 添加渠道信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
public static void writeZipComment(File file, String comment) throws IOException {
    if (hasZipCommentMagic(file)) {
        throw new MarketExistsException("Zip comment already exists, ignore.");
    }
    // {@see java.util.zip.ZipOutputStream.writeEND}
    byte[] data = comment.getBytes(UTF_8);
    final RandomAccessFile raf = new RandomAccessFile(file, "rw");
    raf.seek(file.length() - SHORT_LENGTH);
    // write zip comment length
    // (content field length + length field length + magic field length)
    writeShort(data.length + SHORT_LENGTH + MAGIC.length, raf);
    // write content
    writeBytes(data, raf);
    // write content length
    writeShort(data.length, raf);
    // write magic bytes
    writeBytes(MAGIC, raf);
    raf.close();
}

2) 读取渠道信息

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
public static String readZipComment(File file) throws IOException {
    RandomAccessFile raf = null;
    try {
        raf = new RandomAccessFile(file, "r");
        long index = raf.length();
        byte[] buffer = new byte[MAGIC.length];
        index -= MAGIC.length;
        // read magic bytes
        raf.seek(index);
        raf.readFully(buffer);
        // if magic bytes matched
        if (isMagicMatched(buffer)) {
            index -= SHORT_LENGTH;
            raf.seek(index);
            // read content length field
            int length = readShort(raf);
            if (length > 0) {
                index -= length;
                raf.seek(index);
                // read content bytes
                byte[] bytesComment = new byte[length];
                raf.readFully(bytesComment);
                return new String(bytesComment, UTF_8);
            } else {
                throw new MarketNotFoundException("Zip comment content not found");
            }
        } else {
            throw new MarketNotFoundException("Zip comment magic bytes not found");
        }
    } finally {
        if (raf != null) {
            raf.close();
        }
    }
}

2 walle

打包原理:通过在Apk中的APK Signature Block区块添加自定义的渠道信息来生成渠道包。

使用APK V2签名将在APK中心目录部分之前插入一个APK签名块到APK文件。在APK签名块内,v2签名和签名者身份信息存储在APK Signing Block中。v1和v2签名文件前后对比。

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

1
2
3
4
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

1)APK签名块(APK Signing Block)

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

2)APK Signing Block 格式

偏移量 字节数 描述
0 8 size of block 以字节(uint64), 实际长度为+8
8 8 键值对的起始标记,存储键值对的大小,也就是后面pair的,key+value 占用的byte
16 4+n key 为 4byte,value 为变化的
(4+n)+16 8 size of block 以字节为单位,与第一个字段(uint64)相同
(4+n)+24 16 magic “APK Sig Block 42”

3)查找APK Signing Block

首先通过ZIP End of Central Directory中存储的信息找出ZIP Central Directory数据偏移量,然后就可以找到APK Signing Block的位置。再去除特定的字节找出APK Signing Block的起始位置。