Handler消息传递机制(二) 教你认清Handler,Looper,MessageQueue

摘要:
在上篇文章中跟大家介绍了更新UI的几种方法,由此引出来的Handler消息传递的过程。这篇文章主要跟大家讲解,Handler,Looper,MessageQue他们分别是什么以及之间是如何合作的。当前,还是从源码看起来。

Handler

由第一篇文章可以知道,通过那几种方法去更新UI,最终都是调用到了handler.post(),那么这个Handler究竟是什么呢?
我们看下官方的说法

A Handler allows you to send and process Message and Runnable objects associated with a thread's MessageQueue. Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler, it is bound to the thread / message queue of the thread that is creating it -- from that point on, it will deliver messages and runnables to that message queue and execute them as they come out of the message queue.

There are two main uses for a Handler: (1) to schedule messages and runnables to be executed as some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.

用粗略的英语翻译下就是:
Handler允许你发送一些处理信息和一些线程队列的Runnable对象。
Handler里面每一个实例都与一个单一的线程和消息队列绑定一起。当你创建了一个Handler,也就一定创建了线程或者消息队列。之后就可以传送分发消息和Runnabes到消息队列中,等待消息队列的执行。也就是说,Handler主要作用在于:
①在不同的线程中排列消息或Runnable,等待调度
②执行调度的消息或Runnable

很清晰的阐明Handler是用来进行消息传递和执行的,那么Handller是如何进行消息传递和执行的,我们来进一步看下源码

  • 先看下Handler的构造方法
    /* @param callback The callback interface in which to handle messages, or null.
     * @param async If true, the handler calls {@link Message#setAsynchronous(boolean)} for
     * each {@link Message} that is sent to it or {@link Runnable} that is posted to it.
     *
     * @hide
     */
    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的调用是同步还是异步,默认是同步的
可以看到,创建Handler中也有Looper和MessageQueue,注意,我是说有,而不是说创建,也就是说Looper的创建并不是在Handler,而是在线程中创建的,这一点在介绍Looper的时候会更大家说。有了Looper和MessageQueue,正好应正了API说的,Handler是用来进行消息传递的,而消息传递正是通过Looper和MessageQueue两者之间的协调工作进行的。

Looper

相信看到这里,你会迫不及待的想要知道Looper了,别急,慢慢来。先看官方介绍

Class used to run a message loop for a thread. Threads by default do not have a message loop associated with them; to create one, call prepare() in the thread that is to run the loop, and then loop() to have it process messages until the loop is stopped.

Most interaction with a message loop is through the Handler class.

为线程运行一个消息循环,默认的线程并不会有消息循环,这里的默认线程就相当于自己new Thread一样,想要创建一个消息循环的话则要先调用Looper.prepare()来运行循环,然后再调用Looper.loop()开始处理消息,直到循环停止。

我们来看下Looper的源代码

public final class Looper {
    ...

    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    private static Looper sMainLooper;  // guarded by Looper.class

    final MessageQueue mQueue;
    final Thread mThread;

}

这里列出来的几个成员变量都是很重要的。

  • 首先这里有个静态的成员变量ThreadLocal , 注意这里面的形参是Looper。这里跟大家解释下ThreadLocal这个类,当然,你们也可以自己搜索的,这里就简单的跟大家介绍下。
    ThreadLocal相当于一个线程存储类,可以将当前类的有关信息保存在当前的线程中,这个类的信息就相当于绑定在这个线程。可以看到这里的类是Looper,也就是将Looper存储在当前线程,ThreadLocal可以通过get在当前线程中拿出Looper,同样可以在当前线程中set进Looper。

  • mMainLooper,也就是当前Looper对象的引用

好,回到前面说的,前面说到线程要使用循环消息就要先调用到Looper.prepare(),我们看下prepare里面做了什么


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

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

    ...

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

    ...
    /*
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     */
    public static @Nullable Looper myLooper() {
        return sThreadLocal.get();
    }

可以看到prepare里面会先去从ThreadLocal中get个Looper,如果get到了,则报错,提示”一个线程只能有一个Looper“,这个好理解,Looper是与线程绑定的。为空的就调用了 threadLocal.set方法,把新创建的Looper放到ThreadLocal里,而Looper的构造方法中,则创建了MessageQueue和Thread。这里可能大家有个疑问,明明这里获取当前线程但是也没见得线程mThread有在ThreadLocal中跟Looper绑定,我们看下ThreadLocal里面set的源码

 public void set(T value) {
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values == null) {
            values = initializeValues(currentThread);
        }
        values.put(this, value);
    }

可以看到ThreadLocal里面还是自己调用获取当前线程的方法去绑定。

可见Looper.prepare方法则是进行创建Looper以及创建MessageQueue的作用,为之后loop做好准备。
通过调用Looper.myLooper()就可以获取到Looper对象了。

下面这个则是最关键的,那就是Looper.loop()方法,prepare只是构建基础东西,而loop则是真正的执行消息调度操作。

