甘 佳 張茂凡 周志寰 任 想 潘 婭
(1.西南科技大學計算機科學與技術學院 四川綿陽 621010;2.西南科技大學計算機應用研究所 四川綿陽 621010)
在移動互聯網的發展過程中,Android系統因其強大的可交互性和開源性,逐漸占領了移動終端的市場。據調研機構Gartner全球智能手機銷售報告顯示,2018年第一季度手機市場中,Android系統的市場份額高達85.9%[1]。但也因其開源性,Android終端應用濫用手機硬件資源導致手機卡頓,影響用戶體驗。由于Android系統碎片化嚴重,設備性能也參差不齊,給應用的穩定流暢運行帶來了巨大挑戰。因此,在應用正式上線前,應對其進行盡可能完善的性能測試。目前,針對手機應用的性能測試點包括有CPU、內存、流量、電量、網絡、啟動耗時、過度繪制等。但是在啟動耗時、過度繪制方面,通常只能獲取大致的性能數據,不能對問題分析進行準確定位。
國內移動互聯網公司針對Android應用的性能測試,主要分為兩種方案:一是借助工具,實時采集手機性能數據;二是在測試APK中插入測試樁,獲取性能數據。第一種方案的主要代表有騰訊的GT 和訊飛的iTest 等。GT提供了兩套性能測試解決方案,其中一種是將其提供的SDK嵌入APK內,從而達到開發調試的目的,另一種是提供測試工具的APK,與待測應用一起安裝在手機上,操作待測應用,實時展示性能測試結果。iTest工具與GT的后一套方案類似,也是提供測試工具APK,對待測應用進行實時測試及記錄。使用測試工具APK來進行性能測試的方案雖然能夠實時獲取手機性能數據,但發現問題后測試人員不能快速地進行問題定位。為了更好地解決問題定位,Google為Android開發者提供了許多獲取性能數據的API,開發者只需在測試APK中調用API,便能進行性能問題定位。但是這種方案缺點在于要在源碼中插入測試API調用代碼,而插入測試代碼的APK只能作為測試版本,不能發布到應用商店。
本文提出一種性能自動化測試方案,利用反編譯技術獲得應用源碼,在源碼中插入測試樁函數,重新打包后進行性能測試,從而獲取更精準的性能測試數據文件,自動解析后展示異常數據,能夠對測試工作進行有效輔助,提高測試效率。
為了更好地定位性能問題,本文提出將反編譯技術應用到測試過程中,測試設計流程如圖1所示。測試人員只要提供待測APK以及測試樁的Java代碼,然后運行本框架,最后只需等待測試結果即可。
整個方案分為三部分:反編譯APK并插入代碼、運行APK獲取并解析性能數據、展示數據及定位問題。其中,第一部分是本文的核心工作,主要利用反編譯技術獲得APK源碼,根據需要測試Activity名稱,將測試樁函數插入原APK中的對應文件中,并重新編譯生成測試包。

圖1 方案設計流程圖Fig.1 Flow chart of the scheme design
本文所提出的性能自動化測試方案,在具體框架實現及應用時有七大模塊,包括:
(1)反編譯APK;
(2)將Java代碼編譯為smali代碼;
(3)插入smali代碼后對APK重新打包、重簽名;
(4)編寫功能自動化測試腳本;
(5)運行APK獲取性能數據文件;
(6)解析性能數據文件;
(7)可視化性能數據定位問題。
反編譯APK并插入測試樁是本方案的關鍵部分,整個流程如圖2所示。
在Android Studio等編譯器中,開發完成的Android應用點擊運行,編譯器會將應用打包成一個后綴為.apk的文件。該文件可以在Android系統的手機或者平板電腦等終端上下載并安裝。一般情況下,Android應用被打包成.apk文件之后,就無法逆向該文件得到應用的源代碼以及資源文件。但在APK未加殼的情況下,通過一些開發者工具,還是可以將APK進行反編譯并得到開發這個應用時使用的資源文件(如圖片等) 、源代碼等。

