Android消息处理机制

Android的消息驱动机制主要包含这几个概念:消息的表示,消息的队列,消息循环,消息处理。下面分别介绍这几个概念:Message就是消息的表示,MessageQueue是消息队列,Looper是用于循环取出消息并进行处理的,Handler是消息循环从消息队列中取出消息后进行处理。

平常最常使用的就是Message和Handler,如果用过HandlerThread或者自己实现类似HandlerThread的东西还会接触Looper,而MessageQueue是Looper内部使用的,对于标准的SDK,我们是无法实例化并使用的(构造函数是包可见性)。

平时我们接触到的Looper、Message、Handler都是用Java实现的,Android作为基于Linux的系统,底层用C、C++实现,而且还有NDK的存在,消息驱动的模型怎么可能只存在于JAVA层,实际上,在Native层存在与Java层对应的类如Looper、MessageQueue等。

1.初始化消息队列:

首先来看一下如果一个线程想实现消息循环应该怎么做,以HandlerThread为例:

public void run() {

mTid = Process.myTid();

Looper.prepare();

synchronized(this) {

mLooper = Looper.myLooper();

notifyAll();

}

Process.setThreadPriority(mPriority);

onLooperPrepared();

Looper.loop();

mTid = -1;

}

首先调用prepare初始化MessageQueue与Looper,然后调用loop进入消息循环,先看一下Looper.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));

}

重载函数,quitAllowed默认为true,从名字可以看出来就是消息循环是否可以退出,默认是可以退出的,Main线程(UI线程)初始化消息循环时会调用prepareMainLooper,传进去的是false,使用了ThreadLocal,每个线程可以初始化一个Looper。

再来看一下Looper在初始化时都做了什么:

private Looper(boolean quitAllowed) {

mQueue = new MessageQueue(quitAllowed);

mRun = true;

mThread = Thread.currentThread();

}

MessageQueue(boolean quitAllowed) {

mQuitAllowed = quitAllowed;

nativeInit();

}

在Looper初始化时,新建了一个MessageQueue的对象保存了在成员mQueue中,MessageQueue的构造函数是包可见性,所以我们是无法直接使用的,在MessageQueue初始化的时候调用了nativeInit,这是一个Native方法:


在nativeInit中,new了一个Native层的MessageQueue的对象,并将其地址保存在了Java层MessageQueue的成员mPtr中,Android中有好多这样的实现,一个类在Java层与Native层都有实现,通过JNI的GetFieldID与SetIntField把Native层的类的地址保存到Java层类的实例的mPtr成员中,比如Parcel。


在NativeMessageQueue的构造函数中获得了一个Native层的Looper对象,Native层的Looper也使用了线程本地存储,注意new Looper时传入了参数false。


Native层的Looper使用了epoll。初始化了一个管道,用mWakeWritePipeFd与mWakeReadPipeFd分别保存了管道的写端与读端,并监听了读端的EPPOLLIN事件。注意下初始化列表的值,mAllowNonCallbacks的值为false。

mAllowNonCallbacks是做什么的?使用epoll仅为了监听mWakeReadPipeFd的事件?其实Native Looper不仅可以监听这一个描述符,Looper还提供了addFd方法。


fd表示要监听的描述符。ident表示要监听的事件的标识,值必须>=0或者为ALOOPER_POLL_CALLBACK(-2),event表示要监听的事件,callback是事件发生时的回调函数,mAllowNonCallbacks的作用就在于此,当mAllowNonCallbacks为true时允许callback为NULL,在pollOnce中ident作为结果返回,否则不允许callback为空,当callback不为NULL时,ident的值会被忽略。还是直接看代码方便理解:


如果callback为空会检查mAllowNonCallbacks看是否允许callback为空,如果允许callback为空还会检测ident是否>=0.如果callback不为空会把ident的值赋值为ALOOPER_POLL_CALLBACK,不管传进来的是什么值。


接下来把传进来的参数值封装到一个Request结构体中,并以描述符为键保存到一个KeyedVector mRequests中,然后通过epoll_ctl添加或替换(如果这个描述符之前有调用addFD添加监听)对这个描述符事件的监听。


发送消息


