在安卓中,Handler负责消息的传递,它提供了一套在线程间传递消息的机制。Handler与looper、message、messageQueue一起,共同构成了安卓的消息发送框架。初学安卓的朋友可能对安卓的线程模型感到困惑,也会感到有很多限制,UI线程既不能等待,又不能做耗时操作,非UI线程可以做耗时操作,却不能改变UI。但这些问题却可以通过handler——线程间的消息传递来解决。那么handler内部是怎么实现的呢?它的实现原理是什么?让我们一起来探个究竟。
下面是Handler的一个基础用法。我们首先在主线程里定义了一个Handler,然后新启动一个线程,在该线程里通过该handler的post方法可以向主线程发送消息。在post方法下的runnable会运行在主线程中,这样就实现了“在非UI线程更新UI”的操作。
private Handler mHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mHandler = new Handler();
new Thread(new Runnable() {
@Override
public void run() {
mHandler.post(new Runnable() {
@Override
public void run() {
//在这里可以更新UI
...
}
});
}
}).start();
}
在讲Handler机制之前,我们首先需要讲一下与handler有关的几个类:ThreadLocal、MessageQueue、Message和Looper。ThreadLocal的作用为提供一个只与单个线程有关的变量,在我的博客ThreadLocal源码浅析一文中对ThreadLocal进行了详细讲解,有兴趣的朋友可以看看。MessageQueue则是一个消息队列,以队列的形式存储着handler发送的消息(Message)。而Looper则是消息机制的核心类,它封装了ThreadLocal,MessageQueue,负责从消息队列中轮询、获取消息并发送等核心操作。它的两个核心方法prepare()和loop()方法也是本篇博客的重点。下面我们来看一下Looper是怎么实现的。
首先来看下Looper类的几个重要属性:
public final class Looper {
private static final String TAG = "Looper";
// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
private static Looper sMainLooper; // guarded by Looper.class
final MessageQueue mQueue;
final Thread mThread;
private Printer mLogging;
...
}
Looper定义了几个比较重要的属性:
sThreadLocal
sMainLooper
mQueue
sThreadLocal用于存储每个线程的looper,即使得每一个线程都有一份自己的looper(如果设置的话),sMainLooper则为主线程的looper,mQueue则为Looper所包含的消息队列。要注意的是,sThreadLocal和sMainLooper都被设为static的,即在一个进程中,系统只存在一个mainLooper,sThreadLocal也被各个线程共享,只不过每个线程在sThreadLocal中取到的值不同(见ThreadLocal源码浅析)。
Looper还有两个非常重要方法:prepare()和loop()。先来看下prepare()方法:
public static void prepare() {
prepare(true);
}
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.prepare()方法时,会将一个Looper的新实例放入threadLocal中,也就是说,当前线程和一个Looper绑定了,我们可以理解为该线程变成了“looper线程”,这个线程拥有了自己单独的looper实例,也就意味着拥有了单独的messageQueue、Printer等。此外,一个线程只能绑定一次Looper,再次绑定会抛出RuntimeException异常。
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.recycleUnchecked();
}
}
/**
* Return the Looper object associated with the current thread. Returns
* null if the calling thread is not associated with a Looper.
*/
public static Looper myLooper() {
return sThreadLocal.get();
}
Loop方法也比较清晰:首先,loop方法会调用myLooper()方法从ThreadLocal里返回当前线程所绑定的Looper,接着,得到这个Looper的MessageQueue,然后,loop()方法会定义一个无限循环,不断地从这个MessageQueue里获取消息( Message msg = queue.next(); ),并发送给对应的handler(msg.target.dispatchMessage(msg);)。所以,我们在使用Looper的时候,首先要调用它的prepare()方法,将当前线程变成“looper线程”,再调用looper.loop()方法循环获取消息。
举个小例子:
new Thread("lzq thread") {
@Override
public void run() {
Looper.prepare();//将本线程变为Looper线程
Handler myHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
Log.v("lzq", Thread.currentThread().getName());
}
};
myHandler.sendEmptyMessage(0);
Looper.loop();//由于是无限循环,要在线程最后调用
}
}.start();
可以看到,打印出的消息为lzq thread,证明handler运行在了子线程。幸运的是,在安卓的主线程中已经为我们做好了这些事情,在操作主线程的时候我们只要调用handler发送和接收消息就可以了。
我们再来看看Handler是怎样向MessageQueue里push消息的。首先看下Handler的构造方法:
Handler的构造方法很多,这里例举出两个:
public Handler(boolean async) {
this(null, async);
}
public Handler(Callback callback, boolean async) {
...
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;
}
上面的构造方法会调用下面这个。通过第二个构造方法可以看到,Handler内部包含了Looper的实例mLooper,和MessageQueue mQueue。在默认情况下,mLooper被实例化成当前线程绑定的looper,而mQueue则为该looper内部的MessageQueue。当我们采用handler调用sendMessage或post的时候:
public final boolean sendMessage(Message msg)
{
return sendMessageDelayed(msg, 0);
}
sendMessage和post方法都会调用一系列的方法,但最终会转到这个方法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
通过enqueueMessage方法,待发送的Message会被放入到handler对应的looper的MessageQueue中,进而等待执行了。
我们再回过头来看最开始的例子,handler定义在主线程中,也就是说,该handler关联了主线程的looper,这样的话,当其它线程调用这个handler的post()或sendMessage()方法时,本质上是将message放入主线程looper的messageQueue中,再适时进行执行,这样就实现了“在非UI线程更新UI”的操作。因为本质上还是通过主线程更新UI,所以在该handler中不能做耗时的操作。
总结:
Handler与looper、message、messageQueue一起,共同构成了安卓的消息发送框架。先从looper说起,looper有两个核心方法:prepare()和loop()。prepare方法会将一个looper实例放在当前线程的threadlocal中,并使looper维护一个唯一的MessageQueue,使当前线程变成looper线程,loop方法则是构建一个无线循环,循环遍历messageQueue,将messageQueue中的message取出并发送,最后会调用对应handler的handleMessage方法处理。总之,looper负责绑定线程,循环处理消息。而handler负责接收并传递消息。handler内部需要绑定一个looper。我们要做的就是调用handler.sendmessage方法,他会将message发送给handler对应的looper下面的Messagequeue中,再通过looper.loop方法进行调用处理。我们再调用handler.handleMessage接收。这样就实现了线程间的通信。