閱讀579 返回首頁    go 汽車大全


深度性能測試:功能決定現在,性能決定未來!

  深度性能測試能協助測試人員發現APP中存在的深層次性能問題,直接定位多項性能問題及瓶頸的根本原因,方便開發者快速提升APP性能表現,使得APP運行得更加穩定。MQC深度性能測試能夠幫助開發者發現深層次的性能問題,更精準地定位問題。
  
  功能決定現在,性能決定未來!

一、 內存泄漏

  內存泄漏是指由於代碼編寫不當導致不再使用的對象無法得到及時釋放。內存泄漏產生的內存垃圾不僅浪費資源,拖慢運行效率,甚至還可能造成內存溢出,直接導致應用崩潰。

  對於Android應用,比較容易發生泄漏的是Activity、Fragment對象,此類對象的共性是其都有一定的生命周期。以Activity為例,一個Activity實例的生命起始於onCreate(),終結於onDestroy()。當一個Activity不再使用時,係統會調用回調方法Activity.onDestroy()方法做一些清理操作。但是對於Activity對象本身所占內存,則完全由虛擬機的垃圾回收器來完成回收。垃圾回收器會檢查該實例是否被持有強引用,如果存在指向該對象的強引用,則不會回收其所占內存空間,這塊內存空間也就成了內存垃圾。由此可見內存泄漏是由不當的強引用導致的。

  MQC支持對Activity、Fragment對象的內存泄漏檢測,檢測結果可在性能報告-性能問題模塊查看。

1.1 對象的引用鏈

  從GC ROOT到泄漏對象的引用鏈能精準地定位導致內存泄漏的原因。對象無法被垃圾回收器回收,一定是由於GC ROOT直接或間接持有了它的強引用。

  常見的GCROOT有:聲明為static的變量,未停止的線程,Application對象,甚至是棧內存中的局部變量。

**1.2 Android中常見的內存泄露 **

a.集合中對象沒清理造成的內存泄露

  編程過程中,我們常常會把一些對象加入到集合中。在我們不再需要該對象時,如果沒有及時把它從集合中清理掉,就會導致這個集合占用的內存越來越大。同時如果這個集合是靜態的話,那情況就更嚴重了。如下的代碼段中在每次啟動Activity的時候都往靜態集合中添加了一個對象,如果Activity被頻繁啟動,set將不斷變大,影響APP的正常運行。

file
  所以,集合中不再使用的對象應及時釋放掉。上述代碼應該在Activity的onDestroy()方法中,及時清理set裏的元素,避免無用對象繼續存在強引用,例如:

file
  這樣可以保證set持有的強引用都被釋放。

b. 單例模式造成的內存泄漏

  單例的靜態特性使得其生命周期可能跟應用的生命周期一樣長,如果使用不恰當的話,很容易造成內存泄漏。
  如下代碼是一個簡單的單例模式實現:

file
  在創建單例的時候,如果我們傳入當前Activity的Context,例如:

file
  單例testContextHelper裏麵一直保存著該Activity的引用,當這個Context 對應的 Activity 退出時,由於該 Context 的引用一直被單例對象持有,所以該Activity占用的內存並不會被回收,造成泄漏。在使用單例模式時,一定要避免持有短生命周期對象的引用,比如上述代碼在引用Context時可以使用Application的Context代替Activity的Context,即:
file

  因為Application在應用的運行過程中一直存在,不會退出。

c. 非靜態內部類創建靜態實例造成的內存泄漏

  在啟動頻繁的Activity中,為了避免反複創建某些資源,提高加載速度,我們可能會在Activity內部創建一個靜態實例,每次啟動Activity時都會使用該實例,如下代碼:

file
  此時Activity內部有一個靜態單例,且為非靜態內部類的實例。由於非靜態內部類默認會持有外部類的引用,並且該類創建了一個靜態實例,該實例的生命周期和應用的一樣長,這就導致了該靜態實例一直會持有該Activity的引用,導致Activity的內存資源不能正常回收。為了避免這一問題,在使用過程中,正確的做法是將內部類設為靜態類或者變成單獨的類。

d. 使用handler時的內存問題

  在Android應用中,Handler通過發送Message與其他線程交互,發出的Message被存儲在目標線程的MessageQueue中的,並且Message不一定馬上就被處理,駐留時間可能比較久。比如我們用Handler發送一個延時比較久的Message:

file
  而Message中持有Handler實例的強引用,如果Message在Queue中一直存在,就會導致Handler實例無法被回收,而Handler持有Activity的強引用,Activity對象也不會被回收,這就造成了實例泄露。所以,在創建Handler時,最好使用弱引用來引用目標Activity對象,比如:

file
  這樣可以避免由於Handler持有強引用導致Activity無法回收。

e. 靜態成員變量造成的內存泄露

  如果成員變量被聲明為 static,其生命周期將與整個應用進程的生命周期一樣。如果靜態變量直接或間接強引用了某一短生命周期對象(比如Activity),這會導致即使app切到後台,這部分內存也不會被釋放。下麵的錯誤示範代碼中,在Activity啟動的時候,直接將其引用賦給了靜態變量obj,會導致該Activity一直不能被回收,導致內存泄露。

file
  因此,在使用靜態變量時,應該避免其持有短生命周期對象的強引用,可以使用弱引用來代替強引用。

f. 資源未關閉造成的內存泄漏

  對於使用了BroadcastReceiver,ContentObserver,File,遊標Cursor,Stream,Bitmap等資源的使用,應該在Activity銷毀時及時關閉或者注銷,否則這些資源將可能不會被回收,造成內存泄漏。雖然有些係統程序,它本身可以自動取消注冊的(非即時),但是我們還是應該在我們的程序中明確的取消注冊,程序結束時應該把所有的注冊都取消掉。

1.3 MQC提供的內存泄露分析

  MQC提供的深度性能測試能夠幫助您發現並定位發生了內存泄露的地方。當發生內存泄漏時,測試報告中會給出發生泄露的內存大小,泄露類型,發生泄露的對象,以及該對象的引用鏈等信息,下圖是MQC檢測到的APP內存泄漏案例。

file
  圖中可以看到檢測到了兩條內存泄漏信息。並指出了泄露內存的大小和對象類型,均為Activity對象泄露。查看引用鏈得知,APP在ActivityManager裏麵持有了所有Activity的強引用,最終導致Activity退出後無法回收,屬於前述介紹的集合對象使用不當造成的內存泄露。可以看到,MQC提供的內存泄漏分析能直接定位到相關代碼,方便您快速修複BUG。

二、 內存溢出

  內存溢出(OOM, Out OfMemory)是指當已存在的對象的占用了絕大部分或者全部分配給該進程的內存空間時,如果進程再申請新的內存空間,由於沒有空餘內存可用於分配,或可分配的內存不夠滿足申請者的需求,此時係統就會拋出內存溢出異常。

2.1 常見的內存溢出原因

  很大一部分內存溢出都是由於內存泄露導致,由於已分配的內存被泄露對象占用並且無法釋放,隨著泄露的對象實例越來越多,導致可用內存越來越少,最終當內存耗盡時,係統就會拋出內存溢出異常。此時隻要解決了內存泄露,也就解決了內存溢出。
  

  另一個內存溢出的重要原因就是應用加載了多個占用內存較多的對象。比如應用在運行過程中加載並保存了多個較大的Bitmap,導致可用內存急劇減少。因此,在代碼編寫過程中,對於可能占據大量內存空間的對象,我們應該使用軟引用或虛引用持有該對象,使得係統GC能在內存吃緊時回收該對象釋放空間。並且在不使用Bitmap時,應及時recycle,主動釋放內存空間。

2.2 MQC提供的內存溢出分析

  在應用拋出內存溢出時,深度性能測試會主動捕獲這一異常,給出拋出該異常的堆棧信息。並分析當前應用進程占用的總內存大小,已分配的內存大小和可用內存大小,方便開發者定位問題。
  
  如下圖,測試報告中首先給出發生內存溢出的機型,同時指出檢測到內存溢出時應用自身和設備內存的使用情況,可以看到Native Heap和VM Heap的空餘內存都已不多。打開StackTrace後,可以看到出現OOM錯誤的代碼行,由此我們發現可能是在加載Bitmap的時候導致的內存溢出。圖中紅色箭頭所指的地方是應用自身的代碼,我們根據這些提示就能夠快速找到源文件中出錯的代碼,立即修複。

file

三、內存抖動

  內存抖動指的是短時間內大量對象被創建和回收。由於短時間內產生了大量的對象,需要分配大量內存,此時需要垃圾回收器(GC)頻繁工作,回收不再使用的對象來騰出內存空間。GC的頻繁啟動占用了一定的係統資源,最終影響應用表現。

3.1 常見的內存抖動

  常見的內存抖動主要是由於在循環或其他場合中不停地創建新對象,並且短時間內這些對象又被釋放。瞬間產生大量的對象會嚴重占用內存區域,當達到閥值,剩餘空間不夠的時候觸發GC。即使每次分配的對象占用了很少的內存,但是他們疊加在一起會增加Heap的壓力。GC啟動時會占用CPU等資源,直接導致應用運行受到影響,可能出現界麵操作不流暢等現象。

