Handler机制源码解析

一、基本

Handler有很多方法可以用来发送消息,一系列的post、send方法。Handler机制通常用来进行异步消息处理,比如常见的在子线程得到结果通过Handler发送给出去,在主线程收到结果进行UI更新。

1、通过sendMessage来发送消息

通常发送消息可以这样做:这里只是发送了一个空的消息,Message提供了很多字段用于携带数据。

Message msg=Message.obtain();
handler.sendMessage(msg);

对于接受消息,可以派生一个Handler的子类,并重写它的 handleMessage() 方法,消息最终会传递到这个方法中,我们可以在这里处理消息。

  static Handler handler=new Handler(){
        @Override
        public void handleMessage(Message msg) {
            //do some things
        }
    };

2、通过post来发送消息

handler.post(new Runnable() {
            @Override
            public void run() {
                //这里是具体要执行的任务
            }
});

当我们将这个Runnable对象投递出去,会在handler端被接收到,并调用它的run方法,这样run方法的逻辑代码就可以在Handler所在线程执行了。

当我们发送消息,这条消息会被插入到消息队列中,同时会去唤醒处理消息的队列,队列被唤醒后就会去取出消息,然后将消息分发给对应的handler处理。现在让我们通过源码来看看它的本质。

二、消息是如何插入消息队列的?

当我们调用了handler.sendMessage(msg);它会将消息插入消息队列中,如何插入的呢?

Handler机制源码解析_第1张图片

 

sendMessage发送消息最终是通过调用消息队列的enqueueMessage方法将消息插入消息队列的,这里有两点需要注意的地方:

delayMillis代表了延迟多久处理这个消息,我们发送延迟消息就是通过它实现的。
mAsynchronous代表了发送消息的Handler是否是发送异步消息的Handler,如果是就将消息设置为异步消息,其实就是设置一个标志位,发送异步消息的Handler可以在构造的时候指定。关于异步消息先了解下就好。

Handler机制源码解析_第2张图片

 

继续跟进,看看消息队列是如何插入消息的?

boolean enqueueMessage(Message msg, long when) {
            //...
            //mMessages是消息链表的表头
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
             //如果消息链表为空||当前的消息需要立即执行||当前的消息比消息链表头部的消息执行要早
            if (p == null || when == 0 || when < p.when) {
                // 上面的条件都满足就将消息插入到链表的表头
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
               
                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;
    }

 

这样消息就成功插入消息队列了,可以看到消息队列的数据结构其实就是一个链表,并且越早执行的消息在链表中的位置越靠前。当将消息插入链表后就去通知处理消息的线程去取出消息,将其分发给对应的handler。

三、消息是如何取出和分发的?

取出消息,分发消息是借助Looper来实现的,如果我们的Handler是在主线程创建的,我们不用关心Looper的创建,因为应用的入口函数ActivityThread.main中已经创建好了,不过如果我们想在子线程中创建Handler就需要自己创建Looper了。

 new Thread(new Runnable() {
            @Override
            public void run() {
                Looper.prepare();
                handler=new Handler(){
                    @Override
                    public void handleMessage(Message msg) {
                        //do some things
                    }
                };
                Looper.loop();
            }
        }).start();

Looper.prepare方法会创建一个Looper对象,保存在线程的本地变量中,同时Looper持有一个消息队列。

 public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
        //从这个可以看到一个线程只能调用一次prepare,只能有一个Looper。
        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);
    }

现在我们可以知道,一个线程只有一个Looper,并且只有一个消息队列,但可以有多个Handler。

知道了Looper和消息队列的创建,再来看看Looper.loop();是如何分发消息的?

 public static void loop() {

        //获取线程本地变量保存的Looper
        final Looper me = myLooper();
        if (me == null) {//在调用loop方法之前应该先调用Looper.prepare()
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }

        //取出Looper持有的消息队列
        final MessageQueue queue = me.mQueue;
        //循环,有消息就取出,分发给对应的handler处理
        for (;;) {

            //当有消息时queue.next()才会返回改消息,否则阻塞
            Message msg = queue.next(); 
            if (msg == null) {//当出错了或者消息队列退出了queue.next()才会返回null
                return;
            }
            
            //分发消息给对应的handler,这里消息持有发送者,target是在发送消息时设置的
            msg.target.dispatchMessage(msg);
               
            
            msg.recycleUnchecked();
        }
    }

为了理清主线,这里省略了很多代码,loop()方法主要的工作就两点:

  1. 如果没消息,queue.next() 就阻塞在这里,有消息了就返回消息。
  2. msg.target.dispatchMessage(msg)  取出消息后将消息分发给对应的handler。

