android|Android学习------Handler源码分析到手写简版Handler实现

从Handler的使用,到源码分析 学习Android多年了,还没有看过Handler源码,只是知道Handler是用来发送消息更新UI的,那么具体的实现是怎么样的呢?
那么今天,就来偷窥一下Handler源码的世界。
1.Hander的基本使用 1.1 基本使用,Demo演示
首先我们来一个简单的例子,看看平时我们Handler是怎么使用的,然后逐步分析里面的源码实现。
【android|Android学习------Handler源码分析到手写简版Handler实现】创建一个新的工程,HandlerSample
android|Android学习------Handler源码分析到手写简版Handler实现
文章图片

我们来看看实际效果
android|Android学习------Handler源码分析到手写简版Handler实现
文章图片

从上图我们可以看到,经过三秒延迟以后,中间的TextView确实已经更新了。那么我们试试不通过handler发送消息,直接更新我们试试可不可以呢?
修改后的代码如下,直接在主线程中更新UI
android|Android学习------Handler源码分析到手写简版Handler实现
文章图片

我们再来看看效果,这里还是直接用动图的方式展示
android|Android学习------Handler源码分析到手写简版Handler实现
文章图片

可以看到,咦,这是神马什么情况,这是什么鬼。,不是更新UI不能在子线程操作吗。 那么接下来我们就先来分下一下这个问题。
1.2 子线程可以更新UI吗?
我们一直认为的就是,在子线程中是不可以更新UI的,但是上图中展现出来的效果,在子线程中确实刷新了UI,这是什么情况呢? 别着急,我们再修改一下代码看看效果。
android|Android学习------Handler源码分析到手写简版Handler实现
文章图片

在上图中,我们可以看到,添加了一句代码 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类中哪里调用的呢,我们来看看
android|Android学习------Handler源码分析到手写简版Handler实现
文章图片

图上的mParent就是ViewRootImpl,这里我们大概可以明白了。如果在这个条件 (mParent != null && !mParent.isLayoutRequested()都满足的时候,才会调用到requestLayout方法的,如果其中一个不满足条件就不会调用到这个checkThread,那么不满足的条件一个就是ViewRootImpl是null的,看下这个实在哪里赋值的。
android|Android学习------Handler源码分析到手写简版Handler实现
文章图片

可以看到是在调用了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,是一个消息队列,看看赋值的地方
android|Android学习------Handler源码分析到手写简版Handler实现
文章图片

这里可以看到赋值的地方是在创建Handler创建的地方就会被赋值。 这个MessageQueue是Looper的一个成员变量。 通过
MessageQueue把Message添加到一个MessageQueue中就完成了消息的发送。
###1.3.2 Message的执行
上面我们既然已经把消息发送到了MessageQueue中,那么它是在那里发出执行的呢,我们可以反向推一下,我们知道一般我们在Handler中有一个handleMessage方法,我们看看这个方法会在哪里调用、
android|Android学习------Handler源码分析到手写简版Handler实现
文章图片

可以看到,是在一个叫dispatchMessage的方法里面执行的,那么这个方法在哪里调用的呢? 我们来到Looper.loop方法,可以看到
android|Android学习------Handler源码分析到手写简版Handler实现
文章图片

msg.targer是什么东西呢,
android|Android学习------Handler源码分析到手写简版Handler实现
文章图片

可以看到target是一个handler对象,那么实际上就是调用了handler里面的dispatchMessage方法。
这个时候需要看一个类。ActivityThread的一个Main方法。
android|Android学习------Handler源码分析到手写简版Handler实现
文章图片

在这个类创建的时候就会去创建一个主线程的Looper,然后会调用loop方法,不断的取出消息进行处理。 具体就可以看看 looper.loop() 的方法了。
android|Android学习------Handler源码分析到手写简版Handler实现
文章图片

在Looper里面,有一个msg对象,从MessageQueue.next中取出一个Message,然后调用message.target.dispatchMessage方法,实际上就是调用的Handler里面的dispatchMessage方法。
到这里基本上完成了。消息的发送---->处理。
下面我写了一些伪代码,大致模仿Handler的消息处理机制。 用java 程序写的。 大致看一下吧。
android|Android学习------Handler源码分析到手写简版Handler实现
文章图片

从图上可以看到,这是一个普通的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里面的。看到这里,大家明白了吗?

    推荐阅读