一张图带你深入了解Handler机制
前言
这两天正好闲着,遂回顾了一下以前觉得理解的很模糊的东西。Handler机制便是其中之一。
尽管我们经常用Handler进行线程之间的通信。但是对其中的过程,可能没太怎么去了解。即便有了解,可能也被复杂的逻辑给整蒙了。又是Looper
又是MessageQueue
的。我就是被整蒙圈的一员。但是今天,我将它重新梳理了一遍,在此记录一下。一方面是便于以后查看,另一方面加深自己的理解。
Handler机制思维导图
先上一张我画的思维导图,通过这张图,你就能清晰明了的了解Handler
,Looper
、Message
、MessageQueue
它们之间的千丝袜缕了。
Handler机制.png该图比较大,建议在电脑上观看效果会更好。https://github.com/KCrason/kcrason.github.io/blob/master/uploads/images/Handler%E6%9C%BA%E5%88%B6.png
Handler机制流程分析
流程概述
首先,我们知道,通常在Android Activity
中使用Handler
的流程是:创建Handler
对象->在异步线程中使用Handler
对象发送消息。然后就能在Handler
的回调中收到消息。而事实上Handler
机制的起始并不是从创建Handler
对象开始,而是从Looper.preare()
开始的。之后还要调用Looper.loop()
整个过程才算真正完成。在Activity
之所以看不到Looper
相关的代码,是因为主线程已经做了。ActivityThread
(主线程)的main方法源码
public static void main(String[] args) {
SamplingProfilerIntegration.start();
...省略部分代码
//初始化Looper
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false);
...省略部分代码
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
//开始循环取消息
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
而你在异步线程中使用Handler,它应该是下面这样的逻辑:
new Thread(new Runnable() {
@Override
public void run() {
//初始化Looper
Looper.prepare();
//创建Handler对象
Handler handler = new Handler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
return false;
}
});
//开始循环取消息
Looper.loop();
}
}).start();
知道了使用规则,那么下面我们根据这个规则来分析它们之前的联系。
初始化Looper
调用Looper.preare()或Looper.prepareMainLooper
方法开始,就是创建Looper
对象的起点。那么在Looper.preare()
中到底干了啥呢。我们看源码:
public static void prepare() {
prepare(true);
}
//quitAllowed是否允许终止循环取消息。
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));
}
可以看到,很简单,就是 new
了一个Looper
。然后将Looper对象设置进sThreadLocal
。sThreadLocal
是Looper
类的一个常量对象,其在在Looper
类中的声明:
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
我们接着看Looper
的构造函数:
private Looper(boolean quitAllowed) {
//创建一个MessageQueue赋值给mQueue
mQueue = new MessageQueue(quitAllowed);
//获取当前线程赋值给mThread
mThread = Thread.currentThread();
}
至此,Looper
的初始化工作完成。
神奇的ThreadLocal
之所以说ThreadLocal
很神奇,是因为它确实很神(好像是废话~),它的神奇之处在于同一个ThreadLocal
对象它可以在不同的线程间的赋值互不影响。什么意思呢?例子说明:
private void testTreadLocal() {
//创建ThreadLocal对象
final ThreadLocal<String> threadLocal = new ThreadLocal<>();
Thread oneThread = new Thread(new Runnable() {
@Override
public void run() {
//oneThread设置值OneThreadValue
threadLocal.set("OneThreadValue");
//打印oneThread下的结果
Log.i("KCrason", "testTreadLocal: " + threadLocal.get());
}
});
Thread twoThread = new Thread(new Runnable() {
@Override
public void run() {
//twoThread设置值TwoThreadValue
threadLocal.set("TwoThreadValue");
//打印twoThread下结果
Log.i("KCrason", "testTreadLocal: " + threadLocal.get());
}
});
oneThread.start();
twoThread.start();
//主线程中设置值MainThreadValue
threadLocal.set("MainThreadValue");
//打印mainThread下结果
Log.i("KCrason", "testTreadLocal: " + threadLocal.get());
}
输出结果:
ThreadLocal测试结果通过输出结果,我们知道,其实
ThreadLocal
就是对不同线程的赋值拷贝了一个副本进而保存了。到此,我相信对于ThreadLocal
,你已经能够理解了,至于它具体是如何实现的,这里暂不分析,你也可以去查看源码进行深入了解。
Handler的创建
同Looper
一样,我们来照样从构造函数开始分析Handler
的创建,看它到底干了啥?首先,Handler有多个构造函数,但它们无一例外的都会回到下面两个构造函数中来。
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;
}
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
事实上,通过比较,我们发现他们俩干的事其实都是一样的。那都干啥了呢,分为两种情况:
1、如果有传递Looper
,那么直接将传递的Looper
对象赋值给mLooper
。
2、如果没有传递Looper
,则通过Looper.myLooper()
获取Looper
对象。然后赋值给mLooper
。
前面我们提到初始化Looper
时会将创建的Looper
对象保存在Looper
的sThreadLocal
中,而Looper.myLooper()
正是通过sThreadLocal
去获取了在该线程下的Looper
对象。
Looper中的myLooper方法源码
public static @Nullable Looper myLooper() {
//通过sThreadLocal的get()返回创建的Looper对象。
return sThreadLocal.get();
}
与此同时,通过获取到的mLooper
对象去获取在Looper
中创建的MessageQueue
对象并赋值给Handler的变量mQueue
,最后将Callback
对象进行赋值。Handler
的初始化完成。
Looper.loop()开启循环获取消息
事实上,在初始化Looper
之后,就可以直接调用Looper.loop()
进行循环取消息进行处理。只不过那时MessageQueue
中还没有消息,循环就处于block
状态罢了。我们看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;
...省略部分代码
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...省略部分代码
try {
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
...省略部分代码
msg.recycleUnchecked();
}
}
其流程简单来讲,就是通过一个死循环使用MessageQueue
的next()
方法不断的从MessageQueue
中取出消息,如果为null
,则block
。否则,通过msg.target.dispatchMessage(msg)
将消息分发。你可能会有所疑问,msg.target
又是个什么鬼?没关系,我们接着往下分析。
使用Handler发送消息
首先,在使用Handler
发送消息时,不管是哪种方式,最终都会调用sendMessageAtTime()
方法,其中mHandler.post(Runnable runnable)
也是先将runnable
封装成一个Message
最后调用sendMessageAtTime()
,根据源码分析:
//将runable封装到Message中
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
//将Runnable对象r赋值给Message的callback变量。
m.callback = r;
return m;
}
//发送消息
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);
}
在sendMessageAtTime()
中,我们注意到,其最后调用了enqueueMessage()
,下面我们看enqueueMessage()
又干了什么。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//将Handler对象赋值给msg.target
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
//将消息加入到MessageQueue(消息队列)
return queue.enqueueMessage(msg, uptimeMillis);
}
从enqueueMessage()
中,我们知道了上面那个msg.target
是个啥了,它其实就是当前的Handler
对象。queue.enqueueMessage()
即真正意义上的将Message加入到消息队列中。最后通过Handler
对象的dispatchMessage()
方法,最终将消息回调出来。最后我们再看看dispatchMessage()
中干了啥:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
显示判断msg.callback
是否为null
,而msg.callback
实际上就是使用mHandler.post(Runnable r)
发送的Runnable
的对象。
1、如果msg.callback
不为null
,调用handleCallback
,而handleCallback
就是调用了msg.callback.run()
2、如果msg.callback
为null
,再判断是否设置了Callback
接口,设置了回调Callback
接口中的handleMessage()
,没有设置直接回调Handler
的空方法handleMessage()
。
至此,整个流程结束。
一些疑问
尽管,通过上面的分析,我相信你对Handler
的机制已经有了更深的理解。但是一直以来,有两个问题依然深深的困扰着我。
1、Handler
究竟是如何进行不同线程切换的?
上面我们提到,通常而言,我们会使用Handler
在异步线程中发送消息,之后都会调用enqueueMessage()
方法将消息加入到消息队列中,而我们所使用的MessageQueue
对象却是来源MainThread(主线程)
中Looper
所创建的。也就是说,Handler
发送消息时,MessageQueue
的对象是运行在异步线程中的。而使用Looper.loop()开启循环后,MessageQueue
的对象是运行在MainThread(主线程)
的。这也是为什么在enqueueMessage()
中,将消息加入到消息队列需要增加synchronized
关键字,因为我们需要确保MessageQueue
对象在同一时刻只能有一个线程访问。这一点,在MessageQueue#enqueueMessage()
的源码得到验证:
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
//注意这里的synchronized字段的作用。
synchronized (this) {
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
...具体的加入到队列的逻辑我们不分析,省略该部分代码
}
return true;
}
通过上面的分析,我们可以得出结论:Handler
的不同线程数据传输其实就是通过共享变量来实现的,而他们之间的介质便是Message
。
2、view.post(Runnable runable)
和mHandler.post(Runnable runnable)
之间的关联是什么?为何在Activity
的onCreate()
中调用view.gitHeight()
返回0,而使用view.post(Runnable runable)
之后可以正确的返回View
的高度?
关于这个问题,我将会用另一篇文章来单独分析~