Handler机制中线程切换和消息阻塞再剖析

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/yus201120/article/details/82015980

1、Handler是如何实现线程转换的?
如果要理解知识点,我们必须要清楚MessageQueue、Looper、和Handler的内在联系和运行机制。总体来说,Looper.prepare()方法为当前线程创建了一个Looper对象和一个MessageQueue对象,然后把他们放到线程的本地存储区ThreadLocal里面,Looper.loop()方法变开始不断从MessageQueue的队列里面去获取Message消息对象。当Handler发送一条消息的时候,它是把消息存放在了创建它的那个线程里面的MessageQueue队列里面,这个时候取出来执行的时候就会切换线程了。比如线程A里面创建了MessageQueue mqueA,在线程A里面实例化一个Handler handlerA,然后此时在线程B里面,用handlerA发送一个消息,此时就会把消息存放到mqueA,而此时线程A的Looper正在不断地取消息,取到这条消息之后再执行,此时当然是在线程A去执行这条消息了哦,线程也就转换了。如下图所示:
这里写图片描述

2、消息队列的建立
在使用Handler的之前,首先建立消息队列。通过Looper.prepare()方法建立:

final MessageQueue mQueue;//MessageQueue 其实是Looper的一个成员变量

public static void prepare() {
        prepare(true);
}
private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            //一个线程只能创建一个Looper,也只能有一个MessageQueue
            throw new RuntimeException("Only one Looper may be created per thread");
        } 
        //在这里把Looper的对象存在线程的本地存储区里面
        sThreadLocal.set(new Looper(quitAllowed));  
 }

在Looper的构造方法里面,创建了MessageQueue对象,即消息队列的建立

    private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

3、消息队列的循环
通过Looper.loop()方法开启消息的循环:

public static void loop() {
    final Looper me = myLooper();  //从ThreadLocal中获取到之前存入的Looper对象
    final MessageQueue queue = me.mQueue; //获取到消息队列
    //开启无限循环
    for (;;) {
            Message msg = queue.next(); // might block,此方法可能阻塞
            msg.target.dispatchMessage(msg);  //把消息给到handler处理
            msg.recycleUnchecked(); //回收这个Message对象
     }
 }

下面我们接着看看MessageQueue的next()方法:

307 Message next() {
315
316        int pendingIdleHandlerCount = -1; // 这个表示等待执行的Handler的数量
317        int nextPollTimeoutMillis = 0;
318        for (;;) {
323            nativePollOnce(ptr, nextPollTimeoutMillis);  //这里调用naive方法操作管道,由nextPollTimeoutMillis决定是否需要阻塞
            //nextPollTimeoutMillis为0的时候表示不阻塞,为-1的时候表示一直阻塞直到被唤醒,为数值的时候表示阻塞的时长
324
325            synchronized (this) {
326                // Try to retrieve the next message.  Return if found.
327                final long now = SystemClock.uptimeMillis();
328                Message prevMsg = null;
329                Message msg = mMessages;
337                if (msg != null) {
338                    if (now < msg.when) {
339                        // Next message is not ready.  Set a timeout to wake up when it is ready.
340                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
341                    } else {
342                        // Got a message.
343                        mBlocked = false;
344                        if (prevMsg != null) {
345                            prevMsg.next = msg.next;
346                        } else {
347                            mMessages = msg.next;
348                        }
349                        msg.next = null;
350                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
351                        msg.markInUse();
352                        return msg;
353                    }
354                } else {
355                    // No more messages.
356                    nextPollTimeoutMillis = -1;
357                }
358
359                // Process the quit message now that all pending messages have been handled.
360                if (mQuitting) {
361                    dispose();
362                    return null;
363                }
364
365                // If first time idle, then get the number of idlers to run.
366                // Idle handles only run if the queue is empty or if the first message
367                // in the queue (possibly a barrier) is due to be handled in the future.
368                if (pendingIdleHandlerCount < 0
369                        && (mMessages == null || now < mMessages.when)) {
370                    pendingIdleHandlerCount = mIdleHandlers.size();
371                }
372                if (pendingIdleHandlerCount <= 0) {
373                    // No idle handlers to run.  Loop and wait some more.
374                    mBlocked = true;//这里就把阻塞的标志位置为true,然后在循环开始的地方进入阻塞状态
375                    continue;
376                }
377
378                if (mPendingIdleHandlers == null) {
379                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
380                }
381                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
382            }
406
407            // While calling an idle handler, a new message could have been delivered
408            // so go back and look again for a pending message without waiting.
409            nextPollTimeoutMillis = 0;
410        }
411    }

