#前言:

  • 在上一篇Handler文章中,我们详细探讨了Handler的sendMessage函数都做了一些什么操作。
  • 接下来,我们来看一看消息被Handler塞进MessageQueue中以后,后续的操作都是什么,最后又是怎么样交回给Handler的。

Looper

Looper是什么呢?

  • Looper是一个消息循环器,本质上来说它就是一个死循环,它不断地在循环遍历自己内部的消息队列(MessageQueue),如果发现有消息要处理,就通知Handler处理
  • 我们看到在Handler的构造中,有对Looper的一个获取:
Looper.myLooper();

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();//可以看到我们通过一个ThreadLocal的变量,获取到了Looper对象
}
  • 消息循环器就是这样获取的,它存放在了ThreadLocal变量中,从代码上看,它是每个线程独一份的存在。也就是一个线程只能有一个Looper。
  • ThreadLocal是一个比较有意思的东西,我们暂时先放下,后面会讲。

Looper创建

  • 那么Looper既然是从ThreadLocal中获取的,那肯定有一个地方创建它吧?
  • 简单,我们先去看一看Looper的构造器:
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}
  • Looper的构造器很有意思,其实只是简单地创建了一个MessageQueue成员。MessageQueue同学很重要啊,但是现在还没到你发炎呢,你先坐下。
  • 同时Looper记录了一下,自己当前处于哪一个线程。
  • 这没啥好说的,主要还是得看看谁调用了Looper的构造器,把Looper带到大家面前来的:
public static void prepare() {
    //实际回调另一个重载函数
    prepare(true);
}

private static void prepare(boolean quitAllowed) {
    if (sThreadLocal.get() != null) {
        //它会先检查ThreadLocal中是不是已经存储了一个Looper,如果是,就报错。因为很明确的:一个线程只能存在一个Looper
        throw new RuntimeException("Only one Looper may be created per thread");
    }
    sThreadLocal.set(new Looper(quitAllowed));//Looper创建好了,还要塞到ThreadLocal中去。
}
  • 哦,找了半天,还是在Looper自己的内部,有一个静态函数prepare()。

  • 哎呀,熟悉Android开发的同学都很了解呀,这个prepare()函数可能跟在座的各位都有不浅的关系。对,说你呢,你是不是有时候在子线程用Handler,抛出过这么一个大名鼎鼎的异常?

    Can't create handler inside thread x that has not called Looper.prepare()

  • 对,就是它,prepare()函数就是用来给线程创建一个对应的Looper对象的函数。

  • 我们注意看下prepare函数里,实际做了一些什么骚操作:

    • 首先去查看了sThreadLocal的get()函数,看是否能够获取到Looper对象,如果可以的话,直接抛出异常:

      Only one Looper may be created per thread

      • 从这里可以证明,一个线程,确定一定以及肯定,只会存在一个Looper对象。

Looper启动:

  • 关于Looper,我们还有一个概念要介绍一下。Looper在构建出来以后,它毕竟是以消息循环器存在的,那这个循环肯定是要启动起来的。
  • 大家也一定都还记得,Looper.loop()函数。loop()函数就是启动Looper的消息循环器进行工作,取出消息进行处理。
  • 因为loop()函数颇长,我们只讨论比较关键的设计原理的部分:
public static void loop() {
    final Looper me = myLooper();//获取Looper对象
    if (me == null) {
        //发现没,到处都是校验Looper是否创建的逻辑,没走Looper.prepare()的朋友,说你呢,别到处看
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    final MessageQueue queue = me.mQueue;//从Looper对象中,获取消息队列MessageQueue
    ...

    for (;;) {
        //从消息队列中,调用next()函数获取下一个消息Message对象
        //这里注意了,源码中提示了一个"might block"的注释,可以暂时先理解为,可能会阻塞的意思
    	//前面我们也说道,MessageQueue入队操作的时候,也有一个needWake的变量,和阻塞有关
        //也就是说当queue的next()函数没有消息的时候,会进行阻塞操作,避免死循环持续执行。毕竟这也会消耗大量的资源不是。
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
            //队列没有消息的话,就不循环了,Looper停止
        }

        ...
        try {
            //这里比较关键,msg对象调用了自己的一个名为:target的成员
            //这个成员,其实就是Handler
            //换句话说,每一个对象中都有一个绑定的Handler对象
            //消息的处理,最终是通过msg-->Handler进行传递的
            msg.target.dispatchMessage(msg);
        } finally {
            ...
        }
        ...

        msg.recycleUnchecked();//Message对象回收
    }
}
  • 总结来说就是,loop()函数首先获取到一个合法的Looper对象,然后拿到Looper的MessageQueue成员。这个我们在Looper的构造器部分提到了。一个Looper只有一个MessageQueue对象。
  • 消息队列MessageQueue管理着所有的消息,调用它的next()就能获取到下一个Message对象。
  • Message对象我们后面会详细介绍,现在大家可以暂时理解为:Message-->Handler是绑定起来的,msg找到Handler,然后将自己回调给Handler
  • loop()函数最关健的地方,就是启动Looper的消息死循环,不断地去队列里轮询消息,保证Handler发送的每一条消息都能够进行处理。

