Handler, MessageQueue, Looper概述
众所周知,Handler的运行需要底层MQ和lopper的支撑,它主要用来实现线程切换。比如读取本地文件内容,读取完毕之后将其展示到UI,如果我们直接使用主线程去读物文件,当文件很大时,则会出现ANR错误。那么对于这种耗时操作我们必须在一个新的线程中去执行,执行完毕之后将结果通知给主线程即可。
MessageQueue简称MQ,直译为消息队列,虽然名为队列,但他使用了单向链表来存储消息。
由于MQ仅仅用来存储消息,那么我们如何操作消息呢?Looper应用而生。Looper会以死循环的形式去查询MQ中是否包含新的消息,如果有,则将该消息发送给创建Handler的线程。
想一想,Android是如何限制在非UI线程中访问UI的呢?为什么要限制呢? android中使用了ViewRootImpl对UI操作做校验。至于为什么要限制,则是因为UI操作不是线程安全的。如果对UI操作加锁,一则效率低下,二则处理复杂。
Handler实现原理
Handler只能在拥有Looper的线程中创建(因为在UI线程中,系统已经自动为我们创建好Looper了,所以我们可以直接新建Handler来实现线程切换;系统还提供了HandlerThread来创建带有Looper的线程,这样我们也可以在HandlerThread中直接创建Handler)。
也就是说Handler中有Looper,Looper中又有MQ,那么三者的关系就很明确了。Handler先发送消息给MQ,然后Looper遍历MQ将查找到的Message发送给Handler。
泛泛而谈太过空洞,我们结合实例来说说吧~之前我们提到过HandlerThread,它自带Looper,那么我们可以通过分析它的原来来弄懂Looper的具体使用过程及原理。HandlerThread的源码不长,我们一段一段来分析。
public class HandlerThread extends Thread {
int mPriority;
int mTid = -1;
Looper mLooper;
public HandlerThread(String name) {
super(name);
mPriority = Process.THREAD_PRIORITY_DEFAULT;
}
/**
* Constructs a HandlerThread.
* @param name
* @param priority The priority to run the thread at. The value supplied must be from
* {@link android.os.Process} and not from java.lang.Thread.
*/
public HandlerThread(String name, int priority) {
super(name);
mPriority = priority;
}
/**
* Call back method that can be explicitly overridden if needed to execute some
* setup before Looper loops.
*/
protected void onLooperPrepared() {
}
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
/**
* 返回当前线程对应的Looper,如果线程还没有start,或者线程死亡了,则返回null。
* @return The looper.
*/
public Looper getLooper() {
if (!isAlive()) {
return null;
}
// If the thread has been started, wait until the looper has been created.
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}
public boolean quit() {
Looper looper = getLooper();
if (looper != null) {
looper.quit();
return true;
}
return false;
}
public boolean quitSafely() {
Looper looper = getLooper();
if (looper != null) {
looper.quitSafely();
return true;
}
return false;
}
public int getThreadId() {
return mTid;
}
}
HandlerThread首先是一个Thread,在构造函数中初始化了当前线程的名字和优先级。每当启动Thread时,则必定调用run方法(废话),那么我们看看在run方法中到底干了什么呢?
@Override
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}
首先调用Looper.prepare()方法为当前线程准备好Looper,那么我们来看看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中使用了ThreadLocal来保存每个Thread对应的Looper对象。换句话说,一个线程对应一个Looper,而在Looper中又使用了ThreadLocal类型的类对象保存了多个Looper。
然后调用Looper.myLooper()来获取当前线程对应的Looper对象:
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
紧接着就直接调用了Looper.loop()方法,我们来看看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(); // 如果队列为空,则阻塞(next方法内部中一死循环形式实现)
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
msg.target.dispatchMessage(msg);
msg.recycleUnchecked();
}
}
可以看到,loop()很简单,遍历MQ,如果MQ中有Message,则将message分发给对应的Handler,如果没有对退出。
至此,Looper本身的而逻辑就很清楚了。Looper仅负责保存各个线程的looper对象(使用ThreadLocal)和遍历MQ并将MQ中的消息分发给target。
那么我有一个疑问,既然当MQ为空时,loop()方法会结束并返回,那么如果我在在loop()方法结束之后发送了一个message给MQ,Looper是怎么监听到我发送了这个消息呢?
这里需要注意的一点事queue.next()方法在队列为空时会阻塞当前线程,直到队列中有新的消息到来或者要线程结束时释放锁。
那么我又有一个疑问,既然已经阻塞在MQ.next()中,那么此时当前线程即处于阻塞状态,怎么继续往下执行呢?呵呵哒。
我们再来详细捋一遍handler的运行过程:
首先在Thread-1中准备Looper,将Thread-1保存在Looper#ThreadLocal对象中,然后调用Looper .loop()方法,让Thread-1进入死循环中去持续查找MQ中是否有消息。如果MQ为空,则将Thread-1阻塞在MQ.next()方法中。那么Thread-1处于blocking状态。回想一下,Handler的作用是完成线程间切换的。我们来看看下面的代码:
//按钮点击响应事件
public void startHandlerThread(View view) {
Log.i("HandlerThread", "startHandlerThread");
HandlerThread ht = new HandlerThread("handlerTest") {
@Override
public void run() {
Log.i("HandlerThread", "run: Thread-1 is starting");
try {
Thread.sleep(20000);
} catch (InterruptedException e) {
e.printStackTrace();
}
super.run();
}
};
ht.start();
Looper looper = ht.getLooper();
Handler handler = new Handler(looper) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Log.i("HandlerThread", "handleMessage: " + msg.obj);
}
};
//启动Thread-2
Log.i("HandlerThread", "startHandlerThread: 阻塞了主线程");
Thread t = new Thread(new Thread2(handler));
t.start();
}
private class Thread2 implements Runnable {
private Handler mHandler;
Thread2(Handler handler) {
mHandler = handler;
}
@Override
public void run() {
Message message = new Message();
message.what = 2;
message.obj = "我来自Thread2";
mHandler.sendMessage(message);
}
}
先摆出这段代码的执行结果吧!
08-31 10:06:31.988 15642-15642/cn.codemperor.summary I/HandlerThread: startHandlerThread
08-31 10:06:31.990 15642-16752/cn.codemperor.summary I/HandlerThread: run: Thread-1 is starting
08-31 10:06:51.994 15642-15642/cn.codemperor.summary I/HandlerThread: startHandlerThread: 阻塞了主线程
08-31 10:06:51.994 15642-15642/cn.codemperor.summary I/HandlerThread: Thread2 is accessed
08-31 10:06:52.002 15642-16752/cn.codemperor.summary I/HandlerThread: handleMessage: 我来自Thread2
可以看到第2行和第3行之间的相差20秒左右。这是因为我们在HandlerThread中sleep了20秒,而HandlerThread#super.run()就一直被阻塞,导致HandlerThread#Looper无法被初始化,那么ht.getLooper()就一直处于wait状态(因此主线程也处于阻塞状态,所以如果在sleep期间点击了屏幕,则会出现ANR),直到Looper被实例化。那么下面的Handler无法被初始化,Thread2也无法启动。也就是说我们无法在 HandlerThread中做耗时操作吗?哈哈,还是太天真了,Google怎么会开发出如此鸡肋的东西。HandlerThread是具有Looper的子线程,Handler的初始化使用了HandlerThread中的Looper,也就是说Handler中的handleMessage是执行在子线程中的,那么我们就可以在handleMessage中执行耗时操作呀!哈哈,代码如下:
Handler handler = new Handler(looper) {
@Override
public void handleMessage(Message msg) {
super.handleMessage(msg);
Toast.makeText(HandlerActivity.this, "HandlerActivity", Toast.LENGTH_LONG).show();
try {
Thread.sleep(10000);
Log.i("HandlerThread", "handleMessage: " + msg.obj);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
};
至此,关于Handler, HandlerThrad, Looper, MQ的知识点基本就已经清楚了。