1、 Introduction
對普通使用者而言,類如記憶體佔用高、耗流量、耗電量等效能問題可能不會輕易發現,但是卡頓問題使用者一定會立馬直觀的感受到。本文就帶你一覽卡頓的發生、檢測、及優化。
2、 The Final Reason Why It Block ?
在《Android效能優化(二)之佈局優化面面觀》中我們說到:60fps VS 16ms。
60幀每秒是目前最合適的影象顯示速度,也是絕大部分Android裝置設定的除錯頻率,如果在16ms內順利完成介面重新整理操作可以展示出流暢的畫面,而由於任何原因導致接收到VSYNC訊號的時候無法完成本次重新整理操作,就會產生掉幀的現象,重新整理幀率自然也就跟著下降(假定重新整理幀率由正常的60fps降到30fps,使用者就會明顯感知到卡頓)。
3、 The Ways Lead To Block
3.1 UI執行緒中的耗時操作
- UI執行緒中有I/O讀寫、資料庫訪問等耗時操作;
3.2 複雜、不合理的佈局以及OverDraw
- 不合理的佈局雖然可以完成功能,但隨著控制元件數量越多、佈局巢狀層次越深,展開佈局花費的時間幾乎是線性增長,效能也就越差;
- 避免OverDraw導致的效能損耗;
- 可以參考《Android效能優化(二)之佈局優化面面觀》
3.3 記憶體使用異常導致的卡頓
- 記憶體抖動、記憶體洩漏都會導致:GC的次數越多、消耗在GC上的時間越長,CPU花在介面繪製上的時間相應越短;
- 可以參考《Android效能優化(四)之記憶體優化實戰》
3.4 錯誤的非同步方式
- 對執行緒開啟方式的不同選擇以及不同配置都可能導致卡頓的發生;
- 在《Android效能優化(一)之啟動加速35%》一文中說到過:**不正確的非同步任務不僅不能較好的完成非同步任務,反而會加劇卡頓。**關於非同步任務開啟的選擇,之後會出一篇詳細的文章,可以先參考啟動加速的文章。
4、 The Ways To Find Block
4.1 StickMode
StrictMode類是Android 2.3 (API 9)引入的一個工具類,可以用來幫助開發者發現程式碼中的一些不規範的問題,以達到提升應用響應能力的目的。可以設定不同的執行緒檢測策略、虛擬機器檢測策略。
public void onCreate() {
if (DEVELOPER_MODE) {
// 執行緒檢測策略
StrictMode.setThreadPolicy(new StrictMode.ThreadPolicy.Builder()
.detectDiskReads()
.detectDiskWrites()
.detectNetwork() // or .detectAll() for all detectable problems
.penaltyLog()
.build());
// 虛擬機器檢測策略
StrictMode.setVmPolicy(new StrictMode.VmPolicy.Builder()
.detectLeakedSqlLiteObjects()
.detectLeakedClosableObjects()
.penaltyLog()
.penaltyDeath()
.build());
}
super.onCreate();
}
複製程式碼
當違規操作發生時,可以根據自定義的策略記錄下Log或者Crash,以便於跟蹤改善。
4.2 TraceView
與StickMode相比,TraceView簡直是發現Block根源的神器,不僅能看出每個方法消耗的時間、發生次數,並且可以進行排序,直接從最耗時的方法開始處優化;
關於TraceView的使用及分析,請參考《Android效能優化(一)之啟動加速35%》第五章節。
4.3 AndroidPerformanceMonitor
AndroidPerformanceMonitor 是一個檢測卡頓的開源庫,前身是BlockCanary,更前身則是LeakCanary。而其使用與LeakCanary也比較相似,可以自主設定卡頓檢測時間,檢測到的卡頓同樣是以Notification展示,在使用體驗上也相當類似,與LeakCanary可以說是孿生兄弟。
原理 利用了Looper.loop()中每個Message被分發前後的Log列印,而我們設定自己的Printer就可以根據Log的不同的處理:
- Message分發前,使用HandlerThread延時傳送一個Runnable,這個時間可自己設定;
- Message在規定的時間內完成分發,則會取消掉這個Runnable;
- Message沒有在規定的時間內(實際上是規定時間的0.8)完成分發,那這個Runnable就會被執行,可以獲取到當前的堆疊資訊;
// This must be in a local variable, in case a UI event sets the logger
Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
msg.target.dispatchMessage(msg);
if (logging != null) {
logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
}
複製程式碼
4.4 ANR-WatchDog
ANR-WatchDog同樣是一個檢測卡頓的檢測庫,與AndroidPerformanceMonitor不一樣的是它的原理相對簡單:
- 原理是開啟一個執行緒,持續迴圈不斷的往UI執行緒中Post一個Runnable(修改一個數的大小),然後在規定時間之後檢測這個Runnable是否被執行(數的大小有沒有 被修改過來)。沒有被執行的話說明主執行緒執行上一個Message超時,然後獲取當前堆疊資訊;
- ANR-WatchDog的原理更加簡單,但是根據使用情況來看準確性不及AndroidPerformanceMonitor高,而且可設定的配置不如AndroidPerformanceMonitor豐富;
4.5 Choreographer
我們知道Android系統每隔16ms都會發出VSYNC訊號,觸發UI的繪製,而我們可以拿到回撥的監聽。如果16ms沒有回撥的話我們就知道發生了卡頓。
Choreographer.getInstance().postFrameCallback(new Choreographer.FrameCallback() {
@Override
public void doFrame(long l) {
}
});
複製程式碼
備註:這種方式的原理也比較簡單,但是可用性不高,只能測出介面繪製的卡頓
5、 The Ways To Avoid Block
在第三節我們分析了產生卡頓的原因,那麼避免卡頓的方法就很簡單了:反其向行知即可。
5.1 將耗時操作移到非同步中
- 類如I/O讀寫、資料庫訪問等都應該採用非同步的方式,不能有“只是一個很小的檔案”之類的想法,防微杜漸;
5.2 合理優化佈局,避免OverDraw。
- 同樣的實現功能介面,不同的佈局方式產生的View個數以及渲染耗時差異是數倍的,恐怖的差異可以參考《Android效能優化(二)之佈局優化面面觀》;
5.3 合理優化記憶體
- 節省記憶體的分配空間,儘可能的降低GC的頻率,縮短GC的平均時間;CPU不被佔用,卡頓的機率就會更低;
5.4 正確使用非同步
- 再次強調一遍:耗時操作不能都直接隨意交給非同步,不正確的非同步使用方式反而會加劇卡頓;
6、 The Normal Ways Of Dealing Block
1. 開發中使用AndroidPerformanceMonitor檢測卡頓進行處理; 2. 任何耗時操作正確的移到非同步裡; 3. 合理優化佈局,避免OverDraw; 4. 優化記憶體分配,減少GC頻率,這一般不是某個介面的事情,是一項長期工作;
7、 Block Check Of System
系統對Block有檢測嗎?那必須有!大名鼎鼎的ANR就來於此。
ANR的來龍去脈:觸發場景、分析方法,你真的都清楚嗎?歡迎關注下一篇文章,帶你細究ANR不為人知的那些事。
歡迎關注微信公眾號:定期分享Java、Android乾貨!