馬思峻 肖 榮 成江偉
(上海理想信息產業(集團)有限公司 上海 201315)
?
Android應用性能數據采集探針研究
馬思峻 肖 榮 成江偉
(上海理想信息產業(集團)有限公司 上海 201315)
從社交到電商、旅游、醫療、教育、餐飲和出行,移動應用逐漸成為人們日常生活的一部分。移動應用開發商之間的競爭越來越激烈,移動應用想要提升用戶體驗和增加用戶數,從人們真實使用的手機上采集性能數據、崩潰日志至關重要。針對通過修改源代碼埋樁方式采集移動應用性能數據有諸多缺陷,提出使用面向切面編程對Android源代碼進行預編譯處理,能夠在不需要手工修改代碼的前提下自動插入性能數據采集探針代碼。該方法已經在多個項目中實際使用,使用該方法處理過的Android應用能夠遠程上報符合要求的應用性能和用戶使用習慣數據。證明該方法在實際使用中具有可行性,為Android應用性能數據采集提供了新的思路。
Andriod 應用性能監控 面向切面編程 Java代理 ASM
隨著智能手機的迅速普及,移動互聯網正在改變我們的衣食住行,許多互聯網公司看到了其中巨大的商機,開發了各種移動應用滿足我們的工作和生活需求。但是移動應用爭奪用戶之間的競爭空前激烈,許多移動應用的生存周期不到一年,一個交互的卡頓就會導致用戶卸載應用。一款成功的移動應用不但要有突破性的功能,還要有良好的用戶體驗。
移動應用上線僅僅是個開始,為了增加用戶數,提高保有率和使用時間,后期有大量的運維工作。而且Android平臺的碎片化,不可能在每家廠商各種型號的手機上做完整測試,錯誤不可避免。所以需要從用戶當前正在使用的手機中采集各種性能數據和崩潰日志,為市場營銷和錯誤修正工作提供分析數據。
業內對該問題提出了很多解決方案,國內最常用的移動統計平臺是百度移動統計、友盟統計。它們使用的方法是在每個頁面的onResume、onPause方法中插入統計頁面時長代碼,自定義事件在應用程序中埋點來統計用戶的點擊行為,但這種方式有諸多弊端:
(1) 原有代碼變更頻繁,讓插入標記的代碼也經常變動,非常耗時而且容易引入錯誤,給開發工作帶來額外負擔。
(2) 插入代碼的粒度決定了采集數據的詳細程度,而且只能采集可以預測到的錯誤。
(3) Android經常使用多線程編程和異步請求,讓采集性能數據變得困難。
國外facebook工程師Delyan Kratunov撰文介紹了他們收集Android應用性能檢測數據的新方法,核心是基于規則的字節碼重寫器。該重寫器可以匹配代碼位置,然后插入或操作代碼,要實現該方法的過程中會遇到一些需要克服的問題,新方法已經使用在facebook開發的應用中。這給了我們使用新方法的思路,不需要手動在頁面和函數執行前后插入代碼,而是使用面向切面編程AOP[1]方式就能在原有代碼中嵌入性能數據采集探針,采集到我們需要的數據。國內廠商聽云等都在努力研發相關的產品。
本文詳細介紹了使用AOP方法實現Android移動應用性能數據采集探針的實現原理和相關技術,架構和組件間的關系以及關鍵技術難點。
Android應用使用Java語言開發,Java中的相關技術可以借鑒到Android開發中。Java中已經存在面向切面編程技術,可以通過預編譯方式或運行期動態代理實現在不修改源代碼的情況下給程序動態統一添加代碼。允許分離應用的業務邏輯和系統服務,將日志記錄、性能統計、異常處理等代碼從業務邏輯代碼中劃分出來,修改這些代碼可以不影響原有業務邏輯,這給了我們在Android系統上實現性能數據采集的新思路。
JDK1.5引入了Java代理技術javaagent[2],可以在main運行前動態替換和修改類的定義,JDK1.6增強了功能,在運行時也能動態替換和修改類。對Java class類進行修改,有很多字節碼庫可以做選擇,常用的有javaassit、asm[3-4]。兩者相比較,javaassit是基于源碼,asm是基于字節碼,asm開發復雜,而且需要了解字節碼的知識,但是性能更快、靈活性也高。不能因為引入性能數據采集探針而影響原有應用的性能,所以使用asm修改Java編譯出的class文件。
Android編譯時先使用Java編譯器把Java文件編譯成class文件,Android 4.4以前通過dex命令把class文件編譯成dex文件,Android 4.4以后把class文件編譯成art文件[5],然后打包簽名生成安裝包。在javac編譯Java源代碼生成class文件之前把Android SDK中和網絡請求、異步任務、Sqllite、Activity、異常處理相關的類替換掉,加入性能數據采集代碼,最后生成的安裝包就會自動包含探針功能。由于Android sdk的相應類替換掉是程序自動運行的,沒有必要擔心會漏掉監測數據。
文獻[6]分析了該方法理論上是可行的,但在實際開發過程中會遇到許多問題。首先,許多應用都使用了很多第三方庫,除了需要替換Android SDK原有的類之外,還要替換很多第三方類庫,例如網絡請求除了java.net.URL外,還有Httpclient、okHttp2、okHttp3庫[7]。其次,替換java class文件引入的應用性能數據采集和上報功能不能導致應用程序安裝包膨脹,不能影響原有應用的業務邏輯和性能。最后要使用方便,不給開發人員增加額外負擔,只需要簡單的代碼添加就能引入性能數據采集功能,支持各種Android開發工具,例如Eclipse、Android Studio。
性能數據采集探針由三部分構成:第一部分java rewriter重寫jar包,匹配類名和方法名注入替換類和替換方法;第二部分agent實現插入性能統計代碼的Android SDK、第三方庫的替換類和采集數據上報服務器;第三部分Eclipse插件[8],將探針安裝到Eclipse開發環境中。三部分包含的功能和之間的關系如圖1所示。

