背景
在app開(kāi)發(fā)過(guò)程中互站,經(jīng)常會(huì)出現(xiàn)由于在主線程執(zhí)行某些耗時(shí)操作(例如db,文件io操作等)導(dǎo)致頁(yè)面顯示會(huì)出現(xiàn)卡頓,影響用戶體驗(yàn)抠璃。本文主要講解一種基于Handler Message機(jī)制的卡頓檢測(cè)方案术奖,
原理 :Android系統(tǒng)在執(zhí)行每個(gè)方法的時(shí)候礁遵,會(huì)在方法的開(kāi)始和結(jié)束打印日志。
在方法開(kāi)始執(zhí)行的時(shí)候采记,會(huì)打印
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
在方法結(jié)束執(zhí)行的時(shí)候佣耐,會(huì)打印
07-01 13:07:02.036 13471-13471/com.apache.fastandroid D/BlockDetector: BlockDetector init println x = <<<<< Finished to Handler (android.os.Handler) {29d2efdd} com.squareup.leakcanary.AndroidWatchExecutor$1@dae1898
07-01 13:07:02.275 13471-13471/com.apache.fastandroid D/BlockDetector: BlockDetector init println x = <<<<< Finished to Handler (android.os.Handler) {29d2efdd} com.squareup.leakcanary.AndroidWatchExecutor$1@3c2ef8f1
07-01 13:07:02.521 13471-13471/com.apache.fastandroid D/BlockDetector: BlockDetector init println x = <<<<< Finished to Handler (android.os.Handler) {29d2efdd} com.squareup.leakcanary.AndroidWatchExecutor$1@2b3f9cd6
07-01 13:07:02.758 13471-13471/com.apache.fastandroid D/BlockDetector: BlockDetector init println x = <<<<< Finished to Handler (android.os.Handler) {29d2efdd} com.squareup.leakcanary.AndroidWatchExecutor$1@2f632b57
07-01 13:07:03.003 13471-13471/com.apache.fastandroid D/BlockDetector: BlockDetector init println x = <<<<< Finished to Handler (android.os.Handler) {29d2efdd} com.squareup.leakcanary.AndroidWatchExecutor$1@b82644
07-01 13:07:03.247 13471-13471/com.apache.fastandroid D/BlockDetector: BlockDetector init println x = <<<<< Finished to Handler (android.os.Handler) {29d2efdd} com.squareup.leakcanary.AndroidWatchExecutor$1@6a9332d
07-01 13:07:03.505 13471-13471/com.apache.fastandroid D/BlockDetector: BlockDetector init println x = <<<<< Finished to Handler (android.os.Handler) {29d2efdd} com.squareup.leakcanary.AndroidWatchExecutor$1@2570b062
07-01 13:07:07.634 13471-13471/com.apache.fastandroid D/BlockDetector: BlockDetector init println x = <<<<< Finished to Handler (android.os.Handler) {29d2efdd} com.squareup.leakcanary.AndroidWatchExecutor$1@148525f3
利用這個(gè)特點(diǎn),然后就可以攔截這些打印日志唧龄,在>>>>> Dispatching的時(shí)候兼砖,延時(shí)特定時(shí)間(例如500ms)開(kāi)始監(jiān)控耗時(shí), 在<<<<< Finished的時(shí)候結(jié)束監(jiān)控,取消延時(shí)執(zhí)行代碼邏輯讽挟。如果延時(shí)的代碼邏輯有執(zhí)行懒叛,說(shuō)明當(dāng)前方法耗時(shí)超過(guò)了500ms,則把對(duì)應(yīng)的堆棧信息打印出來(lái)耽梅。
操作步驟
具體代碼如下:
/**
*
* 檢測(cè)主線程卡頓
*/
public class BlockDetector {
public static void init() {
if(DebugUtils.isDebug()) {
Looper.getMainLooper().setMessageLogging(new Printer() {
//分發(fā)和處理消息開(kāi)始前的log
private static final String START = ">>>>> Dispatching";
//分發(fā)和處理消息結(jié)束后的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)) {
//開(kāi)始計(jì)時(shí)
BlockMonitor.getInstance().startMonitor();
}
if (x.startsWith(END)) {
//結(jié)束計(jì)時(shí)
BlockMonitor.getInstance().removeMonitor();
}
}
});
}
}
public class BlockMonitor {
private static final String TAG = "=============BlockMonitor============= \n %s";
private static BlockMonitor sInstance = new BlockMonitor();
private Handler mIoHandler;
//方法耗時(shí)的卡口,500毫秒
private static final long TIME_BLOCK = 500L;
//存放一個(gè)msg周期的卡頓堆棧信息薛窥,防止重復(fù)打印
private Set<String> mBlockStackTrace;
private BlockMonitor() {
HandlerThread logThread = new HandlerThread("BlockMonitor");
logThread.start();
mIoHandler = new Handler(logThread.getLooper());
mBlockStackTrace = Collections.synchronizedSet(new HashSet<String>());
}
private Runnable mLogRunnable = new Runnable() {
@Override
public void run() {
//繼續(xù)檢測(cè)
startMonitor();
//打印出執(zhí)行的耗時(shí)方法的棧消息
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;
}
/**
* 開(kāi)始計(jì)時(shí)
*/
public void startMonitor() {
NLog.d("BlockDetector startMonitor");
mIoHandler.postDelayed(mLogRunnable, TIME_BLOCK);
}
/**
* 停止計(jì)時(shí)
*/
public void removeMonitor() {
NLog.d("BlockDetector removeMonitor");
mIoHandler.removeCallbacks(mLogRunnable);
mBlockStackTrace.clear();
}
}