Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> Android高級開發之性能優化典范

Android高級開發之性能優化典范

編輯:關於Android編程

本章介紹android高級開發中,對於性能方面的處理。主要包括電量,視圖,內存三個性能方面的知識點。

1.視圖性能

(1)Overdraw簡介

    Overdraw就是過度繪制,是指在一幀的時間內(16.67ms)像素被繪制了多次,理論上一個像素每次只繪制一次是最優的,但是由於重疊的布 局導致一些像素會被多次繪制,而每次繪制都會對應到CPU的一組繪圖命令和GPU的一些操作,當這個操作耗時超過16.67ms時,就會出現掉幀現象,表現為應用卡頓,所以對重疊不可見元素的重復繪制會產生額外的開銷,需要盡量減少Overdraw的發生。

(2)Overdraw檢測

    Android提供了測量Overdraw的選項,在開發者選項-調試GPU過度繪制(Show GPU Overdraw),打開選項就可以看到當前頁面Overdraw的狀態,就可以觀察屏幕的繪制狀態。該工具會使用三種不同的顏色繪制屏幕,來指示 overdraw發生在哪裡以及程度如何,其中:

 •沒有顏色: 意味著沒有overdraw。像素只畫了一次。

 •藍色: 意味著overdraw 1倍。像素繪制了兩次。大片的藍色還是可以接受的(若整個窗口是藍色的,可以擺脫一層)。 

•綠色: 意味著overdraw 2倍。像素繪制了三次。中等大小的綠色區域是可以接受的但你應該嘗試優化、減少它們。

•淺紅: 意味著overdraw 3倍。像素繪制了四次,小范圍可以接受。

 •暗紅: 意味著overdraw 4倍。像素繪制了五次或者更多。這是錯誤的,要修復它們。

提高程序在視圖方面的性能, 總的原則就是:盡量避免重疊不可見元素的繪制。

(3)Overdraw改良

1)合理選擇控件容器

    Android提供的Layout控件主要包括 LinearLayout、TableLayout、FrameLayout、RelativeLayout。同一個界面可以使用不同的容器控件來表達,但是各個容器控件描述界面的復雜度是不一樣的。一般來LinearLayout最易,RelativeLayout較復雜。 LinearLayout只能用來描述一個方向上連續排列的控件,而RelativeLayout幾乎可以用於描述任意復雜度的界面。。綜上所述: LinearLayout易用,效率高,表達能力有限。RelativeLayout復雜,表達能力強,效率低。從減少overdraw的角度來看,LinearLayout會增加控件數的層級,自然是RelativeLayout 更優,但是當某一界面在使用LinearLayout並不會比RelativeLayout帶來更多的控件數和控件層級時,LinearLayout則是首選。

2)去掉window的默認背景

   當使用Android自帶的一些主題時,window會被默認添加一個純色的背景,這個背景是被DecorView持有的。當自定義布局時又添加了一張背景圖或者設置背景色,那麼DecorView的background此時是無用的,但是它會產生一次Overdraw,帶來繪制性能損耗。去掉window的背景可以在onCreate()中setContentView()之後調用 getWindow().setBackgroundDrawable(null);或者在theme中添加 android:windowbackground="null"。

3)去掉其他不必要的背景

    父容器若已經有了背景,可不設置對應子控件的背景,及大的布局背景已經設置,應避免設置局部重復的背景。

4)自定義View處理

    對於自定義的view視圖,可以通過canvas.clipRect()來幫助系統識別那些可見的區域。這個方法可以指定一塊矩形區域,只有在這個區域內才會被繪制,其他的區域會被忽視。這個API可以很好的幫助那些有多組重疊組件的自定義View來控制顯示的區域。同時clipRect方法還可以幫助節約CPU與GPU資源,在clipRect區域之外的繪制指令都不會被執行,那些部分內容在矩形區域內的組件,仍然會得到繪制。除了clipRect方法之外,我們還可以使用canvas.quickreject()來判斷是否沒和某個矩形相交,從而跳過那些非矩形區域內的繪制操作。

