Android Handler消息机制的原理细节

        本文内容基于Android 27的源码,并参考了Gityuan大神的文章http://gityuan.com/2015/12/26/handler-message-framework/。同时,这篇文章算是个人对消息机制的知识点的笔记记录,所以更多的是站在自己的角度来分析问题。

       对于Android系统来说,Binder和Handler是两大利剑,分别实现了进程间和线程间的通讯。Android的消息机制,主要包括Hander,Looper,Message和MessageQueue四个数据类型,但从概念上讲,核心是线程和消息队列,一切操作围绕某个线程和它对应的消息队列展开,Handler,Looper,MessageQueue这三个类都会和同一个线程绑定。主要原理为通过Threadlocal让每个线程具备了一个消息队列,消息队列一方面作为存储消息的数据结构,另一方面负责消息具体的入列,出列,阻塞等核心操作;而Handler负责将消息发送到相应线程的消息队列中,并对出列的消息进行处理;而Looper则通过循环,不断的尝试获取消息并对获取到的消息进行分发,交给消息对应的target(Handler)来处理,然后在消息处理完毕后进行回收,回收到消息池中。

       所以通过消息机制,可以实现线程间的通信,进行数据的传递;也可以作为线程间通信的协议,让一个线程通知另一个线程执行某段业务逻辑。同时,也可以实现线程切换,执行延迟,循环执行的操作。接下来我们分别对各个类来进行分析。

一:Handler

       Handler是消息机制中我们接触最多的类,可以理解为消息机制的调度器。它会和某个线程及相应的消息队列绑定在一起,负责向这个线程的消息队列里发送消息,并对出列的消息进行处理。和哪个线程绑定在一起,就会向哪个线程的消息队列里发送消息,同时处理消息的代码也就在哪个线程里执行,但是可以从其他任意一个线程向该线程发送消息。里面的方法按照作用可以分为5个方面:构造函数,获取消息对象,发送消息,处理消息和删除消息。Handler可以理解为一个辅助类,主要是为了方便我们使用,主要的操作内部都交给了Message和MessageQueue来执行。

1)构造函数

   Handler的构造函数根据带不带Looper参数,可以分为两类:

不带Looper的:

不带Looper的构造函数

通过这个构造函数构造的Handler对象,会和当前线程的Looper和MessageQueue绑定在一起,如果当前线程没有Looper,则会抛异常。并由参数async来决定当前Handler对象发送的消息是异步的还是同步的。

带Looper的构造函数:

带Looper的构造函数

通过这个构造函数构造的Handler对象,会和指定的Looper绑定在一起,也就是和Looper 对应的线程绑定在一起,而这个线程也不一定是当前代码所在的线程。这个构造函数的主要使用场景是,像网络,图片加载等框架,都会通过一个和主线程Looper绑定的Handler对象来发送消息,实现线程的切换.

2)获取Message对象相关函数

消息池字段

       Message内部有一个sPool的静态字段和next指针,通过单链表的方式在整个应用内部维护了一个消息池。并且不建议我们直接new Message 的对象(虽然Message构造函数是public),而是从这个消息池里取可复用的对象。

Handler

       Message提供了一系列的obtainMessage的函数,同样Handler也提供了一系列类似的函数。内部也是调用了Message的相关函数,不一样的地方在于将Message的target指向了自己.

3)发送消息的相关函数

        Handler只能发送普通的消息,同步异步均可,包括Runable和Message,这两个最终都会封装成Message对象,Runable会通过getPostMessage函数转换为Message对象,区别在于callBack字段是不是null。由对应的postxxx()和sendxxx()函数来负责发送,可以马上执行,延迟执行以及在特定时间执行.其中包括一个empty Message,也就是只包含what字段的消息.

发送消息相关函数

         这些函数会返回一个boolean,如果成功发送到了对应的队列里,返回true,否则,返回false.返回false 一般是因为Looper和MessageQueue已经退出了.而这些函数最终会调用enqueueMessage函数.

消息入列

将target指向自己,如果当前Hander异步,则所有的消息全部异步,否则就忽略不管了,由对应的MessageQueue完成入列操作.

4)删除消息的函数

Handler还有可以删除消息的函数:removeMessages,removeCallbacksAndMessages等,以及判断是否包含某个消息的函数,比如hasMessages(int what),hasMessages(int what, Object object)等,具体的都交给了MessageQueue来完成。

5)处理消息的函数

        Handler另一个重要的函数是处理消息,当消息出列的时候由Looper完成分发,交给对应的target(Handler)的dispatchMessage函数来处理,并且在Handler对应的Looper所在的线程执行。

