簡介
Matrix是微信終端自研和正在使用的一套APM(Application Performance Management)系統(tǒng)。 Matrix-ApkChecker 作為Matrix系統(tǒng)的一部分,是針對android安裝包的分析檢測工具,根據(jù)一系列設(shè)定好的規(guī)則檢測apk是否存在特定的問題,并輸出較為詳細(xì)的檢測結(jié)果報(bào)告,用于分析排查問題以及版本追蹤。Matrix-ApkChecker以一個(gè)jar包的形式提供使用,通過命令行執(zhí)行 java -jar ApkChecker.jar 即可運(yùn)行。
Matrix-ApkChecker 的使用
簡單使用
java -jar E:/ApkChecker.jar --config E:/apk_config.json
將參數(shù)以json格式寫在apk_config.json中,具體如下:
(具體使用只需要替換"--apk"、"--mappingTxt"、"--output"、"--rTxt"的內(nèi)容)
{
"--apk": "E:/app_1.1.3_1812270_2019-01-08.apk",
"--mappingTxt": "E:/android/project/app-1/app/build/outputs/mapping/release/mapping.txt",
"--output": "E:/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": "5",
"--order": "desc",
"--suffix": "png, jpg, jpeg, gif, arsc"
},
{
"name": "-countMethod",
"--group": "package"
},
{
"name": "-checkResProguard"
},
{
"name": "-findNonAlphaPng",
"--min": "5"
},
{
"name": "-checkMultiLibrary"
},
{
"name": "-uncompressedFile",
"--suffix": "png, jpg, jpeg, gif, arsc"
},
{
"name": "-countR"
},
{
"name": "-duplicatedFile"
},
{
"name": "-unusedResources",
"--rTxt": "E:/android/project/app-1/app/build/intermediates/symbols/release/R.txt",
"--ignoreResources": ["R.raw.*",
"R.style.*",
"R.attr.*",
"R.id.*",
"R.string.ignore_*"
]
},
{
"name": "-unusedAssets",
"--ignoreAssets": ["*.so"]
}
]
}
具體使用 直接在命令行執(zhí)行
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的命令行參數(shù)比較多,主要包括global參數(shù)和option參數(shù)兩類:
- global
--apk 輸入apk文件路徑(默認(rèn)文件名以apk結(jié)尾即可)
--mappingTxt 代碼混淆mapping文件路徑 (默認(rèn)文件名是mapping.txt)
--resMappingTxt 資源混淆mapping文件路徑(默認(rèn)文件名是resguard-mapping.txt)
--input 包含了上述輸入文件的目錄(給定--input之后,則可以省略上述輸入文件參數(shù),但上述輸入文件必須使用默認(rèn)文件名)
--unzip 解壓apk的輸出目錄
--output 輸出結(jié)果文件路徑(不含后綴,會根據(jù)format決定輸出文件的后綴)
--format 結(jié)果文件的輸出格式(例如 html、json等)
--formatJar 實(shí)現(xiàn)了自定義結(jié)果文件輸出格式的jar包
--formatConfig 對結(jié)果文件輸出格式的一些配置項(xiàng)(json數(shù)組格式)
global參數(shù)之后緊跟若干個(gè)Option,這些Option是可選的,一個(gè)Option表示針對apk的一個(gè)檢測選項(xiàng)。
- option參數(shù)
manifest 從AndroidManifest.xml文件中讀取apk的全局信息,如packageName、versionCode等。
-
fileSize 列出超過一定大小的文件,可按文件后綴過濾,并且按文件大小排序
--min 文件大小最小閾值,單位是KB --order 按照文件大小升序(asc)或者降序(desc)排列 --suffix 按照文件后綴過濾,使用","作為多個(gè)文件后綴的分隔符 -
countMethod 統(tǒng)計(jì)方法數(shù)
group 輸出結(jié)果按照類名(class)或者包名(package)來分組 checkResProguard 檢查是否經(jīng)過了資源混淆(AndResGuard)
-
findNonAlphaPng 發(fā)現(xiàn)不含alpha通道的png文件
min png文件大小最小閾值,單位是KB checkMultiLibrary 檢查是否包含多個(gè)ABI版本的動態(tài)庫
-
uncompressedFile 發(fā)現(xiàn)未經(jīng)壓縮的文件類型(即該類型的所有文件都未經(jīng)壓縮)
suffix 按照文件后綴過濾,使用","作為多個(gè)文件后綴的分隔符 countR 統(tǒng)計(jì)apk中包含的R類以及R類中的field count
duplicatedFile 發(fā)現(xiàn)冗余的文件,按照文件大小降序排序
-
checkMultiSTL 檢查是否有多個(gè)動態(tài)庫靜態(tài)鏈接了STL
toolnm nm工具的路徑 -
unusedResources 發(fā)現(xiàn)apk中包含的無用資源
rTxt R.txt文件的路徑(如果在全局參數(shù)中給定了--input,則可以省略) ignoreResources 需要忽略的資源,使用","作為多個(gè)資源名稱的分隔符 -
unusedAssets 發(fā)現(xiàn)apk中包含的無用assets文件
ignoreAssets 需要忽略的assets文件,使用","作為多個(gè)文件的分隔符 -
unstrippedSo 發(fā)現(xiàn)apk中未經(jīng)裁剪的動態(tài)庫文件
toolnm nm工具的路徑
除了直接在命令行中帶上詳細(xì)參數(shù)外,也可以將參數(shù)配置以json的格式寫到一個(gè)配置文件中,然后在命令行中使用
config CONFIG-FILE_PATH
指定配置文件的路徑。一個(gè)典型的配置文件格式如下:
{
"--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默認(rèn)提供 html 、json、mm.html 以及 mm.json 四種輸出格式。
功能
Matrix-ApkChecker 當(dāng)前主要包含以下功能
- 讀取manifest的信息
從AndroidManifest.xml文件中讀取apk的全局信息,如packageName、versionCode等。
- 按文件大小排序列出apk中包含的文件
列出超過一定大小的文件,可按文件后綴過濾,并且按文件大小排序
- 統(tǒng)計(jì)方法數(shù)
統(tǒng)計(jì)dex包含的方法數(shù),并支持將輸出結(jié)果按照類名(class)或者包名(package)來分組
- 檢查是否經(jīng)過了資源混淆(AndResGuard)
檢查apk是否經(jīng)過了資源混淆,推薦使用資源混淆來進(jìn)一步減小apk的大小
- 搜索不含alpha通道的png文件
對于不含alpha通道的png文件,可以轉(zhuǎn)成jpg格式來減少文件的大小
- 檢查是否包含多個(gè)ABI版本的動態(tài)庫
so文件的大小可能會在apk文件大小中占很大的比例,可以考慮在apk中只包含一個(gè)ABI版本的動態(tài)庫
- 搜索未經(jīng)壓縮的文件類型
某個(gè)文件類型的所有文件都沒有經(jīng)過壓縮,可以考慮是否需要壓縮
- 統(tǒng)計(jì)apk中包含的R類以及R類中的field count
編譯之后,代碼中對資源的引用都會優(yōu)化成int常量,除了R.styleable之外,其他的R類其實(shí)都可以刪除
- 搜索冗余的文件
對于兩個(gè)內(nèi)容完全相同的文件,應(yīng)該去冗余
- 檢查是否有多個(gè)動態(tài)庫靜態(tài)鏈接了STL
如果有多個(gè)動態(tài)庫都依賴了STL,應(yīng)該采用動態(tài)鏈接的方式而非多個(gè)動態(tài)庫都去靜態(tài)鏈接STL
- 搜索apk中包含的無用資源
apk中未經(jīng)使用到的資源,應(yīng)該予以刪除
- 搜索apk中包含的無用assets文件
apk中未經(jīng)使用的assets文件,應(yīng)該予以刪除
- 搜索apk中未經(jīng)裁剪的動態(tài)庫文件
動態(tài)庫經(jīng)過裁剪之后,文件大小通常會減小很多
示例分析
下面,我們對一個(gè)示例apk使用Matrix-ApkChecker進(jìn)行檢查,并根據(jù)檢查的結(jié)果進(jìn)行針對性的減包優(yōu)化。
從Matrix-ApkChecker的輸出結(jié)果中可以看到示例apk的相關(guān)全局信息如下圖所示:


對于示例apk,我們使用Matrix-ApkChecker進(jìn)行了全面檢查,主要發(fā)現(xiàn)以下幾個(gè)問題:
- png文件(不包括.9.png)未經(jīng)壓縮,可以考慮一定程度的壓縮 uncompress_file3.png
-
存在一些冗余的文件,文件內(nèi)容相同的文件應(yīng)該只保留一份 duplicated_file4.png
-
存在無用資源,包括未使用的系統(tǒng)support包中的資源、第三方資源包中的無用資源以及示例app定義的資源 unused_resources5.png
-
存在無用的assets資源,應(yīng)該刪除unused_assets6.png
針對上述Matrix-ApkChecker檢測出來的問題,做如下針對性的優(yōu)化:
- 首先刪除冗余文件
res/drawable-xxxhdpi 目下存在與 res/drawable 目錄內(nèi)容相同的文件,刪除 res/drawable 目錄下的 icon.png 以及 round.png。 刪除之后,可以看到示例apk中png文件縮小了23.89 KB 。
ret-sub-duplicate7.png
- 將png文件轉(zhuǎn)換成webp格式
從示例輸出中可以看到,示例apk的 minSdkVersion 是18,android對于API >= 18的版本已經(jīng)支持透明的webp。使用Android Studio自帶的webp轉(zhuǎn)換功能,選擇無損壓縮,將部分png文件(不含 .9.png )轉(zhuǎn)成webp之后,示例apk的大小縮小了 7.03 KB

- 刪除無用的assets文件
將assets/music目錄下的 .mp3 文件刪除,示例apk的大小縮減了 69.39 KB
ret-convert-file8.png
- 刪除無用資源
可以看到刪除之后,apk中無用資源大大減少,同時(shí)示例apk中arsc文件大小縮減了 36.99 KB
ret-sub-unused-resource10.png
經(jīng)過上述優(yōu)化,示例apk的大小一共縮減了 137.3 KB 。
實(shí)現(xiàn)原理
首先來看下Matrix-ApkChecker的整體工作流程
1.輸入的Apk文件首先會經(jīng)過UnzipTask處理,解壓到指定目錄,在這一步還會做一些全局的準(zhǔn)備工作,包括反混淆類名(讀取mapping.txt)、反混淆資源(讀取resMapping.txt)、統(tǒng)計(jì)文件大小等。
2.接下來的若干Task即用來實(shí)現(xiàn)各種檢查規(guī)則,這些Task可以并行執(zhí)行,下面一一簡單介紹各個(gè)Task的實(shí)現(xiàn)方法:
- ManifestAnalyzeTask 用于讀取AndroidManifest.xml中的信息,如:packageName、verisonCode、clientVersion等。
實(shí)現(xiàn)方法:利用ApkTool中的 AXmlResourceParser 來解析二進(jìn)制的AndroidManifest.xml文件,并且可以反混淆出AndroidManifest.xml中引用的資源名稱。
- ShowFileSizeTask 根據(jù)文件大小以及文件后綴名來過濾出超過指定大小的文件,并按照升序或降序排列結(jié)果。
實(shí)現(xiàn)方法:直接利用UnzipTask中統(tǒng)計(jì)的文件大小來過濾輸出結(jié)果。
- MethodCountTask 可以統(tǒng)計(jì)出各個(gè)Dex中的方法數(shù),并按照類名或者包名來分組輸出結(jié)果。
實(shí)現(xiàn)方法:利用google開源的 com.android.dexdeps 類庫來讀取dex文件,統(tǒng)計(jì)方法數(shù)。
- ResProguardCheckTask 可以判斷apk是否經(jīng)過了資源混淆
實(shí)現(xiàn)方法:資源混淆之后的res文件夾會重命名成r,直接判斷是否存在文件夾r即可判斷是否經(jīng)過了資源混淆。
- FindNonAlphaPngTask 可以檢測出apk中非透明的png文件
實(shí)現(xiàn)方法:通過 java.awt.BufferedImage 類讀取png文件并判斷是否有alpha通道。
- MultiLibCheckTask 可以判斷apk中是否有針對多個(gè)ABI的so
實(shí)現(xiàn)方法:直接判斷l(xiāng)ib文件夾下是否包含多個(gè)目錄。
- CheckMultiSTLTask 可以檢測apk中的so是否靜態(tài)鏈接STL
實(shí)現(xiàn)方法:通過nm工具來讀取so的符號表,如果出現(xiàn) std:: 即表示so靜態(tài)鏈接了STL。
- CountRTask 可以統(tǒng)計(jì)R類以及R類的中的field數(shù)目
實(shí)現(xiàn)方法:同樣是利用 com.android.dexdeps 類庫來讀取dex文件,找出R類以及field數(shù)目。
- UncompressedFileTask 可以檢測出未經(jīng)壓縮的文件類型
實(shí)現(xiàn)方法:直接利用UnzipTask中統(tǒng)計(jì)的各個(gè)文件的壓縮前和壓縮后的大小,判斷壓縮前和壓縮后大小是否相等。
- DuplicatedFileTask 可以檢測出冗余的文件
實(shí)現(xiàn)方法:通過比較文件的MD5是否相等來判斷文件內(nèi)容是否相同。
- UnusedResourceTask 可以檢測出apk中未使用的資源,對于getIdentifier獲取的資源可以加入白名單
實(shí)現(xiàn)方法: (1)過讀取R.txt獲取apk中聲明的所有資源得到declareResourceSet; (2)通過讀取smali文件中引用資源的指令(包括通過reference和直接通過資源id引用資源)得出class中引用的資源classRefResourceSet; (3)通過ApkTool解析res目錄下的xml文件、AndroidManifest.xml 以及 resource.arsc 得出資源之間的引用關(guān)系; (4)根據(jù)上述幾步得到的中間數(shù)據(jù)即可確定出apk中未使用到的資源。
- UnusedAssetsTask 可以檢測出apk中未使用的assets文件
實(shí)現(xiàn)方法:搜索smali文件中引用字符串常量的指令,判斷引用的字符串常量是否某個(gè)assets文件的名稱
- UnStrippedSoCheckTask 可以檢測出apk中未經(jīng)裁剪的動態(tài)庫文件
實(shí)現(xiàn)方法:使用nm工具讀取動態(tài)庫文件的符號表,若輸出結(jié)果中包含no symbols字樣則表示該動態(tài)庫已經(jīng)過裁剪
3.每個(gè)Task的輸出結(jié)果保存在json對象中,然后通過 OutputFormater 來對輸出結(jié)果進(jìn)一步加工(可以轉(zhuǎn)成html格式),也可以實(shí)現(xiàn)自己的OutputFormater自定義輸出內(nèi)容的格式。
Matrix-ApkChecker 的特點(diǎn)
- 以可執(zhí)行jar的方式提供使用,便于應(yīng)用到持續(xù)集成系統(tǒng)中
微信在Jenkins上部署了Matrix-ApkChecker來檢查編譯產(chǎn)出的Apk,并將結(jié)果輸出到APM系統(tǒng)中匯總分析。
- 可通過擴(kuò)展Task自定義更多的檢查規(guī)則
前述所有的檢查Task都是繼承自ApkTask,開發(fā)者也可以通過繼承ApkTask類來擴(kuò)展實(shí)現(xiàn)自定義的檢查規(guī)則。
- 可自定義檢查的輸出結(jié)果格式,便于將檢查結(jié)果展示在UI
Matrix-ApkChecker支持json格式和html格式的輸出結(jié)果,默認(rèn)的輸出結(jié)果包含了最詳盡的信息,開發(fā)者可以通過自定義輸出結(jié)果的Formater來過濾精簡輸出信息。 只需要以下三步就可以實(shí)現(xiàn)自定義的輸出結(jié)果格式: 1.繼承TaskJsonResult或者TaskHtmlResult來精簡自定義每個(gè)Task的輸出信息mmtaskjsonresult12.png
2.繼承TaskResultRegistry并在其中注冊自定義輸出格式的名稱和實(shí)現(xiàn)類taskresultregistry13.png
3.將上述實(shí)現(xiàn)類打包成jar,并在Manifest文件中聲明注冊類的信息taskresultregistry14.png
最后在使用Matrix-ApkChecker時(shí)通過
--formatJar參數(shù)指定自定義輸出格式的jar包。
在微信終端APM系統(tǒng)中的應(yīng)用
微信終端監(jiān)控系統(tǒng)使用 Matrix-ApkChecker 來監(jiān)測微信每個(gè)版本的apk大小變化,并針對每個(gè)版本提出優(yōu)化issue和優(yōu)化的suggesstion。
-
版本追蹤 從下圖可以直觀看到微信多個(gè)版本的apk大小變化趨勢。chart-line15.png
-
版本issue 針對每個(gè)版本提出可以優(yōu)化的issue,如下圖所示:chart-issue16.png
- 版本詳情














