UI卡顿检测的两种方法

前言:我们都知道android开发负责的就是移动端用户与界面的交互,是用户和后端的桥梁,一个美观,流畅的界面大大提高用户的操作体验。但在一些情况下,炫酷的界面布局,复杂的动画或者自定义控件的绘制会造成一定的UI卡顿,这与我们设计的原则是相悖的。那么,造成UI卡顿的原因无非也就那几种,重要的是怎么检测是哪个地方造成了界面卡顿。

造成卡顿有可能发生在XML文件中,也可能是我们代码中的逻辑太复杂造成的,那么我们分两种不同的方法来分别对这两种情况进行具体检测

1.首先,我们看看是否是我们的界面是否过度绘制

手机开发者选项打开,我们看看里面有个显示边界布局的选项,打开它


我们可以看到,我们的手机界面布满一层层的色层次

打开我们的app


可以了解到,我们的布局绘制了越多层,红色就越深,越不合理,

像下面布局就是楼主为了实现一个自定义的特殊布局,嵌套了太多层布局,导致出现UI卡顿,可以看到我们的界面


了解了这些,我们就可以看到我们的布局是否合理,定位哪里可能造成了UI卡顿




2.卡顿如果是发生在代码部分,那么怎么定位

首先我们来了解一下dispatchMessage这个方法



1.  dispatchMessage方法在哪

dispatchMessage()是在Looper.loop()里调用,源码如下:

[java]  view plain  copy
  1. public static void loop() {  
  2.     final Looper me = myLooper();  
  3.     if (me == null) {  
  4.         throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");  
  5.     }  
  6.     final MessageQueue queue = me.mQueue;  
  7.   
  8.     // Make sure the identity of this thread is that of the local process,  
  9.     // and keep track of what that identity token actually is.  
  10.     Binder.clearCallingIdentity();  
  11.     final long ident = Binder.clearCallingIdentity();  
  12.   
  13.     for (;;) {  
  14.         Message msg = queue.next(); // might block  
  15.         if (msg == null) {  
  16.             // No message indicates that the message queue is quitting.  
  17.             return;  
  18.         }  
  19.   
  20.         // This must be in a local variable, in case a UI event sets the logger  
  21.         Printer logging = me.mLogging;  
  22.         if (logging != null) {  
  23.             logging.println(">>>>> Dispatching to " + msg.target + " " +  
  24.                     msg.callback + ": " + msg.what);  
  25.         }  
  26.   
  27.         msg.target.dispatchMessage(msg);  
  28.   
  29.         if (logging != null) {  
  30.             logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);  
  31.         }  
  32.   
  33.         // Make sure that during the course of dispatching the  
  34.         // identity of the thread wasn't corrupted.  
  35.         final long newIdent = Binder.clearCallingIdentity();  
  36.         if (ident != newIdent) {  
  37.             Log.wtf(TAG, "Thread identity changed from 0x"  
  38.                     + Long.toHexString(ident) + " to 0x"  
  39.                     + Long.toHexString(newIdent) + " while dispatching to "  
  40.                     + msg.target.getClass().getName() + " "  
  41.                     + msg.callback + " what=" + msg.what);  
  42.         }  
  43.   
  44.         msg.recycleUnchecked();  
  45.     }  
  46. }  

所以说,第27行的代码就是可能发生UI卡顿的地方。注意这行代码的前后,有两个logging。也就是说在执行第27代码的前后,如果设置了logging,会分别打印出“>>>>> Dispatching to”和“<<<<< Finished to”这样的Log。这样就给我们监视两次Log之间的时间差,来判断是否发生了卡顿。


2.  设置logging

主要看一下21行的mLogging是什么,源码如下所示:

[java]  view plain  copy
  1. public final class Looper {  
  2.     private Printer mLogging;  
  3.     public void setMessageLogging(@Nullable Printer printer) {  
  4.         mLogging = printer;  
  5.     }  
  6. }  
  7. public interface Printer {  
  8.     void println(String x);  
  9. }  

LoopermLogging是私有的,并且提供了setMessageLogging(@Nullable Printer printer)方法,所以我们可以自己实现一个Printer,在通过setMessageLogging()方法传入即可

[java]  view plain  copy
  1. public class AppContext extends Application {  
  2.     @Override  
  3.     public void onCreate() {  
  4.         super.onCreate();  
  5.        Looper.getMainLooper().setMessageLogging(new Printer() {  
  6.   
  7.             private static final String START = ">>>>> Dispatching";  
  8.             private static final String END = "<<<<< Finished";  
  9.   
  10.             @Override  
  11.             public void println(String x) {  
  12.                 if (x.startsWith(START)) {  
  13.                     LogMonitor.getInstance().startMonitor();  
  14.                 }  
  15.                 if (x.startsWith(END)) {  
  16.                     LogMonitor.getInstance().removeMonitor();  
  17.                 }  
  18.             }  
  19.         });  
  20.     }  
  21. }  

当我们设置了mLogging之后,loop()方法中就会回调logging.println,并将带有“>>>>> Dispatching to”和“<<<<< Finished to”的字符串传入,我们就可以拿到这两条信息。

如果“>>>>> Dispatching to”信号发生了,我们就假定发生了卡顿(这里我们设定1秒钟的卡顿判定阈值),并且发送一个延迟1秒钟的任务,这个任务就用于在子线程打印出造成卡顿的UI线程里的堆栈信息。而如果没有卡顿,即在1秒钟之内我们检测到了“<<<<< Finished to”信号,就会移除这个延迟1秒的任务。

 

3.  LogMonitor的实现

[java]  view plain  copy
  1. public class LogMonitor {  
  2.     private static LogMonitor sInstance = new LogMonitor();  
  3.     private HandlerThread mHandlerThread = new HandlerThread("log");  
  4.     private Handler mHandler;  
  5.   
  6.     private LogMonitor() {  
  7.         mHandlerThread.start();  
  8.         mHandler = new Handler(mHandlerThread.getLooper());  
  9.     }  
  10.   
  11.     private static Runnable mRunnable = new Runnable() {  
  12.         @Override  
  13.         public void run() {  
  14.             StringBuilder sb = new StringBuilder();  
  15.             StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace();  
  16.             for (StackTraceElement s : stackTrace) {  
  17.                 sb.append(s.toString() + "\n");  
  18.             }  
  19.             Log.e("TAG", sb.toString());  
  20.         }  
  21.     };  
  22.   
  23.     public static LogMonitor getInstance() {  
  24.         return sInstance;  
  25.     }  
  26.   
  27.     public void startMonitor() {  
  28.         mHandler.postDelayed(mRunnable, 1000);  
  29.     }  
  30.   
  31.     public void removeMonitor() {  
  32.         mHandler.removeCallbacks(mRunnable);  
  33.     }  
  34.   
  35. }  

这里我们使用HandlerThread来构造一个HandlerHandlerThread继承自Thread,实际上就一个Thread,只不过它比普通的Thread多了一个Looper,对外提供自己这个Looper对象的get方法,然后创建Handler时将HandlerThread中的looper对象传入。这样我们的mHandler对象就是与HandlerThread这个非UI线程绑定的了,这样它处理耗时操作将不会阻塞UI

总之,如果UI线程阻塞超过1秒,就会在子线程中执行mRunnable,打印出UI线程当前的堆栈信息,如果处理消息没有超过1秒,则会实时的remove掉这个mRunnable

 

4.  测试

Activity中设置一个按钮,并且设置点击后睡3秒。便可以看见打印出的Log信息。帮助我们定位到耗时的地方。



  • 2
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值