dispatchMessage

       处理逻辑是:首先判断消息的callback是不是null,不为null,意味着是一个Runable,然后调用对应的run函数。否则,在Handler的callback不为null的情况下,交由callback的handleMessage来处理。同时,这个callback可以控制这个消息还要不要继续分发,所以可以利用callback来做一些类似于拦截,hook等操作。最后,交由Handler自己的handleMessage函数来处理.所以,大多数都会对Handler的handleMessage函数重写,然后使用自己的业务逻辑。

二:Looper

        Android的消息机制,从概念上来讲,核心是围绕消息队列展开的。通过ThreadLocal,让每个Thread具备一个消息队列,然后才展开对消息队列相关的操作处理。而源码中,消息队列是通过Looper来添加的。具体样本代码为:

LooperThread

       Looper一方面让某个线程具备了和它一对一的Looper和MessageQueue对象,另一方面对这个MessageQueue轮询,不断的尝试从里面获取消息,获取到了之后进行分发和回收,而这些操作都在这个线程中执行。重要的函数主要由:

1)Looper().prepare()

Looper().prepare()

        通过prepare(),给当前的线程设置Looper。并在构造Looper对象时,构造对应的MessageQueue对象。并且,同一个线程,prepare()函数不可以调用多次,否则抛异常.

Looper构造函数

        同时,Looper的构造函数里可以指定是否允许退出,进而确定MessageQueue可否退出。我们调用prepare()函数都是可以退出的,但是主线程的是不允许退出的.

prepareMainLooper

2)Looper.loop()

        loop()是Looper最重要的函数,它内部通过循环,不断的调用对应的MessageQueue的next()函数来获取消息,这个函数可能会阻塞。如果next()返回了null,意味着这个Looper和MessageQueue可以退出了,那么就会退出loop()函数。当获取到message之后,调用target.dispatchMessage(msg)来分发并处理消息,并在处理完毕后通过recycleUnchecked()来回收,具体的流程可以参考Gityuan大神的这张简图

loop函数

Looper中除了prepare()和loop()这两个重要函数外,还有几个常用的函数:

myLooper()----得到当前线程的Looper

getMainLooper---得到主线程的Looper

myQueue()----得到当前线程的MessageQueue

quit()/quitSafely---退出,具体的可见MessageQueue的讲解.

        至此,Handler和Looper的细节总结完了,可以看出Handler和Looper更像是辅助类,帮助我们使用消息机制的内容,真正核心的内容还是交由Message和MessageQueue来完成。

三:Message

       Message,消息,它的作用可以从两方面来理解,一方面可以理解为携带任意数据信息的对象,在线程建进行数据/消息传递;另一方面,可以理解为线程间通信的协议,将协议的关键信息封装到Message对象中,另外一个Thread接收到了之后进行解析,然后执行相应的业务逻辑。Message既可以像我们敲代码一样软件发送,也可以像触摸事件那样通过硬件发送。分为普通消息和屏障消息,屏障消息不能通过Handler来发送,它没有target,不用处理,只是类似于栅栏拦截,需要通过MessageQueue来发送,但是函数隐藏,主要是系统使用。而普通消息既包括异步消息和同步消息,又可分为Runable和Message。异步消息主要针对屏障消息的,如果队列中出现了屏障消息,并且还是头元素,那么它就会像栅栏一样拦截,导致之后的同步消息没有办法分发处理,但是异步消息不受到这个限制,依然可以分发被处理。

1)数据信息

       Message通过几个字段来包含数据信息,这些信息可以分为两种,一种是标识Message,另一种用于携带各种信息的。

标识字段:what。int类型,方便接收方来区别Message.同时,一个Empty Message就是只包括这个字段

信息字段:arg1,arg2,obj,data。前面两个arg用于携带简单的int数据,后面两个用于复杂数据。

除此之外,还有when,target,next,callback等字段。分表代表消息的时间,Handler,下一个指针(单链表)以及是不是Runable.

2)重要函数

Message中重要的函数就是Message 对象的获取和回收。

获取对象:

        Message内部有一些列的obtainMessage函数,不同的函数对不同的Message 字段来赋值.最根本还是obtain()函数

obtain()

       前面我们说过,Message的sPool字段代表一个由单链表实现的消息池。当消息池里有可使用的Message对象的时候,直接拿过来使用,并把sPool指向下一个指针next。同时会把flags的in-use状态清除。如果没有,则new一个新的.

回收对象

Looper的loop()函数会在消息被处理完毕后,通过recycleUnChecked()函数来回收这个消息对象

