本文是《移動App性能評測與優化》的讀書筆記。
PS:說是讀書筆記,其實就是摘錄。
移動App的性能測試主要包括:內存使用情況、電量消耗、功能的流暢度等;
1. 內存
1.1 內存的主要組成索引:
-
Native Heap : Native 代碼分配的內存,虛擬機和Android框架本身也會分配;
-
Dalvik Heap : Java 代碼分配的對象;
-
Dalvik Other : 類的數據結構和索引;
-
so mmap : Native 代碼和常量;
-
dex mmap :Java 代碼和常量;
1.2 內存測試工具
-
Android Studio / Memory Monitor : 觀察 Dalvik 內存;
-
dumpsys meminfo : 觀察整體內存;
-
smaps : 觀察整體內存的詳細組成;
-
MAT : 詳細分析 Dalvik內存;
1.3 一個類的內存消耗
虛擬機在創建對象時的操作:
-
loadClass,將類信息從 dex 文件中加載進內存:
-
讀取 .dexx mmap 中 class 對應的數據;
-
分配 native-heap 和 dalvik-heap 內存創建 class 對象;
-
分配 dalvik-LinearAlloc 存放 class 數據;
-
分配 delvik-aux-structure 存放 class 數據;
-
-
new instance 操作,創建對象實例
-
執行 .dex mmap 中
和 的代碼; -
分配 delvik-heap 創建 class對象實例;
-
dex mmap
在Android應用中的作用是映射classes.dex文件
1.4 dex 優化
省略掉dex的文件結構(自行查閱)
為了節省空間,dex將原先在 class 文件中重復的信息集中放置在一起,並以索引和指針的形式支持快速訪問。
dex 文件中數據基本是按類名的字母順序進行排列的,這樣同樣包名的類會排在一起,但程序實際執行時,同一個包下的類並不會全部調用到,而是跨包進行交互,但 mmap 加載了整個頁面,可能會有很多無用的數據。
優化;
在APK的編譯流程中,Proguard 混淆工具正好是能夠對類名進行修改的,可以根據程序運行時的邏輯,將那些會互相調用的類改為為同一個 package 名,這樣就可以使它們的數據排布在一起。
1.5 MAT(Memory Analyzer Tool)
使用MAT來分析應用的內存使用情況。通常在使用MAT打開hprof
文件后,能夠在首頁看到Top Comnsumers和 component Report等功能,我們可以快速定位一些大塊的內存消耗。但我們在分析時會發現系統資源類占據了很大一部分內存,因此為去除這部分對分析的干擾,我們在使用AndroidSDK
提供的hprof-conv
轉換時需要增加一個參數:
hporf- conv [-z] <infile><outfile> -z:exclude non-app heaps,such as Zygote
如果hprof文件是已經轉換過的,則可以使用OQL:
//在數據中尋找應用的Application類對象,將對象地址轉換為十進制后輸入以下查詢語句:
select * from instanceof java.langObject s where s.@objectAddress> 1107296256
//(后面那串數字應該是Application類對象的地址)
采用這兩種方法后,再使用MAT來分析就可以比較容易發現自身代碼的內存問題。
1.6 測試經驗
-
MAT 是探索 Java 堆並發現問題和好幫手,能夠迅速發現常見的圖片和大數組等問題;
-
內存碎片問題一般隱藏在對象的地址中;
-
如需要測試非 Dalvik部分,有必要了解 Linux 的進程和內存原理、內存共享機制,熟悉常用命令行工具;
-
內存分配的最小單位是頁面,通常為4KB,這個限制會引發各種問題;
1.7 性能優化
-
盡量不要在循環中創建很多臨時變量;
-
可以將大型的循環拆散、分段或者按需執行;
-
引入SDK庫和調用新的系統API里需要考慮成本;
-
除了Dalvik堆內存,還有其他類型的內存在了解了原理后也能夠進行分析和優化;
-
dex 文件有很多優化空間。在仔細統計並調整了dex文件的順序后,往往可以節約1M以上的 mmap 內存;
2. 耗電
在保證用戶的必要體驗前提下,盡可能減少不必要的操作。幾個優化方法:
方法一:CPU時間片
當應用退到后台運行時,盡量減少應用的主動運行,當檢測到CPU時間片消耗異常時,深入線程進行分析;
使用 DDMS 的 traceview 工具:獲取進程運行過程中的 traceview,定位CPU占用率異常的方法。
方法二 wake lock
前台運行時運不要去注冊 wake lock。 此時注冊沒有任何意義,卻會被計算到應用電量消耗中。后台運行時,在保證業務需要的前提下,應盡量減少注冊 wake lock;降低對系統的喚醒頻率,使用 partial wake lock 代替 wake lock;
方法三 傳感器
合理地設置 GPS 的使用時長和使用頻率;
方法四 雲省電策略
可考慮定期上報用戶手機電量數據的方式來分析問題;
3. 流暢度
3.1 分析工具
-
hierarchy Viewer ,幫助我們去分析UI布局的情況;
-
Tracer for OpenGL ES,可以記錄和分析APP每一幀的繪制過程,以及列出所有乃至的OpenGL ES 的繪制函數和耗時;該工具操作后會生成一份記錄App繪制過程和gltrace文件,
-
Lint 掃描,發現代碼中的流暢度性能問題;
-
Traceview,跟蹤程序性能,具體到每一個函數的耗時和調用次數
-
Systrace ,獲取App運行時線程的信息以及Api執行情況
< merge > 標簽:用於減少View樹的層次來優化 Android 的布局,通過該標簽可以把 < merge > 標簽里的UI合到上一層的 layout中。
< ViewStub> 標簽,最大的優點是當你需要時才會加載,使用它並不會影響UI初始化時的性能。各種不常用的布局可以使用該標簽來減少內存使用量,加快渲染速度。< ViewStub> 是一個不可見的,大小為0的View。
對於不常用的 UI 可以考慮使用 < ViewStub> 標簽替代 GONE 來提高 UI 性能:
將 View 的可見性設置為 GONE,在 Inflate 布局時 View仍然會被 Inflate,也就是說仍然會創建對象,會被 實例化。而 ViewStud 是一個 輕量級的 View,它是一個看不見、不占布局位置、占用資源非常小的控件。
3.2 Perforjmance中的16個問題
-
DrawAllocation: 避免在繪制或者解析布局(draw/layout)時分配對象,比如在Ondraw()中實例化 Paint 對象;
-
Wakelock, 手機不能進入休眠狀態,導致手機一直保持在高耗電狀態;
-
Recycle :某些資源,比如 TypedArrays 、 VelocityTrackers,用完后應該被回收,但是忘記回收。
-
ObsoleteLayoutParam : Layout中無用的參數;
-
UseCompoundDrawables,可優化的布局;
-
HandlerLeak: Handler 的使用不當導致內存泄漏;
-
UseSparseArrays ,盡量用 Android 的SparseArray 代替 Hashmap;
-
UseValueOf : 需要常量對象時,不應該直接 new, 應該使用 ValueOf 轉換。比如需要整數 42 的對象,不要直接用 new Integer(42),應該用 Intener.vallueOf(42),這樣可以省內存;
-
DisableBaselineAlignment: 如果 LinearLayout 被用於嵌套 layout空間 計算,它的 android:baselineAligned 屬性應該設置成 false ,以加速 layout 計算;
-
InefficientWeight : 當線性布局里只有一個控件,並且使用了weight 屬性,最好把 weidth 和 height 設置為0,這樣可以省略布局的 measure 過程;
-
FloatMath, 使用 FloatMath 代替 Math;
-
NestedWeights : 避免嵌套 weight ,那將拖累執行效率。
-
UnusedResources / UnusedIds, 未被使用的資源會使程序變大,並且編譯速度降低;
-
Overdraw: 如果為 RootView 指定一個背景 Drawable,會先用Theme 的背景繪制一遍,然后才用指定的背景,這就是所謂的 “Overdraw” ,可以設置 theme 的background 為 null 來避免;
-
UselessLeaf / UselessParent : View 或 view 的父親沒有用,應該把它移除,避免影響加深布局的層次;
-
UnusedNamespace : 有些代碼沒必要使用 namespace ,會影響代碼執行效率;
4. 網絡優化
考慮點:
-
分小片傳輸一個文件(圖片),這樣當某一個分片失敗時,只需要重傳這一個分片就可以,而不用重傳整個文件;
-
不同類型的移動互聯網下的分片初始大小應該有所不同;
-
在上傳一個文件的過程中,應當盡可能動態增大分片大小,以減小分片數量;
-
確定每個分片是否要繼續增大之前,要檢查網絡類型是否發生了改變;
-
分片一旦傳輸失敗,應當使用該網絡下的初始分片大小進行重試;
重點優化優質網絡下的傳輸速度,而不特意優化差網絡下的速度;
5. apk瘦身
5.1 瘦身關鍵點:
-
代碼部分:冗余代碼、無用功能、代碼混淆、方法數縮減;
-
資源部分:冗余資源、資源混淆、圖片處理(壓縮、圖片轉換、點9圖化等);
-
對整個安裝包做7zip極限壓縮;
Android 系統安裝一個應用的過程中,其中有一步是對 Dex 進行優化,優化的過程是使用專門的工具 DexOpt。DexOpt 是在第一次加載Dex文件的時候執行的。在DexOpt的過程會生成一個ODEX文件。
早期的 DexOpt 有兩個問題:
-
DexOpt 會把每一個類的方法的id 檢索起來,存在一個鏈表的結構里的,但是這個鏈表的長度是用一個 short 類型來保存的,導致了方法 id 的數目不能超過 65536(2^16);
-
DexOpt 使用 LinearAlloc 來存儲應用的方法信息,LinearAlloc是一個固定大小的緩沖區(4,5,8,16),當方法數量過多也會導致超出緩沖區大小時,也會造成 DexOpt 崩潰;
5.2 縮減方法數的方法
-
避免在內部類中訪問外部類的私有方法或變量;當在 java 內部類(包括 匿名內部類)中訪問 外部類的私有方法或變量時,編譯器會生成額外的方法;
-
避免調用派生類中的未被覆蓋( override) 的方法;避免在派生類中調用未覆蓋的基類的方法;避免用派生為對象調用派生類中未被覆蓋的基類的方法。因為當調用派生類中的未被覆蓋的方法時,會多產生一個方法數;
-
去掉部分類的get 、set 方法;
5.3 代碼混淆
代碼混淆( Obfuscated code)也叫花指令;對代碼進行 Proguard 后,也可以比較大的減小代碼的體積(即 dex 的體積);