通过Looper.prepare初始化好消息队列后就可以调用Looper.loop进入消息循环了,然后我们就可以向消息队列发送消息,消息循环就会取出消息进行处理,在看消息处理之前,先看一下消息是怎么被添加到消息队列的。

在Java层,message类表示一个消息对象,要发送消息首先就要先获得一个消息对象,message类的构造函数是public的,但是不建议直接new Message,Message内部保存了一个缓存的消息池,我们可以用obtain从缓存池获得一个消息,Message使用完后系统会调用recycle回收,如果自己new很多Message,每次使用完后系统放入缓存池,会占用很多内存的。


Message内部通过next成员实现了一个链表,这样sPool就为了一个Messages的缓存链表。

消息对象获取到了怎么发送呢,大家都知道是通过Handler的post、sendMessage等方法,其实这些方法最终都是调用的同一个方法sendMessageAtTime:


sendMessageAtTime获取到消息队列然后调用enqueueMessage方法,消息队列mQueue是从与Handler关联的Looper获得的。


enqueueMessage将message的target设置为当前的handler,然后调用MessageQueue的enqueueMessage,在调用queue.enqueueMessage之前判断了mAsynchronous,从名字看是异步消息的意思,要明白Asynchronous的作用,需要先了解一个概念Barrier。


Barrier与Asynchronous Message


Barrier是什么意思呢?从名字看是一个拦截器,在这个拦截器后面的消息都暂时无法执行,直到这个拦截器被移除了,MessageQueue有一个函数叫enqueueSyncBarier可以添加一个Barrier。


在enqueueSyncBarrier中,obtain了一个Message,并设置msg.arg1 = token,token仅是一个每次调用enqueueSyncBarrier时自增的int值,目的是每次调用enqueueSyncBarrier时返回唯一的一个token,这个Message同样需要设置执行时间,然后插入到消息队列,特殊的是这个Message没有设置target,即msg.target为null。


如果队列头部的消息的target为null就表示他是个barrier,因为只有两种方法往mMessages中添加消息,一种是enqueueMessage,另一种是enqueueBarrier,而enqueueMessage中如果mst.target为null是直接抛异常的,后面会看到。


所谓的异步消息其实就是这样的,我们可以通过enqueueBarrier往消息队列中插入一个barrier,那么队列中执行时间在这个barrier以后的同步消息都会被这个barrier拦截住无法执行,直到我们调用removeBarrier移除了这个barrier,而异步消息则没有影响,消息默认就是同步消息,除非我们调用了Message的setSynchronous,这个方法是隐藏的,只有在初始化handler时通过参数指定往这个handler发送的消息都是异步的,这样在Handler的enqueueMessage中就会调用Message的setAsynchronous设置消息是异步的,从上面handler.enqueueMessaeg的代码中可以看到。


所谓异步消息,其实只有一个作用,就是在设置barrier时仍可以不受barrier的影响被正常处理,如果没有设置barrier,异步消息就与同步消息没有区别,可以通过removeSyncBarrier移除Barrier。


参数token就是enqueueSyncBarrier的返回值,如果没有调用指定的token不存在是会抛异常的。


在enqueueMessage中首先判断,如果当前的消息队列为空,或者新添加的消息的执行时间when是0,或者新添加的消息的执行时间比消息队列头的消息的执行时间还早,就把消息添加到消息队列头(消息队列按时间排序),否则就要找到合适的位置将当前消息添加到消息队列。


Native发送消息


消息模型不只是Java层用的,Native层也可以用,前面也看到了消息队列初始化时也同时初始化了Native层的Looper与NativeMessageQueue,所以Native层应该也是可以发送消息的。与Java层不同的是,Native是通过Looper发消息的,同样所有的发送方法最终是通过sendMessageAtTime。


Native Message只有一个int型的what字段用来区分不同的消息,sendMessageAtTime指定了Message,Message要执行的事件when,与处理这个消息的Handler:Messagehandler,然后用MessageEnvelope封装了time,MessageHandler与Message,native层发的消息都保存到了mMessageEnvelopes中,mMessageEnvelopes是一个Vector<MessageEnvelope>,native层消息同样是按时间排序,与Java层的消息分别保存在两个队列中。


消息循环