5)ViewStub高效占位符

    當遇到這樣的情況,運行時動態根據條件來決定顯示哪個View或布局。常用的做法是把View都寫在上面,先把它們的可見性都設為 View.GONE,然後在代碼中動態的更改它的可見性。這種模式的缺點是耗費資源。雖然把View 的初始View.GONE但是在Inflate布局的時候View仍然會被Inflate,程序運行時仍然會創建對象,會被實例化,會被設置屬性,會耗費內存等資源。

   推薦的做法是使用android.view.ViewStub,ViewStub是一個輕量級的View,它一個看不見的,不占布局位置,占用資源非常小的控件。可以為ViewStub指定一個布局,在Inflate布局的時候,只有ViewStub會被初始化,然後當ViewStub被設置為可見的時候,或是調用了ViewStub.inflate()的時候,ViewStub所向的布局就會被Inflate和實例化,然後ViewStub的布局屬性都會傳給它所指向的布局。這樣,就可以使用ViewStub來方便的在運行時,要還是不要顯示某個布局。

6)善用draw9patch

    給ImageView加一個邊框,通常在ImageView後面設置一張背景圖,露出邊框便完美解決問題,此時這個 ImageView,設置了兩層drawable,兩層drawable的重疊區域去繪制了兩次,導致 overdraw。優化方案: 將背景drawable制作成draw9patch,並且將和前景重疊的部分設置為透明。由於Android的2D渲染器會優化draw9patch中的透明區域,從而優化了這次overdraw。

7)Merge 

    使用Merge標簽來做容器控件。第一種子視圖不需要指定任何針對父視圖的布局屬性,就是說父容器僅僅是個容器,子視圖只需要直接添加到父視圖上用於顯示 就 行。另外一種是假如需要在LinearLayout裡面嵌入一個布局 (或者視圖),而恰恰這個布局(或者視圖)的根節點也是LinearLayout,這樣就多了一層沒有用的嵌套,無疑這樣只會拖慢程序速度。而這個時候如 果我們使用merge根標簽就可以避免那樣的問題。

2.內存性能

(1)內存分配與回收

    每一個進程的Dalvik Heap都反映了使用內存的占用范圍。這就是通常邏輯意義上提到的Dalvik Heap Size,它可以隨著需要進行增長,但是增長行為會有一個系統為它設定上限。

    邏輯上講的Heap Size和實際物理意義上使用的內存大小是不對等的,Proportional Set Size(PSS)記錄了應用程序自身占用以及與其他進程進行共享的內存。

    Android 系統並不會對Heap中空閒內存區域做碎片整理。系統僅僅會在新的內存分配之前判斷Heap的尾端剩余空間是否足夠,如果空間不夠會觸發GC操作,從而騰 出更多空閒的內存空間。在Android的高級系統版本裡面針對Heap空間有一個Generational Heap Memory的模型,最近分配的對象會存放在Young Generation區域。當這個對象在該區域停留的時間達到一定程度,它會被移動到Old Generation,最後累積一定時間再移動到Permanent Generation區域。系統會根據內存中不同的內存數據類型分別執行不同的GC操作。例如,剛分配到Young Generation區域的對象通常更容易被銷毀回收,同時在Young Generation區域的GC操作速度會比Old Generation區域的GC操作速度更快(如圖1所示)。

 

圖1  根據不同內存數據類型執行不同GC操作

    每一個Generation的內存區域都有固定的大小。隨著新的對象陸續被分配到此區域,當對象總的大小臨近這一級別內存區域的閥值時,會觸發GC操作,以便騰出空間來存放其他新的對象(如圖2所示)。

 

圖2  對象值臨近閥值觸發GC操作

    通常情況下,GC發生的時候,所有的線程都是會被暫停的。執行GC所占用的時間和它發生在哪一個Generation也有關系,Young Generation中的每次GC操作時間是最短的,Old Generation其次,Permanent Generation最長。執行時間的長短也和當前Generation中的對象數量有關,遍歷樹結構查找20000個對象比起遍歷50個對象自然是要慢 很多的。

(2)內存測試插件

1)LeakCanary簡介

    LeakCanary是一個用於檢測內存洩漏的工具,可以用於Java和Android,是由著名開源組織Square貢獻。

