深入理解Handler机制

文章目录

      • 消息机制概述
      • 储备知识
        • ThreadLocal
        • epoll线程唤醒技术
      • Handler
        • 消息发送
        • 消息处理
      • Looper
        • prepare()
        • loop()
      • MessageQueue
        • 插入消息
        • next()
        • SyncBarrier
        • epoll使用
        • 消息池
      • 扩展说明
        • Handler如何跨进程通信
      • 消息机制的应用
        • ActivityThread
        • HandlerThread
      • 按优先级处理的消息机制

消息机制概述

  在Android系统主要的通信机制有binder机制和handler消息处理机制,前者多用于进程间的通信,后者则多用于进程内线程间的通信。其实,android进程就是基于消息驱动的。

  一般而言,消息处理机制都应含以下几个要素:

  • 消息对象
  • 消息的投送者
  • 目标消息队列
  • 消息的处理(轮询器)

  在Android系统中上述要素大致对应为Message、Handler、MessageQueue、Looper。当然,从细节上来说Handler不止作为消息的发送方存在,同时还承担了消息处理的dispatch工作。在多数开发场景(主要是MainThread环境)中,得益于系统良好的封装性,开发者可在无感知Looper与MessageQueue的存在下只需一个Handler就可完成消息的投送以及处理,十分简单好用。也因于此,使得开发者难以一窥全貌,了解消息处理机制的核心所在。

  那么handler消息处理机制都有什么特点呢?

  • 一个android线程至多对应一个Looper。不难理解,轮询器处于循环处理消息的过程中,loop()循环之后的代码根本无法执行到。
  • 一个Looper始终只轮询一个MessageQueue。具体表现为在Looper构造方法中创建了MessageQueue,并在Looper接下来的过程中都只使用此MessageQueue。
  • Handler与Looper(MessageQueue)之间可以是多对一的关系。
  • 消息队列中的消息FIFO顺序处理。顺序由插入时间保证。

  在了解上述特点之后,有两个问题值得思考,1.如何保证线程与Looper为一对一关系?2.当前轮询器中如何在没有消息时让位CPU资源,实现阻塞式轮询?下边介绍的储备知识点就是解答这两问题之关键。

储备知识

ThreadLocal

  基于如何在多线程并发环境下,防止线程变量被其他线程打扰或篡改的问题,java早在1.2版本时就引入了Thread的本地存储技术。在当前线程某处set保存变量时,开发者可在该线程任意地方get取出变量,从而完成线程内部变量共享。

  上述所说的线程本地变量都是保存在Thread内部成员变量localValues中。在下边我们贴了Thread类的部分代码,可见localValues是ThreadLocal.Values类型,Values实质上就是一个简化版的hashMap,其内部维护了K-V结构的可扩容数组,开发者通过Thread本地存储的变量都是以一定的映射关系按照K-V形式存放于此。当然,细心的朋友可以观察到inheritableValues同样也是K-V结构的,这里稍提一嘴,inheritableValues所存储的变量不仅线程内部可以访问,当前线程所创建的子线程同样可以访问(其对应的管理方为InheritableThreadLocal类)。

public class Thread implements Runnable {
    ...
    /**
     * Normal thread local values.
     */
    ThreadLocal.Values localValues;

    /**
     * Inheritable thread local values.
     */
    ThreadLocal.Values inheritableValues;
    ...
}

  介绍完线程本地变所保存的位置后,接下来就是如何把线程变量本地化了。然而,通篇查找Thread的方法,却并无直接的set、get方式实现这个功能,这又是怎么回事呢?

  线程变量本地化是通过本小节的主角ThreadLocal完成的。ThreadLocal是通过set/get方式从调用线程的localValues中存取其本地指定类型的变量,我们知道Thread的localValues类似是K-V结构的Map,如果开发者通过ThreadLocal存取变量,则K对应当前ThreadLocal的引用,V类型则由ThreadLocal的泛型指定。

  在百度词条对ThreadLocal的定义中,其多次强调“ThreadLocal并不是一个Thread,而是Thread的局部变量”。我想这并不准确。ThreadLocal应该是作为线程指定类型的本地变量的管理者而存在,ThreadLocal与线程之间是一对多的关系,也就是说一个ThreadLocal具备管理多个Thread的localValues的能力。按此理解,我们经常见到ThreadLocal如下的定义方式(截自Looper类)也就见怪不怪了。

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

  在了解ThreadLocal的意义之后,我们从代码层面来看下。此类所拥有的方法不多,核心方法如下:

 public T get();
 public void set(T value);
 public void remove() ;
 Values initializeValues(Thread current);

  既然ThreadLocal要负责管理各个线程的localValues,那么有一点很关键,它需要具备知道上述核心方法的调用线程的能力,这样才能做到精确打击。事实上,ThreadLocal也确实是这么做的。

  我们以它的set方法为例,在下边代码中通过Thread.currentThread()定位到调用者当前所处的线程,而内部的value()方法即是从当前线程中取出localValues(本地变量存储区),如果为空,则还需初始化localValues。最后,则是通过values的put()方法把当前ThreadLocal和value作为K-V放入当前线程的本地变量存储区中。其实,如果您还愿意继续深究下put方法的话,你会发现K存放的是WeakReference(this),这是基于ThreadLocal和Thread的生命周期不一致可能导致内存泄漏的角度来考虑的。

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

  同理,ThreadLocal的get方法的操作与上边类似。并且从下可知所有通过ThreadLocal存储的线程本地变量都是保存在了Values的table数组中,并且第n个本地变量Value在table中的保存位置index与其所依赖的Key在table数组中的index角标为2n和2n+1的关系。如果通过get找不到调用线程的value,则通过values.getAfterMiss()返回一个空变量。

    public T get() {
        // Optimized for the fast path.
        Thread currentThread = Thread.currentThread();
        Values values = values(currentThread);
        if (values != null) {
            Object[] table = values.table;
            int index = hash & values.mask;
            if (this.reference == table[index]) {
                return (T) table[index + 1];
            }
        } else {
            values = initializeValues(currentThread);
        }

        return (T) values.getAfterMiss(this);
    }

  通过上边的说明,我想对ThreadLocal线程本地存储技术应该大致了解了。那么我们再来回答一下上边的问题1即如何保证线程与Looper为一对一关系。Android的消息机制就用到了ThreadLocal线程本地存储技术。我们知道ThreadLocal通过set/get负责管理调用线程的本地变量,即在set变量后,可在当前线程任意地方通过get取出该变量。那么我们就可通过get判断是否为null来确定当前变量是否set过。事实上,这也是下边的Looper.prepare()方法如何判断在同一线程只被调用了一次的根本。