3.2 MQC提供的內存抖動分析

  MQC能夠監控係統的每一次GC,並給出GC發生時的內存使用情況,如下圖所示。

file
  圖中給出了3種GC發生的時刻和內存變化的曲線圖。3種GC分別為:
  GC_EXPLICIT:應用主動調用System.gc()產生的GC事件

  GC_FOR_ALLOC:內存分配時,發現可用內存不夠時觸發的GC事件

  GC_CONCURRENT:已分配的內存大小達到某一閾值時會觸發的GC事件
  

  其中後兩種是係統自己決定啟動的GC,應用無法控製。但是我們可以優化代碼,避免頻繁生成和回收對象,比如不要在循環中頻繁new新的對象。

四、界麵卡頓

  界麵卡頓指的是短時間內界麵對用戶操作沒有響應。應用在出現卡頓的時候,就算知道是哪個頁麵出了問題,但是很難定位到具體的代碼。應用卡頓檢測就是幫助您快速定位卡頓的具體位置,方便您進行針對性的修複。

4.1 常見的界麵卡頓原因

  Android應用的UI繪製和用戶操作消息分發都發生在應用主線程,如果主線程來不及處理UI更新和響應用戶操作,用戶就會感覺應用發生了卡頓。因此卡頓發生時嚐嚐伴隨著主線程阻塞。如果在主線程中進行磁盤讀寫、網絡操作或者大量計算時,嚐嚐會導致主線程被阻塞,發生界麵卡頓。

4.2 MQC提供的界麵卡頓分析

file
  如上圖所示,在應用運行過程中出現卡頓時,MQC會記錄當前卡頓的時長,例如圖中為1935ms,用於給開發者評估本次卡頓的嚴重性,隨後給出發生卡頓時係統CPU和內存的使用情況等信息輔助開發者分析問題。也會給出卡頓發生時的應用調用的完整堆棧,用於定位發生卡頓的代碼,MQC同時歸納出具體的關鍵代碼,免去開發者在大量堆棧中尋找關鍵行的麻煩。

五、過度繪製

  過度繪製一般指的是屏幕上的某些區域在一幀中被多次繪製,一般是在界麵的同一個地方疊加了多個控件。這樣會加重GPU的工作負擔,可能導致應用運行過程中頻繁掉幀,影響用戶體驗。

5.1 過度繪製詳細介紹

  當手機開啟過度繪製時,屏幕上會標記發生過度繪製的區域,並根據不同的繪製次數使用不同的顏色,顏色標識從好到差依次是:藍色-綠色-淡紅色-紅色,分別代表該區域被繪製1次、2次、3次和4次。一般情況下,最好把繪製控製在2次以下,3次繪製有時候是不能避免的,盡量避免,4次的繪製基本上是不允許的。
為了減少過度繪製,開發者應減少複雜的、層級較多的布局,去掉多餘的背景色。簡單的界麵盡量使用線性布局;較為複雜的界麵可以使用相對布局,避免嵌套過多的線性布局。可以使用ViewStub來動態加載界麵。

5.2 MQC提供的過度繪製分析

  MQC實時監測界麵的過度繪製指數,當該指數大於1.5時,MQC認為該界麵可能需要優化。最終,測試報告中會指出應用每個界麵的過度繪製指數,並配合測試視頻將過度繪製指數與Activity關聯起來,並告訴開發者該界麵對應的Activity。如下圖所示。

  如上圖所示,在應用運行過程中出現卡頓時,MQC會記錄當前卡頓的時長,例如圖中為1935ms,用於給開發者評估本次卡頓的嚴重性,隨後給出發生卡頓時係統CPU和內存的使用情況等信息輔助開發者分析問題。也會給出卡頓發生時的應用調用的完整堆棧,用於定位發生卡頓的代碼,MQC同時歸納出具體的關鍵代碼,免去開發者在大量堆棧中尋找關鍵行的麻煩。

file