4、消息加入到队列中
发送消息是由Handler自己发送的,我们看看它是怎么把消息放到MessageQueue里面的。
在Handler的最终的构造函数中,从Looper中获取到本线程的MessageQueue对象:

  public Handler(Callback callback, boolean async) {

        mLooper = Looper.myLooper();
        if (mLooper == null) {
           throw new RuntimeException(
                "Can't create handler inside thread that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;

    }

然后在发送的时候,最终会调用sendMessageAtTime:

  public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;
        if (queue == null) {
            RuntimeException e = new RuntimeException(
                    this + " sendMessageAtTime() called with no mQueue");
            Log.w("Looper", e.getMessage(), e);
            return false;
        }
        return enqueueMessage(queue, msg, uptimeMillis);
    }

enqueueMessage方法会调用MessageQueue的入队方法:

 private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this; //在这里看到,msg的target就是handler本身,后面取出消息并分发消息的时候,就是交给handler自己处理
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

接下来就是调用MessageQueue里面的enqueueMessage方法,

    boolean enqueueMessage(Message msg, long when) {
            msg.markInUse();
            msg.when = when;  //when表示执行的时间点
            Message p = mMessages; //mMessages相当于队列最前面需要处理的Message
            boolean needWake; //是否需要唤醒阻塞
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                //队列里面已经没有其他消息,或者队列最前面的执行时间比需要插入的消息的执行时间大的时候,直接操作链表,把消息放到队列最前面。
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;//注意此处,如果当前的队列是出于阻塞的状态,那么mBlocked就会为true
            } else {
                // Inserted within the middle of the queue.  Usually we don't have to wake
                // up the event queue unless there is a barrier at the head of the queue
                // and the message is the earliest asynchronous message in the queue.
                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;
            }

            // We can assume mPtr != 0 because mQuitting is false.
           if (needWake) {
                nativeWake(mPtr);  //调用native方法,唤醒阻塞;
            }
        }
       return true;
   }

5、阻塞与唤醒
从上面看到,在消息队列获取下一个消息的时候,可能会发生阻塞。主要由两种情况导致:a、消息队列已经没有消息了,这个时候nextPollTimeoutMillis置为-1,当前线程进入无限睡眠等待,直到其他线程有消息放入,并把它唤醒。 b、消息队列头的消息执行时间大于当前时间,那么该线程需要睡眠等待后再执行该消息。进入阻塞前,就会把mBlocked标志位置为true。
而在MessageQueue入队的时候,当发现队列是空的,或者发现入队的消息的执行时间比当前处理的消息执行时间小的时候,就会去判断是否是阻塞的标志位mBlocked是否为true,如果是true表示当前线程处于阻塞状态,就会去把当前线程唤醒。

6、Message对象池
其实,我们通过handler发送的消息被利用完之后,这个对象并没有被释放和销毁,而是把它的所有消息清空,标志位复位之后,放到了Message自己维护的一个链表当中。我们通过Message.obtain()方法,就可以获取到一个Message对象供我们使用。谷歌官方也推荐使用obtain()方法获取一个消息对象,而不是通过new的方式。

public final class Message implements Parcelable {

        private static final Object sPoolSync = new Object();
        private static Message sPool;

        public void recycle() {
            //判断消息是否正在使用
            if (isInUse()) {
                if (gCheckRecycle) {
                    throw new IllegalStateException("This message cannot be recycled because it "
                            + "is still in use.");
                }
                return;
            }
            //对于不再使用的消息加入消息池
            recycleUnchecked();
        }

        void recycleUnchecked() {
            //将消息标记为FLAG_IN_USE并清空关于它的其他信息
            flags = FLAG_IN_USE;
            what = 0;
            arg1 = 0;
            arg2 = 0;
            obj = null;
            replyTo = null;
            sendingUid = -1;
            when = 0;
            target = null;
            callback = null;
            data = null;

            synchronized (sPoolSync) {
                //当消息池没有满时,将消息加入消息池
                if (sPoolSize < MAX_POOL_SIZE) {
                    //将sPool存放在next变量中
                    next = sPool;
                    //sPool引用当前对象
                    sPool = this;
                    //消息池数量自增1
                    sPoolSize++;
                }
            }
        }
}
public final class Message implements Parcelable {

    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {

                //sPool当前持有的消息对象将作为结果返回
                Message m = sPool;
                //将m的后继重新赋值给sPool,这其实一个链表的删除操作
                sPool = m.next;
                //m的后继置为空
                m.next = null;
                //清除 in-use 标志位
                m.flags = 0;
                //消息池大小自减1
                sPoolSize--;
                return m;
            }
        }
        //当对象池为空时,直接创建新的Message()对象。
        return new Message();
    }
}

Message的对象链表的实现很有意思,首先定义一个静态的对象sPool以及它的后继对象next。当Message回收的时候,直接把对象的next指向对头,然后sPool指向该节点,那么该节点就成了队头;获取一个对象的时候,直接把sPool指向的对象返回,同时sPool指向下一个节点。

猜你喜欢

转载自blog.csdn.net/yus201120/article/details/82015980