美文网首页程序员
关于Handler消息处理机制的几个问题?

关于Handler消息处理机制的几个问题?

作者: Jesse_zhao | 来源:发表于2016-12-19 00:08 被阅读129次

    Android系统中的两大机制:Binder IPC机制和Handler消息机制;前者用于进程间通信,后者用于同一个进程中的线程间通信。作为一名Android developer,你肯定知道Handler消息处理机制的原理,就不做描述了。这里抛出两个问题:(第一个问题我是在知乎上看到的,引出的第二个问题,然后又看了一遍Handler消息处理机制的源码,然后就有了这篇文章!)

    1、为什么主线程不会因为Looper.loop()里的死循环卡死?
    2、message延时处理如何实现的?

    如果你已经知道了答案,那你就不用浪费时间往下看了。

    第一个问题:

    我们知道源码Looper类中的loop()方法写了一个死循环:

    /**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    public static void loop() {
       ....//省略部分源码
        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);
            ....//省略部分源码
            msg.recycleUnchecked();
        }
    }
    

    如何证明调用loop()方法后线程会阻塞呢?请看

    public class MyThread extends Thread {
      private static final String TAG="MyThread";
      private Handler mHandler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Log.e(TAG, "handleMessage:-->"+msg.what);
        }
      };
      @Override
      public void run() {
        super.run();
        Looper.prepare();
        new MyThread2().start();
        Looper.loop();
        Log.e(TAG, "是否会执行?");
      }
    }
    

    执行以上代码,你会发现Log.e(TAG, "是否会执行?")是不会执行的。既然如此,我们的主线程也是这么干的啊,难道是我们写错了?这就引出了第一个问题

    1、当looper去messageQueue中取消息时调用的是queue.next(); 当没有消息的时候,就会阻塞在nativePollOnce(ptr, nextPollTimeoutMillis);这个方法中,具体的就涉及到native方法的执行的,说是会让线程休眠让出cpu(抱歉没去深入native层)。

    2、loop()方法会阻塞,那么其他的操作是怎么做到的?大牛说了会创建新的线程,然后发送消息到ActivityThread类中Handler去处理。//这个,呃,todo...

    再来看第二个问题:

    handler中发送消息的几个方法

     mHandler.sendEmptyMessageAtTime(1, 0);
     mHandler.sendEmptyMessageAtTime(2, SystemClock.uptimeMillis() + 2000);
     mHandler.sendEmptyMessage(3);
     mHandler.sendEmptyMessageDelayed(4, 3000);
    

    这几个方法其实最后都会调用handler中的enqueueMessage方法

    private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }
    

    再看messageQueue中的enqueueMessage方法:

    boolean enqueueMessage(Message msg, long when) {
       ....//省略部分源码
        synchronized (this) {
            ....//省略部分源码
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            //下面这段关键代码是给message进行排序
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
                needWake = mBlocked && p.target == null && msg.isAsynchronous();
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    if (needWake && p.isAsynchronous()) {
                        needWake = false;
                    }
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            }
          ....//省略部分源码
        }
        return true;
    }
    

    这个方法会将handler发送的消息加入到消息队列,这个消息队列是的先后顺序是按照msg.when来排列的。通过next()来取出的消息,当消息还没到执行的时间时,那么这个方法就会阻塞。具体看如下源码:

    Message next() {
       ....//省略部分源码
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
    
            nativePollOnce(ptr, nextPollTimeoutMillis);
    
            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
    
                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }
    
                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                if (pendingIdleHandlerCount < 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);
            }
           ....//省略部分源码
            nextPollTimeoutMillis = 0;
        }
    }
    

    总结

    1、主线程不会因为loop()死循环而卡死,使用当没有消息的时,线程会进入休眠让出cpu,具体实现是nativePollOnce(ptr, nextPollTimeoutMillis)方法。

    2、message的延时处理是通过判断handler发送消息时设定的执行时间,未到执行时间则和上述情况一样进入休眠让出cpu。

    相关文章

      网友评论

        本文标题:关于Handler消息处理机制的几个问题?

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