/**
     * Run the message queue in this thread. Be sure to call
     * {@link #quit()} to end the loop.
     */
    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;
            }
            ...
            msg.target.dispatchMessage(msg);
            ...
            msg.recycleUnchecked();
        }
    }

可以看到Looper中就是进行MessageQueue的调度,里面for循环获取Message,通过msg.target.dispatchMessage(msg) 来调度Message,直到所有消息执行完则退出。
这里msg.target则是对应的Handler,也就是处理Message还是通过Handler来处理,而把Message加入队列,其实也是Handler,这也应证了前面说的 Handler是用来排列消息和调度消息的。这里有必要了解Message是如何存储在MessageQueue,我们看下Message的结构


/**
 * ...
 * <p class="note">While the constructor of Message is public, the best way to get
 * one of these is to call {@link #obtain Message.obtain()} or one of the
 * {@link Handler#obtainMessage Handler.obtainMessage()} methods, which will pull
 * them from a pool of recycled objects.</p>
 */
public final class Message implements Parcelable {
    ...
    public int what;
    public Object obj;
    ...
    /*package*/ Handler target;
    /*package*/ Runnable callback;
    ...
    // sometimes we store linked lists of these things
    /*package*/ Message next;
    ...
}

开头的注释则说明为了更好的回收利用Message,创建Message最好使用Message.obtain()来获取,相应大家看到这个应该不会陌生,可能之前有用过,但是不明白为什么这样。
好了,看它里面几个重要的成员变量。
* Handler target:表示Message来自于哪一个Handler,前面也说到了,Message是通过Handler来调度的。在创建Message中就绑定了对应的Handler,才能进行调度处理

 public static Message obtain(Handler h) {
        Message m = obtain();
        m.target = h;

        return m;
    }
  • Message next:注释里面说了,用来链式存储Message,也就是说Message的存储是链式存储,通过每一个Message.next来指定下一个Message
  • int what , Object obj , Runnable callback:则是对应的消息内容。

好,回看Looper.loop(),里面处理消息是通过

msg.target.dispatchMessage(msg);

也是handler.dispatchMessage

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

看到handleCallback和handleMessage都差不多,最终都是调用到了callback接口。这个callback接口就是

 public interface Callback {
        public boolean handleMessage(Message msg);
    }

而我们以往使用Handler都是

Handler handler = new Handler(){
            @Override
            public void handleMessage(Message msg) {
                super.handleMessage(msg);
            }
        }

重写了Handler里面的handlerMessage,也就是在这里进行消息的处理。
到这里大家对Handler,Looper,MessageQueue他们之间的功能作用都很清晰了。上面介绍的就是消息的调度过程,但是还忘了,这些消息是在哪里发送出去的,也就是如何添加到MessageQueue。
这里回想第一篇文章讲的,子线程更新UI最终是调用了handler.post(Runnable ),没错,就是这个
我们来看下这个的源码就知道了。

public final boolean post(Runnable r)
    {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }
//这里getPostMessage则是根据Runnable来创建对应的Message,大家可以点进去看。

public final boolean sendMessageDelayed(Message msg, long delayMillis)
    {
        ...
        return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
    }

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

可以看到handler.post里面做了
创建对应的Message -> 把Message通过queue.enqueueMessage加入到链表中
而queue.enqueueMessage是如何存放Message

 boolean enqueueMessage(Message msg, long when) {
            ...
            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
           ...
                // 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;
                ...
        }
        return true;
    }

看for循环里面,找到之前链表的末节点,再通过msg.next = p,把当前Message放入到后面。之后呢就等待调度了。

至此,Handler,Message,Looper,MessageQueue他们之间的工作相信大家有个清楚的概念了

小结

  • 消息的传递处理都是在Handler中
  • Message以链表的形式存储
  • Looper是与对应的线程绑定的,线程里要进行消息传递就必须要先调用Looper.prepare来创建Looper,官网里有个例子就是
  class LooperThread extends Thread {
      public Handler mHandler;

      public void run() {
          Looper.prepare();

          mHandler = new Handler() {
              public void handleMessage(Message msg) {
                  // process incoming messages here
              }
          };

          Looper.loop();
      }
  }
  • Looper是用来循环消息的,它的创建通过Looper.prepare,而开始调度通过Looper.loop,循环从MessageQueue取出消息来调度
  • Handler通过Looper,Looper里的MessageQueue来进行消息传递处理

疑问


  • 平常使用Handler的时候好像我没有调用Looper.prepare啊,没有调用也可以直接使用Hander来进行消息传递处理?

答:要知道,平常我们用的Handler都是运行在主线程,也就是ActivityThread,而ActivityThread则是应用启动配置后,就创建了。ActivityThread的创建使用调用它的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");
    }

可以看到,Looper已经在ActivityThread创建的时候就创建了,这也就是为什么平常我们使用Handler的时候不用调用Looper的原因,如果要在自己的线程中进行消息传递,则需要调用Looper.prepare,loop了。就像官网所言。

如果有什么错误的,还得麻烦大家替我指正下。共勉。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值