epoll线程唤醒技术

  先引用下百度词条对epoll的解释。

epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。另一点原因就是获取事件的时候,它无须遍历整个被侦听的描述符集,只要遍历那些被内核IO事件异步唤醒而加入Ready队列的描述符集合就行了。epoll除了提供select/poll那种IO事件的水平触发(Level Triggered)外,还提供了边缘触发(Edge Triggered),这就使得用户空间程序有可能缓存IO状态,减少epoll_wait/epoll_pwait的调用,提高应用程序效率。

  简单来说epoll就是为了提高CPU利用率,而对I/O操作的具体事件进行监听,当监听到事件发生时,会唤醒被阻塞的方法。

  其具体可监听的有file、socket、pipe等I/O流。为了了解epoll的作用,我们拿pipe管道来打个比方。pipe是由内核管理的一块缓冲区构成,正如其名字那样,其分为输入(write)和输出(read)两端。由于输入端和输出端都采取轮询方式读或写数据,由于缓冲区大小受限,且两端轮询基于CPU利用率的关系都采用阻塞式轮询的方式。也就是说对于两端的代码工作者,都是基于有活就开始干活,没活了让出CPU资源方便其它程序使用。对于这种阻塞式轮询的方式,则需要某种机制来解除阻塞。类比于pipe读写两端,就对应于在缓冲区不空时,需通知输出端干活;缓冲区满时,需要通知输入端暂停干活;缓冲区空时,需要通知输出端停止干活;缓冲区不满时,需通知输入端开始干活。我们这里介绍的epoll就是为了解决类似pipe的这四种缓冲区事件监听的机制。

  epoll共有以下三个相关的函数。

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event * events, int maxevents, int timeout);

  --> epoll_create 创建一个epoll对象,size用于告诉内核监听事件的最大数目。

  --> epoll_ctl epoll对象的事件注册/删除/修改函数。此函数总计有四个参数,第一个参数用于告知事件注册的目的地,第二个参数则用于表明当前函数操作类型,第三个参数用于表述当前事件需要监听的fd对象,第四个函数则表示具体监听什么事件。

  epoll操作类型有:

  EPOLL_CTL_ADD:注册新的fd到epfd中。

  EPOLL_CTL_MOD:修改已经注册的fd的监听事件。

  EPOLL_CTL_DEL:从epfd中删除一个fd。

  epoll事件类型:

  EPOLLIN :表示对应的文件描述符可以读(包括对端SOCKET正常关闭);

  EPOLLOUT:表示对应的文件描述符可以写;

  EPOLLPRI:表示对应的文件描述符有紧急的数据可读(这里应该表示有带外数据到来);

  EPOLLERR:表示对应的文件描述符发生错误;

  EPOLLHUP:表示对应的文件描述符被挂断;

  EPOLLET: 将EPOLL设为边缘触发(Edge Triggered)模式,这是相对于水平触发(Level Triggered)来说的;

  EPOLLONESHOT:只监听一次事件,当监听完这次事件之后,如果还需要继续监听这个socket的话,需要再次把这个socket加入到EPOLL队列里。

  --> epoll_wait 用于监听通过epoll_ctl函数所注册事件的发生,该监听是可阻塞式的,具体通过第四个参数timeout指定,如果timeout=-1,则当前epoll_wait会阻塞于此直到epoll_ctl事件所注册监听事件的发生;如果timeout=0,则即时返回监听结果;如果timeout>0,则指代epoll_wait函数至多阻塞的毫秒时间,该时间段内有监听事件发生,则返回监听结果。

  通过上边的说明我们大概了解到了epoll的作用,下边则通过简单例子说明epoll_create、epoll_ctl、epoll_wait在程序中如何发生作用。在通过epoll_ctl监听事件后,epoll_wait每次都会阻塞式的等待监听事件的发生。

int main(int argc, char **argv)  
{  
    //创建epoll
    mEpollFd = epoll_create(MAX_EVENTS);  
    //监听事件
    eventItem.events = EPOLLIN;
    eventItem.data.fd = listenFd;
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, listenFd, & eventItem);
    while(1){  
        ...
        //等待事件发生-阻塞式
        struct epoll_event events[MAX_EVENTS]; 
        int fds = epoll_wait(mEpollFd, events, MAX_EVENTS, 1000);  
        //do something
        ...
    }  
    return 0;  
}  

  在Android消息机制中,当消息轮询器中发现没有消息时,就不能做非阻塞式忙轮询,而是需要在没有消息的时间段,或者消息未到执行时间之前的时间段中,把CPU资源让渡出去,并在消息到来时在唤醒轮询器,这就是上边所说的epoll技术在消息机制中所起的作用。

Handler

  从概述中的消息机制要素的角度来看,一条消息的生命周期过程首先是从“消息的投送者”开始的。在Android消息机制中这个消息投送者的角色就是本小节要讨论的Handler。Handler作为作为消息投送者,它不光要知道投递什么消息,更关键的是它需要知道把消息投送到哪里去!这就好比快递员在送快递时需要知道该快递的地址一样。按此理解,在Handler中一定有一个投送目标,那么这个投送目标是什么呢?目标就是MessageQueue,每个Handler都需要知道自己需要把消息投送到哪个消息队列即MessageQueue中。这也解释了为什么Handler所有的构造方法中都直接或间接的需要Looper,因为MessageQueue是在Looper中创建的,是Looper的成员变量!只有拥有了Looper,Handler才能知道投送的目标。

  所有的Handler构造最终都会调用以下之一。其中的mQueue就是消息最终投送到的地方。也许你还看到Handler把Looper也变成成员变量了,会思考Looper会不会也会在Handler中有不为可知的用处呢?很遗憾的告诉你,这个mLooper变量在Handler中就是个鸡肋,当然,这也是由于这也是由于Handler在整个消息机制中所处的角色决定的。

    public Handler(Callback callback, boolean async) {
        if (FIND_POTENTIAL_LEAKS) {
            final Class<? extends Handler> klass = getClass();
            if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                    (klass.getModifiers() & Modifier.STATIC) == 0) {
                Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                    klass.getCanonicalName());
            }
        }

        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;
		//默认false
        mAsynchronous = async;
    }

    public Handler(Looper looper, Callback callback, boolean async) {
        mLooper = looper;
        mQueue = looper.mQueue;
        mCallback = callback;
        mAsynchronous = async;
    }