六、啟動分析

  啟動分析通過分析應用啟動過程產生的trace文件來得到應用的啟動時間等信息。通常來說,Android應用的啟動方式分為兩種:冷啟動和熱啟動。

  冷啟動:當啟動應用時,後台沒有該應用的進程,此時係統會創建一個新的進程分配給該應用。冷啟動因為係統會創建一個新的進程分配給它,所以會先創建和初始化Application類,隨後創建和初始化MainActivity類(包括一係列的測量、布局、繪製),最後顯示在界麵上。

  熱啟動:當啟動應用時,後台已有該應用的進程(例:按back鍵、home鍵,應用雖然會退出,但是該應用的進程是依然會保留在後台,可進入任務列表查看),這種啟動會從已有的進程中來啟動應用。熱啟動因為會從已有的進程中來啟動,所以熱啟動就不會創建新的Application,而是直接創建和初始化MainActivity,而不必創建和初始化Application,因為一個應用從新進程的創建到進程的銷毀,Application隻會初始化一次。一般來講,熱啟動時間都會在一定程度上小於冷啟動時間。

  MQC會分析應用的冷啟動時間和熱啟動時間,給開發者作為參考。同時給出啟動分階段耗時分析、耗時方法定位、啟動過程函數調用關係等更詳細的信息,可以幫助開發者快速發現啟動到底卡在哪了。

file

七、嚴苛模式(StrictMode)

  嚴苛模式(StrictMode)是一個開發輔助工具,可以幫助開發者發現那些由於編碼過程中不注意而造成的問題。

7.1嚴苛模式的詳細介紹

  StrictMode經常用於捕獲那些在應用主線程中進行的磁盤讀寫操作和網絡請求。由於應用主線程是接收UI操作消息和執行界麵渲染的地方,為了使應用運行更加流暢和更快響應,請盡量不要在主線程執行磁盤操作和網絡請求。當然,這也是避免係統彈出ANR對話框和提高應用穩定性的好方法。一旦檢測到違反策略(policyviolation),係統將會輸出一條相關的日誌,其一般包含一個調用棧,來顯示應用在何處發生違例。

  注意:盡管Android設備的磁盤一般都是閃存盤,然而實際中很多設備隻能以很有限的並發數來操作文件係統。雖然磁盤讀寫很快,但是具體過程中可能由於其他進程占用了I/O接口,等待的過程會導致整個磁盤操作流程比較慢。如果可以,請盡量假設磁盤讀寫是一個比較耗時的操作。
  
  

  StricMode除了可以檢測主線程的磁盤操作和網絡請求以外,還可以發現主線程中執行時間較長的方法。當應用中有繼承了Closeable接口的對象沒有關閉的時候,例如文件流等,或者沒有使用HTTPS進行網絡請求,或者同一個Activity的實例太多,StrictMode都會給出提示。其能發現的錯誤主要包括:
a.應用在主線程中進行磁盤讀寫;
b.應用在主線程中進行網絡請求;
c.應用在主線程中的某些自定義方法的執行時間比較長;
d.SQLCursor對象在使用之後沒有關閉;
e.繼承了Closeable接口的對象在使用之後沒有關閉;
f. 某一Activity有較多的實例;
g.文件讀取接口暴露給外部應用;
h.注冊某些對象(廣播接收器、觀察者、Listener等)後沒有取消注冊;
i. 沒有使用加密網絡(HTTPS)進行網絡數據傳輸。

7.2 MQC提供的嚴苛模式檢測

  在MQC深度性能測試檢測到您的應用存在違反上述要求的時候,MQC首先會指出應用違反了哪些嚴苛模式的策略,隨後分析問題發生時的應用堆棧信息,指出問題出現在哪兒,並統計該問題出現了多少次。針對在檢測到的主線程操作(例如出現主線程磁盤操作,主線程網絡操作等)時,還會給出該操作的持續時間等信息,輔助開發者評估問題的嚴重程度。

file
  上圖顯示MQC檢測到應用中存在主線程IO的情況,具體是在主線程中進行了文件讀取操作,最長的一次持續了2356毫秒,測試過程中一共出現了62次磁盤讀取操作。根據後麵的堆棧信息,可以看到com.stephen.performance.MainActivity類裏麵的readFile方法是在主線程中執行的,因此這裏我們就可以針對這一信息來進行修改。

  MQC測試平台是為廣大企業客戶和移動開發者提供真機測試服務的雲平台,擁有大量熱門機型,提供7x24全天候服務。

  我們致力於提供專業、穩定、全麵、高價值的自動化測試能力,以及簡單易用的使用流程、貼心的技術服務,並且幫助客戶以最低的成本、最高的效率發現APP中的各類隱患(APP崩潰、各類兼容性問題、功能性問題、性能問題等),減少用戶流失,提高APP質量和市場競爭力。

聯係我們:

網站地址:https://mqc.aliyun.com
開發者交流旺旺群:335334143
開發者交流QQ群:492028798
客服郵箱:mqc_group@service.alibaba.com
更多精彩技術分享 歡迎關注 MQC公眾號

file

最後更新:2017-07-25 17:02:53

  上一篇:go  智慧醫療有望解決過度醫療帶來的醫療資源浪費!
  下一篇:go  微信小程序,互聯網流量大門即將開啟