2)LeakCanary工作原理

 •RefWatcher.watch()創建一個KeyedWeakReference到北監控的對象。

 •接下來,在後台線程中檢測這個引用是否被清除,如果沒有將會觸發GC。

•如果引用仍然沒有清除,將heap內存dump到一個.hprof的文件存放到手機系統裡。

 •HeapAnalyzerService在另外一個獨立的進程中啟動,使用HeapAnalyzer解析heap內存通過HAHA這個項目

 •HeapAnalyzer計算出到GC ROOTs的最短強引用路徑決定是否發生Leak,然後建立導致洩漏的引用鏈。 結果被回傳到應用程序進程的DisplayLeakService中,然後顯示一個洩漏的通知。

(3)內存優化

1)謹慎使用large heap

    Android設備根據硬件與軟件的設置差異而存在不同大小的內存空間,他們為應用程序設置了不同大小的Heap限制阈值。設計時可以通過調用getMemoryClass()來獲取應用的可用Heap大小。在一些特殊的情景下,你可以通過在manifest的application標簽下添加 largeHeap=true的屬性來為應用聲明一個更大的heap空間。然後,你可以通過getLargeMemoryClass()來獲取到這個更大的heap size阈值。

    然而,聲明得到更大Heap阈值的本意是為了一小部分會消耗大量RAM的應用(例如一個大圖片的編輯應用)。不要輕易的因為你需要使用更多的內存而去請求一個大的Heap Size。只有當你清楚的知道哪裡會使用大量的內存並且知道為什麼這些內存必須被保留時才去使用large heap。因此請謹慎使用large heap屬性。使用額外的內存空間會影響系統整體的用戶體驗,並且會使得每次gc的運行時間更長。

2)綜合考慮設備內存阈值與其他因素設計合適的緩存大小

    例如,在設計ListView或者GridView的Bitmap LRU緩存的時候,需要考慮的點有:

 •應用程序剩下了多少可用的內存空間?

 •有多少圖片會被一次呈現到屏幕上?有多少圖片需要事先緩存好以便快速滑動時能夠立即顯示到屏幕?

 •設備的屏幕大小與密度是多少? 一個xhdpi的設備會比hdpi需要一個更大的Cache來hold住同樣數量的圖片。

 •不同的頁面針對Bitmap的設計的尺寸與配置是什麼,大概會花費多少內存?

 •頁面圖片被訪問的頻率?是否存在其中的一部分比其他的圖片具有更高的訪問頻繁?如果是,也許你想要保存那些最常訪問的到內存中,或者為不同組別的位圖(按訪問頻率分組)設置多個LruCache容器。

3)資源文件需要選擇合適的文件夾進行存放

   hdpi/xhdpi/xxhdpi等等不同dpi的文件夾下的圖片在不同的設備上會經過scale的處理。例如我們只在hdpi的目錄下放置了一 張100100的圖片,那麼根據換算關系,xxhdpi的手機去引用那張圖片就會被拉伸到200200。需要注意到在這種情況下,內存占用是會顯著提高 的。對於不希望被拉伸的圖片,需要放到assets或者nodpi的目錄下。

4)Try catch某些大內存分配的操作

    在某些情況下,我們需要事先評估那些可能發生OOM的代碼,對於這些可能發生OOM的代碼,加入catch機制,可以考慮在catch裡面嘗試一次降級的內存分配操作。例如decode bitmap的時候,catch到OOM,可以嘗試把采樣比例再增加一倍之後,再次嘗試decode。

5)謹慎使用static對象

    因為static的生命周期過長,和應用的進程保持一致,使用不當很可能導致對象洩漏,在Android中應該謹慎使用static對象。

6)特別留意單例對象中不合理的持有

    雖然單例模式簡單實用,提供了很多便利性,但是因為單例的生命周期和應用保持一致,使用不合理很容易出現持有對象的洩漏。

