美文网首页Android拾萃
通过Looper检测代码是否有卡顿

通过Looper检测代码是否有卡顿

作者: 五月花凋谢 | 来源:发表于2018-02-23 16:27 被阅读110次

    我们都知道Android使用消息机制进行UI更新,UI线程也就是主线程里有个Looper,在其loop()方法中会不断取出message,调用其绑定的Handler在主线程执行。如果在handler的dispatchMesaage方法里有耗时操作,就会发生卡顿。

    我们来看下Looper.loop()的源码

    /**
         * Run the message queue in this thread. Be sure to call
         * {@link #quit()} to end the loop.
         */
        public static void loop() {
            final Looper me = myLooper();
            if (me == null) {
                throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
            }
            final MessageQueue queue = me.mQueue;
    
            // Make sure the identity of this thread is that of the local process,
            // and keep track of what that identity token actually is.
            Binder.clearCallingIdentity();
            final long ident = Binder.clearCallingIdentity();
    
            for (;;) {
                Message msg = queue.next(); // might block
                if (msg == null) {
                    // No message indicates that the message queue is quitting.
                    return;
                }
    
                // This must be in a local variable, in case a UI event sets the logger
                final Printer logging = me.mLogging;
                if (logging != null) {
                    logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
                }
    
                final long traceTag = me.mTraceTag;
                if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
                    Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
                }
                try {
                    msg.target.dispatchMessage(msg);
                } finally {
                    if (traceTag != 0) {
                        Trace.traceEnd(traceTag);
                    }
                }
    
                if (logging != null) {
                    logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
                }
    
                // Make sure that during the course of dispatching the
                // identity of the thread wasn't corrupted.
                final long newIdent = Binder.clearCallingIdentity();
                if (ident != newIdent) {
                    Log.wtf(TAG, "Thread identity changed from 0x"
                            + Long.toHexString(ident) + " to 0x"
                            + Long.toHexString(newIdent) + " while dispatching to "
                            + msg.target.getClass().getName() + " "
                            + msg.callback + " what=" + msg.what);
                }
    
                msg.recycleUnchecked();
            }
        }
    

    只要检测 msg.target.dispatchMessage(msg) 的执行时间,就能检测到主线程是否有耗时操作。注意到这行执行代码的前后,有两个logging.println函数,如果设置了mLogging,会分别打印出”>>>>> Dispatching to “和”<<<<< Finished to “这样的日志,这样我们就可以通过两次log的时间差值,来计算dispatchMessage的执行时间,从而设置阈值判断是否发生了卡顿。

    如何给Looper设置mLogging?
    在源码中定义了私有的mLogging,但是提供了赋值方法。

    private Printer mLogging;
    public void setMessageLogging(@Nullable Printer printer) {
            mLogging = printer;
        }
    

    我们可以自己实现一个Printer并赋值。

          Looper.getMainLooper().setMessageLogging(new Printer() {
                private static final String START = ">>>>> Dispatching";
                private static final String END = "<<<<< Finished";
    
                @Override
                public void println(String x) {
                    if (x.startsWith(START)) {
                    //从这里开启一个定时任务来打印方法的堆栈信息
                    }
                    if (x.startsWith(END)) {
                      //从这里取消定时任务
                    }
                }
            });
    

    我们设定一个阈值为1000ms,当匹配到>>>>> Dispatching时,开启定时任务,会在1000ms 后执行任务,这个任务负责打印UI线程的堆栈信息。如果消息低于1000ms内执行完成,就可以匹配到<<<<< Finished日志,那么在打印堆栈任务启动前执行取消了这个任务,则认为没有卡顿的发生;如果消息超过1000ms才执行完毕,此时认为发生了卡顿,并打印UI线程的堆栈信息。

    看下定时任务的代码实现

    public class LooperLog {
        private static LooperLog sInstance = new LooperLog();
        private HandlerThread mLogThread = new HandlerThread("log");
        private Handler mIoHandler;
        private static final long TIME_BLOCK = 1000L;
    
        private LooperLog() {
            mLogThread.start();
            mIoHandler = new Handler(mLogThread.getLooper());
        }
    
        private static Runnable mLogRunnable = new Runnable() {
            @Override
            public void run() {
                StringBuilder sb = new StringBuilder();
                StackTraceElement[] stackTrace = Looper.getMainLooper().getThread().getStackTrace();
                for (StackTraceElement s : stackTrace) {
                    sb.append(s.toString() + "\n");
                }
                Log.i("LogPrinter--", sb.toString());
            }
        };
    
        public static LooperLog getInstance() {
            return sInstance;
        }
    
    
        public void startPrintLog() {
            mIoHandler.postDelayed(mLogRunnable, TIME_BLOCK);
        }
    
        public void canclePrintLog() {
            mIoHandler.removeCallbacks(mLogRunnable);
        }
    }
    

    这里我们使用HandlerThread来构造一个Handler,HandlerThread继承自Thread,实际上就一个Thread,只不过比普通的Thread多了一个Looper,对外提供自己这个Looper对象的getLooper方法,然后创建Handler时将HandlerThread中的looper对象传入。这样我们的mIoHandler对象就是与HandlerThread这个非UI线程绑定的了,它处理耗时操作将不会阻塞UI。如果UI线程阻塞超过1000ms,就会在子线程中执行mLogRunnable,打印出UI线程当前的堆栈信息,如果处理消息没有超过1000ms,则会实时的remove掉这个mLogRunnable任务。

    发生卡顿时打印出堆栈信息的大致内容如下,开发可以通过log定位耗时的地方。

    LogPrinter--: java.lang.Thread.sleep(Native Method)
                                                                        java.lang.Thread.sleep(Thread.java:1031)
                                                                        java.lang.Thread.sleep(Thread.java:985)
                                                                        com.koolearn.android.CommonPperationImpl.startActivityAfterLogin(CommonPperationImpl.java:124)
                                                                        com.koolearn.android.home.MainActivity.onClick(MainActivity.java:490)
                                                                        android.view.View.performClick(View.java:4811)
                                                                        android.view.View$PerformClick.run(View.java:20136)
                                                                        android.os.Handler.handleCallback(Handler.java:815)
                                                                        android.os.Handler.dispatchMessage(Handler.java:104)
                                                                        android.os.Looper.loop(Looper.java:194)
                                                                        android.app.ActivityThread.main(ActivityThread.java:5546)
                                                                        java.lang.reflect.Method.invoke(Native Method)
                                                                        java.lang.reflect.Method.invoke(Method.java:372)
                                                                        com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:964)
                                                                        com.android.internal.os.ZygoteInit.main(ZygoteInit.java:759)
    

    相关文章

      网友评论

        本文标题:通过Looper检测代码是否有卡顿

        本文链接:https://www.haomeiwen.com/subject/qfexxftx.html