Android 检测UI卡顿问题

最近公司接到一些用户反馈说,App卡顿了,甚至App无响应,我们称其为ANR。

先来说说ANR。

ANR

ANR,即 Application Not Responding,应用程序无响应。Android系统对于一些事件需要在一定的时间范围内完成,如果超过预定时间能未能得到有效响应或者响应时间过长,都会造成ANR。

出现ANR的场景有一下几种:

  1. 输入事件(包括按键分发事件)5秒内没有处理完毕。
  2. 执行BroadcastReceiver的onReceive()函数时10秒内没有处理完毕。
  3. Service的各个生命周期函数时20秒内没有处理完毕。

造成以上几种ANR场景的原因有2个:

  1. 主线程执行了耗时操作,比如数据库操作或网络编程。
  2. 其他进程(就是其他程序)占用CPU导致本进程得不到CPU时间片,比如其他进程的频繁读写操作可能会导致这个问题。

检测卡顿

App卡顿,肯定是我们在UI线程执行了什么耗时操作,所以我们要做的是将耗时的操作抓出来。很笨的办法就在UI线程执行的方法(怀疑有耗时操作)中加入打印开始和结束的时间,一般执行时间不会超过1秒。

我在网上找到一篇鸿洋大神关于检测应用中的UI卡顿的博文,发现一种很有趣的方法,在此记录一下。

其基本原理是利用Looper源码中打印Log信息,详细原理请看 Android UI性能优化 检测应用中的UI卡顿

具体代码如下:

public class LooperPrinter {
    private static long lastTime;
    public static void start() {
        Looper.getMainLooper().setMessageLogging(new Printer() {
            private static final String START = ">>>>> Dispatching";
            private static final String END = "<<<<< Finished";
            @Override
            public void println(String content) {
                if (content.startsWith(START)) {
                    LogMonitor.getInstance().startMonitor();
                    lastTime = System.currentTimeMillis();
                }
                if (content.startsWith(END)) {
                    LogMonitor.getInstance().removeMonitor();
                    Log.e("TAG", "run time : " + (System.currentTimeMillis() - lastTime));
                }
            }
        });
    }
}

Looper竟然还能这么用,涨知识了!!

public class LogMonitor {
    private static LogMonitor INSTANCE = new LogMonitor();
    private HandlerThread handlerThread = new HandlerThread("log");
    private Handler handler;
    private static final long TIME_BLOCK = 1000L;
    private LogMonitor() {
        handlerThread.start();
        handler = new Handler(handlerThread.getLooper());
    }
    private static Runnable printRunnable = new Runnable() {
        @Override
        public void run() {
            StringBuilder builder = new StringBuilder();
            StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace();
            for (StackTraceElement element : stackTrace) {
                builder.append(element.toString() + "\n");
            }
            Log.e("TAG", builder.toString());
        }
    };
    public static LogMonitor getInstance() {
        return INSTANCE;
    }
    public void startMonitor() {
        handler.postDelayed(printRunnable, TIME_BLOCK);
    }
    public void removeMonitor() {
        handler.removeCallbacks(printRunnable);
    }
}

做一个倒计时的工具类,实例中是结合HandlerThread和Handler做的计时器,当打印“START”时,调用startMonitor方法,延时(1秒)执行打印堆栈信息,如果在规定时间内没有调用removeMonitor方法,证明没有在规定时间内打印“END”,也就是说在Main Looper中,当前Message执行时间超过规定时间,那么就会执行打印堆栈信息。打印的堆栈信息中会出现执行超过的方法。

解决公司的Bug

我使用以上方法检测了之后,很奇怪,竟然是Android的Resources.updateConfiguration方法导致的。简单说一下,由于公司之前有这么一个需求,要求我们的App字体不要受系统字体大小影响,然后在网上找了这么一个方法:

@Override  
public Resources getResources() {  
    Resources res = super.getResources();    
    Configuration config = new Configuration();    
    config.setToDefaults();    
    res.updateConfiguration(config, res.getDisplayMetrics());  
    return res;  
}  

结果getResources方法用了1568毫秒,然后我把这段去掉之后,就没有这么多了(因为每个时间都没有超过1秒,所以我也不知道是哪个时间了)。我也有尝试去找代替方案,和老大讨论了一下,这个需求不大,就去掉了。希望知道的指点一下,谢谢!!

鸣谢

Android UI性能优化 检测应用中的UI卡顿

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值