Android效能優化(六)之卡頓那些事

頭條祁同偉發表於2017-03-28

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 記憶體使用異常導致的卡頓

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。

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乾貨!

歡迎關注

相關文章