7.3.3 消息的发送过程
完成了Handler的初始化便可以发送消息了。接下来分析消息的发送过程。
1.Java层消息发送
消息创建后需要发送到目标组件进行处理,Android在Handler中提供了两组方法用于发送消息。
post方法包括:post(Runnable)、postDelayed(Runnable, long)、postAtTime(Runnable, long)
send方法包括:sendMessage(Message)、sendMessageDelayed(Message, long)、send MessageAtTime(Message, long)、sendEmptyMessage(int)
从方法声明看,post方法接收的是Runnable的线程参数,而send方法接收的是Message参数,其实这两组方法本质上是一样的。首先分析post方法,代码如下:
public class Handler{
……
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r),0);
}
private static Message getPostMessage(Runnable r){
Message m=Message.obtain();
m.callback=r;//post方法的Runnable参数存入Message的callback成员中
return m;
}
可见post方法内部还是调用了send方法,只是把传入的Runnable参数存入Message对象的callback成员变量中,因此这两组方法本质上是一样的。
接下来分析send方法,代码如下:
public class Handler{
……
//发送需要立即处理的消息
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg,0);
}
//delayMillis表示相对当前时间多久后处理该消息
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if(delayMillis<0){
delayMillis=0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis()+delayMillis);
}
//uptimeMillis表示在何时处理该消息
public boolean sendMessageAtTime(Message msg, long uptimeMillis)
{
boolean sent=false;
//获取Looper中存储的MessageQueue,创建Handler将其关联到自身内部
MessageQueue queue=mQueue;
if(queue!=null){
msg.target=this;//指定处理消息的目标端,this表示由当前Handler处理
sent=queue.enqueueMessage(msg, uptimeMillis);//消息入队
}else{
RuntimeException e=new RuntimeException(
this+"sendMessageAtTime()called with no mQueue");
}
return sent;
}
可见,sendMessage用于发送需要立即处理的消息,sendMessageDelayed用于发送相对于当前时间多久后需要处理的消息,sendMessageAtTime用于发送在指定的绝对时间需要处理的消息。
send方法最终调用MessageQueue的enqueueMessage方法处理消息,其代码如下:
final boolean enqueueMessage(Message msg, long when){
//判断该消息的FLAG_IN_USE标记位是否被设置,新消息不会设置该标记位
if(msg.isInUse()){
throw new AndroidRuntimeException(msg+
"This message is already in use.");
}
//由enqueueMessage方法入队的消息必须指定target
if(msg.target==null){
throw new AndroidRuntimeException("Message must have a target.");
}
boolean needWake;
synchronized(this){
if(mQuiting){//表示处理消息的目标端Handler所在线程已经异常退出
return false;
}
msg.when=when;//记录消息何时开始处理
/mMessages消息队列头部,即要当前处理的消息,p相当于工作指针,指向当前消息/
Message p=mMessages;
/*对应三种情况:1)消息队列为空,2)新消息需要立刻处理,
3)新消息处理时间早于消息队列头部消息的处理时间/
if(p==null||when==0||when<p.when){
msg.next=p;//消息队列头部的消息后移
mMessages=msg;//将新消息插入消息队列头部,这样可以立即处理
/*mBlocked默认为false,表示消息处理线程是否block,消息队列头部
加入了新消息,如果消息处理线程阻塞住,需要唤醒它处理消息/
needWake=mBlocked;
}else{//对应第四种情况:新消息处理时间晚于消息队列头部消息的处理时间
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;//找到新消息插入位置
prev.next=msg;
}
}
if(needWake){//假设需要唤醒消息处理线程,这里还需要执行nativeWake
nativeWake(mPtr);
}
return true;
}
通过post和send方法发送的消息,最终会存入MessageQueue内部的消息队列mMessages中。mMessages是Message类型的对象,其内部的next成员变量引用了下一条Message。Message在消息队列中是按照消息执行时间(when)排列的,因此在消息入队前对应以下四种处理情况:
当p==null,即当前消息队列为null,新加入的消息必然存入消息队列头部。
当when==0,即需要立即处理,此时应该加入消息队列头部。
当when<p.when,即新加入消息的处理时间要早于消息队列头部消息的处理时间,此时新加入消息也需要立即处理,仍然需要把新消息加入消息队列头部。
不满足以上三种情况时,新加入消息的处理时间要晚于消息队列头部消息的处理时间,因此需要遍历消息队列,找到新消息的插入位置。
将新消息加入消息队列只是enqueueMessage方法的第一步,如果新消息加入到消息队列头部,且此时处理消息的线程处于block状态,则需要调用nativeWake方法唤醒消息处理线程。
nativeWake是一个Native方法,其JNI实现方法位于frameworks/base/core/jni/android_os_MessageQueue.cpp中,代码如下:
static void android_os_MessageQueue_nativeWake(JNIEnv*env, jobject obj, jint ptr){
/*ptr表示Java层MessageQueue的mPtr成员变量,该变量
存储了JNI层创建的NativeMessageQueue的地址/
NativeMessageQueue*nativeMessageQueue=
reinterpret_cast<NativeMessageQueue*>(ptr);
return nativeMessageQueue->wake();
}
//NativeMessageQueue的mLooper成员变量指向JNI层的Looper对象
void NativeMessageQueue:wake(){
mLooper->wake();//nativeWake最终调用了JNI层Looper对象的wake方法
}
可见调用Java层MessageQueue的nativeWake方法,最终会调用JNI层Looper对象的wake方法。接下来分析该方法,代码如下:
void Looper:wake(){
ssize_t nWrite;
do{
//向管道中写入字符串"W"
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);
}
}
}
mWakeWritePipeFd是在创建JNI层的Looper对象时创建的管道的写端,这里通过write系统调用向管道写入"W"字符串,这样处理消息的线程会因为I/O事件被唤醒。
2.Native层发送消息
Native层也提供了发送消息的机制,与Java层消息发送机制的区别是:在Native层由Looper(C++)发送消息。Looper(C++)定义于frameworks/native/libs/utils/Looper.cpp中,代码如下:
//Vector<MessageEnvelope>mMessageEnvelopes;//guarded by mLock
void Looper:sendMessage(const sp<MessageHandler>&handler, const Message&message){
nsecs_t now=systemTime(SYSTEM_TIME_MONOTONIC);
sendMessageAtTime(now, handler, message);
}
void Looper:sendMessageDelayed(nsecs_t uptimeDelay,
const sp<MessageHandler>&handler, const Message&message){
nsecs_t now=systemTime(SYSTEM_TIME_MONOTONIC);
sendMessageAtTime(now+uptimeDelay, handler, message);
}
void Looper:sendMessageAtTime(nsecs_t uptime,
const sp<MessageHandler>&handler, const Message&message){
size_t i=0;
{//获取锁
AutoMutex_l(mLock);
//获得Native层消息队列的大小
size_t messageCount=mMessageEnvelopes.size();
//遍历消息队列,找到当前消息的插入位置
while(i<messageCount&&uptime>=mMessageEnvelopes.itemAt(i).uptime){
i+=1;
}
//将消息及其handler封装到MessageEnvelope中,插入mMessageEnvelopes
MessageEnvelope messageEnvelope(uptime, handler, message);
mMessageEnvelopes.insertAt(messageEnvelope, i,1);
if(mSendingMessage){
return;
}
}//释放锁
//仅当消息队列头部有新消息时唤醒poll循环
if(i==0){
wake();
}
}
Native层提供了与Java层相似的三个发送消息的send方法,消息最终通过sendMessageAtTime发送。在sendMessageAtTime方法中,首先在Native层的消息队列中找到新消息的插入位置,然后将新消息封装到MessageEnvelope中插入到消息队列中,最后调用wake方法唤醒消息读端。
MessageEnvelope用于封装当前消息及其处理器,定义于frameworks/native/include/utils/Looper.h中,代码如下:
struct MessageEnvelope{
MessageEnvelope():uptime(0){ }
MessageEnvelope(nsecs_t uptime, const sp<MessageHandler>handler,
const Message&message):
uptime(uptime),handler(handler),message(message){
}
nsecs_t uptime;//消息执行时间,默认为0
sp<MessageHandler>handler;//消息处理器
Message message;//当前发送的消息
};
消息插入到消息队列中,需要唤醒消息的读端,这部分工作由wake方法完成,代码如下:
void Looper:wake(){
ssize_t nWrite;
do{//与Java层一样,依然是向写管道端写入字符串"W"
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层的唤醒机制与Java层是一样的,都是向管道中写入"W"。
消息发送以后,Looper线程在loop()循环中便可以读取消息。接下来分析Looper线程运行过程的最后一步:loop方法。