圖1 組件功能和相互之間的關系
3.1 Java類重寫
通過java-javaagent參數指定rewriter.jar包文件路徑,rewriter.jar在加載class文件之前對字節碼修改。jar包中的META-INF/MAINIFEST.MF文件包含Premain-Class、Agent-Class鍵值對,其值為實現字節碼修改的類名,修改字節碼類中必須實現premain、agentmain方法,參照Java的標準,在此不再展開。
常用的Android應用開發工具有Ant、Eclipse、Maven、Android Studio。為了同時支持四種Android開發工具,需要在每種開發工具執行編譯方法前替換掉和性能數據采集相關類的class字節碼,四種工具在編譯程序時執行的方法是不同的,它們執行的類名和方法如表1所示。

表1 開發工具執行類名和方法

圖2 類字節碼替換調用流程
遵循asm開發規范,為每種開發工具建立各自的字節碼轉換適配器ClassVisitorFactory。ClassVisitorFactory中創建類適配器ClassAdapter,類適配器ClassAdapter中創建類方法轉換工廠MethodVisitorFactory,類方法轉換工廠中創建MethodVisitor對象,MethodVisitor對象實現onMethodEnter方法,onMethodEnter方法調用InvocationDispatcher類的invoke函數,流程圖如圖2所示。
invoke函數負責具體替換Android SDK和第三方庫類,其中使用裝飾者(Decorator)模式,按照順序替換自定義注釋、Activity(頁面)、異步任務、類方法、Android應用上下文,對應的ClassVisitor分別為AnnotatingClassVisitor、ActivityClassVisitor、AsyncTaskClassVisitor、WrapMethodClassVisitor、ContextInitializationClassVisitor。
WrapMethodClassVisitor 負責替換需要采集性能數據的方法,jar中包含配置文件說明原有方法和替換方法的對應關系和替換方式。jvm加載類時讀取方法名,方法名和配置文件進行匹配,如果發現有替換的方法,就Replace或wrapper原有方法的字節碼,表2給出配置文件的一部分內容。

表2 替換方法配置文件
Wrapper和Replace的區別在于,Wrapper是執行完原有方法之后再執行插入的字節代碼,而Replace是直接執行替換代碼。
ActivityClassVisitor在Activity生命周期中的onStart、onStop函數中插入頁面計時開始、結束代碼和應用狀態監聽。AsyncTaskClassVisitor在AsyncTask任務執行函數doInBackground和執行完成函數onPostExecute中插入跟蹤計時代碼。
該jar包編譯和執行時需要引入asm、dom4j、guava、reflections、slf4j等第三方jar包,rewrite.jar的premain或agentmain方法執行結束,涉及應用性能統計的類和方法替換完畢,然后開發工具開始進行Android應用編譯。
3.2 agent代理包
Eclipse插件把agent.jar包添加到Android應用引用的庫文件中,而不是在Java啟動時通過-javaagent參數加載,它是Android性能探針的核心。agent包含插入數據采集代碼的Android sdk和第三方庫的替換類、數據隊列預處理、數據上傳、各種參數配置等功能。
3.2.1 agent代理包架構
代理包架構如圖3所示。

