Android卡顿检测方案
来源:互联网 发布:新浪微博下载mac 编辑:程序博客网 时间:2024/05/13 03:15
本文转载自:http://blog.coderclock.com/2017/06/04/android/AndroidPerformanceTools-BlockLooper/
应用的流畅度最直接的影响了App的用户体验,轻微的卡顿有时导致用户的界面操作需要等待一两秒钟才能生效,严重的卡顿则导致系统直接弹出ANR的提示窗口,让用户选择要继续等待还是关闭应用。
所以,如果想要提升用户体验,就需要尽量避免卡顿的产生,否则用户经历几次类似场景之后,只会动动手指卸载应用,再顺手到应用商店给个差评。关于卡顿的分析方案,已经有以下两种:
- 分析trace文件。通过分析系统的/data/anr/traces.txt,来找到导致UI线程阻塞的源头,这种方案比较适合开发过程中使用,而不适合线上环境;
- 使用BlockCanary开源方案。其原理是利用Looper中的loop输出的>>>>> Dispatching to和<<<<< Finished to这样的log,这种方案适合开发过程和上线的时候使用,但也有个弊端,就是如果系统移除了前面两个log,检测可能会面临失效;
下面就开始说本文要提及的卡顿检测实现方案,原理简单,代码量也不多,只有BlockLooper和BlockError两个类。
基本使用
在Application中调用BlockLooper.initialize进行一些参数初始化,具体参数项可以参照BlockLooper中的Configuration静态内部类,当发生卡顿时,则会在回调(非UI线程中)OnBlockListener。
12345678910111213141516171819202122
public class AndroidPerformanceToolsApplication extends Application {private final static String TAG = AndroidPerformanceToolsApplication.class.getSimpleName();public void onCreate() {super.onCreate();// 初始化相关配置信息BlockLooper.initialize(new BlockLooper.Builder(this).setIgnoreDebugger(true).setReportAllThreadInfo(true).setSaveLog(true).setOnBlockListener(new BlockLooper.OnBlockListener() {//回调在非UI线程public void onBlock(BlockError blockError) {blockError.printStackTrace();//把堆栈信息输出到控制台}}).build());}}
在选择要启动(停止)卡顿检测的时候,调用对应的API
123
BlockLooper.getBlockLooper().start();//启动检测BlockLooper.getBlockLooper().stop();//停止检测
使用上很简单,接下来看一下效果演示和源码实现。
效果演示
制造一个UI阻塞效果
看看AS控制台输出的整个堆栈信息
定位到对应阻塞位置的源码
当然,对线程的信息BlockLooper也不仅输出到控制台,也会帮你缓存到SD上对应的应用缓存目录下,在SD卡上的/Android/data/对应App包名/cache/block/下可以找到,文件名是发生卡顿的时间点,后缀是trace。
源码解读
当App在5s内无法对用户做出的操作进行响应时,系统就会认为发生了ANR。BlockLooper实现上就是利用了这个定义,它继承了Runnable接口,通过initialize传入对应参数配置好后,通过BlockLooper的start()创建一个Thread来跑起这个Runnable,在没有stop之前,BlockLooper会一直执行run方法中的循环,执行步骤如下:
- Step1. 判断是否停止检测UI线程阻塞,未停止则进入Step2;
- Step2. 使用uiHandler不断发送ticker这个Runnable,ticker会对tickCounter进行累加;
- Step3. BlockLooper进入指定时间的sleep(frequency是在initialize时传入,最小不能低于5s);
- Step4. 如果UI线程没有发生阻塞,则sleep过后,tickCounter一定与原来的值不相等,否则一定是UI线程发生阻塞;
- Step5. 发生阻塞后,还需判断是否由于Debug程序引起的,不是则进入Step6;
- Step6. 回调OnBlockListener,以及选择保存当前进程中所有线程的堆栈状态到SD卡等;
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
public class BlockLooper implements Runnable {...private Handler uiHandler = new Handler(Looper.getMainLooper());private Runnable ticker = new Runnable() {public void run() {tickCounter = (tickCounter + 1) % Integer.MAX_VALUE;}};...private void init(Configuration configuration) {this.appContext = configuration.appContext;this.frequency = configuration.frequency < DEFAULT_FREQUENCY ? DEFAULT_FREQUENCY : configuration.frequency;this.ignoreDebugger = configuration.ignoreDebugger;this.reportAllThreadInfo = configuration.reportAllThreadInfo;this.onBlockListener = configuration.onBlockListener;this.saveLog = configuration.saveLog;}public void run() {int lastTickNumber;while (!isStop) { //Step1lastTickNumber = tickCounter;uiHandler.post(ticker); //Step2try {Thread.sleep(frequency); //Step3} catch (InterruptedException e) {e.printStackTrace();break;}if (lastTickNumber == tickCounter) { //Step4if (!ignoreDebugger && Debug.isDebuggerConnected()) { //Step5Log.w(TAG, "当前由调试模式引起消息阻塞引起ANR,可以通过setIgnoreDebugger(true)来忽略调试模式造成的ANR");continue;}BlockError blockError; //Step6if (!reportAllThreadInfo) {blockError = BlockError.getUiThread();} else {blockError = BlockError.getAllThread();}if (onBlockListener != null) {onBlockListener.onBlock(blockError);}if (saveLog) {if (StorageUtils.isMounted()) {File logDir = getLogDirectory();saveLogToSdcard(blockError, logDir);} else {Log.w(TAG, "sdcard is unmounted");}}}}}...public synchronized void start() {if (isStop) {isStop = false;Thread blockThread = new Thread(this);blockThread.setName(LOOPER_NAME);blockThread.start();}}public synchronized void stop() {if (!isStop) {isStop = true;}}......}
介绍完BlockLooper后,再简单说一下BlockError的代码,主要有getUiThread和getAllThread两个方法,分别用户获取UI线程和进程中所有线程的堆栈状态信息,当捕获到BlockError时,会在OnBlockListener中以参数的形式传递回去。
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061
public class BlockError extends Error {private BlockError(ThreadStackInfoWrapper.ThreadStackInfo threadStackInfo) {super("BlockLooper Catch BlockError", threadStackInfo);}public static BlockError getUiThread() {Thread uiThread = Looper.getMainLooper().getThread();StackTraceElement[] stackTraceElements = uiThread.getStackTrace();ThreadStackInfoWrapper.ThreadStackInfo threadStackInfo = new ThreadStackInfoWrapper(getThreadNameAndState(uiThread), stackTraceElements).new ThreadStackInfo(null);return new BlockError(threadStackInfo);}public static BlockError getAllThread() {final Thread uiThread = Looper.getMainLooper().getThread();Map<Thread, StackTraceElement[]> stackTraceElementMap = new TreeMap<Thread, StackTraceElement[]>(new Comparator<Thread>() {public int compare(Thread lhs, Thread rhs) {if (lhs == rhs) {return 0;} else if (lhs == uiThread) {return 1;} else if (rhs == uiThread) {return -1;}return rhs.getName().compareTo(lhs.getName());}});for (Map.Entry<Thread, StackTraceElement[]> entry : Thread.getAllStackTraces().entrySet()) {Thread key = entry.getKey();StackTraceElement[] value = entry.getValue();if (value.length > 0) {stackTraceElementMap.put(key, value);}}//Fix有时候Thread.getAllStackTraces()不包含UI线程的问题if (!stackTraceElementMap.containsKey(uiThread)) {stackTraceElementMap.put(uiThread, uiThread.getStackTrace());}ThreadStackInfoWrapper.ThreadStackInfo threadStackInfo = null;for (Map.Entry<Thread, StackTraceElement[]> entry : stackTraceElementMap.entrySet()) {Thread key = entry.getKey();StackTraceElement[] value = entry.getValue();threadStackInfo = new ThreadStackInfoWrapper(getThreadNameAndState(key), value).new ThreadStackInfo(threadStackInfo);}return new BlockError(threadStackInfo);}...}
总结
以上就是BlockLooper的实现,非常简单,相信大家都看得懂。源码地址:https://github.com/D-clock/AndroidPerformanceTools ,喜欢自取。
- Android卡顿检测方案
- Android卡顿检测方案
- Android卡顿检测方案
- android Ui 卡顿检测
- Android Ui卡顿检测
- Android Studio 卡顿问题解决方案
- Android 检测应用中的UI卡顿
- 解决Android Studio卡顿运行慢的完美方案
- Android UI性能优化 检测应用中的UI卡顿
- Android UI性能优化 检测应用中的UI卡顿
- Android UI性能优化 检测应用中的UI卡顿
- Android UI性能优化 检测应用中的UI卡顿
- Android UI性能优化 检测应用中的UI卡顿
- Android APP UI性能优化 检测UI卡顿
- Android UI性能优化 检测应用中的UI卡顿
- Android 检测应用中的UI卡顿的工具
- Slidingmenu卡顿问题解决方案
- iOS监控:卡顿检测
- MySQL 数据类型
- 数据库SQL优化大总结1之- 百万级数据库优化方案
- python3---->比urllib更为强大的requests
- Android常用实例——实现修改用户头像功能
- Chrome窗口和标签页快捷键
- Android卡顿检测方案
- 初识vue
- BAT文件如何隐藏黑黑的控制台窗口?
- Linux下Nginx安装
- linux 安装rpm mysql
- 有关数字电视的几个概念(OTT/DVB/IPTV、CA/DRM)
- MySQL USING 和 HAVING 用法
- 假如时光倒流,我会这么学习Java
- 反调试技巧总结-原理和实现