android性能優化

1.繪製優化

android view應用層繪製流程:measure-layout-draw

卡頓根本原因:繪製任務太重,繪製一幀內容耗時太長;主線程太忙,導致vsync信號來時還沒有準備好數據導致丟幀。

主線程的關鍵職責是處理用戶交互,在屏幕上繪製像素,並進行加載顯示相關的數據。

主線程主要工作:ui生命週期控制;系統事件處理;消息處理;界面佈局;界面繪製;界面刷新

1.1佈局優化

  1.1.1減少佈局層級:

  merge,include標籤;彈性佈局;合理使用佈局

  1.1.2 避免過度繪製:

xml佈局-控件有重疊且都有設置背景;

view自繪-view ondraw裏面同一個區域被繪製多次(設置-開發者選項-show gpu overdraw工具:

無色-無過度繪製,每個像素繪製1次;

藍色-過度1次;

綠-過度兩次;

淡紅-過度3次;

深紅-過度4次或更多)

1.2合理刷新

  控制刷新頻率:eg:進度條變化不足1%

  避免無必要的刷新:eg:需要刷新的視圖不可見;列表滾動,item上的圖片加載暫停 

  縮小刷新區域:invalidata方法會更新整個視圖,局部的刷新invalidata(Rect dirty),invalidata(left,top,right,bottom)

 1.3提升動畫性能

  流暢度:流暢度是動畫核心,控制每一幀動畫在16ms內完成

  內存:避免內存泄漏,減小內存開銷

  耗電:減小運算量,優化算法,減小cpu佔用

2.內存優化

過多的使用內存會導致程序內存溢出,最終導致oom;過多的使用cpu資源,例如大量耗時任務時會照成卡頓,嚴重時ANR

應用在使用過程中會隨着內存的暫用的增加,當到達一定條件後就會引起GC,在GC的過程中,任何線程(包括UI線程)都會暫停,此時就會引起卡頓的現象。所以頻繁引起GC也會對應用的流暢性以及穩定性造成影響,

內存優化的意義:

減小OOM,提高應用穩定性

減少卡頓,提高應用流暢度

減少內存佔用,提高應用後臺運行時的存活率

減少異常發生,減少代碼邏輯隱患

2.1優化建議

 避免創建過多對象,儘量減小對象的作用域

使用整型來替代枚舉

常量用static final修飾

使用android特有數據結構sparsearray pair

適當使用軟引用和弱引用

內存緩存

內部類儘量使用靜態,減少泄漏

注:在創建對象後,在確定不需要使用該對象時,使對象置空=null可提高內存使用效率

2.2優化內存空間

使用最小內存的對象或者資源可以減小內存開銷,同時讓GC能更高效的回收不需要使用的對象,可以讓進程堆內存保持充足的可用內存,使應用更加穩定高效

2.2.1對象引用

java對象生命週期:創建階段create---應用階段inuse---不可見階段invisible---不可達階段unreachable----收集階段collected----終結階段finalized----對象空間重分配階段deallocated

引用類的主要功能就是能夠引用但仍可被垃圾回收器回收的對象;內存中一個對象可以被多個引用(強,軟,弱,虛)引用;如果沒有指定對象引用類型,默認強引用

a.強引用strong reference:GC不會回收,所以在應用的生命週期中如果不需要使用,一定要釋放或轉弱引用

b.軟引用soft reference:只有在內存空間不足的情況下才會被GC回收(可實現內存敏感的高速緩存)

c.弱引用weak reference:相對於軟引用來說,只具有弱引用的對象擁有更短暫的生命週期,GC掃描內存中一旦發現只有弱引用的對象,不論內存是否足夠,直接回收,不過GC是優先級很低的線程

d.虛引用phantom reference:虛引用類只能用於跟蹤即將對被引用對象進行的收集,虛引用不會決定對象的生命週期,如果一個對象僅持有虛引用,它就和沒有任何引用一樣,在任何時候都可能被垃圾收集器回收

2.2.2減少不必要的內存開銷

autoboxing:

java中爲了能讓基礎數據類型int,boolean等在大多數java容器中運作,需要一個autoboxing(自動裝箱)的操作來吧基礎類型轉換成Intrger,Boolean等,例如發現大量的integer.value就是發生了autoboxing的操作,替換掉默認的容器可以處理這種不必要的內存消耗,如map集合就會發生這種情況

內存複用:

a.利用系統自帶資源:android.R.xxx

