Android 应用程序的消息处理机制
Android应用程序是通过消息来驱动。
创建线程消息队列
Android应用程序的消息队列是使用一个MessageQueue对象来描述,它可以通过调用Looper类的静态函数 prepareMainLooper 或者 prepare 来创建,其中,前者用来为应用程序的主线程创建消息队列;而后者用来为应用程序的其他的子线程创建消息队列。
由于Android应用程序的的消息处理机制不仅可以在java代码中使用,也可以在C++代码中使用,因此,Android系统在C++层中也有一个相应的Looper类和NativeMessageQueue类。其中,java层中Looper类和MessageQueue类是通过C++层中Looper类和NativeMessageQueue类来实现的
java层中的每一个MessageQueue对象都有一个类型为int的成员变量mPtr,它保存了C++层中的一个NativeMessageQueue对象的地址值,这样我们就可以将java层中的一个MessageQueue对象与C++层中的一个NativeMessageQueue对象关联起来。
Java层中的每一个Looper对象内部都有一个类型为MessageQueue 的成员变量mQueue,它指向了一个MessageQueue对象;而在C++层中,每一个NativeMessageQueue对象内部都有一个类型为Looper的成员变量mLooper,它指向了一个C++层的Looper对象。
Java层中的每一个MessageQueue对象都有一个类型为int的成员变量mPtr, 它保存了C++层中的一个NativeMessageQueue对象的地址值,这样我们就可以将java层中的一个MessageQueue对象与C++层中的一个NativeMessageQueue对象关联起来。
Java层中的每一个MessageQueue对象还有一个类型为Message的成员变量mMessages, 它用来描述一个消息队列,我们可以调用MessageQueue类的成员函数enqueueMessage来往里面添加一个消息。
C++层中的Looper对象有两个类型为int的成员变量mWakeReadPipeFd 和 mWakeWritePipeFd,他们分别用来描述一个管道的读端文件描述符和写端文件描述符。当一个线程的消息队列没有消息需要处理,它就会在这个管道的读端描述符上进行睡眠等待,直到其他线程通过这个管道的写端文件描述符来唤醒它为止。
当我们调用Java层的Looper类的静态成员函数prepareMainLooper或者prepare来为一个线程创建一个消息队列时,Java层的Looper类就会在这个线程中创建一个Looper对象和一个MessageQueue对戏那个。在创建Java层的MessageQueue对戏那个的过程中,又会调用它的成员函数nativeInit 在C++层中创建一个NativeMessageQueue对象和一个Looper对象。在创建C++层的Loop而对象时,又会创建一个管道,这个管道的读端文件描述符 和 写端文件描述符就保存在它的成员变量mWakeReadPipeFd 和 mWakeWritePipeFd 中。
Java层中的Looper类的成员函数loop、MessageQueue类的成员函数nativePollOnce和nativeWake,以及C++层中的NativeMessageQueue类的成员函数pollOnce和wake、Looper类的成员函数pollOnce和wake,是与线程的消息循环、消息发送和消息处理相关的,
public class Looper {
// sThreadLocal.get() will return null unless you've called prepare().
private static final ThreadLocal sThreadLocal = new ThreadLocal();
final MessageQueue mQueue;
public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper());
}
}
Looper类有一个类型为ThreadLocal的静态成变量sThreadLocal,它是用来保存线程中Looper对象的。我们可以将Looper类的讲台成员变量sThreadLocal理解为一个线程局部变量,或者一个HashMap,每一个创建了消息队列的Android应用程序线程在里面都有一个关联的Looper对象;而当我们调用这个静态成员变量的成员函数set时,就可以将一个Looper对象与当前线程关联起来。
一个Looper对象在创建的过程,会在内部创建一个MessageQueue对象,并且保存在它的成员变量mQueue中
private Looper() {
mQueue = new MessageQueue();
}
一个MessageQueue对象的创建的过程中,又会在C++层中创建一个NativeMessageQueue对象,这是通过调用MessageQueue类的成员函数nativeInit来实现的。
public class MessageQueue {
private int mPtr;
private native void nativeInit();
MessageQueue() {
nativeInit();
}
}
MessageQueue类的成员函数nativeInit是一个JNI方法,它是由C++层中的函数android_os_MessageQueue_nativeInit 来实现的
static void android_os_MessageQueue_nativeInit(JNIEnv* env, jobject obj) {
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
if (! nativeMessageQueue) {
jniThrowRuntimeException(env, "Unable to allocate native queue");
return;
}
android_os_MessageQueue_setNativeMessageQueue(env, obj, nativeMessageQueue);
}
static struct {
jclass clazz;
jfieldID mPtr; // native object attached to the DVM MessageQueue
} gMessageQueueClassInfo;
FIND_CLASS(gMessageQueueClassInfo.clazz, "android/os/MessageQueue");
GET_FIELD_ID(gMessageQueueClassInfo.mPtr, gMessageQueueClassInfo.clazz,
"mPtr", "I");
static void android_os_MessageQueue_setNativeMessageQueue(JNIEnv* env, jobject messageQueueObj,
NativeMessageQueue* nativeMessageQueue) {
env->SetIntField(messageQueueObj, gMessageQueueClassInfo.mPtr,
reinterpret_cast(nativeMessageQueue));
}
gMessageQueueClassInfo.mPtr 指向的就是MessageQueue的成员变量mPtr, 也就是将native层的 NativeMessageQueue 对象的指针 赋值给java层的对象中的成员变量。从而将java层和native层的连个对象关联起来。
一个NativeMessageQueue对象在创建过程中,又会在内部创建一个C++层的Looper对象,如下所示:
NativeMessageQueue::NativeMessageQueue() {
mLooper = Looper::getForThread();
if (mLooper == NULL) {
mLooper = new Looper(false);
Looper::setForThread(mLooper);
}
}
第2行代码首先调用C++层的Looper类的静态成员函数getForThread来检查是否已经为当前线程创建过一个C++层的Looper对象。如果没有创建过,那么接着第4行代码首先创建一个C++层的Looper对象,并且将它保存在NativeMessageQueue类的成员变量mLooper中,然后第5行代码再调用C++层的Looper类的静态成员函数setForThread将它与当前线程关联起来。
一个C++层的Looper对象在创建的过程,又会在内部创建一个管道
Looper::Looper(bool allowNonCallbacks) :
mAllowNonCallbacks(allowNonCallbacks),
mResponseIndex(0) {
int wakeFds[2];
int result = pipe(wakeFds);
mWakeReadPipeFd = wakeFds[0];
mWakeWritePipeFd = wakeFds[1];
result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake read pipe non-blocking. errno=%d",
errno);
result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);
LOG_ALWAYS_FATAL_IF(result != 0, "Could not make wake write pipe non-blocking. errno=%d",
errno);
#ifdef LOOPER_USES_EPOLL
// Allocate the epoll instance and register the wake pipe.
mEpollFd = epoll_create(EPOLL_SIZE_HINT);
LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance. errno=%d", errno);
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);
}
第5行代码首先调用函数pipe创建一个管道,接着第8行和第9行代码分别将这个管道的读端文件描述符和写端文件描述符保存在C++层的Looper类的成员变量mWakeReadPipeFd 和 mWakeWritePipeFd中。
这个管道在一个线程的消息循环过程中起的作用非常大。首先,当一个线程没有新的消息需要处理时,它就会睡眠在这个读端文件描述符上,直到有新的消息需要处理为止;其次,当其他线程向这个线程的消息队列发送一个消息之后,其他线程就会通过这个管道的写端文件描述符往这个管道写入一个数据,从而将这个线程唤醒,以便它可以对刚才发送到它的消息队列中的消息进行处理。
第14行代码首先调用函数epoll_create 来创建一个epoll实例,并且将它的文件描述符保存在C++层的Looper类的成员变量mEpollFd中。接着将前面所创建的管道的读端文件描述符添加到这个epoll实例中,以便可以对它所描述的管道的写操作进行监听。
Linux系统的epoll机制是为了同时监听多个文件描述符的IO读写事件而设计的,它是一个多路复用IO接口,类似于Linux系统的select机制,但是它是select机制的增强版,如果一个epoll监听了大量的文件描述符的IO读写事件,但是只有少量的文件描述符是活跃的,即只有少量的文件描述符是活跃的,即只有少量的文件描述符将会发生IO读写事件,那么这个epoll实例可以显著减少CPU的使用率,从而提高系统的并发处理能力。
线程消息循环过程
一个Android 应用程序的消息队列创建完成之后,我们就可以调用Looper类的静态成员函数Loop使它进入到一个消息循环总。
Looper.loop
public static final void loop() {
Looper me = myLooper();
MessageQueue queue = me.mQueue;
Binder.clearCallingIdentity();
while (true) {
Message msg = queue.next(); // might block
if (msg != null) {
msg.target.dispatchMessage(msg);
msg.recycle();
}
}
}
首先获得当前线程的消息队列,接着while循环不断地检查这个消息队列是否有新的消息需要处理。如果有新的消息需要处理,那么获得的Message对象msg就不等于null;否则,当前线程就会在MessageQueue类的成员函数next中进入睡眠等待状态,直到有新的消息需要处理为止。
MessageQueue.next
final Message next() {
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(mPtr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
final Message msg = mMessages;
if (msg != null) {
final long when = msg.when;
if (now >= when) {
mBlocked = false;
mMessages = msg.next;
msg.next = null;
return msg;
} else {
nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE);
}
} else {
nextPollTimeoutMillis = -1;
}
}
// 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;
}
}
变量pendingIdleHandlerCount, 用来保存注册到消息队列中的空闲消息处理器的个数,当线程发现它的消息队列没有新的消息需要处理时,不是马上就进入睡眠等待状态,而是先调用注册到他的消息队列中的IdleHandler对象的成员函数queueIdle,以便他们有机会在线程空闲时执行一些操作。
变量nextPollTimeoutMills,用来描述当消息队列中没有新的消息需要处理时,当前线程需要进入睡眠等待状态的时间。如果变量nextPollTimeoutMills的值等于0,那么就表示及时消息队列中没有新的消息需要处理,当前线程也不要进入睡眠等待状态。如果变量nextPollTimeoutMillis的值等于-1,那么就表示当消息队列中没有新的消息需要处理时,当前线程需要无限地处于睡眠等待状态,直到它被其他线程唤醒位置
调用成员函数nativePollOnce时,当前线程可能会进入睡眠等待状态,那么进入睡眠等待状态的时间就由第二个参数nextPollTimeoutMillis来指定。
MessageQueue类内部有一个类型为Message的成员变量mMessages,用来描述当前线程需要处理的消息。当前线程从成员函数nativePollOnce返回来之后,如果它有新的消息需要处理,即MessageQueue类的成员变量mMessages不等于null,那么接下来就会对它进行处理;否则,就会将变量nextPollTimeoutMills的值设置为-1,表示当前线程下次在调用成员函数nativePollOnce时,如果没有新的消息需要处理,那么就要无限地处于睡眠等待状态,知道它被其他线程唤醒位置。
MessageQueue.nativePollOnce
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj,
jint ptr, jint timeoutMillis) {
NativeMessageQueue* nativeMessageQueue = reinterpret_cast(ptr);
nativeMessageQueue->pollOnce(timeoutMillis);
}
void NativeMessageQueue::pollOnce(int timeoutMillis) {
mLooper->pollOnce(timeoutMillis);
}
假设当前线程没有新的消息需要处理,就会在调用函数epoll_wait进入睡眠状态,直到其他线程向它的消息队列发送一个新的消息为止。