圖3 agent代理包架構
ActivityTrace、應用運行時的各種測量值、Agent自身錯誤采集后加入任務隊列,定時任務從隊列中取出數據直接加入上傳數據對象。Http網絡請求和執行函數時間測量值因為需要做數據處理,定時器取出數據通過生產者消費者模式處理后加入上傳數據對象。Http錯誤數據不通過任務隊列直接進入生產者消費者模式處理數據。
3.2.2 數據采集
agent采集的數據有Activity打開及其內部方法執行時間、網絡請求等待時間、agent自身異常、應用運行崩潰異常日志、地理位置、CPU和內存使用率。
(1) 方法執行時間數據采集
以SDK中圖片加載類BitmapFactory為例進行說明,根據配置文件替換為BitmapFactoryInstrumentation,類中傳入文件地址加載圖片的函數decodeFile代碼修改為在執行原有功能前先執行enterMethod進入方法函數,原有功能執行結束后執行exitMethod()退出方法函數。
每個函數執行時都有其所在的上下文,不是Activity就是其他方法,enterMethod函數新建性能跟蹤子對象時傳入它所在父跟蹤對象的唯一標識。子跟蹤對象創建成功后加入父跟蹤對象的子對象集合,并且給子跟蹤對象的函數進入時間屬性設置為當前時間,同時把子跟蹤對象賦值給ThreadLocal線程安全變量。
exitMethod函數從ThreadLocal變量中取出Trace跟蹤對象,給跟蹤對象設置函數執行完成時間戳。然后把函數執行完成采集到的跟蹤對象壓入非阻塞隊列,由數據預處理模塊讀取Trace跟蹤對象進行數據預處理。
BitmapFactory中有很多返回Bitmap的重載函數,在BitmapFactoryInstrumentation中全部實現這些重載函數。在配置文件中記錄了所有要替換的方法和其所在的類,采集GSon、JSONArray、JSONObject、AsyncTask、SQLite等功能執行時的性能數據也采用類似的方法。
(2) Activity性能數據采集
Activity生命周期在啟動時會觸發onCreate,在結束時觸發onDestroy。但有些情況下,例如長時間不操作屏幕鎖屏和用戶按HOME鍵后應用置于后臺,也要認為頁面已經結束運行。在執行Activity的onCreate方法前調用TraceMachine的enterMethod函數,用戶長時間不操作或者應用切入后臺運行后,頁面也會結束跟蹤。然后把ActivityTrace對象壓入非阻塞隊列,由任務隊列將其加入到上傳數據對象中,ActivityTrace包含頁面的進入和退出時間、頁面包含方法執行消耗時間、內存和CPU的取樣值,判斷Activity結束的流程如圖4所示。