圖2 反編譯APK插入測試樁流程圖Fig.2 Flow chart of the decompiled APK inserted intest stub
本文中要用到的反編譯工具是Google公司提供的ApkTool,將待測APK反編譯成 .smali文件。工具的使用方法可以參考官網 ,方案中將ApkTool反編譯命令集成在框架中自動執行。在使用過程中需要注意以下問題:
(1)ApkTool盡量使用最新版本。ApkTool工具新老版本的執行命令存在差異,并且舊版本ApkTool容易導致反編譯過程中出現報錯“Exception in thread "main" brut.androlib.AndrolibException”。
(2)盡量使用未代碼混淆的APK進行測試。混淆代碼后的APK,進行反編譯后,不能完全復原其代碼,會對接下來的測試樁插入造成影響。
在前面的測試思路中也提到,本文需要測試人員提供Java測試樁代碼,再由框架自動編譯為smali代碼插入到源碼中。在通過命令行編譯Android Java代碼文件過程中,如果Java文件引用到許多第三方庫文件,在命令編譯過程中就會報很多錯誤。為了編譯順利通過,本文建議將測試樁代碼中所有引用到的Jar包都放在一個文件夾中,然后再編譯命令中引入該文件夾即可。
在本文方案中,Java文件編譯為smali文件需要通過以下3個步驟:
(1)通過“javac test.java”命令將 .java文件轉化為 .class文件;
(2)通過dx.jar工具命令“dx --dex --output=test.dextest.class”將 .class文件轉換為 .dex文件;
(3)通過baksmali.jar工具命令“java -jar baksmali.jar test.dex”將 .dex文件轉換成 .smali文件。
前面的兩部分介紹了在本方案中是如何實現反編譯APK為Smali代碼以及將Java代碼編譯成smali代碼。在1.2節中的測試思路中也提及,本文提出的方案支持自動化測試,能夠自動化展示性能測試情況,便于定位問題。下面就本文如何實現自動化測試進行介紹。
首先,本框架整體使用Python語言編寫,提供配置文件供測試人員填寫,因此測試人員無需關心框架內部實現細節,而框架只需要解析配置文件來獲得APK信息、Java測試樁代碼等,同時,在配置文件中也配置了代碼所要插入的Activity以及函數位置,這是實現自動化測試的第一步,緊接著便可自動化進行反編譯、插樁等一系列活動。配置文件內容如圖3所示。

圖3 框架配置文件Fig.3 Configuration file of framework
其次,在本框架下進行性能測試時直接運行自動化測試腳本。經過實踐本框架支持任意性能自動化測試框架,如UIautomator,monkey,QT4A等,保證了性能測試的自動化執行,無需人工干預。隨著性能測試的不斷深入,數據也在不斷積累,最終本框架將獲取到的兩類性能數據自動進行數據可視化,呈現在Web頁面上。
綜上所述,本框架在測試過程中不需要進行人工干預,能夠實現自動化測試并展示結果。
為了驗證本文提出的方案能夠對定位性能問題及優化提供幫助,本文針對啟動耗時、過度繪制兩個專項測試點,選擇開源游戲應用《2048》進行實驗。下面首先介紹測試點的測試基本原理再闡述具體的實驗過程和結果。方案如圖4所示。

圖4 實驗方案設計Fig. 4 Experiment scheme design
(1)啟動耗時測試
啟動耗時是指在Android應用中從零開始到頁面Activity加載完成的時間。Google官方文檔中給出兩種測試方法:第一種是使用命令“adb shell am start-w packagename/activity”來啟動耗時相關參考值,如圖5所示。該命令獲取的信息中,各字段及解釋如表1所示。

圖5 adb獲取頁面啟動耗時結果Fig.5 Results of starting time consumption via adb
該方法雖然能夠獲取到應用的啟動耗時等信息,但對應用啟動耗時問題定位沒有幫助。第二種是函數耗時的分析方法:在APK源代碼中,通過插入測試樁函數,輸出一個后綴為 .trace的二進制文件,該文件記錄了函數的耗時、調用次數、隸屬線程等信息;同時,借助Android SDK提供的dmtracedump工具,將 .trace二進制文件解析成可讀的編碼格式,從可讀文件提取出關鍵字來獲得函數耗時等信息。