7)珍惜Services資源

    應用需要在後台使用service,除非它被觸發並執行一個任務,否則其他時候Service都應該是停止狀態。另外需要注意當這個service 完成任務之後因為停止service失敗而引起的內存洩漏。 當你啟動一個Service,系統會傾向為了保留這個Service而一直保留Service所在的進程。這使得進程的運行代價很高,因為系統沒有辦法把 Service所占用的RAM空間騰出來讓給其他組件,另外Service還不能被Paged out。這減少了系統能夠存放到LRU緩存當中的進程數量,它會影響應用之間的切換效率,甚至會導致系統內存使用不穩定,從而無法繼續保持住所有目前正在運行的service。 建議使用IntentService,它會在處理完交代給它的任務之後盡快結束自己。

8)使用ProGuard來剔除不需要的代碼

   ProGuard能夠通過移除不需要的代碼,重命名類,域與方法等等對代碼進行壓縮,優化與混淆。使用ProGuard可以使得你的代碼更加緊湊,這樣能夠減少mapping代碼所需要的內存空間。

9)使用更加輕量的數據結構

   考慮使用ArrayMap/SparseArray而不是HashMap等傳統數據結構。

   HashMap的容器,相比起 Android專門為移動操作系統編寫的ArrayMap容器,在大多數情況下,都顯示效率低下,更占內存。通常的HashMap的實現方式更加消耗內存,因為它需要一個額外的實例對象來記錄Mapping操作。另外,SparseArray更加高效,在於他們避免了對key與value的自動裝箱 (autoboxing),並且避免了裝箱後的解箱。

10)避免在Android裡面使用Enum

  Android 官方培訓課程提到過“Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.”,具體原理請參考《Android性能優化典范(三)》,所以請避免在Android裡面使用到枚舉。

11)減小Bitmap對象的內存占用

    Bitmap是一個極容易消耗內存的大胖子,減小創建出來的Bitmap的內存占用可謂是重中之重,通常來說有以下2個措施:
 •inSampleSize:縮放比例,在把圖片載入內存之前,我們需要先計算出一個合適的縮放比例,避免不必要的大圖載入。
 •decode format:解碼格式,選擇ARGB_8888/RBG_565/ARGB_4444/ALPHA_8,存在很大差異。

12)使用更小的圖片

    在涉及給到資源圖片時,我們需要特別留意這張圖片是否存在可以壓縮的空間,是否可以使用更小的圖片。盡量使用更小的圖片不僅可以減少內存的使用,還能避免出現大量的InflationException。假設有一張很大的圖片被XML文件直接引用,很有可能在初始化視圖時會因為內存不足而發生InflationException,這個問題的根本原因其實是發生了OOM。

13)內存對象的重復利用

  大多數對象的復用,最終實施的方案都是利用對象池技術,要麼是在編寫代碼時顯式地在程序裡創建對象池,然後處理好復用的實現邏輯。要麼就是利用系統框架既有的某些復用特性,減少對象的重復創建,從而降低內存的分配與回收。

14)復用系統自帶的資源

    Android 系統本身內置了很多的資源,比如字符串、顏色、圖片、動畫、樣式以及簡單布局等,這些資源都可以在應用程序中直接引用。這樣做不僅能減少應用程序的自身負重,減小APK的大小,還可以在一定程度上減少內存的開銷,復用性更好。但是也有必要留意Android系統的版本差異性,對那些不同系統版本上表現存在很大差異、不符合需求的情況,還是需要應用程序自身內置進去。

15)注意Cursor對象是否及時關閉

    在程序中我們經常會進行查詢數據庫的操作,但時常會存在不小心使用Cursor之後沒有及時關閉的情況。這些Cursor的洩露,反復多次出現的話會對內存管理產生很大的負面影響,我們需要謹記對Cursor對象的及時關閉。

16)避免在onDraw方法裡面執行對象的創建

   類似onDraw等頻繁調用的方法,一定需要注意避免在這裡做創建對象的操作,因為他會迅速增加內存的使用,而且很容易引起頻繁的gc,甚至是內存抖動。

17)StringBuilder

    在有些時候,代碼中會需要使用到大量的字符串拼接的操作,這種時候有必要考慮使用StringBuilder來替代頻繁的“+”。