圖4 判斷Activity結束流程
(3) 網絡請求等待時間數據采集
以Android應用最常用的網絡請求庫Httpclient為例進行說明,Httpclient中的網絡請求類HttpRequest和響應類Httpresponse都需要HttpEntity屬性,創建包含跟蹤狀態對象的HttpEntity接口實例HttpRequestEntityImp和HttpResponseEntityImpl,賦值給HttpRequest和Httpresponse的HttpEntity屬性。Http請求開始時,向transactionState跟蹤對象置入開始時間、請求地址、請求方法、網絡類型、網絡運營商。如果請求成功,HttpRequestEntityImpl向transactionState置入發送字節數,HttpResponseEntityImpl向transactionState置入接收字節數、請求結束時間,返回狀態碼。如果請求錯誤,置入結束時間、錯誤碼。一個Http請求完成后,transactionState持有從本次Http請求采集到的所有數據,新建對象后壓入非阻塞隊列。
Android Http請求有很多種實現方式,除了Android SDK自帶的HttpURLConnection和HttpClient(Android 6.0已經移除)外,還有OkHttp2、Retrofit[9]等第三方庫,每個第三方庫都要重寫其部分源代碼。加入性能采集代碼。以OkHttp2為例,新建實現Call和CallBack接口并加入transactionState統計對象的實例,在Http請求和響應時,采集本次Http請求的數據,Http請求完成后新建對象壓入非阻塞隊列。
(4) 自身錯誤采集
Agent加入原有應用后不可避免會引入錯誤,為了跟蹤分析錯誤發生的原因和次數,使用埋樁方式,在錯誤可能發生的位置(例如初始化、上傳錯誤日志時)插入代碼,新建錯誤對象計數類,設置錯誤發生的詳細說明,發生次數等屬性,壓入非阻塞隊列。在多線程運行環境中,更改發生錯誤發生次數時,需要使用同步鎖Synchronized[10]。
(5) 崩潰日志采集
根據文獻[11]的分析,在agent初始化時新建實現UncaughtException接口的異常捕獲類,并注冊為程序中默認未捕獲異常處理,當應用出現沒有處理的異常時,由該類來捕獲這些異常。該類將異常發生的時間、Activity的執行軌跡、設備信息、應用信息等以JSON格式保存在本地,每條崩潰記錄都有唯一標識。然后另外啟動一個線程上傳崩潰日志給服務器,上傳成功后從本地刪除已發送的日志。
(6) 位置信息采集
應用運營方需要統計使用者的地域分布,所以在agent包中加入了上傳位置信息的功能,該功能會消耗電量,使用者可以關閉定位功能。如果使用者打開了該功能并且應用中有定位權限,agent就開啟定位監聽,為了不影響用戶使用,定位是采用被動定位方式(PASSIVE_PROVIDER),使用現成的定位提供方,不主動請求位置信息。當其他應用更新了定位信息,系統會保存,應用接收到消息后就直接使用。
3.2.3 數據預處理
數據預處理模塊使用生產者、消費者模式[12]對數據進行處理,agent初始化時為Http請求、Http錯誤、Activity跟蹤測量值、方法執行時間、自身錯誤等數據都建立各自的生產者和消費者類。管理中心管理這些生產者和消費者,可以增加和刪除這些實現生產者Producer接口和消費者Consumer接口的類。從隊列中取出數據后,使用統一的生產者接口方法對數據進行預處理,例如網絡請求HttpTransactionProducer生產者,對數據處理完畢后,發送廣播通知HttpTransactionConsumer消費者接收處理數據。消費者也使用統一的消費者接口接收數據,在消費者中新建Http請求事務對象,設置接收數據、發送數據、請求方法、返回狀態等屬性后放入上傳數據對象。各種數據處理完畢后,上傳數據對象中包含設備信息、數據令牌、Http請求事務數組、Http錯誤數組、Activity跟蹤隊列數組、Session屬性數組、分析事件數組等各項數據。
3.2.4 數據上傳
數據上傳模塊負責所有數據的檢查、序列化、上傳服務器,上傳模塊中有定時器周期性的檢查網絡狀態是否連接。如果網絡狀態已經連接,從非阻塞隊列頭部取出數據,直到隊列為空,最后生成一個統一的上傳對象。上傳模塊把對象中的所有屬性序列化為JSON格式,用Http Post方式上傳服務器。如果發送成功,清除上傳對象中的所有數據,如果發送失敗,記錄發送嘗試次數,等待下次再次發送。
3.3 Eclipse插件
Eclipse插件的作用是讓Eclipse啟動時java虛擬機加載rewriter.jar包,并且給現有Andriod項目添加agent.jar包。
為了使java虛擬機啟動時加載rewriter.jar包,需要在插件中實現IStartup接口的earlyStartup方法,在其中附著重寫類。這樣就可以在Eclispe執行編譯前hook掉Android SDK和第三方庫中的類和方法。因為要使用tools.jar包中的VirtualMachine類,所以java運行環境一定要使用JDK,而不是JRE。earlyStartup方法中附著Instrumentation代理的代碼如下所示:
VirtualMachine vm = VirtualMachine.attach(pid);
vm.loadAgent(jarFilePath); //rewriter.jar的文件路徑
vm.detach();
其二,為了把agent.jar包拷貝到項目的庫文件中,需要在插件中實現IObjectActionDelegate接口的run方法,開發人員在添加應用性能采集功能到項目中時,執行run方法把agent.jar拷貝到項目的libs目錄中。
該Android性能數據探針已經在上海國際馬拉松Andriod項目中得到了實踐檢驗。在應用的首頁面嵌入啟動探針代碼,后臺服務器就能獲得探針上傳的性能數據,以Http請求為例,手機應用的每次Http請求會在60秒內以JSON格式上傳,上傳數據的JSON格式如下所示:
["http://m.shang-ma.com/shm-server/client","wifi",0.12600000202655792,200,0,0,2920,"POST"]
網址:http://m.shang-ma.com/shm-server/client
網絡:wifi
總耗時:0.12600000202655792秒
請求狀態碼:200
errorCode :0
發送字節數 :0
接收字節數:2920
Http方法:POST
嵌入探針后的安裝包和原有包相比體積增加對比如表3所示。

