性能優化專題十三--BlockCanary簡析

Looper 提供的機制

先看看我們熟悉的 Looper 的源碼,裏面實現的功能就是不斷地從 MessageQueue 裏面取出 Message 對象,並加以執行。

for (;;) {
    Message msg = queue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return;
    }

    // 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);
    }

    // ignore some code...

    msg.recycleUnchecked();
}

注意到,在 dispatchMessage 的前後,分別有兩個 log 的輸出事件,而 dispatchMessage 就是線程上的一次消息處理。如果兩次消息處理事件,都超過了 16.67ms, 那就一定發生了卡頓,這也是 BlockCanary 的基礎原理。

BlockCanary 實現了 Printer,我們看看具體的實現。

class LooperMonitor implements Printer {
  @Override
  public void println(String x) {
      if (!mPrintingStarted) {
          mStartTimestamp = System.currentTimeMillis();
          mStartThreadTimestamp = SystemClock.currentThreadTimeMillis();
          mPrintingStarted = true;
          startDump();
      } else {
          final long endTime = System.currentTimeMillis();
          mPrintingStarted = false;
          if (isBlock(endTime)) {
              notifyBlockEvent(endTime);
          }
          stopDump();
      }
  }

  private boolean isBlock(long endTime) {
      return endTime - mStartTimestamp > mBlockThresholdMillis;
  }

  // ignore other codes...
}

這裏實現了 println 方法,而 Looper 中也調用了 println 方法,而且在除非 dump 日誌的情況下,也只有在事件消息前後進行 println 操作。換而言之,我們可以初步認爲兩個 println 調用之間的時間超過 16.67ms 就證明了卡頓。上面的代碼也非常地清晰明瞭說明了這點。

輸出堆棧

在發現卡頓時,還需要提供當前線程的堆棧,這樣才能方便開發人員知曉在哪裏發生了卡頓,而 Java 剛好也提供了類似的機制,代碼也非常的簡單。

@Override
protected void doSample() {
    StringBuilder stringBuilder = new StringBuilder();

    for (StackTraceElement stackTraceElement : mCurrentThread.getStackTrace()) {
        stringBuilder
                .append(stackTraceElement.toString())
                .append(BlockInfo.SEPARATOR);
    }

    synchronized (sStackMap) {
        if (sStackMap.size() == mMaxEntryCount && mMaxEntryCount > 0) {
            sStackMap.remove(sStackMap.keySet().iterator().next());
        }
        sStackMap.put(System.currentTimeMillis(), stringBuilder.toString());
    }
}

BlockCanary 的原理簡介就到這裏,還是比較輕鬆和簡單的。

BlockCanary卡頓檢測流程圖

BlockCanary啓動一個線程負責保存UI線程當前堆棧信息,將堆棧信息以及CPU信息保存分別保存在 mThreadStackEntries和mCpuInfoEntries中,每條信息都以時間撮爲key保存。

BlockCanary註冊了logging來獲取事件開始結束時間。如果檢測到事件處理時間超過閾值(默認值1s),則從mThreadStackEntries中查找T1~T2這段時間內的堆棧信息,並且從mCpuInfoEntries中查找T1~T2這段時間內的CPU及內存信息。並且將信息格式化後保存到本地文件,並且通知用戶。

https://lrh1993.gitbooks.io/android_interview_guide/content/java/concurrence/thread-pool.html

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章