首页 > 代码库 > Android中对消息机制(Handler)的再次解读
Android中对消息机制(Handler)的再次解读
今天遇到一些关于在子线程中操作Handler的问题,感觉又要研究源代码了,但是关于Handler的话,我之前研究过,可以参考这篇文章:http://blog.csdn.net/jiangwei0910410003/article/details/17021809。但是这篇文章没有说的那么深入了,所以这次就更深入的解读一下。
摘要
Android中的应用程序都是通过消息驱动的,系统为每一个应用程序维护一个消息队列(MessageQueue),应用程序的主线程不断的从这个消息队列中获取消息(Looper),然后对这些消息进行处理(Handler),所以这里有三个重要的角色:
*MessageQueue:存放消息(Message)的消息队列
*Looper:轮循从消息队列中取出消息然后分发
*Handler:发送消息和处理消息
他们三者的关系是:MessageQueue和Looper是一一对应的,一个Looper可以对应多个Handler。同时这里还有一个角色就是线程Thread.后面在分析源码的时候会说到这点,当然Thread和Looper的关系也是一一对应的。
下面就从源码中分析一下流程吧(有时候看源码真的能够明白好多事~~)
第一、分析MessageQueue源码
首先来看一下MessageQueue类:
private native static long nativeInit(); private native static void nativeDestroy(long ptr); private native static void nativePollOnce(long ptr, int timeoutMillis); private native static void nativeWake(long ptr); private native static boolean nativeIsIdling(long ptr);这个类中主要就是这几个本地方法,所以只要看懂这些本地方法的实现就可以了。
当然这些本地方法肯定是在MessageQueue中的上层方法中调用的:
*构造方法中调用nativeInit
*获取消息方法next中调用nativePollOnce(其实相当于是处理消息)
*把消息加入到消息队列中的enqueueMessage方法中调用了nativeWake(其实相当于发送消息)
下面来看一下这些本地方法(这里主要是参考了老罗的文章:http://blog.csdn.net/luoshengyang/article/details/6817933)
1、nativeInit
它是在MessageQueue的构造方法中调用的
这个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是不一样的,不过它们是对应的,下面我们进一步分析消息循环的过程的时候,读者就会清楚地了解到它们之间的关系。
这个Looper的创建过程也很重要,不过我们暂时放一放,先分析完android_os_MessageQueue_nativeInit函数的执行,它创建了本地消息队列NativeMessageQueue对象之后,接着调用android_os_MessageQueue_setNativeMessageQueue函数来把这个消息队列对象保存在前面我们在Java层中创建的MessageQueue对象的mPtr成员变量里面:
static void android_os_MessageQueue_setNativeMessageQueue(JNIEnv* env, jobject messageQueueObj, NativeMessageQueue* nativeMessageQueue) { env->SetIntField(messageQueueObj, gMessageQueueClassInfo.mPtr, reinterpret_cast<jint>(nativeMessageQueue)); }这里传进来的参数messageQueueObj即为我们前面在Java层创建的消息队列对象,而gMessageQueueClassInfo.mPtr即表示在Java类MessageQueue中,其成员变量mPtr的偏移量,通过这个偏移量,就可以把这个本地消息队列对象natvieMessageQueue保存在Java层创建的消息队列对象的mPtr成员变量中,这是为了后续我们调用Java层的消息队列对象的其它成员函数进入到JNI层时,能够方便地找回它在JNI层所对应的消息队列对象。
我们再回到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系统调用来创建了一个管道了:
int wakeFds[2]; int result = pipe(wakeFds); ...... mWakeReadPipeFd = wakeFds[0]; mWakeWritePipeFd = wakeFds[1];
插播一、Linux中的通信机制
管道是Linux系统中的一种进程间通信机制,具体可以参考一本书《Linux内核源代码情景分析》中的第6章--传统的Uinx进程间通信。简单来说,管道就是一个文件,在管道的两端,分别是两个打开文件文件描述符,这两个打开文件描述符都是对应同一个文件,其中一个是用来读的,别一个是用来写的,一般的使用方式就是,一个线程通过读文件描述符中来读管道的内容,当管道没有内容时,这个线程就会进入等待状态,而另外一个线程通过写文件描述符来向管道中写入内容,写入内容的时候,如果另一端正有线程正在等待管道中的内容,那么这个线程就会被唤醒。这个等待和唤醒的操作是如何进行的呢,这就要借助Linux系统中的epoll机制了。 Linux系统中的epoll机制为处理大批量句柄而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著减少程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率。但是这里我们其实只需要监控的IO接口只有mWakeReadPipeFd一个,即前面我们所创建的管道的读端,为什么还需要用到epoll呢?有点用牛刀来杀鸡的味道。其实不然,这个Looper类是非常强大的,它除了监控内部所创建的管道接口之外,还提供了addFd接口供外界面调用,外界可以通过这个接口把自己想要监控的IO事件一并加入到这个Looper对象中去,当所有这些被监控的IO接口上面有事件发生时,就会唤醒相应的线程来处理,不过这里我们只关心刚才所创建的管道的IO事件的发生。要使用Linux系统的epoll机制,首先要通过epoll_create来创建一个epoll专用的文件描述符:
mEpollFd = epoll_create(EPOLL_SIZE_HINT);传入的参数EPOLL_SIZE_HINT是在这个mEpollFd上能监控的最大文件描述符数。
接着还要通过epoll_ctl函数来告诉epoll要监控相应的文件描述符的什么事件:
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);这里就是告诉mEpollFd,它要监控mWakeReadPipeFd文件描述符的EPOLLIN事件,即当管道中有内容可读时,就唤醒当前正在等待管道中的内容的线程。
C++层的这个Looper对象创建好了之后,就返回到JNI层的NativeMessageQueue的构造函数,最后就返回到Java层的消息队列MessageQueue的创建过程,这样,Java层的Looper对象就准备好了。有点复杂,我们先小结一下这一步都做了些什么事情:
A. 在Java层,创建了一个Looper对象,这个Looper对象是用来进入消息循环的,它的内部有一个消息队列MessageQueue对象mQueue;
B. 在JNI层,创建了一个NativeMessageQueue对象,这个NativeMessageQueue对象保存在Java层的消息队列对象mQueue的成员变量mPtr中;
C. 在C++层,创建了一个Looper对象,保存在JNI层的NativeMessageQueue对象的成员变量mLooper中,这个对象的作用是,当Java层的消息队列中没有消息时,就使Android应用程序主线程进入等待状态,而当Java层的消息队列中来了新的消息后,就唤醒Android应用程序的主线程来处理这个消息。
到这里nativeInit方法就介绍完了,看来这个方法中做的事情还是很多的。
插播二、面试题
插播移到面试题:说说Handler机制的底层实现原理。
这个问题就是上面的解释,使用Linux中的epoll机制进行消息处理的。
2、nativePollOnce
这个方法是在MessageQueue中的next方法中调用的,其实这个方法会在后面说到Looper类中被调用,主要是从MessageQueue中取出一个Message,然后进行处理。
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; if (Config.LOGV) Log.v("MessageQueue", "Returning message: " + msg); return msg; } else { nextPollTimeoutMillis = (int) Math.min(when - now, Integer.MAX_VALUE); } } else { nextPollTimeoutMillis = -1; } // If first time, then get the number of idlers to run. if (pendingIdleHandlerCount < 0) { pendingIdleHandlerCount = mIdleHandlers.size(); } if (pendingIdleHandlerCount == 0) { // No idle handlers to run. Loop and wait some more. mBlocked = true; continue; } if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } // Run the idle handlers. // We only ever reach this code block during the first iteration. for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler boolean keep = false; try { keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf("MessageQueue", "IdleHandler threw exception", t); } if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } // Reset the idle handler count to 0 so we do not run them again. pendingIdleHandlerCount = 0; // 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; } }
我们就要深入分析一下JNI方法nativePollOnce了,看看它是如何进入等待状态的,这个函数定义在frameworks/base/core/jni/android_os_MessageQueue.cpp文件中:
static void android_os_MessageQueue_nativePollOnce(JNIEnv* env, jobject obj, jint ptr, jint timeoutMillis) { NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr); nativeMessageQueue->pollOnce(timeoutMillis); }这个函数首先是通过传进入的参数ptr取回前面在Java层创建MessageQueue对象时在JNI层创建的NatvieMessageQueue对象,然后调用它的pollOnce函数:
void NativeMessageQueue::pollOnce(int timeoutMillis) { mLooper->pollOnce(timeoutMillis); }这里将操作转发给mLooper对象的pollOnce函数处理,这里的mLooper对象是在C++层的对象,它也是在前面在JNI层创建的NatvieMessageQueue对象时创建的,它的pollOnce函数定义在frameworks/base/libs/utils/Looper.cpp文件中:
int Looper::pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData) { int result = 0; for (;;) { ...... if (result != 0) { ...... return result; } result = pollInner(timeoutMillis); } }为了方便讨论,我们把这个函数的无关部分都去掉,它主要就是调用pollInner函数来进一步操作,如果pollInner返回值不等于0,这个函数就可以返回了。
函数pollInner的定义如下:
int Looper::pollInner(int timeoutMillis) { ...... int result = ALOOPER_POLL_WAKE; ...... #ifdef LOOPER_USES_EPOLL struct epoll_event eventItems[EPOLL_MAX_EVENTS]; int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis); bool acquiredLock = false; #else ...... #endif if (eventCount < 0) { if (errno == EINTR) { goto Done; } LOGW("Poll failed with an unexpected error, errno=%d", errno); result = ALOOPER_POLL_ERROR; goto Done; } if (eventCount == 0) { ...... result = ALOOPER_POLL_TIMEOUT; goto Done; } ...... #ifdef LOOPER_USES_EPOLL for (int i = 0; i < eventCount; i++) { int fd = eventItems[i].data.fd; uint32_t epollEvents = eventItems[i].events; if (fd == mWakeReadPipeFd) { if (epollEvents & EPOLLIN) { awoken(); } else { LOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents); } } else { ...... } } if (acquiredLock) { mLock.unlock(); } Done: ; #else ...... #endif ...... return result; }这里,首先是调用epoll_wait函数来看看epoll专用文件描述符mEpollFd所监控的文件描述符是否有IO事件发生,它设置监控的超时时间为timeoutMillis:
int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);回忆一下前面的Looper的构造函数,我们在里面设置了要监控mWakeReadPipeFd文件描述符的EPOLLIN事件。 当mEpollFd所监控的文件描述符发生了要监控的IO事件后或者监控时间超时后,线程就从epoll_wait返回了,否则线程就会在epoll_wait函数中进入睡眠状态了。返回后如果eventCount等于0,就说明是超时了:
if (eventCount == 0) { ...... result = ALOOPER_POLL_TIMEOUT; goto Done; }如果eventCount不等于0,就说明发生要监控的事件:
for (int i = 0; i < eventCount; i++) { int fd = eventItems[i].data.fd; uint32_t epollEvents = eventItems[i].events; if (fd == mWakeReadPipeFd) { if (epollEvents & EPOLLIN) { awoken(); } else { LOGW("Ignoring unexpected epoll events 0x%x on wake read pipe.", epollEvents); } } else { ...... } }这里我们只关注mWakeReadPipeFd文件描述符上的事件,如果在mWakeReadPipeFd文件描述符上发生了EPOLLIN就说明应用程序中的消息队列里面有新的消息需要处理了,接下来它就会先调用awoken函数清空管道中把内容,以便下次再调用pollInner函数时,知道自从上次处理完消息队列中的消息后,有没有新的消息加进来。
函数awoken的实现很简单,它只是把管道中的内容都读取出来:
void Looper::awoken() { ...... char buffer[16]; ssize_t nRead; do { nRead = read(mWakeReadPipeFd, buffer, sizeof(buffer)); } while ((nRead == -1 && errno == EINTR) || nRead == sizeof(buffer)); }因为当其它的线程向应用程序的消息队列加入新的消息时,会向这个管道写入新的内容来通知应用程序主线程有新的消息需要处理了,下面我们分析消息的发送的时候将会看到。
这样,消息的循环过程就分析完了,这部分逻辑还是比较复杂的,它利用Linux系统中的管道(pipe)进程间通信机制来实现消息的等待和处理,不过,了解了这部分内容之后,下面我们分析消息的发送和处理就简单多了。
3、nativeWake
在enqueueMessage方法中调用的,这个方法会在后面说到的Handler类中发送消息时调用的
final boolean enqueueMessage(Message msg, long when) { ...... final boolean needWake; synchronized (this) { ...... msg.when = when; //Log.d("MessageQueue", "Enqueing: " + msg); Message p = mMessages; if (p == null || when == 0 || when < p.when) { msg.next = p; mMessages = msg; needWake = mBlocked; // new head, might need to wake up } else { Message prev = null; while (p != null && p.when <= when) { prev = p; p = p.next; } msg.next = prev.next; prev.next = msg; needWake = false; // still waiting on head, no need to wake up } } if (needWake) { nativeWake(mPtr); } return true; }把消息加入到消息队列时,分两种情况,一种当前消息队列为空时,这时候应用程序的主线程一般就是处于空闲等待状态了,这时候就要唤醒它,另一种情况是应用程序的消息队列不为空,这时候就不需要唤醒应用程序的主线程了,因为这时候它一定是在忙着处于消息队列中的消息,因此不会处于空闲等待的状态。
第一种情况比较简单,只要把消息放在消息队列头就可以了:
msg.next = p; mMessages = msg; needWake = mBlocked; // new head, might need to wake up第二种情况相对就比较复杂一些了,前面我们说过,当往消息队列中发送消息时,是可以指定消息的处理时间的,而消息队列中的消息,就是按照这个时间从小到大来排序的,因此,当把新的消息加入到消息队列时,就要根据它的处理时间来找到合适的位置,然后再放进消息队列中去:
Message prev = null; while (p != null && p.when <= when) { prev = p; p = p.next; } msg.next = prev.next; prev.next = msg; needWake = false; // still waiting on head, no need to wake up把消息加入到消息队列去后,如果应用程序的主线程正处于空闲等待状态,就需要调用natvieWake函数来唤醒它了,这是一个JNI方法,定义在frameworks/base/core/jni/android_os_MessageQueue.cpp文件中:
static void android_os_MessageQueue_nativeWake(JNIEnv* env, jobject obj, jint ptr) { NativeMessageQueue* nativeMessageQueue = reinterpret_cast<NativeMessageQueue*>(ptr); return nativeMessageQueue->wake(); }这个JNI层的NativeMessageQueue对象我们在前面分析消息循环的时候创建好的,保存在Java层的MessageQueue对象的mPtr成员变量中,这里把它取回来之后,就调用它的wake函数来唤醒应用程序的主线程,这个函数也是定义在frameworks/base/core/jni/android_os_MessageQueue.cpp文件中:
void NativeMessageQueue::wake() { mLooper->wake(); }这里它又通过成员变量mLooper的wake函数来执行操作,这里的mLooper成员变量是一个C++层实现的Looper对象,它定义在frameworks/base/libs/utils/Looper.cpp文件中:
void Looper::wake() { ...... ssize_t nWrite; do { nWrite = write(mWakeWritePipeFd, "W", 1); } while (nWrite == -1 && errno == EINTR); ....... }这个wake函数很简单,只是通过打开文件描述符mWakeWritePipeFd往管道的写入一个"W"字符串。其实,往管道写入什么内容并不重要,往管道写入内容的目的是为了唤醒应用程序的主线程。前面我们在分析应用程序的消息循环时说到,当应用程序的消息队列中没有消息处理时,应用程序的主线程就会进入空闲等待状态,而这个空闲等待状态就是通过调用这个Looper类的pollInner函数来进入的,具体就是在pollInner函数中调用epoll_wait函数来等待管道中有内容可读的。
这时候既然管道中有内容可读了,应用程序的主线程就会从这里的Looper类的pollInner函数返回到JNI层的nativePollOnce函数,最后返回到Java层中的MessageQueue.next函数中去,这里它就会发现消息队列中有新的消息需要处理了,于就会处理这个消息。
第二、分析Looper源码
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>(); private static Looper sMainLooper; // guarded by Looper.class final MessageQueue mQueue;首先来看一下定义的类中定义的变量:
sTreadLocal是一个ThreadLocal类型,关于ThreadLocal类型就是为每一个线程保存一下他所拥有的变量,没有任何玄乎的东西,具体可以查看一下他的源代码,这里就不做介绍了。
sMainLooper是一个Looper,从名字定义上来看,他应该和主线程相关联的
mQueue是一个MessageQueue消息队列,这个就是我们上面已经介绍了他的所有的知识
1、构造方法
看一下他的构造方法:
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }构造方法很简单,就是初始化一下MessageQueue对象,并且获取当前线程实例,这里关于MessageQueue的对象的初始化,其实上面已经说得很详细了,这里面其实是做了很多事情的。这个构造方法其实我们可以认为是Looper和MessageQueue进行绑定,所以Looper和MessageQueue是一一对应的。
2、prepare方法
在来看一下他的一个prepare方法
public static void prepare() { prepare(true); }
为什么要说这个方法呢?
因为这个方法我可以在特定场景下会用到,其实这个方法的作用就是将一个线程和Looper进行绑定。
当然这个方法我们如果想在一个线程中显示Toast的话,会使用这个方法,或者我们可以看一下系统给我定义的HandlerThread类:@Override public void run() { mTid = Process.myTid(); Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } Process.setThreadPriority(mPriority); onLooperPrepared(); Looper.loop(); mTid = -1; }这个就是HandlerThread方法中的run方法,可以看到调用了Looper.prepare()方法。这样我们就可以在HandlerThread中显示Toast了。当然这里说的有点远了,话题转回来。继续说prepare方法,他其实还调用了prepare(boolean...)方法:
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)); }这里我们看到了会初始化一个Looper对象然后将其放到ThreadLocal中,即和当前线程进行绑定。就是和当前线程一一对应了,这个就是我们之前也是说了,ThreadLocal的作用,他就是维护当前线程中所拥有的变量。
3.loop方法
继续看还有一个重要的方法:loop()
/** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */ public static void loop() { final Looper me = myLooper(); if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn‘t called on this thread."); } final MessageQueue queue = me.mQueue; // Make sure the identity of this thread is that of the local process, // and keep track of what that identity token actually is. Binder.clearCallingIdentity(); final long ident = Binder.clearCallingIdentity(); for (;;) { Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } // This must be in a local variable, in case a UI event sets the logger Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } msg.target.dispatchMessage(msg); if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } // Make sure that during the course of dispatching the // identity of the thread wasn‘t corrupted. final long newIdent = Binder.clearCallingIdentity(); if (ident != newIdent) { Log.wtf(TAG, "Thread identity changed from 0x" + Long.toHexString(ident) + " to 0x" + Long.toHexString(newIdent) + " while dispatching to " + msg.target.getClass().getName() + " " + msg.callback + " what=" + msg.what); } msg.recycle(); } }我们可以从之前的HandlerThread的run方法中看到在最后还会调用一下loop方法。这里有两行代码很重要:
Message msg = queue.next(); // might block
msg.target.dispatchMessage(msg);第一行代码是用来从消息队列中取出消息的,这个用到了MessageQueue中的next方法,之前说过这个方法了。
第二行代码就是将取出来的消息进行处理(这里可以查看一下Message类他的target变量其实就是一个Handler)
所以我们这里就可以看出来,Looper的loop方法就是开始从MessageQueue中去消息然后处理消息了的功能。
继续:
public static Looper myLooper() { return sThreadLocal.get(); }这个方法就是获取和当前线程绑定的Looper.
在来看一下
public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } }这个方法从他的字面意思上来看应该是将主线程绑定一个Looper,然后将这个Looper赋值给变量sMainLooper。
这里就得说到说到了。我们知道一般一个Android应用对应一个进程,在这个进程中可能会有多个线程,有一个线程很重要那就是主线程(或者就是我们经常说的UI线程,那么这个线程到底是什么呢?他是否和Looper进行绑定呢?),其实我们搜一下就知道这个UI线程就是ActivityThread,我们可以查看一下他的源代码(frameworks/base/core/java/android/app/ActivityThread.java):
public final class ActivityThread { ...... public static final void main(String[] args) { ...... Looper.prepareMainLooper(); ...... ActivityThread thread = new ActivityThread(); thread.attach(false); ...... Looper.loop(); ...... thread.detach(); ...... } }在他的main方法中,会调用Looper.prepareMainLooper()方法,将主线程和一个Looper进行绑定,也就是说主线程也是有一个Looper的,然后在调用Looper.loop()方法,开始消息处理,这里我们就可以看到,Android中的应用的确基于消息处理机制的。
继续:
public static Looper getMainLooper() { synchronized (Looper.class) { return sMainLooper; } }这个方法就是获取主线程(UI线程)的Looper的,这个方法我们在后面举例子中会用到。
这样我们就介绍完了Looper类,其实这个类中最主要的就是三个方法:
*构造方法:初始化MessageQueue对象,然后将这个MessageQueue和Looper进行绑定,即一一对应的
*prepare方法:初始化一个Looper对象,然后存入到ThreadLocal中,即将Looper和当前线成进行绑定,也是一一对应的
*loop方法:进入循环开始消息的处理,会调用MessageQueue的next方法
其他都是些辅助的方法,当然也是有很多用途的,比如我现在想判断当前线程是不是主线程该怎么做?
这里我们就可以使用myLooper()方法获取当前线程的Looper对象,然后在使用getMainLooper()方法获取主线程的Looper对象,两者就行比较就可以了,当然这里myLooper()方法返回的值可能会为null,因为如果这个线程并没有和Looper进行绑定(就是没有调用prepare方法),那么就是为null,但是getMainLooper()方法返回肯定不为null,因为主线程在初始化的时候就已经绑定了一个Looper。这个是系统执行的。
第三、分析Handler源码
这个类我们之前解释过了,其实他的作用就是用来发送消息和处理消息的,等我们分析完源码之后会发现,其实这个步骤底层并不是他做的,而是MessageQueue对象和底层打交道,底层的Looper对象做的,这个如果还是不理解的话,可以在看看之前的内容。
1、构造方法
首先还是来看一下他的构造方法:
他的构造方法很多,默认构造方法:
public Handler() { this(null, false); }也是我们最经常用的一个方法,在追踪看看他的有参构造方法:
public Handler(Callback callback) { this(callback, false); }这个就是传递一个回调接口对象,这个我们后面会说到,继续看看他的其他构造方法:
public Handler(Callback callback, boolean async) { if (FIND_POTENTIAL_LEAKS) { final Class<? extends Handler> klass = getClass(); if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) && (klass.getModifiers() & Modifier.STATIC) == 0) { Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName()); } } mLooper = Looper.myLooper(); if (mLooper == null) { throw new RuntimeException( "Can‘t create handler inside thread that has not called Looper.prepare()"); } mQueue = mLooper.mQueue; mCallback = callback; mAsynchronous = async; }终于看到点实货了,这个构造方法有很多东西的,看到他会调用Looper.myLooper方法,获取线程对应的Looper,如果这个线程返回的Looper为null,那么会报一个异常,当然这个异常我们会经常见到,就是不能在一个没有和Looper绑定的线程中定义一个Handler。
然后获取到一个Looper的消息队列。
他还有一个构造方法:
public Handler(Looper looper) { this(looper, null, false); }这个在初始化的时候会传递一个Looper进来,也就是说一个Handler只对应一个Looper,但是一个Looper可以有多个Handler,只要在这个绑定了Looper的线程中实例化多个Handler。
但是我们一般在使用Handler的时候,都是使用它的无参构造方法,并且是在主线程中定义的,所以说这个Handler是和主线程对应的,当然我们可以在主线程中定义多个Handler都是可以的
插播面试题
插播一个面试题:一个线程可以对应多个Handler,又因为一个Looper对应一个MessageQueue,那么多个Handler对应一个MessageQueue,问题来了,多个Handler在发送消息以及在处理消息的时候,为什么能做到有条不紊,不会出错呢?
其实这个答案我们可以在Looper中的loop方法中找到,之前在看这个方法的时候,看到他会取出消息然后处理,那时候代码是这样的:
msg.target.dispatchMessage(msg);当时说了,这个target成员变量就是一个Handler,也就是说其实消息队列中的每个消息Message其实他会携带Handler的,这样就知道这个信息是哪个Handler发送的,该又哪个Handler处理了。当然在发送消息的时候会把这个target赋值的,这个后面会看到。
回来继续,主线程默认是绑定了一个Looper的,所以在主线程中定义Handler是没有问题的,当然如果我们想在子线程中定义Handler的话,那么就必须将当前线程绑定一个Looper,调用Looper.prepare()方法就可以了。
插播面试题
面试题问题:如果我现在在子线程中定义了一个Handler,但是我又不想让这个线程绑定一个Looper(即不想额外的调用prepare方法),这时候我们就要用到Looper中的一个方法getMainLooper(),这个就是获取主线程的Looper,然后在用Handler指定的构造方法实例化一下即可
Handler handler = new Handler(Looper.getMainLooper()){ @Override public void dispatchMessage(Message msg){ //handle message... } }其实这样定义之后我们会发现,虽然这个Handler是在子线程中定义的,但是他是和主线程对应的,这点一定要弄清楚。
2、dispatchMessage方法
继续看一下Handler的处理消息的方法dispatchMessage
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }msg变量是一个Message类型的,他的结构很简单,就是一种数据结构(内部实现了Parcelable接口),但是他不仅可以携带数据,还有一个成员变量就是callBack,但是我们查看他的源码之后会发现这个变量的类型起始就是一个Runnable。同时他还会维护一个Handler作为他的target.所以Message的内部实现是很简单的,这里就没有做源码分析了。
这里会做判断,如果callback不为null的话,就调用handleCallback方法:
private static void handleCallback(Message message) { message.callback.run(); }这个方法很简单,就是执行Runnable中的run方法。
如果callback不为null.在做一次判断mCallback是否为null
这个mCallback变量其实就是个回调接口类型CallBack:
public interface Callback { public boolean handleMessage(Message msg); }有一个回调方法handleMessage,这个变量的实例化,我们在前面介绍构造方法的时候看到,可以在那里进行初始化,这样我们在外部就可以实现handleMessage方法了,这个方法执行之后直接就return了,当时如果mCallback为null就执行handleMessage(msg)方法:
public void handleMessage(Message msg) { }好吧,其实他内部没有任何逻辑,也就是说这个dispatchMessage方法其实很简单的,同时我们也会记得,我们一般在使用Handler方法的时候都是自定义一个子类然后覆盖这个方法的。所以说真正执行的是我们自己需要实现的dispatchMessage方法中逻辑代码。我们在分析Looper代码的时候也会发现在他的loop方法中调用了dispatchMessage方法就行处理消息的。
总结一下dispatchMessage方法的执行流程:
1.首先判断msg变量中的callback是否为null,如果不为null,就执行他的run方法(Runnable),然后结束,如果为null,转入2
2.判断mCallback变量是否为null,如果不为null,就调用他的handleMessage(...)回调方法,然后结束,如果为null,注入3
3.执行Handler本身的handleMessage(...)方法
所以我们如果想在在dispatchMessage方法中执行我们自己的逻辑有很多种方式:
1.直接定义子类覆盖dispatchMessage方法
2.定义Handler的时候,传入一个CallBack接口,外部实现他的handleMessage回调方法
3.在发送消息的时候,实例化Message的callback变量(Runnable),将代码放到run方法中
4.直接重写父类的handleMessage方法
3、sendMessage方法
下面继续看一下sendMessage方法
public final boolean sendMessage(Message msg){ return sendMessageDelayed(msg, 0); }看一下sendMessageDelayed方法:
public final boolean sendMessageDelayed(Message msg, long delayMillis){ if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); }就是多个一个判断,延迟时间不能为负数的容错机制
再看一下sendMessageAtTime方法:
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); }这个方法开始有点内容了,获取到消息队列
在看一下enqueueMessage方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
好吧,这里才是重点,这里有两部分内容,第一部分是我们需要将当前的Handler对象赋值给Message中的target,就是每个消息必须
要有一个target,这样就不会发生混乱了(上面讲到的那个面试题中提到的知识就在这里)
还有就是最终会调用消息队列的enqueueMessage方法,这个在之前介绍MessageQueue类的时候已经说过了。
4、postXXX方法
最后在看一个重要的方法:
public final boolean post(Runnable r){ return sendMessageDelayed(getPostMessage(r), 0); }这个方法是发送一个Runnable任务,在看看getPostMessage方法:
private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }这个方法其实就将Runnable转化成一个Message对象。
但是post这个方法其实是非常重要的,后面会说到
总结
到这里把Handler就讲解完了,那么Handler,Looper,MessageQueue都讲解完了,下面来总结一下吧,来张图:
1.首先是Handler,Looper,MessageQueue,Thread之间的关系:
Looper和Message以及Thread他们三个之间是一一对应的关系,一个Looper对应多个Handler
2.在MessageQueue的构造方法中调用了nativeInit方法,这个方法在本地创建了Looper和MessageQueue对象,这个和Java中的Looper,MessageQueue是不一样的,但是你可以把他们看成是一一对应。
拓展
我们在使用Handler的时候主要是因为主线程中不能做耗时的操作,而子线程中又不能刷新控件,所以会使用到Handler.
其实我们如果想在子线程中刷新View的话可以有很多种方式:
1.Handler机制通信(最基础的)
2.Activity类中的runOnUiThread(...)方法
这个又得看源码了:
public final void runOnUiThread(Runnable action) { if (Thread.currentThread() != mUiThread) { mHandler.post(action); } else { action.run(); } }擦,看完源码我们懂了,其实他的内部实现还是用的Handler机制,发送一个Runnbale.
3.View类中的postXXX方法
这个还得看源码:
public boolean postDelayed(Runnable action, long delayMillis) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return attachInfo.mHandler.postDelayed(action, delayMillis); } // Assume that post will succeed later ViewRootImpl.getRunQueue().postDelayed(action, delayMillis); return true; }如果AttachInfo!=null的话,看,还是用的Handler机制
所以说,当我们在主线程中定义了控件,然后使用这些方法来刷新就是通过Handler来实现的。之前也是说对的,Android中的应用就是消息驱动的,后面在分析其他的源码的时候还会发现到处都用到了Handler机制。所以说Handler机制是多么的重要呀~~
(Ps:哎,终于写完了,累死人了,之前解读过一次Handler机制的源码,但是那时候没有这么深入)
Android中对消息机制(Handler)的再次解读