消息发送

  消息发送是Handler必须具备的能力。在平时Handler的使用中我们一般都是用postXXX(Runnable)或者sendXXX(Message),这其实就是把消息发送到消息队列中等待处理。至于为什么会有Runnable和Message两种形式,其实两者本质上都是Message,只不过Handler内部对Runnable包装成了一条消息,具体而言就是以下方式包装。

    private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

    private static Message getPostMessage(Runnable r, Object token) {
        Message m = Message.obtain();
        m.obj = token;
        m.callback = r;
        return m;
    }

  可见,Runnable被包装到Message(即最终被投送的消息格式)的callback中。

  无论通过postXXX()还是sendXXX(),最终都会走到Handler的sendMessageAtTime()方法中,由这个方法执行具体的消息投送工作,这个方法代码具体如下:

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    	//最终都会通过此方法把Message入队
        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;
		//就现有的非hide方法来看,此处进不去
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

  在sendMessageAtTime()方法中,我们可以看见最终消息确实被插入到消息队列mQueue中以等待轮询器轮询处理。这几行代码看似没几行,却包含了部分Android消息机制的重要思想。首先,在入队时指定了msg.target=this,这里其实非常重要,为什么呢?对于消息轮询器Looper来说,其并不关心被轮询到的Message具体的作用,消息具体作用是由消息本身内容决定的,这也就要求被轮询到的Message本身不光要知道自己从何处来(source),也要知道自己往哪里去(target),在上述enqueueMessage()中的msg.target指代为Handler消息发送者本身,也即说明了在消息在被轮询到后还应该回到Handler中来处理,正因如此,在Looper.loop()中当轮询到某条消息时会调用其msg.target.dispatchMessage()。其次,在消息插入MessageQueue时,传入了uptimeMillis时间,我们的消息队列就是据此对消息进行排序的。最后,在enqueueMessage中会根据mAsynchronous的值把当前所发的消息置成同步或者异步消息,关于同步异步消息的内容,详见后边关于SyncBarrier的讲解。

消息处理

  当Looper轮询器从消息队列中把某条消息取出后,会根据Message.target把此消息发给消息接收者来处理,这个target就是Handler本身,也就是说Handler不仅作为Message的发送者,还作为该Message最后的处理者。消息的处理是通过dispatchMessage进行的。

    public void dispatchMessage(Message msg) {
    	//callback本质上是一个Runnable,在handleCallback中调用runnable.run()
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
				//此处通过Callback接口方法的返回值决定是否继续传递
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

  在dispatchMessage中,对在发送时包装的Runnable与Message进行了分解处理。如果是Runnable(保存在msg.callback中),直接调用Runnable的run()方法;如果是Message,则先检查在创建Handler时是否传入了Handler.Callback,如果存在,则视Callback方法返回值决定是否打断消息传递。可以看见对于Message,消息处理最终放入了handleMessage()方法中,这也是为什么我们通常在创建一个Handler时需要重写其handleMessage()方法的关键所在。

  另外,在Handler中,还拥有一些辅助方法来移除/查询消息队列中的Message的功能,这里就不展开说了。

Looper

  Looper即轮询器,它是Android的消息机制能够运行的核心所在,其内部拥有一个MessageQueue消息队列,外界可通过构造Handler往此队列中投送消息,而Looper依靠阻塞式轮询方式把这些消息取出进行后续处理。但是Looper却是依赖于具体的线程,篇首有说过,Looper因为其使用for(;;)语句不断轮询的特点,所以一个线程至多拥有一个Looper,而而这种线程与Looper之间一对一的关系又是依赖于之前介绍的ThreadLocal本地存储技术来保证的。Looper到底怎么运作,我们来看下边代码介绍。

prepare()

  此方法是类方法,用于使用ThreadLocal技术在此方法调用线程构造一个唯一的Looper对象。

    // sThreadLocal.get() will return null unless you've called prepare().
    static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
    
    public static void prepare() {
        prepare(true);
    }

    private static void prepare(boolean quitAllowed) {
    	//同一线程中prepare只能被调用一次
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
		//调用prepare的目的是往ThreadLocal中保存Looper实例,方便在当前线程任意地方取出
        sThreadLocal.set(new Looper(quitAllowed));
    }

  通过prepare方法阅读可以发现,这个方法在同一个线程中是不能被多次调用的,多次调用会抛出Exception。同时为了方便理解,我们也把Looper具体存储管理者sThreadLocal给贴出来了。当然,在该线程任意地方都可以通过以下方式取出唯一Looper。

   /**
     * Return the Looper object associated with the current thread.  Returns
     * null if the calling thread is not associated with a Looper.
     * 在哪个线程调用就取出次线程的Looper,具体逻辑在ThreadLocal的get方法中
     */
    public static Looper myLooper() {
        return sThreadLocal.get();
    }

  还有一个问题在这里需要解答下,为什么在平时我们对消息机制的使用过程中,绝少使用Looper.prepare(),这是因为大多数场景都是在主线程进行的。而主线程的Looper由系统自行创建好了,其别有一个名称叫MainLooper,这个MainLooper并保存在Looper类成员sMainLooper中,基于类成员在进程死亡之前一直存在,所以我们可以在进程任何地方通过Looper.getMainLooper()来获取到它,进而通过Handler往主线程的消息队列投递消息轮询处理。

loop()

  当你看到某个线程调用了Looper.loop()方法时,说明这个线程已经进入事实上的轮询,除非调用quit/quitSafely退出。很明确的说,这个方法的调用依赖于prepare在当前线程事先被调用过。

    public static void loop() {
        //取出保存在ThreadLocal中的Looper实例中的MessageQueue(消息队列)
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;
        Binder.clearCallingIdentity();
        final long ident = Binder.clearCallingIdentity();
		//循环体,用于不停循环队列中的消息
        for (;;) {
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }
			//target是保存在消息中的Handler引用,此处的dispatchMessage所在线程为loop方法调用所在线程
            msg.target.dispatchMessage(msg);

            final long newIdent = Binder.clearCallingIdentity();
			//消息在发送完之后自动回收到保存在Message中的pool中
            msg.recycleUnchecked();
        }
    }

  loop()是一个类方法,正因此,在内部首先需要检查其调用线程的Looper对象是否已经创建,否者无法开启轮询。当检查完后就处于死循环的过程中,在每一次循环时,都试图通过mQueue.next()方法(强烈注意!这个next是阻塞式的)从Looper唯一存在的消息队列MessageQueue中取出一条消息进行处理,具体怎么处理前边有分析过,就是通过在消息投送时保存在msg.target的Handler来进行剩余的dispatch部分工作;最后当消息处理完时,通过调用当前轮询到的消息的recycleUnchecked()来进行回收。

  会什么会有这个消息回收呢?这是基于消息复用以减少内存消耗的需要。至于回收到哪里去,回收的消息又如何再利用。这里先不展开,我们在下个小节会详细介绍。