recycleUnchecked()

        recycleUnchecked()函数一方面将flag设置为in-use状态,并且把其他的字段清空,另一方面,在sPool还没有满的情况下,sPool最多为MAX_POOL_SIZE 50个,将该消息对象添加到消息池中,并称为消息池的头元素。处于in-use状态的Message是没办法通过recycle()来回收的,当消息入列或者分发的过程中都是这个状态。

recycle()

       其中的gCheckRecycle,在Android 5.0之后为true,所以会抛异常.

四:MessageQueue

       接下来,我们涉及到了消息机制的核心消息队列MessageQueue。MessageQueue虽然翻译过来是队列,但数据结构是单链表,以mMessages作为头元素,Message的next为下一个指针。它负责完成消息机制的核心操作,包括消息入列,出列,删除消息,是否包含某个消息以及退出等。MessageQueue里面包含几个native函数,负责核心工作。而MessageQueue可以理解为消息机制Java层和C++层的连接桥梁,并通过这些native函数实现了消息队列的阻塞和唤醒。

native函数

1)next()

        next()函数的代码100多行,截图不方便,就不截整体的图片了,我们一点点的分析。

判断mPtr

       首先,mPtr是个int类型的字段,和native函数交互时用的,当为0时,意味着消息队列退出并且被dispose了。

dispose

        next()函数在Looper.loop()中被调用,尝试从队列里获取消息。这个函数要么返回null,代表退出了;要么返回某个Message对象。否则就会阻塞。它内部也有一个for循环。

nativePoolOnce

        nativePoolOnce是阻塞操作,nextPollTimeoutMillis代表阻塞的时间,-1可以理解为一直阻塞下去,直到手动唤醒。当队列中无消息,或者头元素为屏障消息,而队列中没有异步消息的时候,会设置为-1。刚开始nextPollTimeoutMillis为0,可以继续向下走。

尝试查找异步消息

       首先考虑的是如果当前队列不为空,并且头元素是一个屏障消息,接下来就试着查找异步消息,直到找到或者msg==null。因为屏障消息会阻碍同步消息的分发处理,但异步消息不受限制。

获取到msg之后

       msg就是从队列中拿到的下一个message,并且没有考虑执行时间的因素。如果为null,会有两种情况:一是当前的队列是空的,二是队列不为空,但头元素是一个屏障消息,并且队列内部没有异步消息。这两种情况下,nextPollTimeoutMillis都会设置为-1,然后一直阻塞下去,等待唤醒。

        如果msg不为null,当然这个msg也有可能是异步消息。然后拿该消息的执行时间和当前的时间now对比,如果不可以执行,就把nextPollTimeoutMillis设置为下一次可以执行的时间间隔。如果可以执行,mBlocked设置为false,message标记为in-use,并返回,next函数结束.否则,函数继续向下执行。

执行IdeHandler

        next函数继续向下执行,意味着当前队列要么没有message可返回,要么还没有到头元素的执行时间,不管怎样,都会有阻塞。如果mQuitting=true,则直接dispose,并返回null,结束退出。否则根据pendingIdleHandlerCount,来决定要不要执行IdleHandler。IdleHandler在每一次next函数调用中只会执行一次,因为只有刚开始调用会设置为-1,执行完毕了就设置为0了,即便mIdleHandlers有元素,也只能等着下一次next的时候在执行。这里我们重点关注mBlocked的设置,因为这个字段在enqueueMessage()中很重要,决定了要不要唤醒。关键在于这几行代码:

mBlocked设置

        next函数执行到这里,就代表队列肯定是要阻塞的,原因有三个:队列为空;头元素为屏障消息,但队列中没有异步消息;头元素消息的执行时间还没到。不管因为什么,既然阻塞,mBlocked肯定会被设置为true,而设置的过程又会取决于mIdleHandlers。如果mIdleHandlers为空,则mBlocked直接设置为true,然后继续循环,并通过nativePollOnce(ptr, nextPollTimeoutMillis)来阻塞,其中nextPollTimeoutMillis为-1或者具体的超时时间。如果mIdleHandlers不为空,则会跳过mBlocked的设置,执行mIdleHandlers,并把pendingIdleHandlerCount和nextPollTimeoutMillis设置为0,然后开始下一次循环。而下一次循环还是没有消息,还是会执行到这里,由于pendingIdleHandlerCount=0,就会直接把mBlocked设置为true,然后继续循环,再次通过nativePollOnce函数来阻塞。

       也就是说,因为上面的三个原因会导致next函数的阻塞,并且mBlocked也会是true。但即便如此,这三个原因并不是完全都需要唤醒,其中因为执行时间没到的原因而阻塞时,就可以不唤醒,等超时时间到了就好。但另外两种情况,就需要唤醒,这个在enqueueMessage()添加消息时会有体现。

