Android性能優化
1.啟動優化
概述:啟動速度往往是由一個input事件(以Message的形式)觸發(比如點擊、長按、電源鍵),由一個或者多個Message的執行結束為結尾,而這些Message中一般都有關鍵的界面繪制相關的Message,應用啟動時間介紹見官網:https://developer.android.com/topic/performance/vitals/launch-time?hl=zh-cn
冷啟動:
在冷啟動開始時,系統有三個任務,它們是:
1、加載并啟動應用
2、在啟動后立即顯示應用的空白啟動窗口
3、創建應用進程
系統一創建應用進程,應用進程就負責后續階段:
1、創建應用對象。
2、啟動主線程。
3、創建主 Activity。
4、擴充視圖。
5、布局屏幕。
6、執行初始繪制。
-->Launcher--(啟動應用)
-->SystemServer--(加載并啟動應用-->創建空白啟動窗口-->創建應用進程-->)
-->APP--(創建ActivityThread-->執行main函數--創建主Activity--測量、布局、繪制)
界面切換
Activity切換主要是弄清activity的生命周期過程,A跳轉到B Activity的過程:
A.onPause-->B.onCreate-->B.onStart-->B.onResume-->A.onStop
Fragment切換主要是弄清fragment的生命周期過程,Fragment概述見官網:https://developer.android.com/guide/components/fragments?hl=zh-cn,生命周期可參考這篇文章:https://juejin.cn/post/6844903752114126855
1.1 測試啟動速度:
A.使用高速攝像機或者iphone測試時間;
B.通過打印ActivityManager中Displayed的log查看啟動時間(Activity第一幀顯示的時間)
C.adb shell am start -S -W (Displayed 對應TotalTime,即Activity第一幀顯示的時間)
1.2 啟動速度分析工具:
A.HierarchyView分析布局層次
B.systrace定位問題點 和traceView定位耗時函數分析性能
C.Run->Edit Configurations-->Profiling-->check start recording CPU activity on startup
https://developer.android.com/studio/profile/cpu-profiler
1.3 startingWindow機制
通過startActivity啟動應用時,把Options等信息傳給AMS,AMS創建相應的ActivityRecord,改變其狀態、可見性,WMS創建相應的窗口,改變其可見性、大小、位置等,等到APP頁面繪制完成,動畫加載完成,開始執行動畫,這個時候用戶才看到應用起來了。
當應用界面繪制比較慢時,系統一直處于等待狀態,直到應用告訴系統繪制完成,啟動動畫才開始執行,這也是啟動慢的直接原因。
原生為了解決上面的問題,引入了startingWindow的機制,即應用啟動不依賴于應用界面繪制,只要startingWindow繪制完成并且動畫設置完畢,就展開頁面。
流程如下:
***startActivity ---> 創建ActivityRecord/改變其可見性--->添加StartingWindow---> StartingWindow 繪制完成--->頁面展開
***startActivity--->解析theme屬性--->加載預覽圖--->窗口繪制完成(校驗theme屬性、更新緩存圖)--->應用退出(校驗theme屬性、更新緩存圖)
1.3 檢查初始化代碼
1.4 減少布局復雜度,使用viewstub,預加載
1.5 view耗時:
A.使用overdraw分析界面是否存在過繪制;
B.使用profiling GPU render分析view繪制邏輯是否過于復雜;
C.使用HierachyView分析布局層次;
D.lint找出ui線程中耗時操作
https://developer.android.com/topic/performance/vitals/launch-time
啟動高級性能剖析
https://developer.android.com/studio/profile/android-profiler?hl=zh-cn#advanced-profiling
1.6 啟動速度優化分析思路
1、打開binder調試,方便在trace中顯示binder信息
adb shell am trace-ipc start
抓取結束后,可以執行下面的命令關閉
adb shell am trace-ipc stop
2、trace命令加入irq tag,默認的命令不包含irq,需要自己加irq的TAG,這樣打開Trace之后,就可以看到irq相關的內容,最后的抓trace命令如下:
python /mnt/d/Android/platform-tools/systrace/systrace.py gfx input view webview wm am sm rs bionic power pm ss database network adb idle pdx sched irq freq idle disk workq binder_driver binder_lock -a com.xxx.xxx
3、在APP代碼的每個函數中插入trace,在分析的時候可以看到更詳細的信息
2.內存優化
Log分析,命令分析,工具分析
2.1 adb shell dumpsys meminfo查看內存情況
2.2 DDMS heap查看
2.3 APT工具查看是否有內存泄露,配合重復操作某個路徑
2.4 MAT工具查看Leak Suspects, Histogram分析內存最大類的內部引用
2.5 基于MAT分析,屏蔽代碼,反復測試
2.6 內存抖動導致卡頓:
1.查看虛擬機GC的log
2.使用systrace查看繪制過程線程的執行情況
補充說明:不設置背景>使用顏色值>.9圖片>大像素圖片
https://cs.android.com/android/platform/superproject/+/master:development/scripts/native_heapdump_viewer.py?q=native_heapdump_viewer.py
https://cs.android.com/android/platform/superproject/+/master:bionic/libc/malloc_debug/README.md?q=native_heapdump_viewer.py
2.7 使用LeakCanary檢測內存泄露:
(1)build.gradle添加依賴
(2)application中初始化 LeakCanary.install(this); 在應用內點擊并退出,如果有內存泄露,一小段時間后會在通知欄生成一條通知,點擊可查看內存泄露的引用路徑。
3.卡頓優化
3.1 啟動卡頓,運行卡頓,滑動卡頓;
3.2 log分析:
A.log是否打印頻繁;
B.虛擬機是否有執行GC操作;
3.3 命令分析:
A.adb shell top -m 10 -t -n 5 top命令查看是否有進程占用大量CPU時間,導致主線程無法執行;
B.dumpsys SurfaceFlinger查看刷新率是否正常;
C.dumpsys SurfaceFlinger查看疊加層信息,是否有多余層;
D.dumpsys gfxinfo packagename 查看硬件加速是否開啟;
3.4 工具分析:
A.TraceView使用DDMS采集數據
3.4.1 打開debug屬性,application配置debuggable,系統應用還需配置ro.debuggable=1,persist.service.adb.enable=1;
3.4.2 打開eclipse->DDMS,選擇應用;點擊start method profiling,執行相應過程,點擊stop method profiling
B.TraceView 加代碼采集數據
3.4.3 manifest加入寫入SD卡的權限
3.4.4 在性能關注起始位置,調用方法啟動追蹤,開始創建trace log文件;調用stopMethodTracing方法停止trace過程
3.4.5 執行trace文件進行分析traceview ***demo.trace
3.5 分析時間線面板(選擇線程)和分析面板(所選線程各函數的調用情況)
3.6選擇Incl Cpu Time進行降序排列,得到最耗時的方法
3.7 Systrace
https://developer.android.com/topic/performance/tracing/navigate-report?hl=zh-cn
Systrace會生成包含多個部分的輸出HTML文件。該報告列出了每個進程的線程。如果給定線程會渲染界面幀,該報告還會沿時間軸指明所渲染的幀,當您在報告中從左向右移動時,時間會向前推移。
報告從上到下包含以下幾個部分:
用戶互動
第一部分包含表示應用或游戲中的具體用戶活動的條形圖。這些互動可用作有用的事件標記。
CPU活動
下一部分顯示了表示每個CPU中的線程活動的條形圖。這些條形圖會顯示所有應用中的CPU活動。CPU活動部分可以展開,展開后您就可以查看每個CPU的時鐘頻率。【點擊kernel展開】
系統事件
此部分中的直方圖會顯示特定的系統級事件,例如特定對象的紋理計數和總大小。
值得仔細檢查的直方圖是標記為 SurfaceView 的直方圖。計數表示已傳遞到顯示管道并等待顯示在設備屏幕上的組合幀緩沖區的數量。由于大多數設備都會進行雙重或三重緩沖,因此該計數幾乎總為 0、1 或 2。
顯示幀
這一部分通常是報告中最頂部的部分,描繪了一條多色線條,后面是成堆的條形。這些形狀表示已創建的特定線程的狀態和幀堆棧。堆棧的每個層級代表對 beginSection()
的一次調用,或您為應用或游戲定義的[自定義跟蹤事件]的開頭。
注意:界面線程(即通常運行您的應用或游戲的主線程)始終會顯示為第一個線程。
每個條形堆上方的多色線條表示特定線程隨時間變化的一組狀態。每段線條可以包含以下顏色之一:
綠色:正在運行
線程正在完成與某個進程相關的工作或正在響應中斷。
藍色:可運行
線程可以運行但目前未進行調度。
白色:休眠
線程沒有可執行的任務,可能是因為線程在遇到斥鎖定時被阻止。
橙色:不可中斷的休眠
線程在遇到 I/O 操作時被阻止或正在等待磁盤操作完成。
紫色:可中斷的休眠
線程在遇到另一項內核操作(通常是內存管理)時被阻止。
鍵盤快捷鍵
鍵 | 說明 |
---|---|
W | 放大跟蹤時間軸 |
A | 在跟蹤時間軸上向左平移 |
S | 縮小跟蹤時間軸。 |
D | 在跟蹤時間軸上向右平移。 |
E | 以當前鼠標位置為中定位跟蹤時間軸 |
M | 高亮當前選區 |
1 | 將當前正在使用中的選擇模型更改為“選擇”模式。對應于鼠標選擇器工具欄中顯示的第 1 個按鈕(請參見右圖)。 |
2 | 將當前正在使用中的選擇模型更改為“平移”模式。對應于鼠標選擇器工具欄中顯示的第 2 個按鈕(請參見右圖)。 |
3 | 將當前正在使用中的選擇模型更改為“縮放”模式。對應于鼠標選擇器工具欄中顯示的第 3 個按鈕(請參見右圖)。 |
4 | 將當前正在使用中的選擇模型更改為“計時”模式。對應于鼠標選擇器工具欄中顯示的第 4 個按鈕(請參見右圖)。 |
G | 在當前所選任務的開頭顯示網格。 |
Shift + G | 在當前所選任務的末尾顯示網格 |
向左箭頭 | 在當前選定的時間軸上選擇上一個事件 |
向右箭頭 | 在當前選定的時間軸上選擇下一個事件。 |
識別性能問題
瀏覽 Systrace 報告時,您可以通過執行以下一項或多項操作來更輕松地識別性能問題:
1.通過在時間間隔周圍繪制一個矩形來選擇所需的時間間隔。
2.使用標尺工具標記或突出顯示問題區域。
3.依次點擊 View Options > Highlight VSync,以顯示每項顯示屏刷新操作。
檢查界面幀和提醒
1.Systrace 報告列出了渲染界面幀的每個進程,并指明了沿時間軸渲染的每個幀。在 16.6 毫秒內渲染的必須保持每秒 60 幀穩定幀速率的幀會以綠色圓圈表示。渲染時間超過 16.6 毫秒的幀會以黃色或紅色幀圓圈表示。
2.點擊某個幀圓圈可將其突出顯示,并提供有關系統為渲染該幀所做工作的其他信息,包括提醒。此報告還會顯示系統在渲染該幀時執行的方法。您可以調查這些方法以確定界面卡頓的可能原因。
選擇運行速度慢的幀后,您可能會在報告的底部窗格中看到一條提醒。圖 5 中顯示的提醒指明幀的主要問題是在 [ListView
]回收和重新綁定上花費了太多時間。指向跟蹤記錄中相關事件的鏈接可詳細說明系統在此期間執行的操作。
如需查看此工具在您的跟蹤記錄中發現的每條提醒以及設備觸發每條提醒的次數,請點擊窗口最右側的 Alerts 標簽頁,如圖 6 所示。Alerts 面板可幫助您了解跟蹤記錄中出現的問題以及這些問題導致出現卡頓的頻率。您可以將此面板視為要修正的錯誤列表。通常情況下,只需對一個區域進行細微改動或改進即可移除整組提醒。
如果您發現在界面線程上執行的工作太多,請使用以下方法之一來幫助確定哪些方法占用了過多的 CPU 時間:
- 如果您想了解哪些方法可能會導致瓶頸,請在這些方法中添加跟蹤標記。如需了解詳情,請參閱有關如何在代碼中定義自定義事件的指南。
- 如果您不確定界面瓶頸的來源,請使用 Android Studio 中提供的 CPU 分析器。您可以生成跟蹤日志,然后使用 CPU 分析器導入和檢查這些日志。
4.數據庫優化
4.1 使用SQLiteSpy工具進行數據庫優化:
A.抽取SQL語句,在此工具上進行調試,分析SQL效率,查看執行速度。
4.2 常用優化方向:
A.數據庫讀取優化(索引讀取,按需索取,查詢語句優化,JOIN優化);
B.寫入優化(批量處理,使用觸發器)
4.3 數據庫調試:
A.adb shell content query;
B.contentprovider的query/update/insert方法中打印出調用者進程id(分析死鎖,分析ANR,分析性能;UserId:android.os.Binder.getCallingUid(),Pid:android.os.Binder.getCallingPid());
C.編寫測試apk進行單元測試
4.4 數據庫性能分析:
SQLite benchmarking測試了下數據庫性能
5.廣播優化
5.1 cpu開銷分析,查看cpu使用情況
5.2 借助trace堆棧和log分析
獲取系統服務的堆棧(adb shell bugreport -t > allTrace.txt),查看系統服務16個Binder線程占用情況
5.3 代碼排查
5.4 獲取當前廣播信息
adb shell dumpsys activity broadcasts
5.5 應用內部使用廣播通信,使用LocalBraodcastManager
5.6 執行效率要求高的消息傳遞不使用廣播
6.ANR問題分析
6.1 ANR分類
6.1.1 應用ANR:
(1)按鍵響應超時事件5s
Input dispatching timed out
主線程對輸入事件在5秒內沒有處理完畢發生ANR:當應用程序的Window處于Active狀態并且能夠接收輸入事件(例如按鍵更新、觸摸事件)時,系統底層上報的事件就會被InputDispatcher分發給這個應用程序,應用程序的主線程通過InputChannel讀取輸入事件并交給界面視圖處理。通常會注冊一個監聽器來接收并處理事件,或者創建自定義的試圖控件來處理事件。InputDispatcher運行在系統進程system_server的一個單獨線程中,應用程序的主線程在處理事件的過程中,InputDispatcher會不斷的檢測處理過程是否超時,一旦超時,會通過一系列的回調通知WMS的notifyANR函數,最終會調用到AMS中mHandler對象里的SHOW_NOT_RESPONDING_MSG這個case
(2)廣播處理超時10s
Timeout of broadcast /Receiver during timeout:
**可能原因:1、binder;2、消息隊列;3、onReceive;4、靜態接收者寫入磁盤未完成**
主線程在執行BroadcastReceiver的onReceive函數時10秒內沒有執行完畢發生ANR:ContextImpl發送廣播(sendBroadcast\sendOrderBroadcast\sendStickyBroadcast)APP進程---->broadcastIntent--(binder_1)-->AMS----->processNextBroadcast--->BroadcastQueue(BroadcastHandler埋定時炸彈)----->scheduleReceiver(binder_3)---->handleReceive--->ActivityThread---->BroadcastReceiver(onReceive)--->finishReceiver(binder_2)--->AMS
靜態注冊的接收者超時檢測過程會考慮QueuedWork是否工作尚未完成(SharedPreferences寫入磁盤操作)
(3)服務處理超時20s
Timeout executing service:
**可能原因:1、binder;2、消息隊列;3、onCreate、onStartCommand**
主線程在執行Service的各個生命周期函數時20秒內沒有執行完畢發生ANR:ContextImpl(startService)APP進程---->startService(binder_1)---->AMS---->realStartServiceLocked----->ActiveServices(AMS.MainHandler)----->scheduleCreateService----->binder_3---->handleCreateService---->ActivityThread--->new/attach/onCreate(Service進程)---->serviceDoneExecuting(binder_2)
6.1.2系統ANR:
(1)Watchdog超時60s
6.2 ANR的原因
1、 應用本身:耗時操作,死循環,線程阻塞,線程掛起;
2\ 其他進程:cpu被搶占
6.3 ANR問題分析思路:
1、 event log看具體的ANR時間(關鍵字:am_anr),看看是否跟ANR log能夠對上,以確定ANR Log是否有效
2、 如果應用ANR log有效,則分析應用ANR現場:
(1)根據apps/android.txt獲取出錯進程、出錯原因、CPU占用率、tid、死鎖等信息;
(2)根據anr/traces.txt查看堆棧信息、分析代碼
3、 trace中字段的含義
字段 | 含義 |
---|---|
tid | 線程號 |
sysTid | 線程號 |
Native | 線程狀態,其中 state 也是線程狀態,state=S 代表 Sleeping |
nice | nice 值越小,則優先級越高。因為是主線程此處 nice=-10, 可以看到優先級很高了 |
schedstat | 括號中的 3 個數字,依次是 Running, Runnable, Switch Running 時間:CPU 運行的時間,單位 ns Runable 時間:RQ 隊列的等待時間,單位 ns Switch 次數:CPU 調度切換次數 |
utm | 該線程在用戶態所執行的時間,單位是 jiffies |
stm | 該線程在內核態所執行的時間,單位是 jiffies |
sCount | 此線程被掛起的次數 |
dsCount | 線程被調試器掛起的次數,當一個進程被調試后,sCount 會重置為 0,調試完畢后 sCount 會根據是否被正常掛起增長,但是 dsCount 不會被重置為 0,所以 dsCount 也可以用來判斷這個線程是否被調試過 |
self | 線程本身的地址 |
字段 | 含義 |
---|---|
TERMINATED | Thread.run has returned, but Thread* still around |
RUNNABLE | runnable/running in a JNI native method/suspended by GC or debugger |
TIMED_WAITING | in Object.wait() Thread.sleep() with a timeout |
BLOCKED | blocked on a monitor |
WAITING | in Object.wait()/checking other threads are not run on abort |
NEW | native thread started, not yet ready to run managed code |
4、 如果沒有log,分析代碼(zygote的堆棧dump kill -3 <pid>,輸出的trace會保存在data/anr/trace.txt文件中,分析堆棧log,結合堆棧log確認是當前報錯進程的異常還是其他進程的異常。如果是其他進程的異常,則跟蹤其他進程處理,如果是當前進程的異常,則查看當前進程的主線程狀態是Native,Block或者Waiting。
1)如果是Native,則查看主線程的堆棧信息,分析線程耗時的原因
2)如果是Block,則查看
3)如果是Waiting(掛起狀態),
6.4 系統ANR分析方法
1、查找WATCHDOG信息
2、 查看調用堆棧、分析代碼
3、 分析思路:
(1)分析關鍵操作:比如解鎖、安裝應用、亮滅屏、應用啟動
(2)分析系統關鍵log:A:系統變慢:Slow operation、Slow dispatch、Slow delivery、dvm_lock_sample;B:進程變化:am_kill、lowmemorykiller、ANR;C:cpu info、binder info(是否滿了)、iowait(是否過高)
(3)分析關鍵線程的運行情況
7.兼容性問題分析
7.1 常用分析策略:
A.無法安裝(log分析,apktool);
B.定屏分析(dumpsys input,getevent);
C.界面異常(dumpsys SurfaceFlinger,dumpsys window);
8.apk大小優化
8.1 assets目錄篩查,lint工具清除本地冗余資源(命令行執行)
8.2 findbugs查找冗余代碼,系統使用資源反色排查,使用工具排查應用中多余圖片和未使用資源
8.3 tinypng壓縮圖片
減少程序圖片資源的大小
1 確保在build.gradle文件中開啟了minifEnabled與shrinkResources的屬性,這兩個屬性可以幫助移除那些在程序中使用不到的代碼與資源,幫助減少APP的安裝包大小。
2 有選擇性的提供對應分辨率的圖片資源,系統會自動匹配最合適分辨率的圖片并執行拉伸或者壓縮的處理。
3 在符合條件的情況下,使用Vertor Drawable替代傳統的PNG/JPEG圖片,能夠極大的減少圖片資源的大小。傳統模式下,針對不同dpi的手機都需要提供一套PNG/JPEG的圖片,而如果使用Vector Drawable的話,只需要一個XML文件即可。
使用VectorDrawable還可以避免因為使用幀動畫導致的圖片資源過多的情況,如下圖所示
4 盡量復用已經存在的資源圖片,使用代碼的方式對已有的資源進行復用,如下圖所示:
5、針對啟動速度不影響的應用,不編譯odex文件
6、互聯網的應用放老的版本
7、不重要的應用從system分區摞到data分區,并確保不可卸載
8、OS版本裁剪功能
9.功耗優化
9.1 常見耗電原因:
1.通過wakelock鎖定;
2.alarm頻繁喚醒異常;
3.頻繁通過網絡設備喚醒;
4.異常持有外設耗電,(LCD、MODEM、AP、GPU對功耗影響)界面顏色偏白,持有亮屏鎖不釋放;
5.后臺消耗cpu資源
9.2 常見應用原因:
1.log過多,頻繁啟動,文件解析,大數據量傳輸,復雜算法運算;
2.寫存儲,申請連續內存時等待;
3.多核調度,調頻策略變化;
4.運營商網絡影響,NV參數設置不合理;
5.刷新頻率,過度繪制,圖層成熟,圖片形狀,裁剪圖片,動畫曲線,字體陰影
9.3 優化方向:
界面顏色風格選擇:
9.4 分析思路
9.4.1. 確認現象:1.確認問題點 ,2.熄屏/亮屏查看 events log查看 screen_toggled,3.電量查看 events log中查看battery_level,電量下降是否平滑
9.4.2 分析cpu耗電:1.wakelock是否常時間持有,kernel log中搜索UTC,確認熄屏后kernel是否很快待機,cpu時間是否有跳變,是否有多個watchdog打印;2.定位wakelock,查看應用持鎖和系統持鎖,分析長時間未休眠原因,kernel log中搜索aborted,分析是否進程凍結失敗,kernel log中搜索failed看是否設備suspend失敗;3.統計喚醒源
9.4.3.分析網絡數據,4.分析外設耗電,5.電量檢測是否有誤差
9.4.4.OLED屏,不同顏色界面對功耗影響非常大
9.5 應用電流分析思路:
9.5.1 top命令查看CPU占有率(adb shell top -t -m 10)
9.5.2 ftrace查看線程的運行情況
9.6 應用界面對功耗的影響
9.6.1 1.圖片資源(圖片色值明暗程度)2.view數量,圖層數量,view區域重疊,運算復雜度,字體陰影
9.6.2 聯系人頭像方形變圓形,裁剪算法更復雜;item行高變低,單頁view數量更多。
9.6.3 減少不必要的刷新,通過開發者選項查看是否仍在刷新
打開systrace 網站
1.chrome://tracing
systrace工作原理
http://www.lxweimin.com/p/6f528e862d31
10.網絡優化
思路:緩存
1.讓應用可離線使用——應用開發者行為(如:電子郵件客戶端撰寫、發送、閱讀、移動、刪除郵件)
2.使用GcmNetworkManager和ContentProvider——網絡數據緩存,應用開發者行為
3.網絡請求去重——應用開發者行為(如:不會變化的數據只網絡請求一次,緩存起來)
思路:根據網絡狀態決定行為
4.網絡請求劃分優先級——應用開發者行為(如:文本請求高于富媒體請求)
5.低速網絡下減少網絡請求——應用開發者行為(如:網速慢,只下載低分辨率媒體)
6.檢測網絡變化,然后更改應用行為——應用開發者行為(如:新聞閱讀器應用2G網絡時一次獲取3篇文章,使用WLAN時一次獲取20篇文章)