Android Hanlder消息机制源码浅析

开始

Android的消息机制主要是围绕Handler展开的,而对于Handler大家都再熟悉不过了,在开发过程中,有时候需要在子线程中进行耗时的操作,比如访问网络、I/O操作,当耗时操作完成之后需要在UI上做一些改变,由于Android开发的限制,我们不能在子线程中直接访问UI控件,这个时候就可以通过Hanlder将更新UI的操作切换的主线程中执行。

为什么Android系统不允许在子线程中更新UI呢?因为Android的UI控件不是线程安全的,如果在多线程中并发访问可能会导致一些不可预期的并发问题。那为什么不对UI控件的访问加上同步操作呢?主要是因为加锁会让UI访问的逻辑变得复杂,同时加锁也会降低访问的效率。

很多人认为Handler的作用是更新UI,的确没错,但是更新UI仅仅是Handler的一个特殊使用场景。

Android消息机制模型

Android的消息机制并不复杂,主要涉及一下这几个类:
Handler: 负责消息的发送和处理;
Message: 消息对象;
MessageQueue:消息队列,负责接收Handler发送过来的消息,它的实现是一个单链表;
Looper: 消息循环器,负责遍历取出消息队列里面的消息;

下图为Handler的消息模型

源码解析

关于Hander的使用这里就不多说,现在我们就从源码上看看,这几个类是如何运作的。

Handler的构造方法有很多,这里只要关注这一个:

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;
        mCallback = callback;
        mAsynchronous = async;

    }

这里可以看到Handler中的mLooper是通过Looper.myLooper()获得的,现在看看它里边的实现:

static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

 public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

这里直接是从sThreadLocal 中获取的,那么一定事先在某个地方会把Looper放进sThreadLocal里面。

我们知道,在主线程中的Handler是可以直接使用的,但在子线程中要先调用Looper.prepare()和Looper.loop()这两个方法,这是因为在应用程序启动的入口中,系统已经帮我们调用了,这个入口就在ActivityThead的main()方法中,这里我们可以看一下:

public static void main(String[] args) {

        ...

        Looper.prepareMainLooper();

        ActivityThread thread = new ActivityThread();
        thread.attach(false);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        ...

        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

和Java程序的main方法一样,Android应用的main方法就是ActivityThread中,关于这部分知识我们以后再讨论。这里可以看到调用了Looper.prepareMainLooper(); 和 Looper.loop();

那么我们就看看Looper.prepareMainLooper(); 里做了什么操作:

public static void prepareMainLooper() {  
        prepare(false);
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();
        }
    }

这里可以看到是调用了prepare(),之后再给sMainLooper赋值,方便以后使用。

private static void prepare(boolean quitAllowed) {  
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

就是这里了,创建了Looper并set到了sThreadLocal中,所以前边我们可以调用myLooper()获取到。这里需要注意的是,在不同的线程中调用myLooper()获取到的Looper对象是不同的,明明是同一个对象里的同一个字段,为什么会不同呢?这就是ThreadLocal的作用了。

ThreadLocal的工作原理
ThreadLocal是一个线程内部的数据存储类,通过它可以再指定的线程中存储数据,只有在同个线程中可以获取到。一般来说,当某些数据是以线程为作用域并且不同线程具有不同的数据副本的时候,就可以考虑采用ThreadLocal,比如我们这里的Looper。那么它是怎么实现这个功能的呢?接下来我们看看它的set()和get()方法。

注意:不同版本的源码中ThreadLocal的实现有所不同,这里讲解的是Android-25中的.
public void set(T value) {  
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null)
            map.set(this, value);
        else
            createMap(t, value);
    }

ThreadLocalMap getMap(Thread t) {  
        return t.threadLocals;
    }

可以看到Thread对象里面有一个ThreadLocalMap属性,数据就是保存在这个key-value的map里面的,保存的时候key就是当前ThreadLocal对象,所以在不同的线程中可以获取到不同的副本,因为数据是保存在Thread这个对象里面的。

接下来回到Looper.prepare()里:

private static void prepare(boolean quitAllowed) {  
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

这里Looper是在这里创建的:

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

可以看到MessageQueue是在Looper里创建的一个对象,quitAllowed参数的意思是是否允许停止的意思,主线程中传入的是false,因为一旦主线程的Looper停止了,整个Android系统的消息通信就瘫痪了。

现在Looper的创建流程就完成了,现在回到Handler的构造方法中:

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;
        mCallback = callback;
        mAsynchronous = async;

    }

Handler获得了当前线程中的Looper和MessageQueue,现在就可以发消息了。sendMessage()方法最终会调用下面的方法:

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);
    }

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

代码很明了,就是往MessageQueue里面插入消息,这里值得注意的地方是msg.target = this,这里把当前Handler的引用赋给了target属性,主要是为了回调使用。接着往下看:

boolean enqueueMessage(Message msg, long when) {

       ...

        synchronized (this) {

           ...

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            if (p == null || when == 0 || when < p.when) {
                // 此时,新消息会插入到链表的表头,这意味着队列需要调整唤醒时间啦。
                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;
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

前面说到过MessageQueue是通过链表存储消息的,mMessages就是链表的表头。
查入消息的动作并不复杂,无非是在消息链表中找到合适的位置,插入Message节点而已。因为消息链表是按时间进行排序的,所以主要是在比对Message携带的when信息。消息链表的首个节点对应着最先将被处理的消息,如果Message被插到链表的头部了,就意味着队列的最近唤醒时间也应该被调整了,因此needWake会被设为true,以便代码下方可以走进nativeWake()。

好了现在消息插入到MessageQueue后,就到了最后一步取出消息了,先看看Looper.loop()方法:

public static void loop()  
{
    final Looper me = myLooper();
    . . . 
    final MessageQueue queue = me.mQueue;

    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    for (;;) {
        Message msg = queue.next(); // might block
        . . .
        msg.target.dispatchMessage(msg);  // 派发消息
        . . . 
        final long newIdent = Binder.clearCallingIdentity();
        . . .
        msg.recycle();
    }
}

这里是一个死循环遍历MessageQueue里的消息,并调用msg.tartget.dispatchMessage(msg)来分发消息,这里的target对象就是发送该消息的Handler对象,如果队列里面没有消息的时候,queue.next()会阻塞。

Message next()  
{
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;

    for (;;) {
        . . . . . .
        nativePollOnce(mPtr, nextPollTimeoutMillis);    // 阻塞于此
        . . . . . .
            // 获取next消息,如能得到就返回之。
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;  // 先尝试拿消息队列里当前第一个消息

            if (msg != null && msg.target == null) {
                // 如果从队列里拿到的msg是个“同步分割栏”,那么就寻找其后第一个“异步消息”
                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 (false) Log.v("MessageQueue", "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 (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;
            }
        . . . . . .
        // 处理idle handlers部分
        for (int i = 0; i < pendingIdleHandlerCount; i++) {
            final IdleHandler idler = mPendingIdleHandlers[i];
            mPendingIdleHandlers[i] = null; // release the reference to the handler

            boolean keep = false;
            try {
                keep = idler.queueIdle();
            } catch (Throwable t) {
                Log.wtf("MessageQueue", "IdleHandler threw exception", t);
            }

            if (!keep) {
                synchronized (this) {
                    mIdleHandlers.remove(idler);
                }
            }
        }

        pendingIdleHandlerCount = 0;
        nextPollTimeoutMillis = 0;
    }
}

这个函数里的for循环并不是起循环摘取消息节点的作用,而是为了连贯“当前时间点”和“处理下一条消息的时间点”。简单地说,当“定时机制”触发“摘取一条消息”的动作时,会判断事件队列的首条消息是否真的到时了,如果已经到时了,就直接返回这个msg,而如果尚未到时,则会努力计算一个较精确的等待时间(nextPollTimeoutMillis),计算完后,那个for循环会掉过头再次调用到nativePollOnce(mPtr, nextPollTimeoutMillis),进入阻塞状态,从而等待合适的时长。

最后,看看Handler是如何处理从队列里取出来的消息的:

public void dispatchMessage(Message msg) {  
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

这里有几个消息处理分支:
1.如果发送的Message有设置callback方法,那么就会回调该方法;
2.如果Handler设置了mCallback,则会回调该方法,并且根据方法返回值判断要不要走 handleMessage();
3.如果以上都没设置则走handleMessage()。

提问

到这里整个消息机制就大体说完了,其实流程还是比较清晰的。我在第一次学习的时候产生了几点疑问,这里来个自问自答吧.

1.主线程的入口中调用了Looper.loope()后进入死循环 ,那么主线程如何处理其他UI操作呢?
答:其实整个Android应用都是围绕着这个消息机制运作的,包括Activity、Service的运行,View的绘制,事件交互,它们都是通过向MainLooper中的MessageQueue发送消息进行通讯的,如果Looper.loop()方法退出了,那么程序就得退出了。这部分知识属于ActivityThread,下面会专门写一篇关于这部分知识的文章。

2.Handler是怎么切换线程的?
答:第一次完整的看下整个消息流程后,却没想明白它是怎么做到线程切换的,其实很简单,因为Looper线程是一直循环遍历着队列的,handler通过将不同线程里的消息发送到消息队列,正是通过消息队列这个中介实现了线程的切换。

3.子线程的Handler可以更新UI操作吗?
答:是可以的,但必须传入的是MainLooper,因为决定Handler运行在哪个线程的是Looper.loop()方法是运行在哪个线程,看下面的例子:

public class MainActivity extends AppCompatActivity implements View.OnClickListener {

    private TextView textView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        textView = (TextView) findViewById(R.id.text);
        findViewById(R.id.btn).setOnClickListener(this);
    }

    @Override
    public void onClick(View v) {

        new Thread(new Runnable() {
            @Override
            public void run() {
                Handler handler = new Handler(Looper.getMainLooper(), new Handler.Callback() {
                    @Override
                    public boolean handleMessage(Message msg) {
                        switch (msg.what) {
                            case 1:
                                textView.setText("更新成功");

                                break;
                        }
                        return false;
                    }
                });
                handler.sendEmptyMessage(1);
            }
        }).start();
    }
}

参考 聊一聊Android的消息机制 、 Android开发艺术探索