ApkChecker_new Android 包大小檢測及趨勢統計

linpengcheng發表於2020-05-07

GitHub地址 :https://github.com/pengchenglin/ApkChecker_new

根據@xinxiAndroid 包大小檢查測試方案,增加了android bundle包的檢測,和一個web的頁面展示。
使用到的工具有:

基於FlaskBootstrap 優化了報告展示的UI頁面。少廢話,先看東西。

如何執行

由於使用到了matrix-apk-canary-0.6.5.jarbundletool-all-0.15.0.jar兩個jar包
首先要確保java環境已配置正確

  1. clone專案到本地
  2. 安裝python3及相關依賴的庫pip3 install -r requirements.txt
  3. 使用 gunicorn執行
cd /GitHub/ApkChecker_new
gunicorn -w 4 -b 0.0.0.0:9876 server:app --timeout 300
# 300秒延時等待防止上傳apk後,後臺處理時長過長的保護性等待,沒加可能會導致aab的包處理超時導致報告生成失敗

App Bundle

什麼是App Bundle

App Bundle 是一種新的安卓編譯打包方式,編譯工具可以根據CPU架構型別、螢幕解析度、語言等維度將一個傳統的App 打包成一個App集合;使用者下載App時,應用市場會根據終端三種維度的型別提供僅適配此終端的App子集,從而在提供相同業務功能的前提下,節省使用者的網路流量與終端裝置空間。(副檔名為 .aab)
App bundle 是經過簽名的二進位制檔案,可將應用的程式碼和資源整理到模組中,如下圖所示。以藍色標識的目錄(例如 drawable/、values/ 和 lib/ 目錄)表示 Google Play 用來為每個模組建立配置 APK 的程式碼和資源。

更多關於App Bundle的資料可以從檢視 Android App Bundle 簡介

使用 bundletool 命令列工具進行測試

App Bundle的副檔名為 .aab,使用adb install是沒辦法直接將應用安裝到手機的。Google 提供了bundletool 命令列工具實現aab檔案的安裝及資訊檢視。
當 bundletool 根據您的 app bundle 生成 APK 時,它會將這些 APK 納入到一個名為“APK set archive”的容器中,該容器以 .apks 作為副檔名。請使用 bundletool build-apks 命令來生成apks

java -jar bundletool-all-0.15.0.jar build-apks --bundle=/MyApp/my_app.aab --output=/MyApp/my_app.apks    

apks裡面的內容如下,不同的app有所不同

由於部分版本(Android 5.0 以下) 不支援app Bundle功能,個別場景我們需要返回全量包,想要完整的apk,可以在上面的命令列後面加上--mode=universal的引數就行

java -jar bundletool-all-0.15.0.jar build-apks --bundle=/MyApp/my_app.aab --output=/MyApp/my_app.apks --mode=universal

apks裡面就只有一個完整的universal.apk,解壓出來直接adb install universal.apk就好啦

生成的apks 安裝到android裝置上需要執行下面的命令:

java -jar bundletool-all-0.15.0.jar  install-apks --apks=/MyApp/my_app.apks

估算apks檔案實際安裝到手機上的apk大小,可以使用下面的命令:

java -jar bundletool-all-0.15.0.jar get-size total --apks=/MyApp/my_app.apks

更多bundletool的使用說明可以檢視:https://developer.android.com/studio/command-line/bundletool

Matrix-APKChecker