MessageQueue

  MessageQueue主要是完成Android消息机制中未处理消息的保存及排序等需要,需要具备基本的插入消息、读取消息的功能。基于单个消息节点创建以及删除方便的考虑,MessageQueue内部维护了一个mMessages单向链表来作为其消息结构。消息作为单链表中的节点,其内部需要维护下一个消息节点的引用。当调用MessageQueue的插入、查询、删除消息等操作时,其实就是对其内部这个单链表进行操作。

public final class Message implements Parcelable {
    ...
    // sometimes we store linked lists of these things
    /*package*/ Message next;
    ...
}

  消息队列MessageQueue是在new Looper对象的时候创建的,这也说明了在篇首时我们描述的Looper与MessageQueue是一对一的关系。在消息队列创建时,其同时调用了native层的nativeInit()方法,此方法具体在android_os_MessageQueue.cpp中,nativeInit最终调用到NativeMessageQueue类的构造方法。

NativeMessageQueue::NativeMessageQueue() : mInCallback(false), mExceptionObj(NULL) {
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        mLooper = new Looper(false);
        Looper::setForThread(mLooper);
    }
}

  从以上代码可知在MessageQueue创建时,在native层也构建了一套Android消息机制以专门为当前程序的native层服务。当然,由于本人C++水平有限,这里暂时不对native层的消息机制铺开来说了。

插入消息

  在之前介绍Handler时曾强调其最终是将经过包装的Message插入到创建Handler时从当前线程所获取到的mQueue中,具体调用的就是消息队列mQueue.enqueueMessage()方法。

   boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }
		//因为插入队列这个过程可能在不同的线程调用到,为链表增删的安全性,所以此处需要加锁
        synchronized (this) {
            if (mQuitting) {
                IllegalStateException e = new IllegalStateException(
                        msg.target + " sending message to a Handler on a dead thread");
                Log.w("MessageQueue", e.getMessage(), e);
                msg.recycle();
                return false;
            }

            msg.markInUse();
            msg.when = when;
            Message p = mMessages;
            boolean needWake;
			//如果链表为空或者当前插入的Message的when要小于链头的when,则把当前msg作为链头
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } 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;
				//根据when排队,when的值越大越往后,如果when值相同,则在同when最后边
                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;
            }

            //在插入数据时,如果当前线程处于休眠态,或者当前消息是一个异步消息,则需要唤醒当前线程
            if (needWake) {
                nativeWake(mPtr);
            }
        }
        return true;
    }

  enqueueMessage内部首先对消息的合法性进行了一些判断,如果当前需要插入的消息合法,则根据消息的when值插入到mMessages链表中合适的位置。在插入完成之后,就如同生产者消费者模式那样,如果消费者next进入了mBlocked=true即阻塞状态,则生产者enqueueMessage还需要根据实际情况选择去nativeWake唤醒消费者,那么什么时候我们需要唤醒被阻塞的next方法呢?从上边代码可以看出,会根据插入消息的when时间,或者消息是否是异步消息决定去wake被阻塞的方法。

  且基于对mMessages的保护所有可能影响到mMessages链表的操作都被加上同步锁MessageQueue.this,以防止其被意外篡改。

  关于nativeWake是如何唤醒被阻塞方法next,以及next被阻塞的条件如何?在下边分析。

