背景
在app開發過程中,經常會出現由于在主線程執行某些耗時操作(例如db,檔案io操作等)導緻頁面顯示會出現卡頓,影響使用者體驗。本文主要講解一種基于Handler Message機制的卡頓檢測方案,
原理 :Android系統在執行每個方法的時候,會在方法的開始和結束列印日志。
在方法開始執行的時候,會列印
07-01 13:06:25.139 13471-13471/"" D/BlockDetector: BlockDetector init println x = >>>>> Dispatching to Handler (android.app.ActivityThread$H) {34d05903} null: 102
07-01 13:06:25.168 13471-13471/"" D/BlockDetector: BlockDetector init println x = >>>>> Dispatching to Handler (android.view.ViewRootImpl$ViewRootHandler) {3a8c2969} null: 6
07-01 13:06:25.219 13471-13471/"" D/BlockDetector: BlockDetector init println x = >>>>> Dispatching to Handler (android.view.ViewRootImpl$ViewRootHandler) {3a8c2969} null: 8
在方法結束執行的時候,會列印
07-01 13:07:02.036 13471-13471/com.apache.fastandroid D/BlockDetector: BlockDetector init println x = <<<<< Finished to Handler (android.os.Handler) {29d2efdd} [email protected]
07-01 13:07:02.275 13471-13471/com.apache.fastandroid D/BlockDetector: BlockDetector init println x = <<<<< Finished to Handler (android.os.Handler) {29d2efdd} [email protected]
07-01 13:07:02.521 13471-13471/com.apache.fastandroid D/BlockDetector: BlockDetector init println x = <<<<< Finished to Handler (android.os.Handler) {29d2efdd} [email protected]
07-01 13:07:02.758 13471-13471/com.apache.fastandroid D/BlockDetector: BlockDetector init println x = <<<<< Finished to Handler (android.os.Handler) {29d2efdd} [email protected]
07-01 13:07:03.003 13471-13471/com.apache.fastandroid D/BlockDetector: BlockDetector init println x = <<<<< Finished to Handler (android.os.Handler) {29d2efdd} [email protected]
07-01 13:07:03.247 13471-13471/com.apache.fastandroid D/BlockDetector: BlockDetector init println x = <<<<< Finished to Handler (android.os.Handler) {29d2efdd} [email protected]
07-01 13:07:03.505 13471-13471/com.apache.fastandroid D/BlockDetector: BlockDetector init println x = <<<<< Finished to Handler (android.os.Handler) {29d2efdd} [email protected]
07-01 13:07:07.634 13471-13471/com.apache.fastandroid D/BlockDetector: BlockDetector init println x = <<<<< Finished to Handler (android.os.Handler) {29d2efdd} [email protected]
利用這個特點,然後就可以攔截這些列印日志,在>>>>> Dispatching的時候,延時特定時間(例如500ms)開始監控耗時, 在<<<<< Finished的時候結束監控,取消延時執行代碼邏輯。如果延時的代碼邏輯有執行,說明目前方法耗時超過了500ms,則把對應的堆棧資訊列印出來。
操作步驟
具體代碼如下:
public class BlockDetector {
public static void init() {
if(DebugUtils.isDebug()) {
Looper.getMainLooper().setMessageLogging(new Printer() {
//分發和處理消息開始前的log
private static final String START = ">>>>> Dispatching";
//分發和處理消息結束後的log
private static final String END = "<<<<< Finished";
@Override
public void println(String x) {
NLog.d("BlockDetector init println x = %s",x);
if (x.startsWith(START)) {
//開始計時
BlockMonitor.getInstance().startMonitor();
}
if (x.startsWith(END)) {
//結束計時
BlockMonitor.getInstance().removeMonitor();
}
}
});
}
}
public class BlockMonitor {
private static final String TAG = "=============BlockMonitor============= \n %s";
private static BlockMonitor sInstance = new BlockMonitor();
private Handler mIoHandler;
//方法耗時的卡口,500毫秒
private static final long TIME_BLOCK = 500L;
//存放一個msg周期的卡頓堆棧資訊,防止重複列印
private Set mBlockStackTrace;
private BlockMonitor() {
HandlerThread logThread = new HandlerThread("BlockMonitor");
logThread.start();
mIoHandler = new Handler(logThread.getLooper());
mBlockStackTrace = Collections.synchronizedSet(new HashSet());
}
private Runnable mLogRunnable = new Runnable() {
@Override
public void run() {
//繼續檢測
startMonitor();
//列印出執行的耗時方法的棧消息
StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace();
StringBuilder sb = new StringBuilder();
for (StackTraceElement s : stackTrace) {
sb.append(s.toString());
sb.append("\n");
}
String s = sb.toString();
if (!mBlockStackTrace.contains(s)) {
mBlockStackTrace.add(s);
//BlockLogUtils.e(TAG, s);
NLog.e(TAG, s);
}
}
};
public static BlockMonitor getInstance() {
return sInstance;
}
public void startMonitor() {
NLog.d("BlockDetector startMonitor");
mIoHandler.postDelayed(mLogRunnable, TIME_BLOCK);
}
public void removeMonitor() {
NLog.d("BlockDetector removeMonitor");
mIoHandler.removeCallbacks(mLogRunnable);
mBlockStackTrace.clear();
}
}