消息队列初始化好了,也知道怎么发消息了,下面就是怎么处理消息了,看Handler.loop函数:

loop每次从MessageQueue取出一个Message,调用msg.target.dispatchMessage(msg),target就是发送message时跟message关联的handler,这样就调用到了熟悉的dispatchMessage,Message被处理后会被recycle。当queue.next返回null时会退出消息循环,接下来就看一下MessageQueue.next是怎么取出消息的,又会在什么时候返回null。


MessageQueue.next首先会调用nativePollOnce,然后如果mQuiting为true就返回null,Looper就会退出消息循环。

接下来取消息队列头部的消息,如果头部消息是barrier(target == null)就往后遍历找到第一个异步消息,接下来检测获取到的消息(消息队列头部的消息或者第一个异步消息),如果为null表示没有消息要执行,设置nextPollTimeoutMillis = -1;否则检测这个消息要执行的时间,如果到执行时间了就将这个消息markInUse并从消息队列移除,然后从next返回到loop;否则设置nextPollTimeoutMillis = (int)Math.min(msg.when - now, Integer.MAX_VALUE),即距离最近要执行的消息还需要多久,无论是当前消息队列没有消息可以执行(设置了Barrier并且没有异步消息或消息队列为空)还是队列头部的消息未到执行时间,都会执行后面的代码,看有没有设置IdleHandler,如果有就运行IdleHandler,当IdleHandler被执行之后会设置nextPollTimeoutMillis = 0.

首先看一下nativePollOnce,native方法,调用JNI,最后调到了Native Looper::pollOnce,并从Java层传进去了nextPollTimeMillis,即Java层的消息队列中执行时间最近的消息还要多久到执行时间。


Java层的消息都保存在了Java层messagequeue的成员mMessages中,Native层的消息都保存在了NativeLooper的mMessageEnvelopes中,这就可以说有两个消息队列,而且都是按时间排列的,timeOutMillis表示Java层下了要执行的消息还要多久执行,mNextMessageUpdate表示Native层下个要执行的消息还要多久执行,如果timeOutMillis 为0,epoll_wait不设置TimeOut直接返回;如果为-1说明Java层无消息直接用Native的time out;否则pollInner取这两个钟的最小值作为timeout调用epoll_wait,当epoll_wait返回时就可能有以下几种情况:

1.出错返回

2.Time Out

3.正常返回,描述符上有事件产生。

如果是前两种情况直接goto Done.

否则就说明FD上有事件发生了,如果是mWakeReadPipeFd的EPOLLIIN事件就调用awoken,如果不是mWakeReadPipeFd,那就是通过addFD 添加的fd,在addFD中将要监听的fd及其events,callback,data封装成了Request对象,并以fd为键保存到了KeyedVector mRequests中,所以在这里就以fd为键获得在addFD时关联的Request,并连同events通过pushResponse加入mResonse队列,Resonse仅是对events与Request的封装,如果是epoll_wait出错或timeout,就没有描述符上有事件,就不用执行这一段代码,所以直接goto DONE了


wake与awoken


在native Looper的构造函数中,通过pipe打开了一个管道,并用mWakeReadPipeFD与mWakeWritePipeFD分别保存了管道的读端与写端,然后用epoll_ctl(mEpollFd,EPOLL_CTL_ADD, mWakeReadPipeFd,&eventItem)监听了读端的EPOLLIN事件,在pollInner中通过epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis)读取事件,那是在什么时候往mWakeWritePipeFD写,又是在什么时候读的mWakeReadPipeFd呢



在头部插入消息不一定调用nativeWake,因为之前可能正在执行IdleHandler,如果执行了IdleHandler,就在IdleHandler执行后把nextPollTimeoutMillis设置为0,下次进入for循环就用0调用nativePollOnce,不需要wake,只有在没有消息可以执行(消息队列为空或没到执行时间)并且没有设置Idlehandler时mBlocked才会为true。

如果Java层的消息队列被barrier block住了并且当前插入的是一个异步消息有可能需要唤醒Looper,因为异步消息可以在barrier下执行,但是这个异步消息一定要是执行时间最早的异步消息。

退出Looper也需要wake,removeSyncBarrier时也可能需要。



  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值