前言
我们都知道在Android中有个不成文的规定
- 主线程可以刷新ui,不能执行耗时操作
- 子线程中可以进行耗时操作,但是不能更新ui
那有什么东西能够很好的兼顾这两个条件呢,这就是引出了Android本身为了解决线程间通信的解决方案,那就是Handler
ThreadLocal简介
对于线程Thread相信大家已经很熟悉了,但是我们今天要讲的是ThreadLocal,可能大家会感到陌生,那么它是一个什么呢,首先从名字上看,直译为"本地线程",但是这个翻译对于它真实的含义我感觉是很难理解的,ThreadLocal并不是用来操作本地的线程,它是一个实现不同线程的数据副本,什么意思呢,就是说当使用ThreadLocal来声明一个变量的时候,它会为每一个线程生成一个对应改线程的变量副本,每一个线程都可以独立改变自身上的变量副本而不会影响到其他线程上的变量副本,虽然看起来都是同一个变量,但是操作的是不同的副本,所以我认为应该称呼这个为“线程本地变量”感觉更合适一点,而Handler的实现原理很大的依赖了ThreadLocal的影响,这个后面会分析
Looper、线程、消息队列的关系
既然说handler是实现子线程更新ui的操作,那么我们是不是可以直接上手举一个例子在子线程中使用handler来更新ui呢
private class LooperThread extends Thread{
@Override
public void run() {
super.run();
Handler handler=new Handler();
//doing something
}
}
此处的代码非常的简单就是使用:LooperThread继承自Thread,然后在run方法中new 了一个Handler对象,然后你运行的话,就会报如下错误
Can’t create handler inside thread that has not called Looper.prepare().
为什么会这样呢,看代码不难发现问题,肯定出在new这个Handler对象的时候,那么我们进入Handler对象的构造函数看一下,构造函数最终都会调用到如下这段代码
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 " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
通过这段代码可以清楚的看到,我们之前报的异常信息就是从这里抛出来的,那么mLooper 为什么会为null呢,继续查看,对应的Looper.myLooper()的源码都做了什么
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
可以看到它是通过ThreadLocal来获取对应的looper对象,按照国际惯例,有get方法肯定也有相应的set方法,我们可以全局的在这个类里面搜索一下set方法,看下是在哪里赋值的呢,通过查找,我们找到了如下代码段
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()这句话,这是因为只有在这里才会对ThreadLocal进行赋值操作
等等感觉有什么不对呀,平时我们在代码中使用Handler都是直接new出来的呀,怎么就没有报这个错误呢,这里怎么就会报这个错误呢,开始愣逼中......
解释这个问题还是要看源码,这是因为在app启动的主入口是在ActivityThread的main函数中,这里贴出相关的源代码
public static void main(String[] args) {
...
//省略无关代码
Process.setArgV0("<pre-initialized>");
//关键处 1,这里就会调用Looper.prepare()
Looper.prepareMainLooper();
// 省略无关代码
...
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
//关键处 2 这里就会进行消息轮询操作
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
所以我们平时在代码中直接new Handler对象的时候,其实它已经调用过了Looper.prepare()这个方法了,所以才不会报错的
通过上溯代码中sThreadLocal.set(new Looper(quitAllowed));这句话,可以看出,这里new 了一个looper对象,继续追溯Looper的构造方法,看下都进行了哪些初始化操作,构造方法的代码如下
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
所以我们可以知道,创建一个looper对象,从而会创建一个messageQueue与它一一对应,同时会保持当前线程
小结
- 一个线程对应一个looper
- 一个looper对应一个messageQueue
- 进而一个线程对应一个messageQueue
- 线程与消息队列和handler一一对应
- 创建一个handler的对象的时候,会获取当前线程相关的Looper实例,从而获得一个消息队列,必要时可以自己传入相应的回调接口
所以在子线程要使用handler代码如下所示
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
}
};
Looper.loop();
}
}
Message的发送和处理过程
平常我们非常常用的方式
- handler.sendMessage(message)——>发送消息
- handleMessage(Message msg){}——>处理消息
我们先来分析下消息的入栈
入栈的都是通过handler提供的一些方法进行入栈,其中涉及的方法有post()、postAtTime()、postDelayed()、postAtFrontOfQueue()等方法进行入栈,追溯这些方法的源码,可以知道,这些最终都会调用到enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)这个方法,下面看一下这个方法的源码如下
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//此处会把当前的handler对象保持到message这个对象中去,关键处
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
这里调用了MessageQueue的enqueueMessage(msg, uptimeMillis),所以继续源码追溯,如下就是MessageQueue的enqueueMessage(msg, uptimeMillis)的源码
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 (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;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
这里主要的逻辑就是对链表进行插入的操作,还把获取的消息时间存入msg.when的变量中,并且实现距离触发时间最短的消息排在最前面,距离触发时间最长的消息排在队列的最后面,如果传入的when为0的话,那这条消息会插入到队列的头部优先得到执行,而上面的消息队列就是创建该handler自动携带的一个消息队列,这个上文有讲到,一个handler就会对应一个messageQueue
消息的出栈
通过上面的分析可以知道,消息是通过handler进行消息的发送的,那么是谁对消息进行处理的呢,怎么处理的呢
带着上面的疑问,我们需要重新看一下activityThread的main函数的源码,通过观察这个源码,我们发现,在Looper.prepareMainLooper();后代码又再一次调用了Looper.loop();,就是上图源码中的关键处2,而消息的处理过程就是通过这个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.");
}
//获取该looper对象对应的消息队列
final MessageQueue queue = me.mQueue;
for (;;) {
//消息队列出栈,可能会发生block
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
try {
//开始对拿到的消息进行分发处理
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
msg.recycleUnchecked();
}
}
已经对源码关键处添加了对应的注释,这里主要逻辑就是死循环不断从消息队列中取得消息,然后在调用msg.target.dispatchMessage(msg);去分发消息,所以主要的处理逻辑是在target的dispatchMessage()方法中,而这个target是什么呢,还记得我们利用handler发送消息的时候,我们就会默认的把消息中的target赋值成当前的handler,所以这个target就是当前message绑定的handler,所以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);
}
}
源码很简单,通过分析可以知道,这个最终会调用到handleMessage(msg);方法,这个就是一般我们重写的那个方法,因为默认是在主线程中创建的handler,所以执行这句话也就一定在主线程中,所以handler才能做到刷新ui,它的线程默认就是主线程,如果在子线程创建的handler同样也是无法更新ui的,这就是线程之间可以利用handler实现线程的切换
图解Handler工作机制
通过上面的介绍,基本上把handler的实现原理分析完了,但是为了方便大家的记忆,可以通过一张图来说明它们之间的关系,此图我也是从网上找的,认为总结的还不错,如有版权问题,请告知,立马删除
示意图如下
最后再补充一点
一个Looper实例可以对应多个handler,因为handler是主动与Looper绑定的,所以同一个Looper上可以绑定多个handler,而且这多个handler都是公用一个messageQueue
网友评论