next()

  在Looper.loop()开启轮询后,由MessageQueue的next方法负责从其内部的单链表mMessages中取出被轮询到的消息,但如果链表中没有消息的话,那么取消息的过程就变成了一个阻塞的过程,会一直阻塞直到当前链表中被插入一条消息。

  next()是整个Android消息机制实现阻塞式轮询的关键之处,但由于其冗长的代码段,也让人望而生畏。下边就是next方法的整个过程,之后我们会有详细的解答。

	//核心方法,阻塞式调用
    Message next() {
        //第1部分
        // Return here if the message loop has already quit and been disposed.
        // This can happen if the application tries to restart a looper after quit
        // which is not supported.
        final long ptr = mPtr;
        if (ptr == 0) {
            return null;
        }
        //第2部分
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        //第3部分
		//由此可见next方法时阻塞式的调用
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            //第3.1部分
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                //第3.2部分
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
				//如果发现当前的消息是SyncBarrier消息(通过target==null判断),则取出消息队列中的异步消息进行处理,而阻塞掉大部分的同步消息
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                //第3.3部分
                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);
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }
                //第3.4部分
                // Process the quit message now that all pending messages have been handled.
                if (mQuitting) {
                    dispose();
                    return null;
                }
                //第3.5部分
                // If first time idle, then get the number of idlers to run.
                // Idle handles only run if the queue is empty or if the first message
                // in the queue (possibly a barrier) is due to be handled in the future.
                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;
                }

                if (mPendingIdleHandlers == null) {
                    mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
                }
                mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
            }

            // Run the idle handlers.
            // We only ever reach this code block during the first iteration.
            for (int i = 0; i < pendingIdleHandlerCount; i++) {
                final IdleHandler idler = mPendingIdleHandlers[i];
                mPendingIdleHandlers[i] = null; // release the reference to the handler

                boolean keep = false;
				//不能因为IdleHandler调用崩溃导致looper退出循环
                try {
                    keep = idler.queueIdle();
                } catch (Throwable t) {
                    Log.wtf("MessageQueue", "IdleHandler threw exception", t);
                }

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

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

  从整体的角度来看,next方法无非就是从消息队列中取出当前符合条件的消息返给轮询器Looper,并在没有消息时阻塞于此以让出CPU资源,并且通知出去Looper阻塞了,大家伙有事的赶紧操起家伙干活,不然一会我忙起来了你们还得排队等CPU资源。这就是next方法的整体套路,但是还有许多细节值得研究,在next代码段里,我们把其分成了若干部分,接下来我们就逐一解读。

  第1部分:用于判断当前消息队列是否已经退出了(具体退出是通过调用MessageQueue.quit()方法),如果已经退出了那么返回null以便Looper退出轮询。这个quit退出的方式有两种,分为安全或不安全,安全是指当前队列可以继续把那些msg.when<=now的消息执行完毕才退出,而不安全则就是暴力清空队列。当然,在一般情况下程序不会主动quit,特别是MainThread,因为quit就代表当前线程已经gameover了。

  第2部分:很简单,就是对两变量进行初始化,但若是结合整个next来看,这两个变量又有着深刻的含义。nextPollTimeoutMillis则表示当前需要阻塞的时间,默认0表示无需阻塞,-1表示一直阻塞,大于0则表示具体阻塞的超时时间。pendingIdleHandlerCount用于记录当next处于阻塞状态时需要通知的“人数”。开发者可以通过MessageQueue的addIdleHandler和removeIdleHandler来进行添加。其实系统就是用IdleHandler的这种机制来做GC回收。

//frameworks/base/core/java/android/app/ActivityThread.java
    final class GcIdler implements MessageQueue.IdleHandler {
        @Override
        public final boolean queueIdle() {
            doGcIfNeeded();
            return false;
        }
    }
    void scheduleGcIdler() {
        if (!mGcIdlerScheduled) {
            mGcIdlerScheduled = true;
            Looper.myQueue().addIdleHandler(mGcIdler);
        }
        mH.removeMessages(H.GC_WHEN_IDLE);
    }

  第3部分:进入next的阻塞式调用直到得到一条Message,这个“直到”就是通过for(;;)循环保证,当找到消息时才会跳出循环。

  --> 第3.1部分:nativePollOnce是一个native层方法,是一种可阻塞式的调用。其有两个参数,参数1是native层消息队列指针(即NativeMessageQueue),参数2则是具体阻塞的时间。关于此处如何阻塞,阻塞时间及如何唤醒,可参见下边epoll在MessageQueue中的使用。

  --> 第3.2部分:此部分是在nativePollOnce阻塞方法被唤醒之后调用,主要是在唤醒之后试图从消息队列中获取一条可用消息。请注意,此部分进入的条件是消息队列mMessages不为空且链表首条消息的msg.target=null才会进入。条件之一的消息队列不为空是很容易理解的,如果消息队列中没有消息,那就不用试图找了。条件二msg.target=null则需要结合下部分SyncBarrier一起才会更好理解。这里先提一嘴,msg.target=null是同步栅栏的标志,当链首消息是同步栅栏SyncBarrier时,则意味着此消息之后的所有同步消息(msg.isAsynchronous()用于判断当前消息时同步还是异步)都不能取出来。

  --> 第3.3部分:当3.2部分试图取出一条消息后(可能为空)需要判断这条消息是否可用。这部分代码其实很明朗,有消息的情况下,通过msg.when判断当前消息是否到了执行时间,如果到了立即返回,如果还没到,则之后会continue循环到上边的nativePollOnce方法阻塞在这里计算出来的时间nextPollTimeoutMillis;在没有消息的情况下,同样之后会continue继续循环到nativePollOnce方法,只不过这时的nextPollTimeoutMillis=-1,也就是不设超时的阻塞,除非调用wake唤醒。

  --> 第3.4部分:此部分则是判断当前是否已经调用了MessageQueue.quit()方法退出。如果mQuitting=true则调用dispose把native层的引用计数-1。这里有个关键问题,当quit方法被调用时,无论是否是安全quit,都会把msg.when>now的消息从链表中删除掉。但是这里我有个疑问,如果在调用quit。之前有加入同步栅栏,那么msg.when

  --> 第3.5部分:如果进入到此部分则说明3.2部分并没有取出到消息或者取出的消息未到执行时间,所以在下次循环时会进入阻塞状态,3.5部分则是在进入下次循环之前把mIdlehandlers集合遍历通知一遍:“Looper轮询进入阻塞状态,暂不使用CPU资源,大伙可以开始干活了”。

  在next方法中,mBlocked变量就是用于标识当前轮询是否阻塞。这也是enqueueMessage方法中唤醒时依据的准绳之一。

SyncBarrier

  在绝大多数开发场景中,同步栅栏都不会被使用到,正因如此,SyncBarrier这个词对我们而言是如此的陌生。首先我们需要了解,Handler也就是消息的投送者其实分为剑宗和气宗两个派别!呃,跑偏了。但是Handler确实有两种不同的派别,两者各自所具有的的能力是稍有差别的,这其中主要体现在两者发送的消息类型不一样,分为同步消息和异步消息两种。在一般情况下,我们所创建的Handler都只具备发送同步消息的能力。这是由于具有异步消息能力的Handler投送者Google并没有对我们开放。下边我们我们再贴一遍Handler的部分构造方法。

    /**
     * @hide
     */
    public Handler(Callback callback, boolean async) {
        ...
        mAsynchronous = async;
    }

    /**
     * @hide
     */
    public Handler(Looper looper, Callback callback, boolean async) {
        ...
        mAsynchronous = async;
    }

  在构造方法中mAsynchronous变量就是标识当前Handler是发送同步消息还是异步消息的Handler。如果去查阅Handler所有构造方法你就会发现,所有 与boolean async参数相关的非hide构造中都是false的,也就是说是同步的。这也说明发送异步消息这种功能“一般人”不能拥有。当然,如果想拥有一个发送异步消息的Handler,使用java反射技术也是可以的。

  在聊完了Handler的两种派别所发送的消息还分同步与异步之后,SyncBarrier就该闪亮登场来了,SyncBarrier说到底其实也是一条Message,它主要是为充当栅栏一样的功能,也就是说单链表中在SyncBarrier之后的同步消息,在这个栅栏移除之前是无法被轮询器轮询到的,此处注意!是同步消息无法被轮询,如果是异步Handler发过来的异步消息则是可以被轮询处理的。几乎,面对SyncBarrier的超能力,我们能接触到的Handler都有那么丢丢无力感。下边就是SyncBarrier发生作用的地方。

        //截自消息队列的next()方法
        ...
        // Try to retrieve the next message.  Return if found.
        final long now = SystemClock.uptimeMillis();
        Message prevMsg = null;
        Message msg = mMessages;
	    //如果发现当前的消息是SyncBarrier消息(通过target==null判断),则取出消息队列中的异步消息进行处理,而阻塞掉大部分的同步消息
        if (msg != null && msg.target == null) {
        // Stalled by a barrier.  Find the next asynchronous message in the queue.
            do {
                prevMsg = msg;
                msg = msg.next;
            } while (msg != null && !msg.isAsynchronous());
        }
        ...

  那么怎样区分一条消息是不是同步栅栏,以及怎么添加和删除这个同步栅栏呢?

  同步栅栏有一个明显的特点,即msg.target=null。这其实也不难理解,普通的消息是需要知道他从何处来,往何处去的,但是同步栅栏不一样,它的职能早就由Android消息机制分配好了,其发生作用也是由Android消息机制本身决定的,没必要也不能把其分配给某个Handler来处理。正因如此,在上边的代码中这个if (msg != null && msg.target == null)判断成立时,就说明单链表的链首是同步栅栏,所以这时候它后边所有的同步消息都被跳过了。

  很容易想象,如果同步栅栏并不依赖于某个具体的Handler(msg.target)。那么其添加和删除过程应该也不是通过Handler来完成的。同步栅栏是通过Looper对象完成添加/删除的,同样,这两也有hide标记,说明其在一般情况下不提供给普通开发者来使用。在下边方法中,需要注意的是,在添加一个SyncBarrier时会返回一个token,这就是删除SyncBarrier是所需要的令牌,这就好比古代兵符一样,兵符匹配上了才能发兵。需要注意的是,SyncBarrier在同一个消息队列中可能不止一个,每一个SyncBarrier都与单独的token一一对应。

    /**
     * @hide
     */
    public int postSyncBarrier() {
        return mQueue.enqueueSyncBarrier(SystemClock.uptimeMillis());
    }


    /**
     * @hide
     */
    public void removeSyncBarrier(int token) {
        mQueue.removeSyncBarrier(token);
    }

  虽然关于异步消息等操作不能轻易接触到,但其在系统中却也有广泛的应用。例如在ViewRootImpl中当进行ViewTree遍历(measure+layout+draw的过程)时就有用到。在进行ViewTree遍历时会给native层的渲染器(mChoreographer)进行画面渲染,在渲染未结束之前我们希望阻塞住线程,让其不处理同步消息。这时候就通过postSyncBarrier()往消息队列中添加了一个即时的同步栅栏,直到渲染器渲染完成并通过mTraversalRunnable回调到java层之后,才把这个同步栅栏通过removeSyncBarrier给删除掉。

    void scheduleTraversals() {
        if (!mTraversalScheduled) {
            mTraversalScheduled = true;
            mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
            mChoreographer.postCallback(
                    Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
           ...
        }
    }
    final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
    final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }
    
    void doTraversal() {
        if (mTraversalScheduled) {
            mTraversalScheduled = false;
            mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
            ...
        }
    }

epoll使用

  我们在上边讨论next方法的时候曾经说过其内部的nativePollOnce方法是阻塞式的,其内部就是用到了epoll实现的阻塞,这也是Looper实现阻塞式轮询的关键。

  nativePollOnce在native层的代码位于android_os_MessageQueue.cpp中,以下就是java层方法与native方法的一一对应关系。

static JNINativeMethod gMessageQueueMethods[] = {
    /* name, signature, funcPtr */
    { "nativeInit", "()J", (void*)android_os_MessageQueue_nativeInit },
    { "nativeDestroy", "(J)V", (void*)android_os_MessageQueue_nativeDestroy },
    { "nativePollOnce", "(JI)V", (void*)android_os_MessageQueue_nativePollOnce },
    { "nativeWake", "(J)V", (void*)android_os_MessageQueue_nativeWake },
    { "nativeIsIdling", "(J)Z", (void*)android_os_MessageQueue_nativeIsIdling }
};



static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jclass clazz,
        jlong ptr, jint timeoutMillis) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    nativeMessageQueue->pollOnce(env, timeoutMillis);
}

