Matrix-ApkChecker — Apk 分析减包利器

简介

Matrix 是微信终端自研和正在使用的一套 APM(应用性能管理)系统。

Matrix-ApkChecker 作为 Matrix 系统的一部分,是针对 android 安装包的分析检测工具,根据一系列设定好的规则检测 apk 是否存在特定的问题,并输出较为详细的检测结果报告,用于分析排查问题以及版本追踪。

功能

Matrix-ApkChecker 当前主要包含以下功能

1. 读取 manifest 的信息

从 AndroidManifest.xml 文件中读取 apk 的全局信息,如 packageName、versionCode 等

2. 按文件大小排序列出 apk 中包含的文件

列出超过一定大小的文件,可按文件后缀过滤,并且按文件大小排序

3. 统计方法数

统计 dex 包含的方法数, 并支持将输出结果按照类名 (class) 或者包名 (package) 来分组

4. 检查是否经过了资源混淆(AndResGuard)

检查 apk 是否经过了资源混淆,推荐使用资源混淆来进一步减小 apk 的大小

5. 搜索不含 alpha 通道的 png 文件

对于不含 alpha 通道的 png 文件,可以转成 jpg 格式来减少文件的大小

6. 检查是否包含多个ABI版本的动态库

so 文件的大小可能会在 apk 文件大小中占很大的比例,可以考虑在 apk 中只包含一个 ABI 版本的动态库

7. 搜索未经压缩的文件类型

某个文件类型的所有文件都没有经过压缩,可以考虑是否需要压缩

8. 统计apk中包含的R类以及R类中的 field count

编译之后,代码中对资源的引用都会优化成 int 常量,除了 R.styleable 之外,其他的 R 类其实都可以删除

9. 搜索冗余的文件

对于两个内容完全相同的文件,应该去冗余

10. 检查是否有多个动态库静态链接了 STL

如果有多个动态库都依赖了 STL ,应该采用动态链接的方式而非多个动态库都去静态链接 STL

11. 搜索 apk 中包含的无用资源

apk 中未经使用到的资源,应该予以删除

12. 搜索apk中包含的无用 assets 文件

apk 中未经使用的 assets 文件,应该予以删除

13. 搜索 apk 中未经裁剪的动态库文件

动态库经过裁剪之后,文件大小通常会减小很多

示例分析

下面,我们对一个示例 apk 使用 Matrix-ApkChecker 进行检查,并根据检查的结果进行针对性的减包优化。

从 Matrix-ApkChecker 的输出结果中可以看到示例 apk 的相关全局信息如下图所示:

示例 apk 中包含的文件按类型统计如下图所示:

对于示例apk,我们使用 Matrix-ApkChecker 进行了全面检查,主要发现以下几个问题:

1. png 文件(不包括 .9.png )未经压缩,可以考虑一定程度的压缩

2. 存在一些冗余的文件,文件内容相同的文件应该只保留一份

3. 存在无用资源,包括未使用的系统 support 包中的资源、第三方资源包中的无用资源以及示例 app 定义的资源

4. 存在无用的 assets 资源,应该删除2. 存在一些冗余的文件,文件内容相同的文件应该只保留一份

针对上面的示例 apk ,我们根据 Matrix-ApkChecker 检测出来的问题,做如下针对性的优化:

1. 删除冗余文件

在 res/drawable-xxxhdpi 目录下存在与 res/drawable 目录内容相同的文件,删除 res/drawable 目录下的  icon.png 以及 round.png。删除之后,可以看到示例 apk 中 png 文件缩小了23.89 KB 

2. 将 png 文件转换成 webp 格式

示例输出中可以看到,示例 apk 的 minSdkVersion 是 18,android 对于 API >= 18的版本已经支持透明的 webp 。使用 Android Studio 自带的 webp 转换功能,选择无损压缩,将部分 png文件(不含 .9.png )全部转成 webp 之后,示例 apk 的大小缩小了 7.03KB

3. 删除无用的assets文件

将 assets/music 目录下的 .mp3 文件删除,示例apk的大小缩减了 69.39 KB

4. 删除无用资源

可以看到删除之后,apk 中无用资源大大减少,同时示例 apk 中 arsc 文件大小缩减了 36.99 KB

经过上述优化,示例 apk 的大小一共缩减了 137.3 KB

实现原理

首先来看下 Matrix-ApkChecker 的整体工作流程

1. 输入的 Apk 文件首先会经过 UnzipTask 处理,解压到指定目录,在这一步还会做一些全局的准备工作,包括反混淆类名(读取 mapping.txt )、反混淆资源(读取 resMapping.txt )、统计文件大小等。 2. 接下来的若干 Task 即用来实现各种检查规则,这些 Task 可以并行执行,下面一一简单介绍各个 Task 的实现方法:

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 获取的资源可以加入白名单

