Matrix 是微信终端自研和正在使用的一套 APM(应用性能管理)系统。git地址:https://github.com/Tencent/matrix
Matrix-ApkChecker 作为 Matrix 系统的一部分,是针对 android 安装包的分析检测工具,根据一系列设定好的规则检测 apk 是否存在特定的问题。但微信没有对其开源,它是以一个jar包的形式提供使用,可以用在持续集成系统里面用于分析排查问题并输出较为详细的检测结果报告。下面记录一下这个工具是从哪些角度分析/哪些技术手段分析问题的。
分析apk包的角度
- 读取 manifest 的信息
- 从 AndroidManifest.xml 文件中读取 apk 的全局信息,如 packageName、versionCode 等
- 按文件大小排序列出 apk 中包含的文件
- 列出超过一定大小的文件,可按文件后缀过滤,并且按文件大小排序,这样作展示会更直观
- 统计方法数
- 统计 dex 包含的方法数, 并将输出结果按照类名 (class) 或者包名 (package) 来分组
- 检查是否经过了资源混淆
- 检查 apk 是否经过了资源混淆,微信团队认为,混淆可以缩减包体积(其实混淆本身对包体积影响微乎其微,但混淆工具一般会自带压缩功能,可以了解一下微信团队的混淆工具AndResGuard)
- 搜索不含 alpha 通道的 png 文件
- alpha通道是用于图像透明/半透明显示的,如果png图片不含alpha通道,则不需要使用png,用jpg更节约空间
- 检查是否包含多个ABI版本的动态库
- so 文件的大小可能会在 apk 文件大小中占很大的比例,可以考虑在 apk 中只包含一个 ABI 版本的动态库
- 搜索未经压缩的文件类型
- 没有压缩的文件当然要考虑压缩~
- 统计apk中包含的R类以及R类中的 field count
- 编译之后,代码中对资源的引用都会优化成 int 常量,除了 R.styleable 之外,其他的 R 类其实都可以删除
- 搜索冗余的文件
- 对于两个内容完全相同的文件,应该去冗余
- 检查是否有多个动态库静态链接了 STL
- 如果有多个动态库都依赖了 STL ,应该采用动态链接的方式而非多个动态库都去静态链接 STL
- 搜索 apk 中包含的无用资源
- apk 中未经使用到的资源,应该予以删除
- 搜索apk中包含的无用 assets 文件
- apk 中未经使用的 assets 文件,应该予以删除(注:assets也是资源文件夹,相对于res来说,它不会生成R文件索引)
- 搜索 apk 中未经裁剪的动态库文件
- 动态库经过裁剪之后,文件大小通常会减小很多,一般来讲可以从几个方面考虑,不需要的字体/语言,或者可以裁剪的图片资源等,比如说字节有些sdk/so团队会为了适配海外app而加入多语言支持,但是如果自己的业务线不需要支持海外业务完全可以把语言部分裁掉
对应技术选型与实现

ManifestAnalyzeTask 用于读取 AndroidManifest.xml 中的信息,如:packageName、verisonCode、clientVersion 等。
- 实现方法:利用 ApkTool 中的 AXmlResourceParser 来解析二进制的 AndroidManifest.xml 文件,并且可以反混淆出 AndroidManifest.xml 中引用的资源名称。
ShowFileSizeTask 根据文件大小以及文件后缀名来过滤出超过指定大小的文件,并按照升序或降序排列结果。
- 实现方法:直接利用 UnzipTask 中统计的文件大小来过滤输出结果。
MethodCountTask 可以统计出各个 Dex 中的方法数,并按照类名或者包名来分组输出结果。
- 实现方法:利用 google 开源的 com.android.dexdeps 类库来读取 dex 文件,统计方法数。
ResProguardCheckTask 可以判断 apk 是否经过了资源混淆
- 实现方法:资源混淆之后的 res 文件夹会重命名成 r ,直接判断是否存在文件夹 r 即可判断是否经过了资源混淆。
FindNonAlphaPngTask 可以检测出 apk 中非透明的 png 文件
- 实现方法:通过 java.awt.BufferedImage 类读取png文件并判断是否有 alpha 通道。
MultiLibCheckTask 可以判断 apk 中是否有针对多个 ABI 的 so
- 实现方法:直接判断 lib 文件夹下是否包含多个目录。
CheckMultiSTLTask 可以检测 apk 中的 so 是否静态链接 STL
- 实现方法:通过 nm 工具来读取 so 的符号表,如果出现 std:: 即表示 so 静态链接了 STL 。
CountRTask 可以统计 R 类以及 R 类的中的 field 数目
- 实现方法:同样是利用 com.android.dexdeps 类库来读取 dex 文件,找出 R 类以及 field 数目。
UncompressedFileTask 可以检测出未经压缩的文件类型
- 实现方法:直接利用 UnzipTask 中统计的各个文件的压缩前和压缩后的大小,判断压缩前和压缩后大小是否相等。
DuplicatedFileTask 可以检测出冗余的文件
- 实现方法:通过比较文件的 MD5 是否相等来判断文件内容是否相同。
UnusedResourceTask 可以检测出 apk 中未使用的资源,对于 getIdentifier 获取的资源可以加入白名单
- 实现方法:
- 过读取 R.txt 获取 apk 中声明的所有资源得到 declareResourceSet ;
- 通过读取 smali 文件中引用资源的指令(包括通过 reference 和直接通过资源 id 引用资源)得出 class 中引用的资源 classRefResourceSet ;
- 通过 ApkTool 解析 res 目录下的 xml 文件、AndroidManifest.xml 以及 resource.arsc 得出资源之间的引用关系;
- 根据上述几步得到的中间数据即可确定出 apk 中未使用到的资源。
我在包体积监控实践中的一些思考
之前在百度实习的时候,曾经用过手百团队的一个包体积分析脚本,python代码部署在流水线上,对打包完的产物进行分析并输出一个html文件,百度的流水线平台可以展示指定的html文件,所以查看分析结果还算方便。但是这个代码分析包的耗时太久了,如果想做成一个分析平台会很不方便(毕竟在前端上传一个安装包之后需要等待好久才能拿到生成的html文件并渲染出来),所以一直在找更好的解决方案,于是找到了这个微信团队的。微信的这套解决方案虽然并行的处理各个分析任务,但也还是有一定耗时,可能分析的粒度和效率很难两全吧。但是https://github.com/pengchenglin/ApkChecker_new 这个方案从前端的角度,也算是避免了上传apk后用户干等的尴尬场景。包体积分析其实算是一种静态代码分析,可以不光局限于包体积这个角度,debug开关检查等静态检查也可以放进这个流程。如果流程较多的话,也完全可以当成一个“小流水线”串行执行,每处理完一个子任务就传输数据到前端进行动态渲染,也是比单纯生成一个html页面更灵活一些。
来字节之后,也做过一个基于代码提交的包体积监控报警工具,将jenkins与gitlab绑定,每次rd git push都会触发jenkins打包,如果当前包大小与该分支上一次打包结果相比高于一个阈值,那么就触发报警。同时在前端也有基于不同分支的包体积上升折线图,方便定位是哪个分支的哪次push导致了发版包的增量。看似很天衣无缝,实则由于git的复杂性,该系统做好之后也遇到很多问题最后也不了了之了,从此也能看到一个平台开发前事先调研工作的重要性。