static void android_os_MessageQueue_nativeWake(JNIEnv* env, jclass clazz, jlong ptr) {
    NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr);
    return nativeMessageQueue->wake();
}
void NativeMessageQueue::pollOnce(JNIEnv* env, int timeoutMillis) {
    mInCallback = true;
    mLooper->pollOnce(timeoutMillis);
    mInCallback = false;
    if (mExceptionObj) {
        env->Throw(mExceptionObj);
        env->DeleteLocalRef(mExceptionObj);
        mExceptionObj = NULL;
    }
}

  可以看见,nativePollOnce的native层方法只是一个中转站,其调用的是本地消息队列NaviveMessageQueue中的pollOnce方法。当然,这个pollOnce内部最终调用的是Looper.cpp中的pollOnce。其后就是我们epoll的主场了。前边我们在讨论java层的MessageQueue的创建曾经提到过,其构造方法中会调用nativeInit方法创建native的消息队列,而在native消息队列NativeMessageQueue(上边有)构造了native层的Looper。可见在java层消息机制创建时,Android也同时为native准备好了,同样的NativeMessageQueue.cpp与Looper.cpp也是一对一的关系。而我们本小节的主角epoll就是在native层的Looper中创建出来的。以下是epoll创建以及监听事件的关键代码。

Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
    int wakeFds[2];
    //构造epoll监听对象pipe
    int result = pipe(wakeFds);
    mWakeReadPipeFd = wakeFds[0];
    mWakeWritePipeFd = wakeFds[1];
    ...
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    ...
    struct epoll_event eventItem;
    memset(& eventItem, 0, sizeof(epoll_event)); // zero out unused members of data field union
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mWakeReadPipeFd;
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, & eventItem);
    ...
}

  在native层的Looper创建时就已经使用epoll监听pipe管道的mWakeReadPipeFd端事件了。通过在篇首储备知识的讲解中,我们可以知道epoll_create用于创建epoll对象,epoll_ctl用于注册监听事件,这两者我们在native层Looper的构造方法中都已看见。而epoll_wait才是epoll实现可阻塞调用的核心方法,它的位置我们不难想象,它一定在java层的消息队列next()调起的方法链的某个地方。没错,就是刚才我们从链上追溯到的mLooper->pollOnce(timeoutMillis)中。native层Looper的pollOnce最终会调用pollInner,在PollInner中我们发现了epoll_wait的身影。

