背景
在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} 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
利用这个特点,然后就可以拦截这些打印日志,在>>>>> 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();
}
}