b.使用viewholder實現converview的視圖複用

c.對象池

d.bitmap對象複用

2.2.3使用最優數據類型

數組:存儲區域連續,尋址容易,插入刪除困難

鏈表:存儲區間離散,尋址困難,插入刪除容易

舉例hashmap和arraymap

hashmap是一個散列鏈表,向hm中put元素時,先根據key.hashcode()計算出元素在數組中的位置,如果該位置已經存放了其他元素,此時該位置就會以鏈表的形式存儲元素,先加入的放在鏈尾,假如key.hashcode()計算的值與已經存在的元素的key的hash值相等,就會存在hash衝突。存在hash衝突時get(key)會先通過key找到索引,再通過key.equals()查找linklist中正確的節點。hashmap會盡量配置一個大的數組來減少潛在的衝突,所以這個向稀疏陣列填入對象的大陣列在內存上是一筆很大的開銷

arraymap使用兩個小數組,而不是一個大數組,其中一個數組記錄key.hashcode的順序列表,另一個根據key的順序記錄k-v值,根據key順序交織在一起。在get(key)時,先計算出key.hashcode()的值,在通過二分查找法對第一個數組查找哦對應的index,然後通過該index直接訪問第二個數組中的k-v值。如果在第二個數組中找到的k與原本需要查找的k不一致就認爲發生了衝突,此時會以該k爲中心點上下查找對應的k-v,直到找到爲止,因此,如果am中的對象增加,需要訪問單獨對象的時間也會延長

總的來說在arraymap中執行出入或刪除在性能上看比hashmap要差,但如果只涉及極小長度的值,例如在1000以下就不需要考慮這個問題了。所以如果對象數量比較少,但是訪問的比較多的時候,插入刪除操作並不頻繁時可以優先考慮使用arraymap

在應用性能優化中總會碰到這類的問題:以時間換空間還是以空間換時間,需要我們找到一個平衡點

2.2.4圖片內存優化

android中一張圖片解碼成位圖格式後,佔用的的內存只和位圖的質量和大小有關,位圖的主要屬性有:圖片長度,圖片寬度,單位像素佔用的字節數。如果從應用資源中加載(drawable)圖片佔用的內存也應該和屏幕密度有一定的關係。在默認情況下,圖片在android中圖片文件解碼成位圖時,會被處理成32bit/像素(紅,綠,藍,透明通道)。圖片在被屏幕渲染之前,首先要被作爲紋理傳送到GPU,這意味着每一張圖片會同時佔用cpu內存和gpu內存。

a.位圖格式

b.inSampleSize

將這個屬性設置爲1時,可以在不加載完整大小圖片的前提下,生成一張只有原始圖片部分大小的新圖片,如inSampleSize爲2時獲得只有1/2大小的圖片,設置爲4就1/4

c.inScaled,inDensity,inTargetDensity

當inScaled設置爲true時,系統會按照現有密度來劃分目標密度,通過派生綻放數來應用到位圖上,使用這個方法會重設圖片大小,並對他應用一個新的過濾。爲了達到最佳的性能組合,需要結合首先使用insamplsesie處理圖片,轉換爲接近目標的2次冪,然後用inDensity和inTargetdensy生成最終想要的準確大小,因爲inSamplesize會減少像素的數量,而基於輸出密碼的需要對像素重新過濾。但獲取資源圖片的大小,需要設置位圖對象的inJustDecodeBounds值爲true,然後繼續解碼圖片文件,這樣才能生成圖片的寬高數據,並允許繼續優化圖片。整體代碼如下:

BitmapFactory.Optionsoptions=newBitmapFactory.Options();

options.inJustDecodeBounds=true;

BitmapFactory.decodeStream(is,null,options);

options.inScaled=true;

options.inDensity=options.outWidth;

options.inSampleSize=4;

options.inTargetDensity=dstWith*options.inSampleSize;

options.inJustDecodeBounds=false;

BitmapFactory.decodeStream(is,null,options);

2.3避免內存泄漏

在當前應用週期內不再使用的對象被GCRoots引用,導致不能回收,使實際可使用內存變小,這種現象在Android應用中稱爲內存泄漏。

2.3.1常見內存泄漏場景

a.資源性對象未關閉:資源性對象(如cursor,file)都會使用緩存,它們不僅在java虛擬機中存在緩存,在虛擬機外也存在緩存,如果僅僅把引用設置爲null,而沒有真正close就會造成內存泄漏,因此,在編寫資源文件讀寫時,都需要在finally中關閉資源性對象,避免在異常情況下資源對象未被釋放的隱患。