int Looper::pollInner(int timeoutMillis) {
    ...
    // We are about to idle.
    mIdling = true;

    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

    // No longer idling.
    mIdling = false;
    ...
    //此处就是事件发生之后对Fd与事件类型判断后对相应缓冲区进行清空,方便epoll_wait重新进入下次等待
    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeReadPipeFd) {
            if (epollEvents & EPOLLIN) {
                awoken();
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents);
            }
        } else {
            ...
        }
}

  其中timeoutMillis是一路从java层的nativePollOnce传过来的,其用于表示在epoll_ctl监听的事件未发生之前epoll_wait最长可能阻塞的时间。

  在epoll_wait开始阻塞之后,那么Android机制又是如何wake唤醒的呢?在java层唤醒是通过位于MessageQueue消息队列中的nativeWake()方法达到的。nativeWake()是native方法。这是因其最终需要把native层的epoll_wait阻塞给唤醒。

    private native static void nativeWake(long ptr);

  在java层,nativeWake主要被三个地方调用:

  第一.MessageQueue被插入消息,也就是enqueueMessage方法调用时,这个方法在上边也有说明。

  第二.当MessageQueue调用quit试图退出轮询时,因为退出之前需要把消息队列中的消息给取空,或者需要next的nativePollOnce不被阻塞,无论基于那种需要,都需要通过nativeWake唤醒epoll_wait的阻塞。

    void quit(boolean safe) {
        if (!mQuitAllowed) {
            throw new IllegalStateException("Main thread not allowed to quit.");
        }

        synchronized (this) {
            if (mQuitting) {
                return;
            }
            mQuitting = true;

            if (safe) {
                removeAllFutureMessagesLocked();
            } else {
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

  第三.当我们移除同步栅栏时,如果有必要也需要通过nativeWake取消掉epoll_wait的阻塞。什么是有必要呢,如果当前同步栅栏移除后链首的Message不是另外一个同步栅栏,且消息队列并未quit时,那么就需要调用。

  说完nativeWake在java层的调用之后,显然,我们还需要清楚在native层他是怎么唤醒epoll_wait的。在经过层层调用之后,nativeWake会调用到native层Looper的wake方法中。

void Looper::wake() {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ wake", this);
#endif

    ssize_t nWrite;
    do {
        nWrite = write(mWakeWritePipeFd, "W", 1);
    } while (nWrite == -1 && errno == EINTR);

    if (nWrite != 1) {
        if (errno != EAGAIN) {
            ALOGW("Could not write wake signal, errno=%d", errno);
        }
    }
}

  从上边native层Looper的构造函数中我们可以看见其通过epoll_ctl监听的是mWakeReadPipeFd输出端的EPOLLIN即“可以读取”事件。当我们往管道的另一端mWakeWritePipeFd写入数据时,这个事件就发生了,也就是说epoll_wait一直等待的事件触发了,所以epoll_wait阻塞消除。当然,在阻塞消除之后的pollInnter中需要把pipe中的数据(实质上就是缓冲区的数据给read出来以清空缓冲区)。这就是pollInner调用到的awoken方法。在通过awoken清空缓冲区之后,就进入了下一次mWakeReadPipeFd的EPOLLIN事件的等待。该awoken的调用可在上边关于pollInner的代码段看见。

void Looper::awoken() {
#if DEBUG_POLL_AND_WAKE
    ALOGD("%p ~ awoken", this);
#endif

    char buffer[16];
    ssize_t nRead;
    do {
        nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer));
    } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer));
}

  至此,整个Android消息机制的阻塞原理就说完了,就是通过epoll机制完成的整个next的阻塞。

消息池

  Message是Android消息机制中具体数据的携带者,开发者或系统可通过使用Handler源源不断的发送Message给轮询器以驱动整个程序的运行。可想而知,当整个程序越复杂,Message如果不在使用之后在复用,程序的Message数量将增长很快,而且频繁new对象将会造成庞大的开销。正基于此,为所有已经处理完毕的废弃消息建立消息池来保存他们,就显得格外重要。

  在Looper轮询器的loop方法中,当调用msg.target.dispatchMessage处理完消息之后,这条Message已经处理完毕。接下来就是对这条Message进行回收,即调用msg.recycleUnchecked()把它加入到程序的消息池中以待下次复用。

    public static void loop() {
        ...
        for (;;) {
            Message msg = queue.next(); // might block
            ...
			//target是保存在消息中的Handler引用,此处的dispatchMessage所在线程为loop方法调用所在线程
            msg.target.dispatchMessage(msg);
            ...
			//消息在发送完之后自动回收到保存在Message中的pool中
            msg.recycleUnchecked();
        }
    }

  这个消息池就是Message中的静态成员sPool,sPool是一个静态的单链表,这表示sPool不光只为某个线程服务,在进程中,所有地方都可以接触到这个消息池。从下边可以看到消息池的最大容量为50,初始大小即sPoolSize为0。

	//进程唯一性的锁
    private static final Object sPoolSync = new Object();
    private static Message sPool;
    private static int sPoolSize = 0;

    private static final int MAX_POOL_SIZE = 50;

  所有已经处理完成的消息都会通过recycleUnchecked加入到消息池,这是因为通过Handler投递的Message都会进入loop()循环中,而循环末尾必然调用recycleUnchecked。而从消息池获取消息则是通过Message.obtain()。在obtain()方法中会首先尝试从消息池中获取一条消息,如果消息池为空,则会自动创建一个新的消息给开发者使用,所以这里不用担心obtain()方法会返回空的问题,由于obtain()有多个重载方法,但原理相同,这里我们仅贴其一。

    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
				//取出来的消息,要去掉链表属性next
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();

  曾经,我们也许通过Handler发送的消息都是通过new Message()的方式得到的,但这是一种不良行为,虽然最后这条消息最终也会调用recycleUnchecked方法,但通过这种方式频繁创建的话,由于sPool消息池最大容量的限制,这条消息将被finalize掉,造成了内存的抖动。所以最优的使用方式可通过Message.Obtain()或mHandler.obtainMessage()(最终也是调用前者)来获取消息。

扩展说明

