Android应用程序线程的消息队列是使用一个MessageQueue对象来描述的,它随着Looper类的创建而创建,即通过调用Looper类的静态成员函数prepareMainLooper或者prepare来创建,其中,prepareMainLooper用来为应用程序的主线程创建消息队列;而prepare用来为应用程序的其他子线程创建消息队列。
注意:prepareMainLooper只能在android应用程序主线程中调用。Android应用程序主线程是一个特殊的线程,因为只有它才可以执行与UI相关的操作,因此,又成为UI线程。其他子线程需要执行一些UI操作时,必须使用函数getMainLooper得到主线程的Looper对象,往主线程的消息队列中发送与UI操作相关的消息。
在分析Android应用程序线程的消息队列的创建之前,我们首先简单介绍一下Looper类和MessageQueue类的实现。
由于Android应用程序的消息处理机制不仅可以在Java代码中使用,也可以在C++代码中使用,因此,Android系统在C++层中有一个相应的Looper类和NativeMessageQueue类。其中,Java层中的Looper类和MessageQueue类是通过C++层中的Looper类和NativeMessageQueue类来实现的,它们的关系如图。
Looper、MessageQueue、Looper(Native)和NativeMessageQueue关系图
注意:Java层的消息处理机制和C++层的消息处理机制没有太大的联系,只是Java层消息处理机制使用了C++层消息处理机制的休眠/唤醒机制。若有时间,后面会讲解C++层消息处理机制。
Java层中的每一个Looper对象内部都有一个类型为MessageQueue的成员变量mQueue,它只想一个MessageQueue对象;在C++层中,每一个NativeMessageQueue对象内部都有一个类型为Looper的成员变量,它指向一个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++层的Looper对象是,又会创建一个管道,这个管道的读端文件描述符和写端文件描述符就保存在它的成员变量mWakeReadPipeFd和mWakeWritePipeFd中。
下面我们对源码进行分析,首先看看时序图。
循环和消息队列创建时序图
简单介绍一下Looper类中的
/*创建一个Looper对象(子线程)*/
public static void prepare() {
prepare(true);
}
/*如果quitAllowed为true则表示该子线程可以销毁,一般
*为主线程之外的其他线程。
*如果quitAllowed为false则表示该线程为主线程
**/
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
/*sThreadLocal保存线程中的Looper对象。*/
sThreadLocal.set(new Looper(quitAllowed));
}
public static void prepareMainLooper() {
/*创建一个Looper对象*/
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
public static Looper getMainLooper() {
synchronized (Looper.class) {
return sMainLooper;
}
}
public static Looper myLooper() {
turn sThreadLocal.get();
}
getMainLooper()用于得到主线程的Looper对象,我们知道主线程是UI线程,不能自己退出。getLooper()用于得到其他子线程的Looper对象。注意到主线程和子线程创建的Looper对象,都被赋值给类型为sThreadLocal的静态变量sThreadLocal ,它具体有什么用,我们后面讨论。子线程和主线程还有一个主要的区别,主线程的Looper对象被赋值给类的静态成员变量sMainLooper。下面开始从源码的角度分析循环和消息队列的创建过程。
frameworks\base\core\java\android\os\Looper.java
/*创建一个Looper对象(子线程)*/
public static void prepare() {
prepare(true);
}
Looper类的静态方法prepare()用来创建一个Looper对象,调用重载同名的静态方法prepare();下面我们看一下重载的成员方法prepare()。
frameworks\base\core\java\android\os\Looper.java
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
/*sThreadLocal保存线程中的Looper对象。*/
sThreadLocal.set(new Looper(quitAllowed));
}
重载函数,quitAllowed默认为true,从名字可以看出来就是消息循环是否可以退出,默认是可退出的,Main线程(UI线程)初始化消息循环时会调用prepareMainLooper,传进去的是false。感兴趣的可以自己查看。最后调用了Looper的构造方法,并且把创建的Looper类保存在ThreadLocal,
每个线程可以初始化一个Looper。ThreadLocal后期我们会单独研究。下面我们看一下Looper类的构造方法。
frameworks\base\core\java\android\os\Looper.java
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Looper类的构造方法,首先创建一个消息循环队列,参数为quitAllowed,意义同上。调用Thread类的currentThread()方法,把当前线程存在Looper的成员变量mThread。下面我们看一下MessageQueue类的构造方法。
frameworks\base\core\java\android\os\MessageQueue.java
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
MessageQueue类的构造方法做了两件事,第一件事是把Looper类传递过来的参数quitAllowed赋值给MessageQueue类的成员变量mQuitAllowed,变量ture为允许退出,false为不能退出。第二件事是调用JNI方法nativeInit(),并把返回值保存在MessageQueue类的成员变量mPtr中。前面我们介绍到,mPtr保存的是Native层的NativeMessageQueue类的地址值。下面我们看一下Native的JNI方法。
frameworks\base\core\jni\android_os_MessageQueue.cpp
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
/*创建一个NativeMessageQueue对象,在内部创建一个C++层的Looper对象*/
NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
if (!nativeMessageQueue) {
jniThrowRuntimeException(env, "Unable to allocate native queue");
return 0;
}
nativeMessageQueue->incStrong(env); /*增加引用计数*/
return reinterpret_cast<jlong>(nativeMessageQueue);
}
从以上代码可以看出,native层的方法主要是创建了一个NativeMessageQueue对象,增加它的强引用计数,把它的地址值返回给调用对象。下面我们看一下Native层的NativeMessageQueue构造方法。
frameworks\base\core\jni\android_os_MessageQueue.cpp
NativeMessageQueue::NativeMessageQueue() : mInCallback(false), mExceptionObj(NULL) {
/*getForThread检查是否已经为当前线程创建一个c++层的Looper对象*/
mLooper = Looper::getForThread();
if (mLooper == NULL) {
mLooper = new Looper(false);
Looper::setForThread(mLooper);
}
}
NativeMessageQueue的构造方法,先调用native层Looper对象的成员方法getForThread(),检查是否已经为当前线程创建一个c++层的Looper对象。如果没有则创建Looper对象,并保存在NativeMessageQueue对象的成员变量mLooper中。下面看一下native层Looper类的构造方法。
system\core\libutils\Looper.cpp
Looper::Looper(bool allowNonCallbacks) :
mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
int wakeFds[2];
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。初始化了一个管道,用mWakeWritePipeFd与mWakeReadPipeFd分别保存了管道的写端与读端,并监听了读端的EPOLLIN事件。epoll机制,感兴趣的自己可以先了解一下,后面我们会补充一下。
思考(以后的拓展方向):
1、注意下初始化列表的值,mAllowNonCallbacks的值为false。mAllowNonCallback是做什么的?
2、使用epoll仅为了监听mWakeReadPipeFd的事件?其实Native Looper不仅可以监听这一个描述符,
Looper还提供了addFd方法。handler机制会怎么处理这些?