美文网首页
Handler中很少注意的小知识

Handler中很少注意的小知识

作者: czfyzzf | 来源:发表于2020-05-24 13:01 被阅读0次

    Looper会不会ANR

    Handler机制是消息处理机制,是Android最重要的机制之一。它能够处理在Android开发中,处理UI的线程处理问题。

    因为我们启动一个Activity时,会调用ActivityThreadmain方法:

        public static void main(String[] args) {
            ...
            Looper.prepareMainLooper();
    
            if (sMainThreadHandler == null) {
                sMainThreadHandler = thread.getHandler();
            }
    
            Looper.loop();
    
         }
    

    thread.getHandler():返回的是一个mH对象,mH是一个继承Handler的H类。

    由上面的代码可以看出,Activity一启动的时候,就会启动Looper.loop去循环遍历MessageQueue取出新消息并处理。

    这里就有一个问题了:为什么handler在主线程中不断循环遍历,调用MessageQueue中的next()方法,为什么不会导致ANR?

    回答这个问题可以分两个方面。第一,我们通过查看MessageQueue的代码可以知道,MessageQueue判断是否触发下个消息的处理流程是通过:nativePollOnce(ptr, nextPollTimeoutMillis); 这个方法。这是一个native方法,跟到native代码中,可以知道它使用epoll机制去监听fd句柄,只要有消息的延时到了就会触发。epoll机制并不会一直占用系统的资源,在空闲的时候会释放掉持有的资源。

    tip:这里又涉及到另一个问题:select / poll / epoll这三种机制为什么要选择epoll机制?它对比其他两种机制有什么优点? > > 有兴趣可以看看袁辉辉的博客 :[select/poll/epoll对比分析](select/poll/epoll对比分析 - Gityuan博客 | 袁辉辉的技术博客)

    第二,我们需要知道ANR的原理是什么,ANR是怎么产生的。ANR可以是AMS启动四大组件的时候的一种埋炸弹拆炸弹的过程。在启动的四大组件的时候,AMS会使用handler发送一个延时的Message消息,这是一个埋炸弹的过程。四大组件启动后会回报AMSAMS收到回报后就会将之前向handler发送的消息remove掉,这是一个拆炸弹的过程。还有一种是input超时,input超时检测是一种被动检测的机制,当有下一个新的input事件到来,上个input事件还没有分发完成才会去检测,这就不细说了。所以,在主线程loop()中,根本就没有形成ANR的条件。

    IdleHandler可以用来干嘛

    实际上,在日常的开发中有些任务是可以等到主线程的空闲的时候再去执行的,但是当主线程需要执行其他任务时,又不会阻塞其他的任务执行。Android给我们这种需求提供了一种方法,MessageQueue.IdleHandler 这个类就可以做这样的工作。这个类的使用也非常简单:

      Looper.myLooper().getQueue().addIdleHandler(new MessageQueue.IdleHandler() {
                @Override
                public boolean queueIdle() {
                    //执行操作
                    return false;
                }
            });
    

    这里的返回值如果是false :这个方法只会去执行一次;true :每次只要主线程有空闲的时候都会去执行我们的操作。

    这种机制在源码中的实现也很简单。

    MessageQueue.java
    
    //添加一个IdleHandler
    public void addIdleHandler(@NonNull IdleHandler handler) {
         if (handler == null) {
                throw new NullPointerException("Can't add a null IdleHandler");
         }
         synchronized (this) {
            mIdleHandlers.add(handler);
        }
    }
    
    Message next() {
    
            int pendingIdleHandlerCount = -1; // -1 only during first iteration
            int nextPollTimeoutMillis = 0;
            for (;;) {
                    ...
                    //判断是否有正在处理消息
                    if (pending Count < 0
                            && (mMessages == null || now < mMessages.when)) {
                        pendingIdleHandlerCount = mIdleHandlers.size();
                    }
                    if (pendingIdleHandlerCount <= 0) {
                        // No idle handlers to run.  Loop and wait some more.
                        mBlocked = true;
                        continue;
                    }
    
                    if (mPendingIdleHandlers == null) {
                        mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                    }
                     mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
                }
    
                //处理我们遍历处理添加的IdleHandler
                for (int i = 0; i < pendingIdleHandlerCount; i++) {
                    final IdleHandler idler = mPendingIdleHandlers[i];
                    mPendingIdleHandlers[i] = null; // release the reference to the handler
    
                    //默认 keep 值为false,只执行一次
                    boolean keep = false;
                    try {
                        keep = idler.queueIdle();
                    } catch (Throwable t) {
                        Log.wtf(TAG, "IdleHandler threw exception", t);
                    }
    
                    //当keep值为false,执行完之后删除IdleHandler
                    if (!keep) {
                        synchronized (this) {
                            mIdleHandlers.remove(idler);
                        }
                    }
                }
    
                // Reset the idle handler count to 0 so we do not run them again.
                pendingIdleHandlerCount = 0;
    
                // While calling an idle handler, a new message could have been delivered
                // so go back and look again for a pending message without waiting.
                nextPollTimeoutMillis = 0;
            }
        }
    

    监听主线程中的耗时

    我们知道Looper在主线程创建的时候就开启了loop()方法循环遍历消息。所以,可以使用这个方法去监听主线程中的耗时操作。

    Looper.getMainLooper().setMessageLogging(new Printer() {
          @Override
          public void println(String x) {
                if (x.contains(">>>>> Dispatching to ")) {
                    //方法开始执行
                        currentTime = System.currentTimeMillis();
                } else if (x.contains("<<<<< Finished to ")) {
                    //方法执行结束
                     long executeTime = System.currentTimeMillis() - currentTime;
                     if (executeTime > 60) {
                           Log.e("TAG", "主线程出现方法耗时");
                     }
                }
          }
    });
    

    其实这个实现耗时监听的原理也很简单。

    Looper.java
    
    public static void loop() {
            final Looper me = myLooper();
            final MessageQueue queue = me.mQueue;
    
            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
                Printer logging = me.mLogging;
                if (logging != null) {
                    logging.println(">>>>> Dispatching to " + msg.target + " " +
                            msg.callback + ": " + msg.what);
                }
    
                msg.target.dispatchMessage(msg);
    
                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();
                msg.recycleUnchecked();
            }
        }
    

    从上面的代码可以看出,在执行 msg.target.dispatchMessage(msg);分发消息前后会都会执行打印。我只要在在打印中增加时间获取就可以监听耗时了。

    消息屏障

    消息屏障是Handler的三种消息类型之一,其他两种分别是:同步消息异步消息。它的作用是插入到MessageQueue的消息处理队列的头部,优先处理该消息。这种消息类型主要是系统中调用,发送消息屏障的方法被private修饰的,不对外公开的。应用的场景,例如,App接受到刷新界面的VSync信号时,就会发送一个消息屏障,优先处理view的绘制,并将数据写到一块surface内存上。

      private int postSyncBarrier(long when) {
            // Enqueue a new sync barrier token.
            // We don't need to wake the queue because the purpose of a barrier is to stall it.
            synchronized (this) {
                final int token = mNextBarrierToken++;
                final Message msg = Message.obtain();
                msg.markInUse();
                msg.when = when;
                msg.arg1 = token;
    
                Message prev = null;
                Message p = mMessages;
                if (when != 0) {
                    while (p != null && p.when <= when) {
                        prev = p;
                        p = p.next;
                    }
                }
                if (prev != null) { // invariant: p == prev.next
                    msg.next = p;
                    prev.next = msg;
                } else {
                    msg.next = p;
                    mMessages = msg;
                }
                return token;
            }
        }
    

    其实,该方法主要就是操作Message链表,将一个新的消息插到表头。


    参考文章
    [Android Handler 通信 - 彻底了解 Handler 的通信过程](Android Handler 通信 - 彻底了解 Handler 的通信过程 - 简书)

    [Android消息机制2-Handler(Native层)] - Gityuan博客 | 袁辉辉的技术博客](http://gityuan.com/2015/12/27/handler-message-native/))

    第一次撸文章写得不好的地方,欢迎指出!

    相关文章

      网友评论

          本文标题:Handler中很少注意的小知识

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