天天看點

Android如何定位卡頓,Android檢測卡頓方法

背景

在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();

}

}