-
Notifications
You must be signed in to change notification settings - Fork 1.9k
Matrix Android ApkChecker
Matrix是微信终端自研和正在使用的一套APM(Application Performance Management)系统。 Matrix-ApkChecker 作为Matrix系统的一部分,是针对android安装包的分析检测工具,根据一系列设定好的规则检测apk是否存在特定的问题,并输出较为详细的检测结果报告,用于分析排查问题以及版本追踪。Matrix-ApkChecker以一个jar包的形式提供使用,通过命令行执行 java -jar ApkChecker.jar 即可运行。
直接在命令行执行
java -jar ApkChecker.jar
即可以查看Matrix-ApkChecker的使用说明 (注意:下面所说的路径为完整路径,非相对路径)
Usages:
--config CONFIG-FILE-PATH
or
[--input INPUT-DIR-PATH] [--apk APK-FILE-PATH] [--unzip APK-UNZIP-PATH] [--mappingTxt MAPPING-FILE-PATH] [--resMappingTxt RESGUARD-MAPPING-FILE-PATH] [--output OUTPUT-PATH] [--format OUTPUT-FORMAT] [--formatJar OUTPUT-FORMAT-JAR] [--formatConfig OUTPUT-FORMAT-CONFIG (json-array format)] [Options]
Options:
-manifest
Read package info from the AndroidManifest.xml.
-fileSize [--min DOWN-LIMIT-SIZE (KB)] [--order ORDER-BY ('asc'|'desc')] [--suffix FILTER-SUFFIX-LIST (split by ',')]
Show files whose size exceed limit size in order.
-countMethod [--group GROUP-BY ('class'|'package')]
Count methods in dex file, output results group by class name or package name.
-checkResProguard
Check if the resguard was applied.
-findNonAlphaPng [--min DOWN-LIMIT-SIZE (KB)]
Find out the non-alpha png-format files whose size exceed limit size in desc order.
-checkMultiLibrary
Check if there are more than one library dir in the 'lib'.
-uncompressedFile [--suffix FILTER-SUFFIX-LIST (split by ',')]
Show uncompressed file types.
-countR
Count the R class.
-duplicatedFile
Find out the duplicated resource files in desc order.
-checkMultiSTL --toolnm TOOL-NM-PATH
Check if there are more than one shared library statically linked the STL.
-unusedResources --rTxt R-TXT-FILE-PATH [--ignoreResources IGNORE-RESOURCES-LIST (split by ',')]
Find out the unused resources.
-unusedAssets [--ignoreAssets IGNORE-ASSETS-LIST (split by ',')]
Find out the unused assets file.
-unstrippedSo --toolnm TOOL-NM-PATH
Find out the unstripped shared library file.
Matrix-ApkChecker的命令行参数比较多,主要包括global参数和option参数两类:
- global
--apk 输入apk文件路径(默认文件名以apk结尾即可)
--mappingTxt 代码混淆mapping文件路径 (默认文件名是mapping.txt)
--resMappingTxt 资源混淆mapping文件路径(默认文件名是resguard-mapping.txt)
--input 包含了上述输入文件的目录(给定--input之后,则可以省略上述输入文件参数,但上述输入文件必须使用默认文件名)
--unzip 解压apk的输出目录
--output 输出结果文件路径(不含后缀,会根据format决定输出文件的后缀)
--format 结果文件的输出格式(例如 html、json等)
--formatJar 实现了自定义结果文件输出格式的jar包
--formatConfig 对结果文件输出格式的一些配置项(json数组格式)
global参数之后紧跟若干个Option,这些Option是可选的,一个Option表示针对apk的一个检测选项。
- option参数
-
manifest 从AndroidManifest.xml文件中读取apk的全局信息,如packageName、versionCode等。
-
fileSize 列出超过一定大小的文件,可按文件后缀过滤,并且按文件大小排序
--min 文件大小最小阈值,单位是KB --order 按照文件大小升序(asc)或者降序(desc)排列 --suffix 按照文件后缀过滤,使用","作为多个文件后缀的分隔符
-
countMethod 统计方法数
group 输出结果按照类名(class)或者包名(package)来分组
-
checkResProguard 检查是否经过了资源混淆(AndResGuard)
-
findNonAlphaPng 发现不含alpha通道的png文件
min png文件大小最小阈值,单位是KB
-
checkMultiLibrary 检查是否包含多个ABI版本的动态库
-
uncompressedFile 发现未经压缩的文件类型(即该类型的所有文件都未经压缩)
suffix 按照文件后缀过滤,使用","作为多个文件后缀的分隔符
-
countR 统计apk中包含的R类以及R类中的field count
-
duplicatedFile 发现冗余的文件,按照文件大小降序排序
-
checkMultiSTL 检查是否有多个动态库静态链接了STL
toolnm nm工具的路径
-
unusedResources 发现apk中包含的无用资源
rTxt R.txt文件的路径(如果在全局参数中给定了--input,则可以省略) ignoreResources 需要忽略的资源,使用","作为多个资源名称的分隔符
-
unusedAssets 发现apk中包含的无用assets文件
ignoreAssets 需要忽略的assets文件,使用","作为多个文件的分隔符
-
unstrippedSo 发现apk中未经裁剪的动态库文件
toolnm nm工具的路径
-
除了直接在命令行中带上详细参数外,也可以将参数配置以json的格式写到一个配置文件中,然后在命令行中使用
config CONFIG-FILE_PATH
指定配置文件的路径。一个典型的配置文件格式如下:
{
"--apk":"/Users/williamjin/SampleApplication/app/build/outputs/apk/release/AndResGuard_app-release-unsigned/app-release-unsigned_unsigned.apk",
"--mappingTxt":"/Users/williamjin/SampleApplication/app/build/outputs/mapping/release/mapping.txt",
"--resMappingTxt":"/Users/williamjin/SampleApplication/app/build/outputs/apk/release/AndResGuard_app-release-unsigned/resource_mapping_app-release-unsigned.txt",
"--output":"/Users/williamjin/SampleApplication/app/build/outputs/apk-checker-result",
"--format":"mm.html,mm.json",
"--formatConfig":
[
{
"name":"-countMethod",
"group":
[
{
"name":"Android System",
"package":"android"
},
{
"name":"java system",
"package":"java"
},
{
"name":"com.tencent.test.$",
"package":"com.tencent.test.$"
}
]
}
],
"options": [
{
"name":"-manifest"
},
{
"name":"-fileSize",
"--min":"10",
"--order":"desc",
"--suffix":"png, jpg, jpeg, gif, arsc"
},
{
"name":"-countMethod",
"--group":"package"
},
{
"name":"-checkResProguard"
},
{
"name":"-findNonAlphaPng",
"--min":"10"
},
{
"name":"-checkMultiLibrary"
},
{
"name":"-uncompressedFile",
"--suffix":"png, jpg, jpeg, gif, arsc"
},
{
"name":"-countR"
},
{
"name":"-duplicatedFile"
},
{
"name":"-checkMultiSTL",
"--toolnm":"/Users/williamjin/Library/Android/sdk/ndk-bundle/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-nm"
},
{
"name":"-unusedResources",
"--rTxt":"/Users/williamjin/SampleApplication/app/build/intermediates/symbols/release/R.txt",
"--ignoreResources"
:["R.raw.*",
"R.style.*",
"R.attr.*",
"R.id.*",
"R.string.ignore_*"
]
},
{
"name":"-unusedAssets",
"--ignoreAssets":["*.so" ]
},
{
"name":"-unstrippedSo",
"--toolnm":"/Users/williamjin/Library/Android/sdk/ndk-bundle/toolchains/arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin/arm-linux-androideabi-nm"
}
]
}
其中,mm.html 和 mm.json 是微信使用的自定义输出格式,Matrix-ApkChecker默认提供 html 、json、mm.html 以及 mm.json 四种输出格式。
Matrix-ApkChecker 当前主要包含以下功能
- 读取manifest的信息
从AndroidManifest.xml文件中读取apk的全局信息,如packageName、versionCode等。
- 按文件大小排序列出apk中包含的文件
列出超过一定大小的文件,可按文件后缀过滤,并且按文件大小排序
- 统计方法数
统计dex包含的方法数,并支持将输出结果按照类名(class)或者包名(package)来分组
- 检查是否经过了资源混淆(AndResGuard)
检查apk是否经过了资源混淆,推荐使用资源混淆来进一步减小apk的大小
- 搜索不含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文件,应该予以删除
- 搜索apk中未经裁剪的动态库文件
动态库经过裁剪之后,文件大小通常会减小很多
下面,我们对一个示例apk使用Matrix-ApkChecker进行检查,并根据检查的结果进行针对性的减包优化。
从Matrix-ApkChecker的输出结果中可以看到示例apk的相关全局信息如下图所示:
示例apk中包含的文件按类型统计如下图所示:
对于示例apk,我们使用Matrix-ApkChecker进行了全面检查,主要发现以下几个问题:
-
png文件(不包括**.9.png**)未经压缩,可以考虑一定程度的压缩
-
存在一些冗余的文件,文件内容相同的文件应该只保留一份
-
存在无用资源,包括未使用的系统support包中的资源、第三方资源包中的无用资源以及示例app定义的资源
-
存在无用的assets资源,应该删除
针对上述Matrix-ApkChecker检测出来的问题,做如下针对性的优化:
- 首先删除冗余文件
res/drawable-xxxhdpi 目下存在与 res/drawable 目录内容相同的文件,删除 res/drawable 目录下的 icon.png 以及 round.png。 删除之后,可以看到示例apk中png文件缩小了23.89 KB 。
- 将png文件转换成webp格式
从示例输出中可以看到,示例apk的 minSdkVersion 是18,android对于API >= 18的版本已经支持透明的webp。使用Android Studio自带的webp转换功能,选择无损压缩,将部分png文件(不含 .9.png )转成webp之后,示例apk的大小缩小了 7.03 KB
- 删除无用的assets文件
将assets/music目录下的 .mp3 文件删除,示例apk的大小缩减了 69.39 KB
- 删除无用资源
可以看到删除之后,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中未使用到的资源。
- UnusedAssetsTask 可以检测出apk中未使用的assets文件
实现方法:搜索smali文件中引用字符串常量的指令,判断引用的字符串常量是否某个assets文件的名称
- UnStrippedSoCheckTask 可以检测出apk中未经裁剪的动态库文件
实现方法:使用nm工具读取动态库文件的符号表,若输出结果中包含no symbols字样则表示该动态库已经过裁剪
3.每个Task的输出结果保存在json对象中,然后通过 *OutputFormater* 来对输出结果进一步加工(可以转成html格式),也可以实现自己的OutputFormater自定义输出内容的格式。
- 以可执行jar的方式提供使用,便于应用到持续集成系统中
微信在Jenkins上部署了Matrix-ApkChecker来检查编译产出的Apk,并将结果输出到APM系统中汇总分析。
- 可通过扩展Task自定义更多的检查规则
前述所有的检查Task都是继承自ApkTask,开发者也可以通过继承ApkTask类来扩展实现自定义的检查规则。
- 可自定义检查的输出结果格式,便于将检查结果展示在UI
Matrix-ApkChecker支持json格式和html格式的输出结果,默认的输出结果包含了最详尽的信息,开发者可以通过自定义输出结果的Formater来过滤精简输出信息。 只需要以下三步就可以实现自定义的输出结果格式: 1.继承TaskJsonResult或者TaskHtmlResult来精简自定义每个Task的输出信息 2.继承TaskResultRegistry并在其中注册自定义输出格式的名称和实现类 3.将上述实现类打包成jar,并在Manifest文件中声明注册类的信息 最后在使用Matrix-ApkChecker时通过
--formatJar
参数指定自定义输出格式的jar包。
微信终端监控系统使用 Matrix-ApkChecker 来监测微信每个版本的apk大小变化,并针对每个版本提出优化issue和优化的suggesstion。
-
版本追踪 从下图可以直观看到微信多个版本的apk大小变化趋势。
-
版本issue 针对每个版本提出可以优化的issue,如下图所示:
-
版本详情
可以进一步查看某个版本apk的详情。下图显示了该版本各类型文件的占比情况: 对于该版本可能存在的问题,也会给出对应的suggesstion :