Android Handler消息机制研习录
知识这种东西,隔段时间不用,就会忘记,翻来覆去看了几遍源码,可是每次用的时候还是忍不住再去复习一下实现过程,生怕自己忘记了。现在将它记录下来,方便日后查看。
从事Android的同学都知道,因为Android的UI控件不是线程安全的,所以Android系统不允许在子线程中访问UI,如果在多线程中并发访问可能会导致UI处于不可预期的状态。解决办法就是通过Android的消息机制,在子线程中发送消息到UI线程,然后在UI线程中更新UI。
Android的消息机制主要指Handler的运行机制,其工作流程大致是这样:Handler在线程1中发送消息到线程2(创建Handler的线程)中的MessageQueue,线程2中的Looper取出消息然后交给Handler处理,此时就将消息从线程1切换到了线程2。这句话不能理解没关系,下面我们详细分析这个流程,主要涉及以下几方面内容:
- MessageQueue的工作原理;
- Looper的工作原理;
- Handler的工作机制;
MessageQueue的工作原理
MessageQueue是一个消息队列,虽然是队列,但其底层是靠单链表实现的,简单提供入栈和出栈功能。消息出栈时伴随着消息被处理,如果没有消息,则MessageQueue处于阻塞状态。一个MessageQueue被一个Looper对象关联。由于MessageQueue比较简单,所以在此不做太多介绍。
Looper的工作原理
在介绍Looper工作原理之前,需要先弄明白一个数据存储结构ThreadLocal。如果某些数据是以线程为作用域,并且各个线程内的数据互不干扰时,就可以用ThreadLocal进行存储;ThreadLocal通过提供的set、get方法实现维护不同线程中的同一套数据的副本。有了ThreadLocal,那么就可以保证,一个线程中持有一个Looper对象并且与其他线程无任何关系。
在Android的消息机制中,Looper扮演着消息循环的角色,面对MessageQueue这个队列,Looper对象不断的从队列中取出消息进行处理,如果队列没有消息,则Looper同队列一起处于阻塞状态,直到有消息到达。来看一下遍历的代码:
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
final Printer logging = me.mLogging;
if (logging != null) {
logging.println(">>>>> Dispatching to " + msg.target + " " +
msg.callback + ": " + msg.what);
}
final long traceTag = me.mTraceTag;
if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
}
try {
msg.target.dispatchMessage(msg);
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
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.recycleUnchecked();
}
}
在Looper.loop()方法内部,首先通过myLooper()方法取出该线程中的Looper对象,myLooper()是如何实现的呢?
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
是不是现在已经很明了了,通过ThreadLocal拿到只属于该线程的Looper对象;回到loop()内部,又拿到了MessageQueue对象,然后就进入了无限循环阶段,在这里会遍历MessageQueue,如果MessageQueue的出栈操作处于阻塞状态,即queue.next()
阻塞,那么loop()也在此处阻塞;反之,消息就会被msg.target.dispatchMessage(msg)
所处理,这里的msg.target
事实上指的就是发送消息的Handler对象;由于loop()方法是运行在创建Handler所在的线程,所以Handler.dispatchMessage(msg)也运行在创建Handler所在线程,如果是主线程,那么就可以进行更新UI的操作了;现在,Looper将消息成功的转给Handler进行处理;至于Handler如何处理消息以及如何发送消息,我们留在下一小节介绍。
Handler的工作机制
Handler的工作主要包含发送消息和接收消息,发送消息通过post以及send系列方法实现,但最终都是调用的send的系列方法,接收消息通过dispatchMessage()方法实现。在介绍发送消息和接收消息之前,先看看Handler的构造方法:
public Handler();
public Handler(Callback callback);
public Handler(Looper looper);
public Handler(Looper looper, Callback callback);
public Handler(boolean async);
public Handler(boolean async);
public Handler(Callback callback, boolean async);
public Handler(Looper looper, Callback callback, boolean async);
这些构造可以分为两类,一类是提供Looper对象,一类是不提供对象;不提供Looper的构造最终调用的是public Handler(Callback callback, boolean async)
方法,先来看看这个方法的实现:
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对象的判断,其它的就是简单的赋值操作;如果创建Handler之前没有调用Looper.prepare()方法来创建Looper对象,那这里就会抛出一个异常;这里可能有人会问,在UI线程创建Handler时并没有调用Looper.prepare()方法,事实上,在UI线程初始化时已经完成了这个操作。而提供Looper对象的构造方法最终调用的是public Handler(Looper looper, Callback callback, boolean async)
方法,这个方法只是简单的赋值操作,所以不再贴代码。
了解了Handler的构造方式,现在再来看看Handler是如何发送消息的,流程如下:
Handler.sendMessage()–>Handler.sendMessageDelayed()–>Handler.sendMessageAtTime()–>Handler.enqueueMessage()—>MessageQueue.enqueueMessage();
当调用post方法发送消息时,会将参数Runnable对象作为Message.callback封装进Message中;可见,发送消息最终目的是消息入栈操作。
现在,再来看Handler是如何通过dispatchMessage(msg)接收消息的:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
首先,判断msg的callback是否存在,msg.callback即通过post系列方法发送消息时的参数Runnable对象,如果存在,就执行该Runnable对象;如果不存在,再判断mCallback是否存在,mCallback是Handler的内部接口,用于处理消息,在Handler的构造方法中可以初始化该接口对象,如果mCallback存在,就执行接口方法处理消息;如果mCallback不存在,就执行handleMessage(msg)方法,该方法是一个空方法,子类必须实现该方法才能处理消息。至此,Handler的工作原理分析完成。
总结
一、Handler:
1、一个线程可以有多个Handler,Handler:Looper => n:1;
2、Handler在子线程中通过post()或者sendMessage()方法发送消息,post内部调用sendMessage方法;这一过程是将消息发送到Handler内部的消息队列中; 该Handler内部的Looper对象属于哪个线程,就会将消息发送到哪个线程中;
3、Handler接收到Looper.loop()方法中的消息后,进行处理,现在已经在Looper所在的进程中了,处理逻辑如下:
- 判断Message.callback();一个Runnable对象,如果存在就执行这个Runnable对象;不存在执行下一步;
- 判断Handler.Callback();一个接口,创建handler的时候如果不想实现Handler的子类,则用该接口对象作为Handler的参数,接口方法中处理消息;如果该接口对象不存在,执行下一步;
- Handler的子类(可以是匿名内部类)重新的handleMessage(message msg)方法处理消息;
4、Handler创建时所在的线程(线程1)就是内部Looper所在的线程,但是发送消失是在子线程(线程2)中执行的,线程2中将消息发送到线程1中关联的Looper的MessageQueue中,那么处理也就是在线程1中了;
二、Looper:
1、一个线程只有一个Looper,一个Looper只能关联一个MessageQueue;
2、Looper的作用是遍历MessageQueue,有消息就处理,没有消息就阻塞(因为MessageQueue的next()方法是一个阻塞方法);
3、Looper内部loop()方法调用Handler.dispatchMessage(Message msg)方法,将轮询到的消息交给Handler进行处理;这个过程是在loop()内部,所以也就是将线程切换到了Looper所在的线程;
4、loop()方法是消息传递及线程切换的核心所在;
三、MessageQueue:
1、MQ是一个消息队列,底层是靠一个链表(存取方便)来实现的;
2、MQ消息出栈时伴随着消息被处理,如果没有消息时处于阻塞状态;
四、ThreadLocal:
1、一个数据结构类型,如果某些数据是以线程为作用域,并且各个线程内的数据互不干扰时,可以用ThreadLocal进行存储;
2、一个Looper对象持有一个ThreadLocal对象;