b.註冊對象未註銷

如果事件註冊後未註銷,會導致觀察者列表中維持着對象的引用,阻止垃圾回收,一般發生在註冊廣播接收器、註冊觀察者等。

c.類的靜態變量持有大數據對象

靜態變量長期維持對象的引用,阻止垃圾回收,如果靜態變量持有大的數據對象,如Bitmap等,就很容易引起內存不足等問題。

d.非靜態內部類的靜態實例

非靜態內部類會維持一個到外部類實例的引用,如果非靜態內部類的實例是靜態的,就會間接長期維持着外部類的引用,阻止被系統回收。

e.Handler臨時性內存泄漏

非靜態內部類會維持一個到外部類實例的引用,如果非靜態內部類的實例是靜態的,就會間接長期維持着外部類的引用,阻止被系統回收。由於AsyncTask內部也是Handler機制,同樣存在內存泄漏的風險。但這種內存泄漏一般是臨時性的。

f.容器中的對象沒清理造成的內存泄漏

通常把一些對象的引用加入集合中,在不需要該對象時,如果沒有把它的引用從集合中清理掉,這個集合就會越來越大。如果這個集合是static,情況就更嚴重。

g.webview

Android中的WebView不僅僅存在很大的兼容性問題,不同Android系統版本中的WebView會有較大的差異,加上不同廠商定製的ROM中的WebView也存在着差異。更嚴重的是WebView都存在內存泄漏的問題,在應用中只要使用一次Webview,內存就不會被釋放掉。通常解決這個問題的辦法是爲WebView開啓獨立的一個進程,使用AIDL與應用的主進程進行通信,WebView所在的進程可以根據業務的需要選擇合適的時機進行銷燬,達到正常釋放內存的目的。

3.穩定性優化

3.1crash監控

3.1.1java層carsh監控

在Android中,Java虛擬機爲每個進程都設置了一個默認UncaughtExceptionHandler,用於處理本進程中未被trycatch的Exception。因此只要實現UncaughtExceptionHandler接口,並在進程啓動時調用Thread.setDefaultUncaughtExceptionHandler(...)傳入自定義的UncaughtExceptionHandler,當出現沒被catch的異常時,就會回調uncaughtException(Threadthread,Throwableex)方法

3.1.2native層carsh監控

Android系統是Linux內核。在Linux上,當程序發生Crash時,也會生成coredump文件,然後分析dump文件查找崩潰的原因。和Linux一樣,在Android系統的Linux內核中,一種重要的進程間通信方式就是Linux的信號機制。Linux信號除了用於正常的進程間通信和同步外,還負責監控系統異常和中斷,當應用程序發生異常時,Linux內核會生成錯誤信號並通知當前進程。應用進程接收到錯誤信號後,可以捕獲該信號並執行對應的信號處理函數

常見AndroidNativeCrash信號

只要在應用程序中註冊了這些信號的處理函數,當JNIcrash時,我們的處理函數就會被調用到,然後獲取dump文件再上傳,後續的工作就和Java層異常邏輯一致了。Android系統除了保留Linux對信號的默認處理方式外,還定義了一些額外的行爲,例如SIGQUIT對SIGQUIT信號的默認行爲是“終止+CORE”,即產生coredump文件後,立即終止運行

3.2ANR

ANR也是Android應用程序無法繼續運行的一種異常,和Crash的區別是,它不一定是由於程序的異常錯誤導致的,一般是應用處理長時間沒有結果響應導致主進程不能處理下一件事情。

ANR(ApplicationNotResponding)即應用無響應,在Android系統中,應用發生的ANR有以下幾種類型:

1.KeyDispatchTimeout最常見的ANR類型是對輸入事件5s內無響應,比如按鍵或觸摸事件在此時間內無響應。

2.BroadcastTimeoutBroadcastReceiver在指定時間(原生系統默認是10s)內無法處理完成,並且沒有結束執行onReceive。

3.ServiceTimeout這種類型在Android應用中出現的概率很小,是指Service在特定的時間(原生系統是20s)內無法處理完成。引起ANR的根本原因總體來說有以下兩種:

1)應用程序自身邏輯有缺陷,或者在某些異常場景觸發了此缺陷,如主線程堵塞、死循環等導致。

2)由於Android設備其他進程的CPU佔用高,導致當前應用進程無法搶佔到CPU時間片。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章