Android效能優化典範(三)
Android效能優化典範的課程最近更新到第三季了,這次一共12個短視訊課程,包括的內容大致有:更高效的ArrayMap容器,使用Android系統提供的特殊容器來避免自動裝箱,避免使用列舉型別,注意onLowMemory與onTrimMemory的回撥,避免記憶體洩漏,高效的位置更新操作,重複layout操作的效能影響,以及使用Batching,Prefetching優化網路請求,壓縮傳輸資料等等使用技巧。下面是對這些課程的總結摘要,認知有限,理解偏差的地方請多多交流指正!
1) Fun with ArrayMaps
程式記憶體的管理是否合理高效對應用的效能有著很大的影響,有的時候對容器的使用不當也會導致記憶體管理效率低下。Android為移動作業系統特意編寫了一些更加高效的容器,例如SparseArray,今天要介紹的是一個新的容器,叫做 ArrayMap。
我們經常會使用到HashMap這個容器,它非常好用,但是卻很佔用記憶體。下圖演示了HashMap的簡要工作原理:
為了解決HashMap更佔記憶體的弊端,Android提供了記憶體效率更高的ArrayMap。它內部使用兩個陣列進行工作,其中一個陣列記錄key hash過後的順序列表,另外一個陣列按key的順序記錄Key-Value值,如下圖所示:
當你想獲取某個value的時候,ArrayMap會計算輸入key轉換過後的hash值,然後對hash陣列使用二分查詢法尋找到對應的index,然後我們可以通過這個index在另外一個陣列中直接訪問到需要的鍵值對。如果在第二個陣列鍵值對中的key和前面輸入的查詢key不一致,那麼就認為是發生了碰撞衝突。為了解決這個問題,我們會以該key為中心點,分別上下展開,逐個去對比查詢,直到找到匹配的值。如下圖所示:
隨著陣列中的物件越來越多,查詢訪問單個物件的花費也會跟著增長,這是在記憶體佔用與訪問時間之間做權衡交換。
既然ArrayMap中的記憶體佔用是連續不間斷的,那麼它是如何處理插入與刪除操作的呢?請看下圖所示,演示了Array的特性:
很明顯,ArrayMap的插入與刪除的效率是不夠高的,但是如果陣列的列表只是在一百這個數量級上,則完全不用擔心這些插入與刪除的效率問題。HashMap與ArrayMap之間的記憶體佔用效率對比圖如下:
與HashMap相比,ArrayMap在迴圈遍歷的時候也更加簡單高效,如下圖所示:
前面演示了很多ArrayMap的優點,但並不是所有情況下都適合使用ArrayMap,我們應該在滿足下面2個條件的時候才考慮使用ArrayMap:
- 物件個數的數量級最好是千以內;
- 資料組織形式包含Map結構。
我們需要學會在特定情形下選擇相對更加高效的實現方式。
2) Beware Autoboxing
有時候效能問題也可能是因為那些不起眼的小細節引起的,例如在程式碼中不經意的“自動裝箱”。我們知道基礎資料型別的大小:boolean(8 bits), int(32 bits), float(32 bits),long(64 bits),為了能夠讓這些基礎資料型別在大多數Java容器中運作,會需要做一個autoboxing的操作,轉換成Boolean,Integer,Float等物件,如下演示了迴圈操作的時候是否發生autoboxing行為的差異:
Autoboxing的行為還經常發生在類似HashMap這樣的容器裡面,對HashMap的增刪改查操作都會發生了大量的autoboxing的行為。
為了避免這些autoboxing帶來的效率問題,Android特地提供了一些如下的Map容器用來替代HashMap,不僅避免了autoboxing,還減少了記憶體佔用:
3) SparseArray Family Ties
為了避免HashMap的autoboxing行為,Android系統提供了SparseBoolMap,SparseIntMap,SparseLongMap,LongSparseMap等容器。關於這些容器的基本原理請參考前面的ArrayMap的介紹,另外這些容器的使用場景也和ArrayMap一致,需要滿足數量級在千以內,資料組織形式需要包含Map結構。
4) The price of ENUMs
在StackOverFlow等問答社群常常出現關於在Android系統裡面使用列舉型別的效能討論,關於這一點,Android官方的Training課程裡面有下面這樣一句話:
Enums often require more than twice as much memory as static constants. You should strictly avoid using enums on Android.
關於enum的效率,請看下面的討論。假設我們有這樣一份程式碼,編譯之後的dex大小是2556 bytes,在此基礎之上,新增一些如下程式碼,這些程式碼使用普通static常量相關作為判斷值:
增加上面那段程式碼之後,編譯成dex的大小是2680 bytes,相比起之前的2556 bytes只增加124 bytes。假如換做使用enum,情況如下:
使用enum之後的dex大小是4188 bytes,相比起2556增加了1632 bytes,增長量是使用static int的13倍。不僅僅如此,使用enum,執行時還會產生額外的記憶體佔用,如下圖所示:
Android官方強烈建議不要在Android程式裡面使用到enum。
5) Trimming and Sharing Memory
Android系統的一大特色是多工,使用者可以隨意在不同的app之間進行快速切換。為了確保你的應用在這種複雜的多工環境中正常執行,我們需要了解下面的知識。
為了讓background的應用能夠迅速的切換到forground,每一個background的應用都會佔用一定的記憶體。Android系統會根據當前的系統記憶體使用情況,決定回收部分background的應用記憶體。如果background的應用從暫停狀態直接被恢復到forground,能夠獲得較快的恢復體驗,如果background應用是從Kill的狀態進行恢復,就會顯得稍微有點慢。
Android系統提供了一些回撥來通知應用的記憶體使用情況,通常來說,當所有的background應用都被kill掉的時候,forground應用會收到onLowMemory()的回撥。在這種情況下,需要儘快釋放當前應用的非必須記憶體資源,從而確保系統能夠穩定繼續執行。Android系統還提供了onTrimMemory()的回撥,當系統記憶體達到某些條件的時候,所有正在執行的應用都會收到這個回撥,同時在這個回撥裡面會傳遞以下的引數,代表不同的記憶體使用情況,下圖介紹了各種不同的回撥引數:
關於每個引數的更多介紹,請參考《 Android Training - 管理應用的記憶體》,另外onTrimMemory()的回撥可以發生在Application,Activity,Fragment,Service,Content Provider。
從Android 4.4開始,ActivityManager提供了isLowRamDevice()的API,通常指的是Heap Size低於512M或者螢幕大小<=800*480的裝置。
6) DO NOT LEAK VIEWS
記憶體洩漏的概念,下面一張圖演示下:
通常來說,View會保持Activity的引用,Activity同時還和其他內部物件也有可能保持引用關係。當螢幕發生旋轉的時候,activity很容易發生洩漏,這樣的話,裡面的view也會發生洩漏。Activity以及view的洩漏是非常嚴重的,為了避免出現洩漏,請特別留意以下的規則:
6.1) 避免使用非同步回撥
非同步回撥被執行的時間不確定,很有可能發生在activity已經被銷燬之後,這不僅僅很容易引起crash,還很容易發生記憶體洩露。
6.2) 避免使用Static物件
因為static的生命週期過長,使用不當很可能導致leak,在Android中應該儘量避免使用static物件。
6.3) 避免把View新增到沒有清除機制的容器裡面
假如把view新增到 WeekHashMap,如果沒有執行清除操作,很可能會導致洩漏。
7) Location & Battery Drain
開啟定位功能是一個相對來說比較耗電的操作,通常來說,我們會使用類似下面這樣的程式碼來發出定位請求:
上面演示中有一個方法是setInterval()指的意思是每隔多長的時間獲取一次位置更新,時間相隔越短,自然花費的電量就越多,但是時間相隔太長,又無法及時獲取到更新的位置資訊。其中存在的一個優化點是,我們可以通過判斷返回的位置資訊是否相同,從而決定設定下次的更新間隔是否增加一倍,通過這種方式可以減少電量的消耗,如下圖所示:
在位置請求的演示程式碼中還有一個方法是setFastestInterval(),因為整個系統中很可能存在其他的應用也在請求位置更新,那些應用很有可能設定的更新間隔時間很短,這種情況下,我們就可以通過setFestestInterval的方法來過濾那些過於頻繁的更新。
通過GPS定位服務相比起使用網路進行定位更加的耗電,但是也相對更加精準一些,他們的圖示關係如下:
為了提供不同精度的定位需求,同時遮蔽實現位置請求的細節,Android提供了下面4種不同精度與耗電量的引數給應用進行設定呼叫,應用只需要決定在適當的場景下使用對應的引數就好了,通過LocationRequest.setPriority()方法傳遞下面的引數就好了。
8) Double Layout Taxation
佈局中的任何一個View一旦發生一些屬性變化,都可能引起很大的連鎖反應。例如某個button的大小突然增加一倍,有可能會導致兄弟檢視的位置變化,也有可能導致父檢視的大小發生改變。當大量的layout()操作被頻繁呼叫執行的時候,就很可能引起丟幀的現象。
例如,在RelativeLayout中,我們通常會定義一些類似alignTop,alignBelow等等屬性,如圖所示:
為了獲得檢視的準確位置,需要經過下面幾個階段。首先子檢視會觸發計算自身位置的操作,然後RelativeLayout使用前面計算出來的位置資訊做邊界的調整的操作,如下面兩張圖所示:
經歷過上面2個步驟,relativeLayout會立即觸發第二次layout()的操作來確定所有子檢視的最終位置與大小資訊。
除了RelativeLayout會發生兩次layout操作之外,LinearLayout也有可能觸發兩次layout操作,通常情況下LinearLayout只會發生一次layout操作,可是一旦呼叫了measureWithLargetChild()方法就會導致觸發兩次layout的操作。另外,通常來說,GridLayout會自動預處理子檢視的關係來避免兩次layout,可是如果GridLayout裡面的某些子檢視使用了weight等複雜的屬性,還是會導致重複的layout操作。
如果只是少量的重複layout本身並不會引起嚴重的效能問題,但是如果它們發生在佈局的根節點,或者是ListView裡面的某個ListItem,這樣就會引起比較嚴重的效能問題。如下圖所示:
我們可以使用Systrace來跟蹤特定的某段操作,如果發現了疑似丟幀的現象,可能就是因為重複layout引起的。通常我們無法避免重複layout,在這種情況下,我們應該儘量保持View Hierarchy的層級比較淺,這樣即使發生重複layout,也不會因為佈局的層級比較深而增大了重複layout的倍數。另外還有一點需要特別注意,在任何時候都請避免呼叫requestLayout()的方法,因為一旦呼叫了requestLayout,會導致該layout的所有父節點都發生重新layout的操作。
9) Network Performance 101
在效能優化第一季與第二季的課程裡面都介紹過,網路請求的操作是非常耗電的,其中在移動蜂窩網路情況下執行網路資料的請求則尤其比較耗電。關於如何減少行動網路下的網路請求的耗電量,有兩個重要的原則需要遵守:第一個是減少行動網路被啟用的時間與次數,第二個是壓縮傳輸資料。
9.1) 減少行動網路被啟用的時間與次數
通常來說,發生網路行為可以劃分為如下圖所示的三種型別,一個是使用者主動觸發的請求,另外被動接收伺服器的返回資料,最後一個是資料上報,行為上報,位置更新等等自定義的後臺操作。
我們絕對堅決肯定不應該使用Polling(輪詢)的方式去執行網路請求,這樣不僅僅會造成嚴重的電量消耗,還會浪費許多網路流量,例如:
Android官方推薦使用Google Cloud Messaging(在大陸,然並卵),這個框架會幫助把更新的資料推送給手機客戶端,效率極高!我們應該遵循下面的規則來處理資料同步的問題:
首先,我們應該使用回退機制來避免固定頻繁的同步請求,例如,在發現返回資料相同的情況下,推遲下次的請求時間,如下圖所示:
其次,我們還可以使用Batching(批處理)的方式來集中發出請求,避免頻繁的間隔請求,如下圖所示:
最後,我們還可以使用Prefetching(預取)的技術提前把一些資料拿到,避免後面頻繁再次發起網路請求,如下圖所示:
Google Play Service中提供了一個叫做 GCMNetworkManager的類來幫助我們實現上面的那些功能,我們只需要呼叫對應的API,設定一些簡單的引數,其餘的工作就都交給Google來幫我們實現了。
9.2) 壓縮傳輸資料
關於壓縮傳輸資料,我們可以學習以下的一些課程(真的夠喝好幾壺了):
- CompressorHead:這系列的課程會介紹壓縮的基本概念以及一些常見的壓縮演算法知識。
- Image Compression:介紹關於圖片的壓縮知識。
- Texture Wranglin:介紹了遊戲開發相關的知識。
- Grabby:介紹了遊戲開發相關的知識。
- Gzip is not enough
- Text Compression
- FlatBuffers
10) Effective Network Batching
在效能優化課程的第一季與第二季裡面,我們都有提到過下面這樣一個網路請求與電量消耗的示意圖:
發起網路請求與接收返回資料都是比較耗電的,在網路硬體模組被啟用之後,會繼續保持幾十秒的電量消耗,直到沒有新的網路操作行為之後,才會進入休眠狀態。前面一個段落介紹了使用Batching的技術來捆綁網路請求,從而達到減少網路請求的頻率。那麼如何實現Batching技術呢?通常來說,我們可以會把那些發出的網路請求,先暫存到一個PendingQueue裡面,等到條件合適的時候再觸發Queue裡面的網路請求。
可是什麼時候才算是條件合適了呢?最簡單粗暴的,例如我們可以在Queue大小到10的時候觸發任務,也可以是當手機開始充電,或者是手機連線到WiFi等情況下才觸發佇列中的任務。手動編寫程式碼去實現這些功能會比較複雜繁瑣,Google為了解決這個問題,為我們提供了GCMNetworkManager來幫助實現那些功能,僅僅只需要呼叫API,設定觸發條件,然後就OK了。
11) Optimizing Network Request Frequencies
前面的段落已經提到了應該減少網路請求的頻率,這是為了減少電量的消耗。我們可以使用Batching,Prefetching的技術來避免頻繁的網路請求。Google提供了GCMNetworkManager來幫助開發者實現那些功能,通過提供的API,我們可以選擇在接入WiFi,開始充電,等待行動網路被啟用等條件下再次啟用網路請求。
12) Effective Prefetching
假設我們有這樣的一個場景,最開始網路請求了一張圖片,隔了10秒需要請求另外一張圖片,再隔6秒會請求第三張圖片,如下圖所示:
類似上面的情況會頻繁觸發網路請求,但是如果我們能夠預先請求後續可能會使用到網路資源,避免頻繁的觸發網路請求,這樣就能夠顯著的減少電量的消耗。可是預先獲取多少資料量是很值得考量的,因為如果預取資料量偏少,就起不到減少頻繁請求的作用,可是如果預取資料過多,就會造成資源的浪費。
我們可以參考在WiFi,4G,3G等不同的網路下設計不同大小的預取資料量,也可以是按照圖片數量或者操作時間來作為閥值。這需要我們需要根據特定的場景,不同的網路情況設計合適的方案。
轉自:http://www.csdn.net/article/2015-08-12/2825447-android-performance-patterns-season-3/1相關文章
- [轉]Android效能優化典範Android優化
- Android效能優化典範(二)Android優化
- Android效能優化典範(2)Android優化
- Android效能優化典範 – 第4季Android優化
- Android效能優化典範第一季Android優化
- Android效能優化----卡頓優化Android優化
- Android效能優化Android優化
- Android效能優化(三)之記憶體管理Android優化記憶體
- Android效能優化篇之計算效能優化Android優化
- Android 效能優化 ---- 啟動優化Android優化
- Oracle效能優化-SQL優化(案例三)Oracle優化SQL
- Android效能優化——效能優化的難題總結Android優化
- Android效能優化(Memory)Android優化
- Android效能優化(上)Android優化
- Android效能優——佈局優化Android優化
- Android效能優化之佈局優化Android優化
- Android效能優化——圖片優化(二)Android優化
- Android效能優化(1)—webview優化篇Android優化WebView
- Android效能優化——程式碼優化(一)Android優化
- Android效能優化 - 記憶體優化Android優化記憶體
- Android效能優化篇之服務優化Android優化
- 九、Android效能優化之網路優化Android優化
- Android 效能優化之記憶體優化Android優化記憶體
- Android 效能優化(八)之網路優化Android優化
- Android效能優化 筆記Android優化筆記
- Android App效能優化[譯]AndroidAPP優化
- Android 中 SQLite 效能優化AndroidSQLite優化
- Android效能優化總結Android優化
- Android效能優化---筆記Android優化筆記
- Android 效能優化(十二)之我為什麼寫效能優化Android優化
- 六、Android效能優化之UI卡頓分析之渲染效能優化Android優化UI
- MySQL 高效能優化規範建議MySql優化
- 大型網站--前端效能優化和規範網站前端優化
- Android效能優化筆記(一)——啟動優化Android優化筆記
- 八、Android效能優化之電量優化(二)Android優化
- [web前端效能優化]效能優化只有三步,你瞭解嗎Web前端優化
- 效能優化漫談之三:效能優化目標的確定和衡量優化
- 《java學習三》jvm效能優化-------調優JavaJVM優化