工具简介
Matrix-ApkChecker — Apk 分析减包利器
Matrix是微信开源的APM工具,APKChecker是其中针对APK文件做静态分析的工具,是Matrix的一部分。上面两篇腾讯云的相关文章,介绍了Matrix&ApkChecker的一些基本功能。本文针对APKChecker的源码做一个简单的分析,聊一下该工具各个功能的实现原理。采用的是Matrix工程,master分支4月16号的代码为分析源码。
依赖库
简单看一下工程目录和gradle配置的依赖关系。显然,前文链接中的ApkChecker的代码目录是matrix-apk-canary。
- libs文件夹中,有apktool的jar包,反编译工具在这里也是意料之中
- resources目录中,有android的framework jar包,分析APK文件一些基础数据类型在Android framework层中定义
- gradle配置中,可以看到依赖了常用的gson工具类、Android的common工具包、本地工程matrix-common
- 走读一下matrix-common,基本上可以判定该module主要是matrix公用的数据结构与工具组件
代码结构
从代码目录上看,它的包名目录定义的十分清晰,基本上可以一目了然:
- exception:异常定义
- job:工作任务,包含任务管理、配置和常量定义
- output:输出
- result:分析结果相关
- task:分析任务,基本可以通过任务命名直接对应上相关文档中的功能
- ApkChecker类:最后输出jar包的程序入口
简单梳理一下类图关系,能够进一步了解源码的设计思路。
可以看出,基本上可以分为四部分:
- 左侧是入口类和核心任务类,里面封装了主要的分析实现过程
- 上侧是基础的任务实现部分,图中省略了很多功能任务类,只列了三个,可以看到一个简单的继承关系,和一个工厂模式的处理
- 右侧是任务结果部分,可以看到主要有Json和html两种格式。这部分主要是针对每个任务的结果输出,同样是一个继承关系和相应的工厂处理
- 下侧是整个分析任务的结果输出,可以简单认为它是对TaskResult的整理和真正的结果文件化输出。依然是两种格式的继承关系和工厂处理
综合起来看,如果对执行流程感兴趣,去看ApkJob类的实现就好了;对每个分析功能感兴趣,去task包目录下找对应的功能实现类就好了;对最后分析结果是如何输出的感兴趣,可以查一下TaskResult和JobResult相关的实现就好了。
核心流程走读
这里简单分析入口类ApkChecker和任务管理类ApkJob。
ApkChecker
入口类只有不到一百行的代码,十分简洁。主要除了封装了main函数入口,还处理了输入参数异常情况下输出HELP提示的过程。
|
|
很明显,main函数封装的主要是ApkJob,核心函数是它的run接口。此处注意,输入参数args,作为ApkJob的构造函数参数传入,想来是作为了该类的成员变量处理了。
ApkJob
该类有530行左右的代码,如前文类图描述的,成员属性包括了两个分析任务的ApkTask的列表、一个输出结果工具JobResult的列表。除此之外,还有前面传进来的参数args和相关的配置参数描述类JobConfig、一个多线程执行器ExecutorService。构造方法中基本上做一些初始化工作。
run函数比较函数流程比较易懂,只有十几行:
|
|
解析参数
解析参数函数涉及到代码量,比较大。因为支持配置文件和参数两种形式的参数,核心解析函数parseGlobalParams()长达126行,经过解析和校验后,在成功的情况下,jobConfig成员被参数设置好,用于后续的分析任务。
在预处理任务列表中添加一个解压任务
预处理任务列表在整个类中,只有此处添加了一个解压任务。之所以设计成列表,应该是考虑代码的可拓展性。从业务上说,在每项检查点的任务执行之前,要先把目标APK解压出来,也是应有之意。
处理配置的输出格式,将所有格式加入输出结果工具列表
可以理解是参数解析好之后,通过传入参数做的第一件事。就是先把输出工具处理好,为后续分析结果的输出做好准备。
执行execute 函数
预处理执行完,就是正经的任务分析流程了。
123456789101112131415161718192021222324252627282930313233343536373839404142434445private void execute() throws Exception {try {for (ApkTask preTask : preTasks) {preTask.init();TaskResult taskResult = preTask.call();if (taskResult != null) {TaskResult formatResult = null;for (JobResult jobResult : jobResults) {formatResult = TaskResultFactory.transferTaskResult(taskResult.taskType, taskResult, jobResult.getFormat(), jobConfig);if (formatResult != null) {jobResult.addTaskResult(formatResult);}}}}for (ApkTask task : taskList) {task.init();}List<Future<TaskResult>> futures = executor.invokeAll(taskList, timeoutSeconds, TimeUnit.SECONDS);for (Future<TaskResult> future : futures) {TaskResult taskResult = future.get();if (taskResult != null) {TaskResult formatResult = null;for (JobResult jobResult : jobResults) {formatResult = TaskResultFactory.transferTaskResult(taskResult.taskType, taskResult, jobResult.getFormat(), jobConfig);if (formatResult != null) {jobResult.addTaskResult(formatResult);}}}}executor.shutdownNow();for (JobResult jobResult : jobResults) {jobResult.output();}Log.d(TAG, "parse apk end, try to delete tmp un zip files");FileUtils.deleteDirectory(new File(jobConfig.getUnzipPath()));} catch (Exception e) {Log.e(TAG, "Task executor execute with error:" + e.getMessage());throw e;}}该函数45行,分开看还是很好理解的。
- 第一个for循环。执行预处理列表任务,主要是解压目标APK的任务,并输出结果
- 中间两个for循环及相关部分。初始化参数配置指定的每个检查任务;executor多线程执行每个任务;将每个任务的检查结果依次添加到输出结果中;关闭executor
- 第三个for循环及相关部分。结果输出工具把分析结果按照参数指定的格式依次输出到文件中;删除解压的APK文件。
小结
这里主要分析了Matrix/ApkChecker的代码结构和主要执行流程。可以大致总结出几点:
- 总体上,代码在各层级命名、包划分、结构设计、函数实现等各方面可读性都很强
- 利用继承和组合特性,使用工厂模式,让代码可拓展性也不错。比如新增一个分析功能,只需要实现一个ApkTask和相关参数类型即可;新增一种输出格式,只需要新增JobResult/TaskResult相关子类和关系即可
- 参数解析部分代码有些冗余,不太利于拓展和阅读
下一篇会学习一下具体检查任务的实现。