实现方法: (1)过读取 R.txt 获取 apk 中声明的所有资源得到 declareResourceSet ; (2)通过读取 smali 文件中引用资源的指令(包括通过 reference 和直接通过资源 id 引用资源)得出 class 中引用的资源 classRefResourceSet ; (3)通过 ApkTool 解析 res 目录下的 xml 文件、AndroidManifest.xml 以及 resource.arsc 得出资源之间的引用关系; (4)根据上述几步得到的中间数据即可确定出 apk 中未使用到的资源。

3. 每个 Task 的输出结果保存在 json 对象中,然后通过 OutputFormater 来对输出结果进一步加工(可以转成 html 格式),也可以实现自己的 OutputFormater 自定义输出内容的格式。

特点&优势

1. 以可执行 jar 的方式提供使用,便于应用到持续集成系统中

微信在 Jenkins 上部署了 Matrix-ApkChecker 来检查编译产出的 Apk ,并将结果输出到 APM 系统中汇总分析。

2. 可通过扩展 Task 自定义更多的检查规则

前述所有的检查 Task 都是继承自 ApkTask ,开发者也可以通过继承 ApkTask  类来扩展实现自定义的检查规则。

3. 可自定义检查的输出结果格式,便于将检查结果展示在 UI

Matrix-ApkChecker 支持 json 格式和 html 格式的输出结果,默认的输出结果包含了最详尽的信息,开发者可以通过自定义输出结果的 Formater 来过滤精简输出信息。 只需要以下三步就可以实现自定义的输出结果格式: 1) 继承 TaskJsonResult 或者 TaskHtmlResult 来精简自定义每个 Task 的输出信息

2) 继续TaskResultRegistry并在其中注册自定义输出格式的名称和实现类

3)将上述实现类打包成 jar , 并在 Manifest 文件中声明注册类的信息

最后在使用 Matrix-ApkChecker 时通过 --formatJar 参数指定自定义输出格式的 jar  包。

在微信终端 APM 系统中的应用

微信终端 APM 系统使用 Matrix-ApkChecker 来监测微信每个版本的 apk 大小变化,并针对每个版本提出优化 issue 和优化的 suggesstion 。

1. 版本追踪

从下图可以直观看到微信多个版本的 apk 大小变化趋势。

2. 版本 issue

针对每个版本提出可以优化的 issue ,如下图所示:

3. 版本详情

可以进一步查看某个版本 apk 的详情。下图显示了该版本各类型文件的占比情况:

4. 对于该版本可能存在的问题,也会给出对应的 suggesstion :

未来计划

Matrix 将在不久之后开源出去,Matrix-ApkChecker 作为其中一部分也将随同  Matrix 一起开源。欢迎大家使用 Matrix 来监控和持续提高 App 的质量,也欢迎大家提出各种意见和建议。

原文发布于微信公众号 - WeMobileDev(WeMobileDev)

原文发表时间:2018-11-30

本文参与腾讯云自媒体分享计划,欢迎正在阅读的你也加入,一起分享。

发表于

我来说两句

0 条评论
登录 后参与评论

相关文章

来自专栏Jerry的SAP技术分享

如何实现Windows宿主系统和虚拟机ubuntu系统文件互相访问

我的宿主操作系统是Windows 10,使用Oracle的Virtual Box安装了Ubuntu。

1133
来自专栏Golang语言社区

几种服务器端IO模型的简单介绍及实现(下)

5、使用事件驱动库libevent的服务器模型 Libevent 是一种高性能事件循环/事件驱动库。 为了实际处理每个请求,libevent 库提供一种事件机制...

3729
来自专栏维C果糖

IntelliJ IDEA 之 HelloWorld 项目创建及相关配置文件介绍

在博文“ IntelliJ IDEA 的使用界面介绍 ”中,咱们通过创建一个 Static Web 项目大致了解了 IntelliJ IDEA 的使用界面,接下...

1889
来自专栏Felix的技术分享

《一个操作系统的实现》笔记(4)-- Boot&Loader

2427
来自专栏用户2442861的专栏

Epoll详解及源码分析

对于水平触发模式(LT):在1处,如果你不做任何操作,内核依旧会不断的通知进程文件描述符准备就绪。

1042
来自专栏用户2442861的专栏

操作系统八内存管理

      CPU可以在一个cpu时钟内执行一个或多个其内置寄存器的指令。而访问内存需多个cpu时钟。由于内存频繁访问,可以再cpu与内存之间增加高速缓存

721
来自专栏狂码一生

QT5程序打包发布,最终生成一个.exe执行程序

1283
来自专栏玄魂工作室

CTF实战10 CSRF跨站请求伪造漏洞

该培训中提及的技术只适用于合法CTF比赛和有合法授权的渗透测试,请勿用于其他非法用途,如用作其他非法用途与本文作者无关

1025
来自专栏用户2442861的专栏

操作系统内存管理——分区、页式、段式管理

内存管理主要包括虚地址、地址变换、内存分配和回收、内存扩充、内存共享和保护等功能。

1261
来自专栏linux驱动个人学习

Linux CFS调度器之唤醒抢占--Linux进程的管理与调度(三十)

table th:nth-of-type(1){ width: 20%; } table th:nth-of-type(2){ width: 20% ; }

1693

扫码关注云+社区