表1 APK啟動耗時字段及含義Table1 Explanation fields of APK starting time consumption
(2)過度繪制測試
過度繪制(Overdraw)描述的是手機和平板等移動終端屏幕上同一個像素在同一幀的時間內被繪制了多次。在一個多層次重疊的UI結構中,如果用戶不可見的界面也存在繪制的行為,一些像素區域會被多次繪制,因此會造成CPU和GPU硬件資源不必要的浪費。在 Android 系統的開發者模式中,有調試 GPU 過度繪制的開關,打開該開關后Android手機屏幕會有藍色、綠色等顏色的色塊,不同顏色色塊代表過度繪制嚴重程度的不同。在Android系統中沒有直接提供接口獲取屏幕過度繪制計數,但Framework/base/core/Java/android/view/HardwareRender.java中的GLRenderer內部類提供了drawOverDrawCounter方法(如圖6所示),調用該方法可以在手機屏幕左下角顯示出當前UI界面過度繪制次數(即overdraw參數的值),因此獲取overdraw參數的值即可知道過度繪制的次數。

圖6 HardwareRender類關鍵代碼Fig.6 The key codes in HarwareRender class
綜上所述,在應用本方案進行測試時,我們將按照這樣的基本測試原理來編寫測試樁函數,進行實驗驗證。
按照實驗方案設計,實驗按照以下步驟進行:
(1)編寫功能自動化腳本運行應用,通過本方案能夠展現應用啟動耗時的情況,結果如圖7所示。從圖中可以直觀看出調用函數com.demo.game2048publish.SplashActivity.waitTime()消耗6 734 us,遠高于其它函數,導致應用啟動耗時較長;

圖7 《2048》測試結果Fig.7 Test result of 2048
(2)通過測試發現的問題,開發人員可以根據函數名定位到應用源碼函數調用的位置,進而發現該函數在啟動頁面的onCreate()方法中被循環調用了1 000次。
(3)在應用源碼中修復該問題,這里將源碼中的循環調用刪除,改成只調用一次waitTime()函數。
(4)修改應用源碼后重新打包生成 .apk文件,使用同樣的運行腳本再進行一次性能數據的獲取,得到的啟動耗時結果如圖8所示,可以看到較優化前,該函數的調用耗時有明顯下降,約為優化前耗時的1/1000。
為了更好地說明應用的啟動耗時在優化前后的變化,實驗在同一部測試機上使用adb命令的方法3次獲取應用的啟動耗時TotalTime值,結果如表2所示。

圖8 《2048》應用啟動耗時優化后結果Fig. 8 Result of 2048 starting time consumption after optimization

測試次數啟動耗時優化前/ms啟動耗時優化后/ms第一次265257第二次266255第三次265258
從表2可以看出,《2048》應用的啟動耗時從優化前的265.3 ms降低為優化后的256.6 ms,啟動耗時優化后降低了8.7 ms。
以上實驗表明,本文提出的啟動耗時測試方案能夠發現并準確定位到應用的啟動耗時問題,問題修復后得到的啟動耗時數據有明顯下降進一步說明定位的有效性。
同理,對過度繪制問題的定位,在本方案下進行測試后可以清楚看到哪個函數過度繪制問題嚴重(如圖9所示),可以方便開發人員準確定位。

圖9 過度繪制可視化圖Fig. 9 Visualization of overdrawing
本文針對Android應用性能測試過程中不易定位的問題現狀,提出了一套自動化測試方案,基于反編譯技術,向APK源碼中插入測試樁函數,并重新生成測試包,輔助測試人員進行性能測試。以啟動耗時、過度繪制為例,應用本方案進行性能測試。通過在某兩款應用上進行實驗,表明相較傳統的方法,該方案能夠更為精準地發現并定位啟動耗時和過度繪制的問題。在編譯APK包時,可同時生成測試包和正式包,規避了測試樁函數人工插入的成本問題,也能解決單獨編譯測試包的耗時問題。該方案目前雖只在啟動耗時、過度繪制兩點上進行應用,但反編譯源碼插入測試樁函數測試同樣也適用于其它專項測試,稍作調整后具備一定的通用性。