如果没有消息queue.next() 就会阻塞在这里,有消息就返回。所以这个方法就是负责从消息队列中取出我们之前插入的消息。

//MessageQueue#next 
Message next() {
            for (;;) {
            //阻塞 等待被唤醒或者超时(当有消息被插入或者超时就会被唤醒)
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                final long now = SystemClock.uptimeMillis();
                //取出消息队列的第一个消息,也就是链表的表头
                Message msg = mMessages;
                //...
                if (msg != null) {
                    if (now < msg.when) {
                        //如果这个消息还没到触发时间就设置nextPollTimeoutMillis,也就是nativePollOnce的超时时间
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        //如果消息到了触发时间就返回这个消息,
                        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 {
                    // 如果没有消息就一直阻塞,直到有消息被插入
                    nextPollTimeoutMillis = -1;
                }

               
        }
    }

消息队列的next()方法是一个阻塞方法,原因就在nativePollOnce,如果没有消息要处理就会被阻塞在这里。之前说过,当我们发送消息,消息会被插入消息队列,然后调用nativeWake(mPtr)唤醒消息处理线程,就是来唤醒nativePollOnce方法的,这里被唤醒后就会去取出最近的一条消息,然后判断这个消息是否到了执行时间,如果没到就设置nextPollTimeoutMillis的值代表还有多久才能执行。nextPollTimeoutMillis是nativePollOnce这个方法的超时时间,超时时间到了,自己就醒了,然后再去取消息,返回消息。

next()这个方法是重点,一定要好好理解,这里我还是省略了许多的代码,只是为了理清主线,不代表这些被省略的代码不重要,事实上他们很重要,不过这些涉及到消息屏障和异步消息,后面有时间再写一篇。

看完消息队列的next()我们就你知道消息是如何从消息队列中取出的,最后再来看看消息是如何分发的?

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

        for (;;) {
            //取出消息
            Message msg = queue.next(); // might block

            //将消息分发给对应的Handler处理
            msg.target.dispatchMessage(msg);
            
        }
    }

 msg.target指向我们发送消息的Handler,这个在发送消息时就设置了。

//Handler#dispatchMessage 
public void dispatchMessage(Message msg) {
        if (msg.callback != null) {//1、如果消息设置了callback
            handleCallback(msg);
        } else {
            if (mCallback != null) {//2、如果设置了全局的Callback
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            //3、这个是创建Handler时我们重写的handleMessage方法
            handleMessage(msg);
        }
    }

这里分三种情况:先看注释1,如果我们的消息设置了callback,就执行 handleCallback(msg)方法,这个callback其实就是一个Runnable。handleCallback(msg)其实就是调用这个Runnable的run方法。

Handler#handleCallback
private static void handleCallback(Message message) {
        message.callback.run();
    }

这个callback其实就是我们调用handler.post方法传入的Runnable对象:

Handler机制源码解析_第3张图片

再看注释2:如果设置了全局的Callback就调用该Callback的handleMessage(msg)去处理消息

Handler机制源码解析_第4张图片

最后注释3:就是调用我们创建Handler时覆写的handleMessage去处理消息。

Handler机制源码解析_第5张图片

到现在Handler的工作机制基本就说完了。关于nativePollOnce阻塞和插入消息后使用nativeWake(mPtr)唤醒阻塞,这两个方法都是native层的,我也没搞清楚,大致理解就是nativePollOnce会阻塞等待描述符有可读的事件,当其他线程往消息队列插入消息后会调用nativeWake,该方法最终会向eventfd写数据,导致nativePollOnce收到可读事件被唤醒,其实就是一个通知的作用。具体可以google下:Linux进程间通信eventfd有关的知识来详细了解。

 

结束语

我们创建一个 Message对象可以直接  new Message() 创建一个,也可以通过 Message.obtain()来获取一个。但我们最好的方式是通过 Message.obtain(),它每次都会从可回收的对象池(以链表的形式)中获取一个Message对象,如果有的话。这样就避免了对象的创建,降低了内存的开销。在消息处理完成后又将其放回对象池便于下次复用。我们注意到在Looper的loop()最后一行:

    public static void loop() {
           ...
            msg.recycleUnchecked();
        
    }

就是对Message对象进行回收。

   void recycleUnchecked() {
        //将消息对象的所有字段置初值
        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) {
                next = sPool;
                sPool = this;
                sPoolSize++;//链表中的对象数+1
            }
        }
    }

                                                                                                                                                                        ————大四上记。

你可能感兴趣的:(Android源码相关)