對我們技術從業者而言,很多時候時候不是我們不知道怎麼做,而是不知道做什麼?今天系統的總結自己關於如何對Android應用進行優化的一些經驗,共計八個維度.
1.佈局優化
爲什麼?
Android系統每個16ms發出VSYNC信號,觸發對UI的渲染,要想達到界面流暢,必須實現60fps,也就意味着大多數的操作必須在16ms完成.
除了上面界面過於複雜導致渲染不能及時完成之外,還存在過度繪製問題.所謂過度繪製就是某個像素在同一幀的時間內被繪製多次.在多層次的UI界面中,如果不可見的UI也在進行繪製,那麼這些重合區域的像素就會被繪製多次,從而浪費大量的CPU和GPU資源.過度繪製也發生在背景重疊的情況下,比如Layout中有自己的背景,同時子View中又有自己的背景.
如何檢測?
- 使用HierarchyViewer來查找Activity中的佈局是否過於複雜
- 在開發者選項中打開Show GPU Overdraw選項進行觀察是否存在過度繪製
- 在開發者選項中選擇Profile GPU Rendering,選中On screen as bar
- 使用TraceView來觀察CPU執行情況
如何優化?
- 減少佈局的層級,合理的使用include,merge,ViewStub
- 自定義組件的onDraw()中避免大量創建臨時對象,比如String,以免頻繁觸發GC
- 自定義組件的onDraw()中,考慮使用canvas.clipRect()繪製需要被繪製的區域
- 對像ListView這樣的組件容器,考慮使用convertView,使用ViewHolder,
- 考慮使用性能更高的組件,比如推薦使用RecycleView來代替ListView,使用staticlayout來實現自動換行
2.內存優化
爲什麼?
資源總是有限的,內存同樣也是一種資源.在Android當中,過度的/不恰當佔用內存資源,會導致應用頻繁被殺死,最終也會影響用戶的整體體驗.任何一名開發者,都應該將節省內存牢記心中.
如何檢測?
- 使用LeakCanary
- 使用MAT分析Java堆
- 使用Android Device Monitor中的Application Tracker追蹤內存分配信息
- Android Studio中的Android Monitor,選擇其中的Memory
如何優化?
-
主動的釋放內存,在onLowMemory()和onTrimMemory()中適當的釋放內存
-
避免內存泄漏和內存溢出
-
在使用Bitmap的時候,考慮對其進行壓縮,使用緩存或者改變顏色模式,比如android默認的顏色格式是ARGB_8888,在要求不高的情況下可以採用RGB__565,這樣每個像素1佔用的內存就可懂4byte到2byte.
-
減少幀動畫的使用,如果需要,通過SurfaceView實現
-
使用更輕量級的數據結構,比如ArrayMap/SparseArray
-
合理的使用相關組件,比如Service和Webview,在不需要的時候主動結束其生命週期
-
合理的使用多進程,比如像音樂播放器類,可以分爲主進程和播放進程
-
使用異步隊列時考慮有界隊列
-
如果你能明確知道HashMap的大小,那就再初始化時爲其制定容量
3.電量優化
爲什麼?
電量是移動設備非常寶貴的資源,作爲一名開發者,有必要爲用戶着想,減少電量的消耗.調查顯示通常只有30%左右的電量是被程序核心的功能所消耗,比如界面渲染,剩下的70%則是被上報數據,位置更新,後臺通知所消耗.
如何檢測?
- 手機選項中通過查看APP的電量消耗的統計數據
- 使用Battery Historian Tool來查看詳細的電量消耗
如何優化?
- 減少喚醒屏幕的次數與持續的時間,正確的使用WakeLock.
- 延遲非必須的操作到充電狀態時,比如日誌上報完全可以在夜間充電時完成,這點可以結合JobScheduler使用
- 使用傳感器採集數據時,一旦不需要記得取消註冊.
- 減少網絡通信,合併通信.
- 合理使用定位功能,減少位置更新頻率以及根據實際情況使用不同精度的定位需求
4.網絡優化
爲什麼?
現在App幾乎都需要聯網操作,做好網絡優化一方面可以提高體驗,另一方面可以減少流量和電量的損耗.另外,無論是對用戶還是網絡服務提供者,網絡同樣是一種資源,任何開發者都不應該假設網絡資源是無限制的.
如何檢測?
- 使用Android Studio裏的Network Traffic Tools來查看網絡請求
- 使用Android Studio中的Monitor
- 使用Fidder或者Charles等抓包工具分析網絡數據包
如何優化?
- 有必要的時候務必做好緩存,無論是圖片還是普通的數據,使用LruCache和DiskLruCache構建自己的緩存系統,並根據實際場景設計緩存策略
- 避免過度的網絡同步,合併相關的網絡請求
- 根據實際場景確定請求策略,避免使用固定的間隔頻率來進行網絡操作.比如連接WiFi並充電的情況下請求頻率可以高,第一次網絡請求失敗後可以使用雙倍的時間間隔來進行下一次
- 減少數據傳輸量,對傳輸的數據做壓縮.如果傳輸的是圖片,需要選擇合適的圖片格式以及根據顯示大小請求合適規格的圖片.對於普通數據,可以考慮使用ProtocalBuffers來減小傳輸數據的大小.
- 某些情況下可以採用IP直連,一方面可以減少DNS解析時間,另一方面可以防止域名劫持
5.啓動優化
爲什麼?
啓動優化看起來並不是那麼必要,但從心理學角度而言,越快的啓動速度往往給用戶以性能好,高效可靠的心理暗示,這就很容易讓用戶對其產生好感,爲你後面打動用戶留下了餘地.
如何檢測?
- 使用Method Tracing
- 使用Systrace,比如在onCreate中添加trace.beginSection()和trace.endSection()
- 使用
adb shell am start -W [packageName]/[packageName.MainActivity]
測量冷啓動時間
如何優化?
- Activity的onCreate()中減少複雜和耗時的操作
- Application的onCreate(),attachBaseContext()中同樣減少複雜和耗時的操作,但是對於很多App在此處會執行大量組件和服務的初始化操作,如果可能考慮並行初始化
- 提供自定義啓動窗口,比如將一張圖片通過設置主題的方式顯示爲啓動窗口.
- 優化佈局
6.體積優化
爲什麼?
對用戶而言,無論是用戶空間還是網絡,亦或是時間,都是資源.體積優化就是爲用戶節省資源的重要一環.如果你現在做的是SDK類產品,那麼體積優化同樣重要.
如何檢測?
- 使用Android Lint檢查沒有使用的資源
如何優化?
- 減少不必要的依賴庫/Jar,在滿足需求的前提下優先選擇體積小的.
- 使用Proguard工具進行代碼瘦身,優化,混淆
- 減少so文件的數量,根據實際情況提供so文件
- 使用Gradle中的shrinkResource來將無用的代碼和資源排除在APK安裝包之外
- 減少圖片資源的大小,考慮圖片壓縮或者使用Vertor Drawable替代png/jpeg
- 有選擇的提供對應分辨率的圖片資源
- 複用已經存在的圖片,多用通過代碼對已有圖片進行變換的方式實現複用
- 使用插件化技術(如果項目簡單就不要使用)
7.性能優化
能發揮出100%的能力就不要只發揮其中的50%,這對應用而言並非壞事.同樣的價格賣給用戶兩輛車,我想大多數人會選擇性能更好的.
如何檢測?
- 使用Lint執行靜態分析,在Android Studio的Analysis->Inspect Code
- 在開發者選項中開啓StrictMode或者在代碼中開啓
- 代碼Review
如何優化?
- 任務並行化,對可能的任務進行並行操作,多借助線程池而非直接使用線程
- 如何需要序列化數據,優先考慮Android自身提供的而非Java提供的Serializable
- 選擇合適的數據結構,明確List/Set/Map/Stack操作的複雜度
- 使用Android提供更高效的容器,比如使用ArrayMap來代替HashMap,此外還是有SparseBoolMap,SparseIntMap,SparseLongMap
- 使用靜態常量代替Enum類型,可以減少至少兩倍的內存消耗
- 使用對象池技術,比如提供想String一樣的對象池
- 使用緩存技術
- 字符串拼接操作有限使用StringBuilder
- 對相關的算法和邏輯進行優化,減少不必要的流程
- 採用JNI,對計算量較大的邏輯將其協程so文件,如圖片處理
業務優化
除了上述比較通用的優化方案之外,也應該花點時間放在業務優化上.很多時候,迫於時間壓迫,當前實現業務的方案並非最優.比如爲了支持多張圖片上傳,很多人直接使用串行操作,儘管這樣做容易實現,但是卻並非最佳.
由於每個產品的業務並不相同,也就很難有通用的優化方案,這裏又兩個目標值得思考:
- 如果有可能,串行業務並行化
- 如果有可能,簡化業務流程.將一大象關進冰箱的方法就是打開冰箱,將大象放進去,最後關閉冰箱.
之所以把業務優化放在最後的根本原因是業務優化的風險較高,需要團隊的整體配合來完成.
該顏色模式下顏色細膩,顯示質量最高,佔用的內存也最大,
- Android中圖片有四種顏色格式,分別是
默認的是ARGB_8888
,其中ARGB分別代表的是透明度,紅色,綠色,藍色,每個值分別用8位來記錄,也就是一個像素會佔用4byte,共32位.
而ARGB_4444
和以上很類似,但是每個值分別用4位來記錄,也就是一個像素會佔用2byte,共16位.RGB_565
則分別用5位,6位,5位來記錄每個值,不存在透明度,每個像素會佔用2byte,共16位.ALPHA_8
:該像素只保存透明度,會佔用1byte,共8位.
在實際應用中而言,值推薦使用ARGB_8888
以及RGB_565
,如果你不需要透明度,那麼就選擇RGB_565
,可以減少一半的內存佔用. ↩