Handler如何跨进程通信

  在多数场景中,开发者都只是在进程内部使用Handler来往不同线程发送Message。这也就让大家忽视了Handler的另外一个神奇的能力即跨进程通信。也就是说Android消息机制不局限于线程间使用,还可跨进程传递Message。

  每个Handler其实内部都有提供有一个信使,这个信使就是下边的MessengerImpl。不难发现MessengerImpl依赖于Binder机制,正因如此,就使得Handler具备了进程通信的能力。但是同时我们也可以发现这个信使是私有的,且getIMessager也因修饰符不对外开放。所以如何获得Handler中的信使也自然而然就成了我们用Handler实现跨进程的关键。

public class Handler {
    ...
    final IMessenger getIMessenger() {
        synchronized (mQueue) {
            if (mMessenger != null) {
                return mMessenger;
            }
            mMessenger = new MessengerImpl();
            return mMessenger;
        }
    }
    ...
    private final class MessengerImpl extends IMessenger.Stub {
        public void send(Message msg) {
            msg.sendingUid = Binder.getCallingUid();
            Handler.this.sendMessage(msg);
        }
    }
    ...
}

  为了获得Handler进程通信的能力,这就需要使用到Messenger类,Messenger本质上是对Handler的包装,同时它也隐藏了binder方面的逻辑。但这不妨碍我们使用它来获取超能力。那该怎么使用Messenger呢?其实就在于Messenger的两个构造方法。

public final class Messenger implements Parcelable {
    ...
    private final IMessenger mTarget;
    public Messenger(Handler target) {
        mTarget = target.getIMessenger();
    }
    ...
    public Messenger(IBinder target) {
        mTarget = IMessenger.Stub.asInterface(target);
    }
}

  参数为Handler的构造方法就是提供Handler的进程,也即Message目标进程。而参数为IBinder的构造方法,则是Message发送进程获取目标进程Handler的主要方式。这两个构造方法分别对两个进程涉及到的Handler/Binder进行了包装。

  举个例子!!!

  假设我们构建一个Service,其单独属于某个进程,那么我们可以将这个Service中的Handler通过以下方式提供给其他进程。通过使用Messenger包装Handler,当然其实质上是通过调用Handler.getIMessenger()来获取到前边提到过的具有跨进程能力的MessengerImpl。它本质上是一个Binder,所以我们可以通过messenger.getBinder()把这个信使暴露出去。至于为什么Messenger可以获取到Handler内部的信差,那当然是因为Messenger与Handler位于同一包目录下啦。

public class MessengerService extends Service {
	private static final String TAG = "MessengerService";
	public MessengerService() {
	}

	@Override
	public IBinder onBind(Intent intent) {
		Messenger messenger = new Messenger(new Handler(){
			@Override
			public void handleMessage(Message msg) {
				super.handleMessage(msg);

				Log.i(TAG, "handleMessage: "+msg.getData().get("data"));
			}
		});

		return messenger.getBinder();
	}
}

  而其他进程想要获取MessengerService的Handler并往其发送消息。那么这个进程应该类似于以下调用。其也是通过Messenger的构造完成了信使的获取,但这个构造方法内部只不过对目标进程Handler内部的MessengerImpl做了asInterface()处理。实质上与一般的bindService方式没有啥区别。

bindService(new Intent(this, MessengerService.class), new ServiceConnection() {
			@Override
			public void onServiceConnected(ComponentName name, IBinder service) {
				Messenger messenger = new Messenger(service);


				for (int i = 0; i < 10; i++) {
					Message message = Message.obtain();
					try {

						message.getData().putString("data","我是来自另外进程的消息"+i);
						messenger.send(message);
					} catch (RemoteException e) {
						e.printStackTrace();
					}
				}
			}

			@Override
			public void onServiceDisconnected(ComponentName name) {

			}
		}, Context.BIND_AUTO_CREATE);

  以上例子的源码在文末有贴出。需要注意的是必须使用Bundle传递常规类型的数据,否则就会报以下错误:

java.lang.RuntimeException: Can't marshal non-Parcelable objects across processes.

消息机制的应用

ActivityThread

  我们知道,Android应用程序是基于消息驱动的,当你从Launcher桌面点开某个应用,而这个应用的入口就是ActivityThread,更确切的说,是ActivityThread中的main函数。而在main函数所在的线程当然就是这个程序的主线程,在这个主线程中,我们就发现了Android消息机制的身影。

    public static void main(String[] args) {
        //初始化工作
        ...
        
        Looper.prepareMainLooper();
		//在new ActivityThread时会创建Handler,所以通过thread.getHandler拿到的当然是主线程的Looper
        ActivityThread thread = new ActivityThread();
        thread.attach(false);

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

        AsyncTask.init();

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

        Looper.loop();

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

  可以看到,与一般线程使用Looper.prepare()创建Looper不同的是,主线程的使用过prepareMainLooper创建了一个sMainLooper。而且在后边调用了Looper.loop()使这个main线程一直处于消息轮询的过程中,如果轮询退出,则意味着主线程即进程挂掉了。这就充分说明,一个android程序的运行,是基于Looper.loop()进行不断消息轮询来驱动的。

HandlerThread

  HandlerThread是对Android消息机制的封装后的Thread。你就需要获取到HandlerThread的Looper对象,就可以从任意地方往这个线程里边发送Message/Runnable。关于HandlerThread如何使用等细节,读者可以阅读我的这篇文章:【多线程】用HandlerThread管理线程和控制异常。

按优先级处理的消息机制

  在介绍完Android的消息机制后,我们不难发现,这套机制的排序是基于msg.when也就是时间来排序的。但某些场景下,可能不能完全满足需要。比方说,根据业务需要,把消息类型分为了控制类型和普通业务类型。然而我们希望控制类型的消息具有优先处理权,也就是说无论在消息队列中还有多少消息,只要这个控制类型消息一投送,就希望消息机制能够第一时间被处理,那么这个情况下,Android本身的Handler消息机制就不适用了。

  所以我们构建了一套基于优先级的消息机制,这套消息机制除了使用Handler的思想之外,还具备了以下不同于Android消息机制的特点。

  1.所有的消息都可分为不同的level优先级,高优先级的消息被优先轮询。

  2.轮询器也基于阻塞式轮询,但其废弃了epoll式的阻塞,而是使用wait/notify方式。

  3.SyncBarrier可阻塞>=某个优先级level的所有的同步消息,直到主动移除它。

  4.兼容于按时间排序的方式。

  以下是源码附送:

  基于优先级的消息机制

  Handler跨进程通信Demo

你可能感兴趣的:(安卓,深入理解Android源码)