1+ # coding = utf-8
2+
3+ """
4+ https://github.com/Tencent/matrix/wiki/Matrix-ApkChecker#matrix-apkchecker-的使用
5+
6+
7+ APK Checker Usage
8+
9+ APK Checker can run independently in Jar (matrix-apk-canary-0.4.10.jar) mode, usage:
10+
11+ java -jar matrix-apk-canary-0.4.10.jar
12+ Usages:
13+ --config CONFIG-FILE-PATH
14+ or
15+ [--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]
16+
17+ Options:
18+ -manifest
19+ Read package info from the AndroidManifest.xml.
20+ -fileSize [--min DOWN-LIMIT-SIZE (KB)] [--order ORDER-BY ('asc'|'desc')] [--suffix FILTER-SUFFIX-LIST (split by ',')]
21+ Show files whose size exceed limit size in order.
22+ -countMethod [--group GROUP-BY ('class'|'package')]
23+ Count methods in dex file, output results group by class name or package name.
24+ -checkResProguard
25+ Check if the resguard was applied.
26+ -findNonAlphaPng [--min DOWN-LIMIT-SIZE (KB)]
27+ Find out the non-alpha png-format files whose size exceed limit size in desc order.
28+ -checkMultiLibrary
29+ Check if there are more than one library dir in the 'lib'.
30+ -uncompressedFile [--suffix FILTER-SUFFIX-LIST (split by ',')]
31+ Show uncompressed file types.
32+ -countR
33+ Count the R class.
34+ -duplicatedFile
35+ Find out the duplicated resource files in desc order.
36+ -checkMultiSTL --toolnm TOOL-NM-PATH
37+ Check if there are more than one shared library statically linked the STL.
38+ -unusedResources --rTxt R-TXT-FILE-PATH [--ignoreResources IGNORE-RESOURCES-LIST (split by ',')]
39+ Find out the unused resources.
40+ -unusedAssets [--ignoreAssets IGNORE-ASSETS-LIST (split by ',')]
41+ Find out the unused assets file.
42+ -unstrippedSo --toolnm TOOL-NM-PATH
43+ Find out the unstripped shared library file.
44+
45+ """
46+ import os
47+ import json
48+ from time import sleep
49+ import matplotlib .pyplot as plt
50+
51+ """
52+
53+ load_dict[0]: Unzip the apk file to current project path.
54+ load_dict[1]: Read package info from the AndroidManifest.xml.
55+ load_dict[2]: Check if the apk handled by resguard.
56+ load_dict[3]: Find out the non-alpha png-format files whose size exceed limit size in desc order.
57+ load_dict[4]: Show uncompressed file types.
58+ load_dict[5]: Find out the duplicated files.
59+ load_dict[6]: Find out the unused assets.
60+ load_dict[7]: Show files whose size exceed limit size in order.
61+ load_dict[8]: Count methods in dex file, output results group by class name or package name.
62+ load_dict[9]: Check if there are more than one library dir in the 'lib'.
63+ load_dict[10]: Count the R class.
64+
65+
66+ 每项 Task 都是 load_dict{} 中的一个元素
67+
68+ task{"":1, "":2, ........"entries":[{}, {}, {}, .....]}
69+
70+ 解析每一项 Task
71+
72+ """
73+
74+ os .popen ("/Users/william/PycharmProjects/System/Mobile/Android/Android_Package_Check/android_check.sh" )
75+
76+ sleep (10 )
77+
78+
79+ json_path = "/Users/william/PycharmProjects/System/Mobile/Android/Android_Package_Check/result/result.json"
80+ with open (json_path , 'r' ) \
81+ as load_f :
82+
83+ load_dict = json .load (load_f )
84+
85+
86+ # 读取manifest的信息:从AndroidManifest.xml文件中读取apk的全局信息,如packageName、versionCode等。
87+ def read_info ():
88+
89+ return \
90+ load_dict [1 ]["taskDescription" ], \
91+ load_dict [1 ]["start-time" ], \
92+ load_dict [1 ]["end-time" ], \
93+ load_dict [1 ]["manifest" ]["package" ], \
94+ load_dict [1 ]["manifest" ]["android:minSdkVersion" ], \
95+ load_dict [1 ]["manifest" ]["android:targetSdkVersion" ], \
96+ load_dict [1 ]["manifest" ]["android:versionCode" ], \
97+ load_dict [1 ]["manifest" ]["android:versionName" ]
98+
99+
100+ # 按文件大小排序列出apk中包含的文件:列出超过一定大小的文件,可按文件后缀过滤,并且按文件大小排序。
101+ def check_unzip ():
102+
103+ unzip_package = []
104+ type_number = len (load_dict [0 ]["entries" ])
105+
106+ for i in range (type_number ):
107+
108+ # round(x/y, 2), 除法保留两位小数
109+ unzip_package .append ("\n " + load_dict [0 ]["entries" ][i ]["suffix" ]
110+ + " == " +
111+ str (round (load_dict [0 ]["entries" ][i ]["total-size" ]/ 1024 , 3 )) + "KB" )
112+
113+ # print(unzip_package[i])
114+
115+ type_sum = "" .join (unzip_package [0 :])
116+
117+ # 画图
118+ file_name = []
119+ file_size = []
120+
121+ for i in range (type_number ):
122+
123+ file_name .append (load_dict [0 ]["entries" ][i ]["suffix" ])
124+
125+ for i in range (type_number ):
126+ file_size .append (load_dict [0 ]["entries" ][i ]["total-size" ])
127+
128+ new_file_name = [x + "\n " for x in file_name ]
129+
130+ plt .figure (figsize = (14 , 9 ))
131+ # explode = [0, 0.1, 0, 0] # 0.1 凸出这部分,
132+ plt .axes (aspect = 1 ) # set this , Figure is round, otherwise it is an ellipse
133+ patches , l_text , p_text = plt .pie (
134+ x = file_size , labels = new_file_name , autopct = '%3.1f %%' ,
135+ shadow = False , labeldistance = 1.1 , startangle = 90 , pctdistance = 0.6 )
136+ '''
137+ labeldistance,文本的位置离远点有多远,1.1指1.1倍半径的位置
138+ autopct,圆里面的文本格式,%3.1f%%表示小数有三位,整数有一位的浮点数
139+ shadow,饼是否有阴影
140+ startangle,起始角度,0,表示从0开始逆时针转,为第一块。一般选择从90度开始比较好看
141+ pctdistance,百分比的text离圆心的距离
142+ patches, l_texts, p_texts,为了得到饼图的返回值,p_texts饼图内部文本的,l_texts饼图外label的文本
143+ '''
144+ # 改变文本的大小,方法是把每一个text遍历。调用set_size方法设置它的属性
145+ for t in l_text :
146+ t .set_size = 70
147+ for t in p_text :
148+ t .set_size = 70
149+ # 设置x,y轴刻度一致,这样饼图才能是圆的
150+ plt .axis ('equal' )
151+ plt .legend ()
152+ plt .savefig ("/Users/william/PycharmProjects/System/Mobile/Android/Android_Package_Check/o.png" )
153+
154+ return \
155+ load_dict [0 ]["taskDescription" ], \
156+ load_dict [0 ]["start-time" ], \
157+ load_dict [0 ]["end-time" ], \
158+ str (round (load_dict [0 ]["total-size" ]/ 1024 / 1024 , 2 )) + " " + "MB" , \
159+ type_sum
160+
161+
162+ # 搜索不含alpha通道的png文件:对于不含alpha通道的png文件,可以转成jpg格式来减少文件的大小
163+ def find_non_alpha ():
164+
165+ non_alpha = []
166+ files_number = len (load_dict [3 ]["files" ])
167+
168+ for i in range (files_number ):
169+
170+ non_alpha .append ("\n \n " + load_dict [3 ]["files" ][i ]["entry-name" ]
171+ + " == " +
172+ str (round (load_dict [3 ]["files" ][i ]["entry-size" ]/ 1024 , 3 )) + "KB" )
173+ print (non_alpha [i ])
174+
175+ files_sum = "" .join (non_alpha [0 :])
176+
177+ return \
178+ load_dict [3 ]["taskDescription" ], \
179+ load_dict [3 ]["start-time" ], \
180+ load_dict [3 ]["end-time" ], \
181+ files_sum
182+
183+
184+ # 搜索未经压缩的文件类型:某个文件类型的所有文件都没有经过压缩,可以考虑是否需要压缩
185+ def show_uncompressed ():
186+
187+ uncompressed = []
188+ files_number = len (load_dict [4 ]["files" ])
189+
190+ for i in range (files_number ):
191+
192+ uncompressed .append ("\n \n " + load_dict [4 ]["files" ][i ]["suffix" ]
193+ + " == " +
194+ str (round (load_dict [4 ]["files" ][i ]["total-size" ]/ 1024 / 1024 , 3 )) + "MB" )
195+ print (uncompressed [i ])
196+
197+ uncompressed_sum = "" .join (uncompressed [0 :])
198+
199+ return \
200+ load_dict [4 ]["taskDescription" ], \
201+ load_dict [4 ]["start-time" ], \
202+ load_dict [4 ]["end-time" ], \
203+ uncompressed_sum
204+
205+
206+ # 搜索冗余的文件:对于两个内容完全相同的文件,应该去冗余
207+ def find_duplicated ():
208+
209+ duplicated = []
210+ duplicated_sum_size = []
211+ duplicated_number = len (load_dict [5 ]["files" ])
212+ size = 0
213+
214+ for i in range (duplicated_number ):
215+
216+ duplicated .append ("\n \n " + "MD5: " + load_dict [5 ]["files" ][i ]["md5" ]
217+ + "\n File_Size: " +
218+ str (round (load_dict [5 ]["files" ][i ]["size" ]/ 1024 , 3 ))
219+ + "KB" + "\n " )
220+ duplicated_sum_size .append (load_dict [5 ]["files" ][i ]["size" ])
221+
222+ size = size + duplicated_sum_size [i ]
223+
224+ size = str (round (size / 1024 , 3 )) + "KB"
225+
226+ files = []
227+ # 单独处理重复的文件路径
228+ for i in range (duplicated_number ):
229+
230+ unit_path_sun = " ======>>> " .join (load_dict [5 ]["files" ][i ]["files" ])
231+
232+ files .append (unit_path_sun )
233+
234+ duplicated_sum = []
235+
236+ for i in range (len (duplicated )):
237+
238+ duplicated_sum .append (duplicated [i ] + files [i ])
239+
240+ duplicated_sum_1 = "" .join (duplicated_sum [0 :])
241+
242+ return \
243+ load_dict [5 ]["taskDescription" ], \
244+ load_dict [5 ]["start-time" ], \
245+ load_dict [5 ]["end-time" ], \
246+ duplicated_sum_1 , \
247+ size
248+
249+
250+ # 按顺序显示大小超过限制大小的文件。
251+ def limit_file ():
252+
253+ limit_ = []
254+ limit_number = len (load_dict [7 ]["files" ])
255+
256+ for i in range (limit_number ):
257+
258+ limit_ .append ("\n " + "Entry-name: " + load_dict [7 ]["files" ][i ]["entry-name" ]
259+ + "\n Entry_Size: " +
260+ str (round (load_dict [7 ]["files" ][i ]["entry-size" ]/ 1024 , 3 ))
261+ + "KB" + "\n " )
262+
263+ limit_sum = "" .join (limit_ [0 :])
264+
265+ # print(limit_sum)
266+
267+ return \
268+ load_dict [7 ]["taskDescription" ], \
269+ load_dict [7 ]["start-time" ], \
270+ load_dict [7 ]["end-time" ], \
271+ limit_sum
272+
273+
274+ # 统计方法数:统计dex包含的方法数,并支持将输出结果按照类名(class)或者包名(package)来分组.
275+ def method_count ():
276+
277+ method = []
278+ method_number = len (load_dict [8 ]["groups" ])
279+
280+ for i in range (method_number ):
281+ method .append ("\n " + "Name: " + load_dict [8 ]["groups" ][i ]["name" ]
282+ + "\n Method-count: " +
283+ str (load_dict [8 ]["groups" ][i ]["method-count" ]) + "\n " )
284+
285+ method_sum = "" .join (method [0 :])
286+ # print(method_sum)
287+
288+ return \
289+ load_dict [8 ]["taskDescription" ], \
290+ load_dict [8 ]["start-time" ], \
291+ load_dict [8 ]["end-time" ], \
292+ load_dict [8 ]["total-methods" ], \
293+ method_sum
294+
295+
296+ # 检查是否包含多个ABI版本的动态库.
297+ def check_multilibrary ():
298+
299+ multilibrary = []
300+ multilibrary_number = len (load_dict [9 ]["lib-dirs" ])
301+
302+ for i in range (multilibrary_number ):
303+ multilibrary .append ("\n " + "lib-dirs: " + load_dict [9 ]["lib-dirs" ][i ] + "\n " )
304+
305+ multilibrary_sum = "" .join (multilibrary [0 :])
306+
307+ print (multilibrary_sum )
308+
309+ return \
310+ load_dict [9 ]["taskDescription" ], \
311+ load_dict [9 ]["start-time" ], \
312+ load_dict [9 ]["end-time" ], \
313+ load_dict [9 ]["multi-lib" ], \
314+ multilibrary_sum
315+
316+
317+ # 统计apk中包含的R类以及R类中的field count:
318+ # 编译之后,代码中对资源的引用都会优化成int常量,除了R.styleable之外,其他的R类其实都可以删除
319+ def r_count ():
320+
321+ r = []
322+ r_number = len (load_dict [10 ]["R-classes" ])
323+
324+ for i in range (r_number ):
325+ r .append ("\n " + "Name: " + load_dict [10 ]["R-classes" ][i ]["name" ]
326+ + "\n R-classes: " +
327+ str (load_dict [10 ]["R-classes" ][i ]["field-count" ]) + "\n " )
328+
329+ r_sum = "" .join (r [0 :])
330+
331+ # print(r_sum)
332+ return \
333+ load_dict [10 ]["taskDescription" ], \
334+ load_dict [10 ]["start-time" ], \
335+ load_dict [10 ]["end-time" ], \
336+ load_dict [10 ]["R-count" ], \
337+ load_dict [10 ]["Field-counts" ], \
338+ r_sum
339+
340+
341+ if __name__ == '__main__' :
342+
343+ # check_unzip()
344+
345+ # read_info()
346+
347+ # find_non_alpha()
348+
349+ # show_uncompressed()
350+
351+ find_duplicated ()
352+
353+ # limit_file()
354+
355+ # method_count()
356+
357+ # check_multilibrary()
358+
359+ # r_count()
0 commit comments