概述
一般讲handler,会讲到这三个Handler 、 Looper 、Message 这三者涉及到一个概念,叫做异步消息处理线程;
- UI线程初始化,会通过ThreadLocal创建一个Looper(轮询器),一个线程对应唯一一个Looper,使用ThreadLocal的目的是保证每一个线程只创建唯一一个Looper。
- Looper在初始化的时候会创建一个messageQueue(阻塞队列),创建完了之后,Looper负责不断的从MessageQueue读消息,如果没有消息,则进入阻塞,有消息则读取消息,调用相应回调处理消息;[使用了消息池]
子线程向消息队列发送消息需要使用Looper.prepare();比如:
Looper.prepare();
Toast.makeText(getApplicationContext(), "子线程的Toast", 0).show();
Looper.loop();
Looper.prepare()的目的是创建一个looper,主线程不需要调用而子线程需要调用的原因是,主线程在初始化的时候就自己创建了一个;
图片描述
(图一)消息池
(图二)创建handler
(图三)使用handler发送消息
源码分析
我们在使用的时候主要是用到了prepare和loop方法在子线程中使用handler,我们先看prepare方法:
1. public static final void prepare() {
2. if (sThreadLocal.get() != null) {
3. throw new RuntimeException(“Only one Looper may be created per thread”);
4. }
5. sThreadLocal.set(new Looper(true));
6. }
从这里看得出来,一个线程中的prepare只能调用一次,否则会出错;
我们先来看看looper的构造方法:
1. private Looper(boolean quitAllowed) {
2. mQueue = new MessageQueue(quitAllowed);
3. mRun = true;
4. mThread = Thread.currentThread();
5. }
在调用prepare的时候创建了一个looper对象,这个对象保留了对该线程的引用,在创建looper的时候创建了一个messagequeue,我们来看下messagequeue怎么构造的:
public class MessageQueue {
......
private int mPtr; // used by native code
private native void nativeInit();
MessageQueue() {
nativeInit();
}
......
}
我们看到nativeInit这个方法,这个JNI方法定义在frameworks/base/core/jni/android_os_MessageQueue.cpp文件中:
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);
}
在JNI中,也相应地创建了一个消息队列NativeMessageQueue,NativeMessageQueue类也是定义在frameworks/base/core/jni/android_os_MessageQueue.cpp文件中,它的创建过程如下所示:
NativeMessageQueue::NativeMessageQueue() {
mLooper = Looper::getForThread();
if (mLooper == NULL) {
mLooper = new Looper(false);
Looper::setForThread(mLooper);
}
}
它主要就是在内部创建了一个Looper对象,注意,这个Looper对象是实现在JNI层的,它与上面Java层中的Looper是不一样的,不过它们是对应的; 我们再回到NativeMessageQueue的构造函数中,看看JNI层的Looper对象的创建过程,即看看它的构造函数是如何实现的,这个Looper类实现在frameworks/base/libs/utils/Looper.cpp文件中:
Looper::Looper(bool allowNonCallbacks) :
mAllowNonCallbacks(allowNonCallbacks),
mResponseIndex(0) {
int wakeFds[2];
int result = pipe(wakeFds);
......
mWakeReadPipeFd = wakeFds[0];
mWakeWritePipeFd = wakeFds[1];
......
#ifdef LOOPER_USES_EPOLL
// Allocate the epoll instance and register the wake pipe.
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);
......
#else
......
#endif
......
}
请注意看前面一部分,它主要就是通过pipe系统调用来创建了一个管道了;然后我们知道管道有一个写入端和一个读取端,looper就在读取端读消息,handler就往写入端写消息,looper拿到消息之后通知唤醒主线程取消息,主线程拿到这个消息通过回调展现出这个消息;
画个图加深下理解,画的不太好,大家啊还是看下面的总结吧;
总结
A. Android应用程序的消息处理机制由消息循环、消息发送和消息处理三个部分组成的。
B. Android应用程序的主线程在进入消息循环过程前,会在内部创建一个Linux管道(Pipe),这个管道的作用是使得Android应用程序主线程在消息队列为空时可以进入空闲等待状态,并且使得当应用程序的消息队列有消息需要处理时唤醒应用程序的主线程。
C. Android应用程序的主线程进入空闲等待状态的方式实际上就是在管道的读端等待管道中有新的内容可读,具体来说就是是通过Linux系统的Epoll机制中的epoll_wait函数进行的。
D. 当往Android应用程序的消息队列中加入新的消息时,会同时往管道中的写端写入内容,通过这种方式就可以唤醒正在等待消息到来的应用程序主线程。
E. 当应用程序主线程在进入空闲等待前,会认为当前线程处理空闲状态,于是就会调用那些已经注册了的IdleHandler接口,使得应用程序有机会在空闲的时候处理一些事情。