表3 加入探針前后安裝包大小對比
嵌入探針后的安裝包和原有包相比運行時每天耗電量差值在5%以內,可用內存差值可以忽略不計。
本文針對Android互聯網應用需要采集各種性能數據和崩潰日志,現有埋樁方式使用不方便,容易漏掉數據和出錯等問題,提出了使用面向AOP方式批量替換需要統計性能數據的類和方法。在編譯時自動把探針功能加入,開發人員只需要添加簡單的啟動性能采集服務代碼,服務器就能獲得需要的性能數據和崩潰日志。
經過實際使用證明,使用該方案開發出來的性能數據采集工具能夠減輕開發人員排查應用錯誤和慢交互的負擔,而且不會影響原有應用的功能和性能。下一步工作將增加對WebView交互性能監測數據的采集和上報。
[1] 張廣紅, 陳平. 關于AOP實現機制和應用的研究[J]. 計算機工程與設計, 2003, 24(8):14-17.
[2] 寧紅巖, 廖小剛. 開發Agent的得力工具:Java[J]. 計算機工程與應用, 2001, 37(11):119-121.
[3] éric Tanter, Toledo R, Pothier G, et al. Flexible metaprogramming and AOP in Java[J]. Science of Computer Programming, 2008, 72(1-2):22-30.
[4] Würthinger T, Wimmer C, Stadler L. Dynamic code evolution for Java[C]// International Conference on the Principles and Practice of Programming in Java. ACM, 2010:10-19.
[5] Yadav R, Bhadoria R S. Performance Analysis for Android Runtime Environment[C]// International Conference on Communication Systems and Network Technologies. 2015.
[6] Ravindranath L, Nath S, Padhye J, et al. Automatic and scalable fault detection for mobile applications[C]// International Conference on Mobile Systems, Applications, and Services. ACM, 2014:190-203.
[7] 喬一乘. 基于Android+JAVA EE架構的校園信息交互系統[D]. 吉林大學, 2012.
[8] 魏楚元, 李陶深, 張增芳. Eclipse:基于插件的下一代通用集成開發環境[J]. 計算機應用與軟件, 2005, 22(6):38-40.
[9] Ciric A R, Floudas C A. A retrofit approach for heat exchanger networks[J]. Computers & Chemical Engineering, 2010, 13(6):703-715.
[10] 徐紹忠, 王乘, 劉小虎. Java并發機制研究[J].計算機工程, 2002,28(4):73-75.
[11] Ajay Kumar Jha, Woo Jin Lee. Capture and Replay Technique for Reproducing Crash in Android Applications[C]// The, Iasted International Conference on Software Engineering. 2013.
[12] 胡浩民.“單生產多重消費”算法的提出與實現[J].計算機應用與軟件, 2008,25(7):116-118.
RESEARCH ON PERFORMANCE DATA ACQUISITION PROBE OF ANDROID APPLICATION
Ma Sijun Xiao Rong Cheng Jiangwei
(ShanghaiIdealInformationIndustry(Group)Co.,Ltd,Shanghai201315,China)
Mobile applications have gradually become part of people’s daily life in social contact, e-commerce, tourism, health care, education, catering and trip. Due to the increasing fierce competition between application developers who want to improve user experience and increase the number of users, how to collect performance data and crash logs from the real mobile phone user is essential. In order to collect mobile application performance data has many defects by modifying the source code embedded pile, this paper proposes a method of pre-compiling the Android source code with aspect-oriented programming. It can automatically insert the performance data acquisition probe code without modifying the code manually. The method is used in many projects, and the Android application processed by this method can report the application performance and the data of user’s habits, which proves the feasibility of this method in practical application. The method for Android’s applied performance data and data collection provides a new way of thinking.
Android APM AOP Java agent ASM
2016-05-18。馬思峻,高工,主研領域:移動應用開發,移動設備管理,移動性能監控。肖榮,教授級高工。成江偉,工程師。
TP311.52
A
10.3969/j.issn.1000-386x.2017.07.036