2)enqueueMessage()

      刚才分析了从MessageQueue中获取Message,接下来分析向MessageQueue中添加Message对象,具体细节为:

enqueueMessage()

       首先,这个函数只能添加普通的消息,也就是带target 的消息,在Handler的sendMessage等函数中被调用。屏障消息不能通过这种方式来添加。并且该Message对象不能处于in-use状态,所以Message.obtain()会把in-use状态清除。

检查是否退出

       如果当前的消息队列已退出,直接回收消息,并返回false,所以对应的Hander.sentMessage等函数也会返回false.

直接添加消息

然后将该Message标记为in-use,并设置when字段。接下来的if判断条件代表了三种情况:

p==null:当前的消息队列是空的

when==0:代表这个Message需要马上执行,这也是Handler.sendMessageAtFrontOfQueue()函数的实现方式

when

        这三种情况下,直接添加进来,并设置为链表的头元素。同时,将mBlocked赋值给needWake,确保当队列为空时,我们添加消息后将队列唤醒。MessageQueue按照消息的执行时间来存储元素,时间一样的按照添加顺序.因为它只判断了when

队列中添加消息并设置mBlocked

        其他的情况都会走else的逻辑.通过for循环,根据执行时间找到对应的位置插入。这里需要注意的是needWake,因为它决定了到底要不要调用nativeWake()函数。

nativeWake

       分析next()函数的时候我们了解到,当消息队列为空或者头元素为屏障消息但是队列中无异步消息时,添加消息的时候需要唤醒队列。这两种情况下的mBlocked也肯定为true,队列为空的时候已经在if语句后的代码中解决了,而针对第二种情况这里也做了判断:如果添加的消息是异步消息,而且是第一个异步消息,那么needWake就会为true,从而通过nativeWake来唤醒。

      至此,消息队列中最重要的两个函数也分析完了,接下来在分析其他的几个重要的函数。

3)退出

       分析Looper的时候看到Looper有两个退出函数:quit()和quitSafely(),一个不安全的一个安全的。两个函数都会导致后续的添加消息失败以及loop()的退出。不安全的删除是把所有的消息全部删除,安全的删除是指可以执行的消息不会删除,没到执行时间的消息会被删除。两个函数内部都交由MessageQueue的quit(boolean safe)完成,安全不安全有参数safe决定.

quit(boolean safe)

       首先,不允许退出的话,调用这个函数是会抛异常的,然后将mQuitting设置为true。根据safe来分别调用removeAllFutureMessagesLocked和removeAllMessagesLocked。首先来看不安全的删除removeAllMessagesLocked

removeAllMessagesLocked

        removeAllMessagesLocked函数逻辑很简单,把链表中的所有元素全部清空,并回收。再看removeAllFutureMessagesLocked

removeAllFutureMessagesLocked

        首先判断头元素的执行时间到没到,如果没到,直接走全部删除的逻辑。如果到了,那就从头元素开始遍历,直至找到空元素或者执行时间还没到的元素,这些元素都保留。如果找到了执行时间还没到的消息,那它之后的消息也都是不可执行的,那么就从这个消息开始,将其后的消息全部回收。

4)屏障消息的发送和删除

        MessageQueue还有postSyncBarrier和removeSyncBarrier来发送和删除屏障消息。但一般我们开发中涉及不到屏障消息,而且这两个函数还是隐藏的,大多数都是系统发送的消息,比如ViewRootImpl.scheduleTraversals的时候,会发送屏障消息。

scheduleTraversals

       屏障消息不能通过Handler来发送,它没有target。它起到了屏障,栅栏的作用,对某个线程的消息队列内的同步消息起到了拦截的作用,等完成了特定的业务逻辑,将屏障消息删除了之后,队列中的同步消息才可以继续执行,异步消息不受此限制。所以屏障消息使用完了之后得通过removeSyncBarrier(int token)来删除,其中的token参数就是postSyncBarrier返回的。

其他函数

        除此之外,MessageQueue内部还有上面这几个函数,用于判断是否包含某个消息,以及删除消息,实现比较简单,这里就不分析了。

       以上就是Android Handler消息机制的原理细节,因为本人水平有限,所以只涉及到了java层的细节,如有错误,欢迎批评指正。

参考文章:http://gityuan.com/2015/12/26/handler-message-framework/

你可能感兴趣的:(Android Handler消息机制的原理细节)