另辟蹊径实现Android多渠道打包
Contents
要先说明的是本文说的“渠道”单指在AndroidManifest.xml
用<meta-data>
定义的一个标识字符串(如友盟统计)。在代码或者通过其他文件定义的方式殊途同归。
说起 Android 多渠道打包,真是八仙过海各显神通:有手动一个个耐心打包的,有用Ant
或Maven
重复跑编译任务的,有用apktool解包后再修改重打包的,有在build.gradle定义一堆flavor
的,乃至有通过apk里META-INF/
下的空文件来定义渠道的。
上述方法各有优劣,在这里就不一一赘述了。
本文要介绍的是另一种方法:直接修改APK中的AndroidManifest.xml
。
上述种种,说白了都是围绕着如何修改AndroidManifest.xml
,如何重打包或是重编译。介绍的这个方法也不外如是,只是无需重打包重编译而已。
首先得知道一点,APK中的AndroidMainfest.xml
,解压出来用文本编辑器可是不能直接打开的,它是aapt
生成的一个二进制的xml格式(被称为AXML
),得用其他工具(如apktool)先解析出来。所以问题来了,如何直接修改这个 AXML 文件?
如何修改AXML中渠道名
想要修改一个文件,你得先了解它的格式。AXML文件格式其实早已有人研究,如:《发布C语言的Android binary XML(AXML)解析代码》,
《AndroidManifest Ambiguity方案原理及代码》。
这里就直接引用结论了:string
在AXML中是存放在StringChunk
中的;string
都是UTF-16编码的;如果需要往AXML中新增string是比较麻烦的(牵一发而动全身…);为了4字节对齐string数据块末尾可能被填充数个0x00…
综上结论,可知,<meta-data>
定义的渠道值是UTF-16编码的string,并且可能被填充数个0x00。
那么为了方便后期修改,我们可以先编译的一个特殊的“占位渠道包”,这个包的渠道名是一个占位字符串,而这个字符串在AXML占的数据块长度能适应所有渠道名的长度。假设一个占位字符串长度16,那么它自然可以被个数小于16的任意字符串所替代,如占位字符串’abcdefghijklmnop’,渠道有’xxxx’,’abcdef789’…
通过这个特殊的渠道包,我们就能够生成所有渠道包。
通过占位渠道包生成其他渠道包
大致步骤如下:
- 解压这个占位渠道包A中的
AndroidManifest.xml
- 用真正的渠道名替换
AndroidManifest.xml
中的占位字符串 - 拷贝一份新的占位渠道包B,删除掉
META-INF/*
和AndroidManifest.xml
- 将修改后的
AndroidManifest.xml
重压缩到新的包B中 - 重命名渠道包B,并签名
- zipalign
- 完成一个渠道
如果你的渠道列表实在非常的多,你大概需要用多线程来优化这个步骤吧!
优劣
这个方法的优点在于:
- 快,比所有需要重编译代码的方法快(包括apktool重打包、gradle定义flavor等);
- 不依赖于第三方工具(都是自己写的实现脚本,算第三方不…)
缺点在于:
- 要重新签名(倒也算不上什么缺点);
- 需要注意占位字符串的长度不要太短了= =
###实现脚本
我实现的版本这里就不献丑了,等你来完善吧!已经放到github上了 https://github.com/Bilibili/apk-channelization
核心代码如下:
python 替换AXML中的字符串:
(注:修改自 https://github.com/wanchouchou/playWithAXML)
1 |
|
签名APK命令形如下:
jarsigner -verbose -sigalg SHA1withRSA -digestalg SHA1 -keystore ~/.android/debug.keystore -storepass android -keypass android path/to/channel.apk AndroidDebugKey
完。
本文思路源于@某因幡,向这只安静的兔子致敬~~