从Handler的使用,到源码分析 学习Android多年了,还没有看过Handler源码,只是知道Handler是用来发送消息更新UI的,那么具体的实现是怎么样的呢?
那么今天,就来偷窥一下Handler源码的世界。
1.Hander的基本使用
1.1 基本使用,Demo演示
首先我们来一个简单的例子,看看平时我们Handler是怎么使用的,然后逐步分析里面的源码实现。
【android|Android学习------Handler源码分析到手写简版Handler实现】创建一个新的工程,HandlerSample
文章图片
我们来看看实际效果
文章图片
从上图我们可以看到,经过三秒延迟以后,中间的TextView确实已经更新了。那么我们试试不通过handler发送消息,直接更新我们试试可不可以呢?
修改后的代码如下,直接在主线程中更新UI
文章图片
我们再来看看效果,这里还是直接用动图的方式展示
文章图片
可以看到,咦,这是神马什么情况,这是什么鬼。,不是更新UI不能在子线程操作吗。 那么接下来我们就先来分下一下这个问题。
1.2 子线程可以更新UI吗?
我们一直认为的就是,在子线程中是不可以更新UI的,但是上图中展现出来的效果,在子线程中确实刷新了UI,这是什么情况呢? 别着急,我们再修改一下代码看看效果。
文章图片
在上图中,我们可以看到,添加了一句代码 SystemClock.sleep(3000) ,然后就发生了异常。 看看这个异常是什么异常
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views.
at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6891)
at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:1048)
at android.view.View.requestLayout(View.java:19781)
at android.view.View.requestLayout(View.java:19781)
at android.view.View.requestLayout(View.java:19781)
at android.view.View.requestLayout(View.java:19781)
at android.view.View.requestLayout(View.java:19781)
at android.view.View.requestLayout(View.java:19781)
at android.support.constraint.ConstraintLayout.requestLayout(ConstraintLayout.java:3172)
at android.view.View.requestLayout(View.java:19781)
at android.widget.TextView.checkForRelayout(TextView.java:7368)
at android.widget.TextView.setText(TextView.java:4480)
at android.widget.TextView.setText(TextView.java:4337)
at android.widget.TextView.setText(TextView.java:4312)
上面的异常抛出了 Only the original thread that created a view hierarchy can touch its views,
google翻译:只有创建视图层次结构的原始线程才能触及其视图。
我们在根据抛出的异常栈找到抛出异常的源码看看,根据上面的异常,我们可以看到 是在ViewRootImpl的checkThread方法中抛出的。
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
上面可以看到,这里有一个检查线程的方法,可以得知mThread是创建视图的原始线程,Thread.currentThread()获取到的是我们的子线程,所以这里就会抛出这个异常,然后我们再继续往上追溯一点,发现是在ViewRootImpl这个类中调用了RequestLayout,那么这个方法又是在View类中哪里调用的呢,我们来看看
文章图片
图上的mParent就是ViewRootImpl,这里我们大概可以明白了。如果在这个条件 (mParent != null && !mParent.isLayoutRequested()都满足的时候,才会调用到requestLayout方法的,如果其中一个不满足条件就不会调用到这个checkThread,那么不满足的条件一个就是ViewRootImpl是null的,看下这个实在哪里赋值的。
文章图片
可以看到是在调用了assignParent这个方法才能赋值到,那么这个方法又是在哪里调用的呢,我们查下资料吧,
https://blog.csdn.net/u013866845/article/details/54408825
https://www.jianshu.com/p/a6fd2c4db80d
这里就不去仔细讲解里面怎么赋值的了,东西还是挺多的。
现在我们大概可以回答这个问题了,因为我们是在Activity的onCreate中创建的时候直接调用了一个新的线程,然后直接在里面更新了UI,这个时候viewRootImpl还没有创建好,导致mParent是null的,所以会绕过线程的检查,这里的原理就相当于是在onCreate中直接 获取一个View的宽高,获取到的数值都是0是一个道理,这个时候view还没有来得及测量绘制完成。所以会有这样的问题。 所以呢,我们还是不要直接在子线程中直接更新UI哦。因为里面有线程检查,因为里面有线程检查,因为里面有线程检查。
1.3 子线程的消息是如何传递到主线程的?
我们接着上面的问题分线,刚才上面讲了从子线程发送消息,这个消息是从子线程发起的,为什么在Handler里面就是主线程了?这里我们再去看看里面源码怎么实现的。
###1.3.1 Message的发送
然后我们会走到下面的方法
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);
}
这里有一个mQueue,是一个消息队列,看看赋值的地方
文章图片
这里可以看到赋值的地方是在创建Handler创建的地方就会被赋值。 这个MessageQueue是Looper的一个成员变量。 通过
MessageQueue把Message添加到一个MessageQueue中就完成了消息的发送。
###1.3.2 Message的执行
上面我们既然已经把消息发送到了MessageQueue中,那么它是在那里发出执行的呢,我们可以反向推一下,我们知道一般我们在Handler中有一个handleMessage方法,我们看看这个方法会在哪里调用、
文章图片
可以看到,是在一个叫dispatchMessage的方法里面执行的,那么这个方法在哪里调用的呢? 我们来到Looper.loop方法,可以看到
文章图片
msg.targer是什么东西呢,
文章图片
可以看到target是一个handler对象,那么实际上就是调用了handler里面的dispatchMessage方法。
这个时候需要看一个类。ActivityThread的一个Main方法。
文章图片
在这个类创建的时候就会去创建一个主线程的Looper,然后会调用loop方法,不断的取出消息进行处理。 具体就可以看看 looper.loop() 的方法了。
文章图片
在Looper里面,有一个msg对象,从MessageQueue.next中取出一个Message,然后调用message.target.dispatchMessage方法,实际上就是调用的Handler里面的dispatchMessage方法。
到这里基本上完成了。消息的发送---->处理。
下面我写了一些伪代码,大致模仿Handler的消息处理机制。 用java 程序写的。 大致看一下吧。
文章图片
从图上可以看到,这是一个普通的java程序,左侧已经写好了我们所需要的几个核心类,Handler,Looper,Message,MessageQueue,在Test程序中我们通过平时使用Handler的方式,去使用。最后的效果在底部,大家可以看到。在子线程里面发送出去的消息,的确是在handler中接受到了,并且是在主线程中。
2.Handler 流程分析 接下来,会展示下里面大致的伪代码处理。 细节部分都未处理,毕竟这不是系统源码,我们只是分析内部处理机制 ,哈哈。
Handler
public class Handler {private Looper looper;
private MessageQueue messageQueue;
public Handler() {
looper = Looper.myLooper();
messageQueue = looper.messageQueue;
}/***
* 处理消息
* @param msg
*/
public void handlerMessage(Message msg) {}
/***
* 分发消息
* @param msg
*/
public void dispatchMessage(Message msg) {
handlerMessage(msg);
}/***
* 发送消息 加入MessageQueue中
* @param message
*/
public void sendMessage(Message message) {
message.target = this;
messageQueue.enqueueMessage(message);
}}
Looper
public class Looper {final MessageQueue messageQueue;
static final ThreadLocal sThreadLocal = new ThreadLocal();
private Looper() {
messageQueue = new MessageQueue();
}static Looper myLooper() {
return sThreadLocal.get();
}static void prepare() {
if (sThreadLocal.get() == null) {
sThreadLocal.set(new Looper());
}
}public static void loop() {
//获取当前线程里面的Looper
final Looper me = myLooper();
//获取MessageQueue
MessageQueue queue = me.messageQueue;
for (;
;
) {
//取出Message
Message next = queue.next();
if (next!=null ) {
next.target.dispatchMessage(next);
}
}
}
}
Message
public class Message {
Object obj;
Handler target;
Message next;
}
MessageQueue
public class MessageQueue {private Message mMessages;
//拿取消息
public Message next() {
Message msg = mMessages;
if (msg == null) {
return null;
}
mMessages=msg.next;
//重置掉Message
msg.next = null;
return msg;
}//存入消息
public void enqueueMessage(Message msg) {
Message p = mMessages;
msg.next = p;
mMessages = msg;
}
}
大致流程如下:
1.首先会通过ActivityThread的main方法,在里面会调用Looper的创建prepareMainLooper的方法,这个时候是在主线程创建的一个Looper
(这个时候会同时会创建一个MessageQueue的对象,还有 这个时候ThreadLocal很重要),
2.然后调用Looper.loop方法,会不断的MessageQueue里面的消息。
3.然后我们会通过创建一个Handler对象,这个时候,会拿到当前线程的Looper对象。然后拿到Looper里的MessageQuere对象,
4.然后在子线程通过Handler发送消息
5.将消息添加到MessageQueue中。
6.这个时候Looper的loop方法还在一直调用着
7.loop里面是一个无限循环,会调用MessageQueue中的next方法,去除Message
8.取出Message后,会拿到message中存储的Handler对象,然后调用Handler的dispathMessage方法,
9.然后调用handlerMessage方法,处理消息。
上面就是大致的流程了,当然里面还有很多的逻辑细节没有体现出来,不过能大致理解这些,也差不太多。 当然比如源码里面dispatchMessage的方法会有这样的判断。
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
会首先判断Mesage中的callback是不是空的,callback其实就是一个Runable,如果不是一个空的就直接执行 runable.run方法 ,看看源码是不是这样,我们找到handlerCallback(msg)方法。 源码如下:
private static void handleCallback(Message message) {
message.callback.run();
}
然后看看有一个判断
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
这里的mCallBack是Handler内部的一个接口回调,是可以在创建Handler的时候时候传入的,这个接口里面的方法也叫handleMessage,和Handler里面的一个内部方法名一样的。 这里的判断意思就是如果你在创建的时候传入了Callback,并且在在Callback调用handleMessage的里面返回为True了,就不会执行Handler对象里面的handleMessage方法了,如果返回false就会执行2次handleMessage,一次是Callback里面的,一次则是Handler里面的。看到这里,大家明白了吗?
推荐阅读
- Android面试题|从源码角度分析Handler核心机制
- Android|带着问题学习 Android Handler 消息机制
- Android进阶之路|Android 面试准备进行曲-Handler源码/面试题
- Android|#Android 消息机制handler详细攻略+源码解析
- handler|handler 和 广播 android,Android_Message_Handler_消息处理机制总结笔记
- Android|Android_Handler机制原理解析和源码分析
- android|源码分析-Android中的消息机制详解
- Android的XML常用标签整理