18)注意Activity的洩漏

 通常來說,Activity的洩漏是內存洩漏裡面最嚴重的問題,它占用的內存多,影響面廣,需要特別注意以下兩種情況導致的Activity洩漏:

 •內部類引用導致Activity的洩露

   最典型的場景是Handler導致的Activity洩漏,如果Handler中有延遲的任務或者是等待執行的任務隊列過長,都有可能因為Handler繼 續執行而導致Activity發生洩漏。此時的引用關系鏈是Looper -> MessageQueue -> Message -> Handler -> Activity。為了解決這個問題,可以在UI退出之前,執行remove Handler消息隊列中的消息與runnable對象。或者是使用Static + WeakReference的方式來達到斷開Handler與Activity之間存在引用關系的目的。

•Activity Context被傳遞到其他實例中,這可能導致自身被引用而發生洩漏。

    內部類引起的洩漏不僅僅會發生在Activity上,其他任何內部類出現的地方,都需要特別留意!可以考慮盡量使用static類型的內部類,同時使用WeakReference的機制來避免因為互相引用而出現的洩露。

19)考慮使用Application Context而不是Activity Context

    對於大部分非必須使用Activity Context的情況(Dialog的Context就必須是Activity Context),都可以考慮使用Application Context而不是Activity的Context,這樣可以避免不經意的Activity洩露。

(3)電量優化

    電量其實是目前手持設備最寶貴的資源之一,大多數設備都需要不斷的充電來維持繼續使用。不幸的是,對於開發者來說,電量優化是他們最後才會考慮的的事情。但是可以確定的是,千萬不能讓你的應用成為消耗電量的大戶。

有下面一些措施能夠顯著減少電量的消耗:

 •我們應該盡量減少喚醒屏幕的次數與持續的時間,使用WakeLock來處理喚醒的問題,能夠正確執行喚醒操作並根據設定及時關閉操作進入睡眠狀態。
 •某些非必須馬上執行的操作,例如上傳歌曲,圖片處理等,可以等到設備處於充電狀態或者電量充足的時候才進行。
 •觸發網絡請求的操作,每次都會保持無線信號持續一段時間,我們可以把零散的網絡請求打包進行一次操作,避免過多的無線信號引起的電量消耗。關於網絡請求引起無線信號的電量消耗。

1)消耗電量的幾個主要原因、功能

 •大數據量的網絡傳輸(網絡)

 •不停的網絡切換(網絡)

 •解析大量的數據(CPU)

2)關於網絡方面的優化

 •網絡請求之前,檢查網絡連接。沒有網絡連接不進行請求

 •判斷網絡類型,針對特定的數據在特定的網絡下請求。例如:大量數據傳輸的時候,在wifi下請求。wifi下下載數據耗電量只有2、3、4G的1/3.

 •使用效率高的解析工具。根據具體業務數據量的大小,選擇合適的解析工具。例如android上面的協議解析一般推薦json。

 •使用GZIP壓縮方式下載數據,能減少網絡流量,縮短下載時間

•合理使用緩存,避免重復操作

 •使用推送,代替循環請求

 •觸發網絡請求的操作,每次都會保持無線信號持續一段時間,我們可以把零散的網絡請求打包進行一次操作,避免過多的無線信號引起的電量消耗。

 •是JobScheduler API所做的事情。它會根據當前的情況與任務,組合出理想的喚醒時間,例如等到正在充電或者連接到WiFi的時候,或者集中任務一起執行。我們可以通過這個API實現很多免費的調度算法。

3)電量優化策略

 •檢查全部喚醒鎖, 是否存在冗余或者無用的位置.

 •集中相關的數據請求, 統一發送; 精簡數據, 減少無用數據的傳輸.

 •分析和統計等非重要操作, 可以在電量充足或連接WIFI時進行, 參考JobScheduler.

 •精簡冗余的服務(Service), 避免長時間執行耗電操作.

 •注意定位信息的獲取, 使用後及時關閉.

以上所述是小編給大家介紹的Android高級開發之性能優化典范的相關知識,希望對大家有所幫在,如果大家有任何疑問請給我留言,小編會及時回復大家的。在此也非常感謝大家對本站網站的支持!

  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved