安卓性能優化
性能優化的幾大考慮
- Mobile Context
- 資源受限
+ 內存,廣泛較小,512MB很常見,開發者的機器通常比用戶的機器高端
+ CPU,核心少,運算能力沒有全開
+ GPU,上傳大的紋理(texture),overdraw
- 內存開銷大,會致使系統換入換出更頻繁,GC更頻繁,APP被kill、被重啓更頻繁,不只會消耗更多電量,並且GC會消耗大量時間,使得應用程序渲染速度低於60fps(GC耗時dalvik 10-20ms,ART 2-3ms)
- 外部存儲與網絡,也是受限的,須要考慮資源的使用、網絡請求的優化
- The Rules: Memory
- Avoid Allocations in Inner Loops
- Avoid Allocations When Possible
+ Cached objects
+ Object pools:注意線程安全問題
+ ArrayList v.s. 數組
+ Android collections classes:HashMap v.s. ArrayMap/SimpleArrayMap
+ Methods with mutated objects
+ Avoid object types when primitive types will do:SparseIntArray,SparseLongArray
+ Avoid arrays of objects
- Avoid Iterators:顯式與隱式(foreach語句),會致使一個Iterator的分配,即使是空集合。
- Avoid Enums
- Avoid Frameworks and Libraries Not Written for Mobile Applications
- Avoid Static Leaks
- Avoid Finalizers
- Avoid Excess Static Initialization
- Trim caches on demand
- Use isLowRamDevice:ActivityManager.isLowRamDevice()
- Avoid Requesting a Large Heap
- Avoid Running Services Longer than Necessary:BroadcastReceiver,IntentService
- Optimize for Code Size
+ Use Proguard to strip out unused code
+ Carefully consider your library dependencies
+ Make sure to understand the cost of any code which is automatically generated
+ Prefer simple, direct solutions to problems rather than creating a lot of infrastructure and abstractions to solve those problems
- The Rules: Performance
- Avoid Expensive Operations During Animations and User Interaction
動畫的每一幀渲染都是在UI線程的,若是有動畫的時候進行耗時操做,極可能致使動畫不流暢,耗時操做包括:
+ Layout:當動畫正在播放的時候,要避免改變View(延遲改變);同時選擇動畫也須要避免會觸發layout的動畫,例如translationX,translationY只會致使延遲的layout操做,而LayoutParams屬性,則會致使即時的layout。
+ Inflation:動畫過程當中避免inflate新的view,好比啓動新的activity,或者ListView滑動到不一樣type的區域。
- Launch Fast
+ Avoid this problem by launching as fast as possible
+ Also, avoid initialization code in your Application object
- Avoid Complex View Hierarchies
+ One approach to avoiding complex nested hierarchies is to use custom views or custom layouts in some situations; it may be cheaper for a single view to draw several pieces of text and icons rather than have a series of nested ViewGroups to accomplish this.
+ 結合的準則就是根據他們是否須要單獨和用戶完成交互(響應點擊事件等)
- Avoid RelativeLayout Near the Top of the View Hierarchy
RelativeLayout須要兩次measurement passes才能肯定佈局正確,嵌套RelativeLayout,是冪乘關係
- Avoid Expensive Operations on the UI Thread
- Minimize Wakeups
- Develop for the Low End
- Measure Performance
- The Rules: Networking
- Don’t Over-Sync:batch it up with other system requests with JobScheduler or GCM Network Manager.
- Avoid Overloading the Server
- Don’t Make Assumptions about the Network
- Develop for Low End Networks
- Design Back-End APIs to Suit Client Usage Patterns:相關數據一個請求分發完畢;不相關的數據分接口分發;客戶端應對獲取的數據具有足夠的信息;
- The Rules: Language and Libraries
- Use Android-Appropriate Data Structures: ArrayMap, SparseArray
- Serialization
+ Parcelable:安卓系統IPC格式;把Parcel寫到磁盤是不安全的;解包方必須能訪問Parcel的類,不然將失敗;特定的類(Bitmap,CursorWindow)將被寫到SharedPreference中,而經過Parcel傳遞的只是文件的fd,存在性能優化的空間,可是也節約了內存;
+ Persistable Bundles:API 21引入,序列化爲XML,支持的類型比Parcel少,可是爲Bundle子類,某些場景方便處理;
+ Avoid Java Serialization:額外開銷更大,性能更差
+ XML and JSON:效率更低,複雜數據應考慮前述選項
- Avoid JNI
+ 須要考慮多種處理器架構,指針用long保存
+ java->jni, jni->java調用開銷都很大,一次JNI調用作儘量多的工做
+ 內存管理,java對象管理jni對應對象的生命週期
+ 錯誤處理,在調用JNI以前檢查參數
+ 參數對象儘可能「傳值」調用,即:展開後傳遞,不要在JNI裏面使用指針訪問成員,避免JNI過程當中對象被回收
- Prefer Primitive Types:內存、性能
- The Rules: Storage
- Avoid Hard-coded File Paths
- Persist Relative Paths Only
- Use Storage Cache for Temporary Files
- Avoid SQLite for Simple Requirements
- Avoid Using Too Many Databases
- Let User Choose Content Storage Location
- The Rules: Framework
- Avoid Architecting Around Application Components
- Services Should Be Bound or Started, Not Both
- Prefer Broadcast over Service for Independent Events:Use broadcasts for delivering independent events; use services for processes with state and on-going lifecycle.
- Avoid Passing Large Objects Through Binder
- Isolate UI processes from Background Services
- The Rules: User Interface
- Avoid Overdraw
- Avoid Null Window Backgrounds
put the background drawable you want on the window itself with the windowBackground theme attribute and let those intervening containers keep their default transparent backgrounds.
- Avoid Disabling the Starting Window(windowDisablePreview/windowBackground)
- Allow Easy Exit from Immersive Mode
- Set Correct Status/Navigation Bar Colors in Starting Window
- Use the Appropriate Context
- Avoid View-Related References in Asynchronous Callbacks
- Design for RTL
- Cache Data Locally
- Cache User Input Locally
- Separate Network and Disk Background Operations
- Tools
- Host Tools
+ Systrace
- Alerts and the Frames, 提示很是詳盡,排查性能問題很方便
- 可是不知道怎麼開啓,monitor開啓報錯,命令行開啓也報錯,是手機系統的緣由?
+ AllocationTracker
- AS集成,點擊按鈕,對APP進行操做,再點擊按鈕結束,capture將在AS中打開,查看哪裏分配了內存,排查內存分配性能問題利器
+ Traceview
- 根據Traceview的結果,查看耗時排名靠前的方法,分析緣由,提升性能
+ Hierarchyviewer
- 查看當前view hierarchy中每一個view的繪製實踐,繪製較慢的該工具會給出提示(不一樣顏色)
+ MAT (Memory Analysis Tool)
- 先經過heap dump把堆快照導出,再經過MAT進行分析(實踐?)
+ Memory Monitor
+ meminfo
- On-device tools
+ StrictMode
+ Profile GPU rendering
+ Debug GPU overdraw
+ Animator duration scale
+ Screenrecord
+ Show hardware layer updates
- Speed up your app
- Rules
+ Always Measure
+ Use[Experience] Slow Device
+ Consider Trade-Offs
- Memory tips
+ Bitmap's pixel format
+ Context Awareness
+ HashMap v.s. ArrayMap/Sparce*Array
- LeakCanary
- Alpha
+ TextView: setTextColor() instead of setAlpha()
+ ImageView: setImageAlpha() instead of setAlpha()
+ CustomView: handle alpha yourself by overriding onSetAlpha(), overriding hasOverlappingRendering()
- Hardware Acceleration
+ view.setLayerType(View.LAYER_TYPE_HARDWARE, null)
+ view.animate()....withLayer().start()
+ 硬件加速很是適用於動畫的渲染,可是也有須要注意的地方,ref
- 硬件加速的原理是GPU會把View繪製的結果緩存起來,後續的更新只須要從新渲染便可,省去了View的繪製,以及繪製指令的傳輸部分,可是硬件加速一開始的時候有額外的初始化工做(緩存)
- 若是View特別簡單,僅僅是一個單顏色區域,那硬件加速的額外開銷可能得不償失
- 若是View在動畫過程當中不斷invalidate,或者其內容不斷變化,硬件加速的效果將大打折扣
- 若是動畫發生在ViewGroup上,而其子View相對於ViewGroup也是在發生變化時,就不該該把硬件加速設置在ViewGroup上,由於動畫過程當中ViewGroup的內容是不斷變化的(子View也在不斷變化),而是應該把加速設置在各個子View上
- GPU存儲空間有限,僅當有必要時才使用硬件加速
- profile GPU rendering和show hardware layers updates是很好的效果評估工具
谷歌安卓團隊對於性能優化的建議
- Android performance patterns系列視頻已經出到了第三季,國內也有安卓大神整理翻譯的中文文字版,但就像讀書同樣,大神寫了完整的書,看的時候仍是要作個筆記的。如下只是針對自身狀況的筆記,僅供參考。
- S1E0: Render Performance
- 60 fps, 16 ms每幀
- S1E1: Understanding Overdraw
- 開發者選項:Show GPU Overdraw
- 避免設置多重背景
- S1E2: Understanding VSYNC
- Refresh Rate:表明了屏幕在一秒內刷新屏幕的次數,這取決於硬件的固定參數,例如60Hz
- Frame Rate:表明了GPU在一秒內繪製操做的幀數,例如30fps,60fps
- 當Refresh rate和Frame rate不一致時,將會發生tearing效果:屏幕的內容被分紅了上下兩部分,分別來自兩幀的內容
+ 原理簡述:視頻(動畫)是由靜態幀快速切換達到的效果(例如60 fps),而每幀是一個圖片,其內容就是一個像素矩陣,顯示屏繪製每幀的時候,是逐行繪製該像素矩陣的,理想狀況下,顯示屏繪製完一幀的像素以後,去獲取下一幀的像素進行繪製,但若是顯示屏繪製第一幀的像素繪製到一半,保存幀像素矩陣的buffer被寫入了第二幀的像素矩陣,而顯示屏並不知道,仍會接着從上一行往下繪製,就會致使繪製出來的圖像上部分屬於上一幀,下部分屬於下一幀,即tearing效果。
- VSync,用來同步GPU和屏幕繪製,在GPU載入新幀以前,要等待屏幕繪製完成上一幀的數據
+ 還有更多關於圖像渲染的內容,三重緩衝等,須要擴展閱讀
- 幀率大於刷新頻率(60 fps)時,顯示很流暢;幀率小於60 fps時,會顯示重複幀;幀率忽然從大於60 fps降到低於60 fps時,用戶就會感覺到卡頓的發生了;
- S1E3: Tool: Profile GPU Rendering
- 開發者選項:Profile GPU Rendering, On screen as bars
- 兩個區域(虛擬鍵盤設備會有三個區域),從上到下分別表示:狀態欄繪製、主窗口繪製、虛擬鍵盤區域繪製
- 三種顏色
+ 藍色:draw time,建立、更新display list所消耗的時間;onDraw函數中使用Canvas調用的draw*函數的執行時間;convert to GPU description, cache as display list;
- 藍色太高,可能由於大量view被invalidate,須要重繪,或者是onDraw方法的邏輯過於複雜,執行時間長
- 紅色:execute time,Android 2D renderer執行display list所消耗的時間(經過Open GL接口,使用GPU繪製);自定義View越複雜,GPU渲染所需時間越長;
- 紅色太高,緣由極可能就是View的構成太複雜;極高的峯值,多是由於從新提交了視圖繪製形成的,並不是view被invalidate,而是相似於View旋轉這樣的變化,須要先清空原有區域,再從新繪製;
- 橙色:process time,CPU通知GPU渲染結束消耗的時間,同步調用
- 橙色太高,多是View太複雜,渲染須要太多時間
- S1E4: Why 60fps? 常識,12 fps近乎於人手快速翻書,24 fps是電影的經常使用幀率,60 fps用於表現絢麗的動畫效果
- S1E5: Android, UI and the GPU
- Resterization(柵格化):繪製Button,Shape,Path,Text,Bitmap等組件最基礎的操做;柵格化就是將這些組件的內容拆分到不一樣的像素上進行顯示;柵格化很耗時,引入GPU就是爲了加快柵格化操做;
- CPU負責把UI組件計算成Polygons,Texture紋理,而後交給GPU進行柵格化渲染
- 所以須要在CPU和GPU之間傳遞數據,OpenGL ES能夠把那些須要渲染的紋理Hold在GPU Memory裏面,在下次須要渲染的時候直接操做。若是更新了GPU所hold住的紋理內容,以前保存的狀態就丟失了,將致使繪製變慢。
- 在安卓系統中,由主題提供的資源(Bitmaps,Drawables等)都是一塊兒打包到統一的Texture紋理當中,而後再傳遞到GPU裏面,這意味着每次你須要使用這些資源的時候,都是直接從紋理裏面進行獲取渲染的,速度將會更快。
- 也有更復雜的組件,例如顯示圖片的時候,須要先通過CPU的計算加載到內存中,而後傳遞給GPU進行渲染。文字的顯示更加複雜,須要先通過CPU換算成紋理,而後再交給GPU進行渲染,回到CPU繪製單個字符的時候,再從新引用通過GPU渲染的內容。動畫則是一個更加複雜的操做流程。
- S1E6: Invalidations, Layouts, and Performance
- DisplayList:包含須要GPU繪製到屏幕上的數據信息
- 在某個View第一次被渲染時,會建立DisplayList,當這個View要顯示到屏幕上時,會執行GPU的繪製指令來進行渲染。若是後續有移動這個View的位置等操做而須要再次渲染這個View時,只須要額外操做一次渲染指令便可。若是修改了View中的某些可見組件,將須要進行建立DisplayList、執行渲染指令整個過程。
- 上述步驟可簡化爲下圖,須要的時間取決於View的複雜度,View重繪引發的View hierarchy的變化的複雜度

int size = list.size();
for (int i = 0; i < size; i++) {
Object object = list.get(i);
...
}
for (Iterator it = list.iterator(); it.hasNext(); ) {
Object object = it.next();
...
}
for (Object object : list) {
...
}
for index (ArrayList) |
2603 |
for index (Vector) |
4664 |
for simple (ArrayList) |
5133 |
Iterator (ArrayList) |
5142 |
for simple (Vector) |
11783 |
Iterator (Vector) |
11778 |
Square團隊的建議
- Eliminating Code Overhead by Jake Wharton
- CPU
+ Do not nest multi-pass layouts: RelativeLayout, LinearLayout with layout_weight...
+ Lazily compute complex data when needed
+ Cache heavy computational results for re-use
+ Consider RenderScript for performance
+ Keep work off main thread
- Memory
+ Use object pools and caches to reduce churn (judiciously)
+ Be mindful of the overhead of enums
+ Do not allocate inside the draw path
+ Use specialized collections instead of JDK collections when appropriate (SparceArray...)
- I/O
+ Batch operations with reasonable back-off policies
+ Use gzip or binary serialization format
+ Cache data offline with TTLs for reloading
+ Use JobScheduler API to batch across OS
- Spectrum of optimizations, not binary
- Do not blindly apply to everything, only appropriate
- Multiple micro-optimizations can improve like macro
- ArrayList分配:會有一個默認初始值,之後空間不夠時按倍增策略進行擴展
+ 若是建立時就知道其大小,則能夠new一個已知容量的ArrayList,避免後面擴容、數據複製的成本
+
- StringBuilder:一樣的,也能夠先給一個預估的大小,而後直接初始化該大小的StringBuilder;安卓開發build時會自動把String的拼接操做轉化爲StringBuilder實現,然而這種自動的轉換未必高效;
+ 例子
java for (int x = 0; x < valueCount; x++) { cleanFiles[x] = new File(directory, key + "." + x); dirtyFiles[x] = new File(directory, key + "." + x + ".tmp"); }
===>>>
java StringBuilder b = new StringBuilder(key).append("."); int truncateTo = b.length(); for (int x = 0; x < valueCount; x++) { b.append(x); cleanFiles[x] = new File(directory, b.toString()); b.append(".tmp"); dirtyFiles[x] = new File(directory, b.toString()); b.setLength(truncateTo); }
- 其餘
+ 對函數的調用(尤爲是虛函數、接口函數)結果,若是同一個做用域中有屢次調用,且結果肯定不變,應該將他們轉化爲一次調用:for (int i = 0, size = list.size(); i < size; i++)
+ 對集合的遍歷,不要使用語法糖,會有額外開銷(Iterator建立、虛函數調用等)
- 性能優化的流程

- Recommendation 1: limit app startup to 2 seconds
- Recommendation 2: eliminate hung methods
- Recommendation 3: measure as often as you can,怎麼、什麼粒度的profiling呢?
- Recommendation 4: know a set of common issues
- ClassLoader.getResourceAsStream()
- Recommendation 5: avoid surprises in 3rd-party SDKs