Matrix-ApkChecker 是微信針對android安裝包的分析檢測工具,根據一系列設定好的規則檢測apk是否存在特定的問題,並輸出較為詳細的檢測結果報告,用於分析排查問題以及版本追蹤。Matrix-ApkChecker以一個jar包的形式提供使用,通過命令列執行 java -jar ApkChecker.jar` 即可執行。
簡單使用命令如下:

java -jar matrix-apk-canary-0.4.10.jar --apk <your_apk_path>

使用配置檔案命令如下:

java -jar matrix-apk-canary-0.4.10.jar -apk <your_apk_path> --config <your_config_path>

具體的使用和操作可以檢視:Matrix Android ApkChecker README

APP Bubdle檔案如何結合Matrix-APKChecker檢查包大小資訊

aab檔案通過bundletool命令列生成apks後(其實apks就是個壓縮檔案),解壓後發現裡面就是按照相關的邏輯生成了多個apk。google會根據裝置的相關配置,安裝上對應的apk達到縮小app大小的目的。
由於我測試的app,打包的aab通過bundletool命令列生成apks發現,其實裡面就是一個base-master.apk 和另外兩個base-arm64_v8a.apk和base-armeabi.apk。後兩其實是根據手機是否為64位分出來的兩個包,包含了不同的資源。只要知道其大小就好了,我只要用Matrix-APKChecker檢查去base-master.apk就可以了。
所以對於App Bundle的aab檔案,我處理的方式是

  1. 先使用bundletool 生成apks
  2. 解壓出apks裡的多個apk
  3. 記錄base-arm64_v8a.apk和base-armeabi.apk的檔案大小,用Matrix-APKChecker檢查去base-master.apk
  4. 然後將資料整理以報告的形式展示

基於Flask的web頁面

上面講了App Bundle 和Matrix-APKChecker的內容,下面講解下Web頁面是怎麼實現的

路由及頁面跳轉

route templates
/ home.html
/<package> dashboard.html、dashboard_aab.html
/<package>/reports/<report_path> report_template.html、report_template_aab.html

dashboard頁面展示

上傳apk

點選上傳apk的按鈕後,彈出的彈框相關操作使用了Bootstrap的模態框(Modal),判斷是否為apk、aab的檔案,上傳到工程目錄下的update資料夾下,執行run,執行生成報告html檔案,並彈出alert 提示後自動跳轉到詳情報告頁面。


server.py

@app.route('/upload', methods=['GET', 'POST'])
def upload():
'''上傳apkaab檔案'''
if request.method == 'GET':
return "is upload file ... "
else:
path = request.form.get('upload_path')
file = request.files['upload_file']
file_name = file.filename
if os.path.splitext(file_name)[-1][1:] not in ['APK', 'apk', 'aab']:
return jsonify({"code": 200, "info": "檔案:%s 不是apk or aab,請重試" % file_name})
else:
base_dir = config.tmp_upload_path
if not os.path.exists(base_dir):
mk_dir(base_dir)
base_dir = config.tmp_upload_path
file.save(os.path.join(base_dir, path, file_name))
logger.info('上傳%s' % file_name)
report_info = run(os.path.join(base_dir, path, file_name))
return jsonify({"code": 200, "info": "%s報告生成成功,即將跳轉%s" % (file_name, report_info['report_path']),'report_info': report_info})

home.html、 dashboard.html、 dashboard_aab.html

$('#upload').click(function() {
var upload_path = $('#upload_path').text();
var formData = new FormData($('#upload-form')[0]);
formData.append("upload_path", upload_path);
window.alert("檔案後臺上傳處理中,請稍後");
$.post({
url: '/upload',
dataType: 'json',
type: 'POST',
data: formData,
async: true,
cashe: false,
contentType: false,
processData: false,
success: function(returndata) {
if (returndata['code'] == 200) {
var info = returndata['info'];
alert(info);
var report_path =returndata['report_info']['report_path'];
window.location.href = report_path;
}
},
error: function(returndata) {
alert("報告生成失敗!")
}
})
});
報告統計頁面展示

統計頁面左側可以切換不同的apk 包檢視相應的包歷史大小資訊,由於沒有使用到資料庫,每次生成的html都會儲存在Project_path/Report/<package_name>/reports的路徑下面,並且更新資料夾下的rs.json檔案。 每次的頁面載入獲取資料都是讀取Project_path/Report/下的檔案目錄結構和相應的rs.json檔案資訊後展現出來的。

  • 預設導航欄顯示的名稱為Project_path/Report/下各個<package_name>資料夾的名字
  • 想要左側的導航欄內的不顯示packagename而是對應的app名稱,可以去config.pydict internalapp中填寫上自己app的包名和APP名稱
  • 同一個包名的,apk和aab會存放在兩個資料夾下,aab的包的會自動生成以_aab結尾的資料夾

由於apk和aab的資料內容有差別,web頁面展示也有所不同,統計頁面分別以dashboard.html、 dashboard_aab.html模板,要是剛開始沒有任何報告的時候(Project_path/Report/都還沒有的時候),則以home.html為模板展示首頁。


server.py

@app.route('/')
@app.route('/<package>')
def statistics(package=None):
'''模板生成統計報告'''
folders = get_report_folders()
if folders:
if not package:
package = list(folders.keys())[0]
else:
pass
if not os.path.exists(os.path.join(config.report_folder, package, 'rs.json')):
pass
else:
reports_info = json.loads(read_file(os.path.join(config.report_folder, package, 'rs.json')))
reports_info['last']['package'] = package
report_list = reports_info['total']
script_list = make_script_str(report_list)

page, per_page, offset = get_page_args(page_parameter="page", per_page_parameter="per_page")
pagination_users = get_users(report_list, offset=offset, per_page=per_page)
pagination = Pagination(
page=page,
per_page=per_page,
total=len(report_list),
record_name="users",
css_framework="bootstrap4"
)

context = {
'urls': pagination_users,
'size_list': script_list[1],
'time_list': script_list[0],
'package': package,
'folders': folders,
'appname': reports_info['last']['appname']
}

if 'aab' in package:
return render_template("dashboard_aab.html", context=context, per_page=per_page, pagination=pagination)
else:
return render_template("dashboard.html", context=context, per_page=per_page, pagination=pagination)
else:
return render_template("home.html")

包打大小曲線圖繪製

報告上的折線圖是從Highcharts 演示 › 可縮放的時間軸修改而來的,在dashboard.html、 dashboard_aab.html內編輯好相應html和js程式碼後,傳入的資料為apk的大小及報告的建立時間,都是在rs.json中已經生成儲存好了的。
詳情報告頁面的,餅圖也是用的Highcharts 演示 › 基礎餅圖

這個網站提供了豐富的報表展示效果,支援線上編輯和除錯,提供多種主題的選擇。

表格及分頁

每個包大小詳情的基本資料組成的表格在web頁面展示,內容傳入也都是在rs.json的total中已經儲存好了的。只需要將資料傳入就行


dashboard.html

<div class="table-responsive">
<table class="
table table-striped table-sm table-bordered">
<thead class="
table-dark">
<tr>
<th>#</th>
<th>建立時間</th>
<th>APP版本</th>
<th>versioncode</th>
<th>包體大小</th>
<th>詳情</th>
</tr>
</thead>
<tbody>
<!--這裡寫for開頭-->
<!--{% for url in context["
urls"] -%}-->
<tr>
<td>{{ loop.index + pagination.skip }}</td>
<td class="
text-left">{{ url['create_time'] }}</td>
<td class="
text-left">{{ url['versionName'] }}</td>
<td class="
text-left">{{ url['versionCode'] }}</td>
<td class="
text-left">{{ url['apksize'] }}</td>
<td><a href="
{{ url['report_path'] }}"><span style="color: #0077FF;">檢視詳情</span></a></td>
</tr>
<!--{% endfor -%}-->
<!--這裡寫for結束-->
</tbody>
</table>
</div>
{{ pagination.links }}

表格要是資料很多,在一個頁面展示就會顯得頁面很長,因此需要對錶格進行分頁切換的處理。就是這個東西

Google了一下發現flask有相應的模組,就直接哪來用就是了。Flask-paginate/


python

from flask_paginate import Pagination, get_page_args
from flask import render_template

def get_users(users,offset=0, per_page=10):
return users[offset: offset + per_page]

@app.route('/')
def pages():
page, per_page, offset = get_page_args(page_parameter="page", per_page_parameter="per_page")
users = [{"create_time": "2020-05-05 19:52:32","size": "39.23"},
{"create_time": "2020-05-06 19:52:32","size": "39.23"},
...
...
]
pagination_users = get_users(users,offset=offset, per_page=per_page)
pagination = Pagination(
page=page,
per_page=per_page,
total=len(users),
record_name="users",
css_framework="bootstrap4"
)
return render_template('index.html', users=pagination_users, per_page=per_page, pagination=pagination)
if __name__ == '__main__':
app.run(host='0.0.0.0', port=9876, debug=True)

index.html

{{ pagination.info }}
{{ pagination.links }}
<table class="table table-striped table-sm table-bordered">
<thead>
<tr>
<th>#</th>
<th>建立時間</th>
<th>包體大小</th>
</tr>
</thead>
<tbody>
{% for url in users -%}
<tr>
<td>{{ loop.index + pagination.skip }}</td>
<td class="
text-left">{{ url['create_time'] }}</td>
<td class="
text-left">{{ url['size'] }}</td>
</tr>
{% endfor -%}

</tbody>
</table>
{{ pagination.links }}

頁面的UI優化

頁面很多是表格的展示,因為是基於和Bootstrap 的,所以根據Bootstrap-Tables配置文件對模板html進行相關的修改就可以展現較為美觀的頁面了。
例如想把表格展示位黑的 ,加上class="table-dark" 就好了。

相關文章