###小结

  • 看到这里,Looper对象的一些关键点,我们都基本过了一遍,其实可以得到一些小总结。
    • Looper在线程中,只有一个
    • MessageQueue在Looper中只有一个,同理线程中也只有一个
    • 子线程想要用Handler,必须调用Looper.prepare(),否则会抛异常,因为Looper是在prepare()里创建的
    • 子线程想要用Handler,还要调用Looper.loop(),否则队列循环不会启动,发出的消息是无法被处理的。

##再说MessageQueue

  • 说完Looper,我们知道了消息循环器的操作。但是还有两个个地方没有细聊,那就是

    Message msg = queue.next(); // might block msg.target.dispatchMessage(msg);

  • 他们关系到三个问题:

    • MessageQueue是怎么获取下一个消息的(出队)?
    • 其次是这个might block注释相关的阻塞是什么?
    • 最后是消息是怎么分发消费的?

###消息出队

  • 首先是消息出队,主要是在loop()函数里,调用了上述的queue.next()函数。
  • MessageQueue的next()函数逻辑也比较多,我简单提炼了一些关键逻辑如下:
Message next() {
    ...
    for (;;) {
        ...
        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;//把mMessages取出,mMessages是在入队的时候赋值的
            if (msg != null && msg.target == null) {
                // Stalled by a barrier.  Find the next asynchronous message in the queue.
    			//这里我觉得是一个兜底策略,当当前消息没有消费的Handler的时候
    			//找它的下一个合法的消息对象
                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();//将Message对象置为正在使用中
                    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;
            }
            ...
        }
        ...
    }
}

  • 这段逻辑主要其实就是队列的查询操作,找到一个当前时间能够进行分发的Message对象进行返回。
  • 找到Message,也就完成了Message的出队操作。那么第一个疑问也就解决了。
  • 接下来就是这个might block注释相关的阻塞是什么?

###队列的阻塞

  • 其实,Looper的概念从一开始就有一个没有理清的概念:

    • 为什么Looper.loop()中明明有一个死循环,并且在线程初始的时候就得调用,还不会导致整个线程死锁?
    • 要知道死循环这个操作,难道不是变相的在调用Thread.sleep()吗?
  • 其实消息队列其实也不是始终处于死循环的。needWake变量就是在控制是否需要调用nativeWake()函数,唤醒底层阻塞操作。

  • MessageQueue中的阻塞操作,其实是利用了Linux pipe/epoll机制。

  • 简单说来,就是next()函数调用的时候,如果没有新的消息了,那就会在这个函数里阻塞起来:

    nativePollOnce(ptr, nextPollTimeoutMillis);

  • 这个函数由底层决定是否执行linux层的阻塞操作。同时传入的nextPollTimeoutMillis变量,就是当有一个指定时间操作的消息时,底层唤醒阻塞的时间变量。

  • 这样的设计,Java层的线程就不会因为Looper的死循环而导致死锁,又能通过这种机制节约资源。

  • 而当nextPollTimeoutMillis为-1传入的时候,是不是就永久阻塞了呢?

    • 不是的。因为在前面Handler的入队操作中,还有needWake变量控制是否需要调用nativeWake(),唤醒阻塞,进行消息循环。
  • 由此,我们基本就知道了Looper最终的结构。也明白了为什么这么一个Bug死循环,也没有阻塞线程的执行,UI线程即使有这么一个死循环,也照样画UI快的飞起。

  • 要知道,很多面试官在聊Handler的时候,都喜欢冷不丁问出来这个问题:

    诶?你说Looper是一个死循环的消息循环器,那为什么没有阻塞主线程,原理是什么

  • 嗯,别怕,勇敢告诉他,都是假死循环!!!

###分发消息

  • 最后一个疑问点了,Message对象也被取到了,阻塞也被唤醒了,消息是怎么回到Handler手里,交到Handler的handleMessage()手上的呢?

  • 其实我们也看到了,关键就在loop()函数中的这一句代码:

    msg.target.dispatchMessage(msg);

  • 最后回调的是msg.target中的函数。因为target我们一早就说了是Handler在Message中的一个成员,其实就是回调Handler中的函数:

public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        //msg中也可以设置Callback,如果设置了,就会优先回调msg中的Callback接口
    	//你可还记得new Handler().post(),传入了一个Runnable对象吧?这里就是优先回调它的
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            //之前说到的,Handler中的mCallback对象,在构造器中初始化
            //如果用户设置了Callback,就回调mCallback
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        //如果上述的回调都没有设置,就回调默认的handleMessage()
        //由此可见,平常我们最常用的回调,其实优先级别是最低的
        handleMessage(msg);
    }
}

#总结

  • 整体走下来,我们可以脉络清晰地知道Handler从创建,到消息发出,到入队、以及出队、又分发给Handler自己的handleMessage(),到底经历了哪些过程。
  • 下面是Handler体系的一些简单的总结:
对象 作用
Looper 消息循环器 每个线程有只能有一个
Message 消息对象 可以有多个,但是又消息池进行复用
MessageQueue 消息队列,存放Message 每个线程有且只有一个
Handler 消息发送、接收、处理着 一个Looper可以有多个Handler进行绑定,绑定后Handler的消息只能发给这个Looper,虽然可以多个但是不建议new太多,会内存泄漏的
  • 关于Handler、Looper、Message、MessageQueue,我们都已经介绍完了。最后还剩下一个ThreadLocal,但是这个ThreadLocal其实不是在Android系统特有的东西,他是一个Java特性的概念。感兴趣的同学可以看我的下篇,我们慢慢再讲讲。

 评论