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

从Handler的使用,到源码分析

学习Android多年了,还没有看过Handler源码,只是知道Handler是用来发送消息更新UI的,那么具体的实现是怎么样的呢?
那么今天,就来偷窥一下Handler源码的世界。

1.Hander的基本使用

1.1 基本使用,Demo演示

首先我们来一个简单的例子,看看平时我们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<Looper> sThreadLocal = new ThreadLocal<Looper>();

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里面的。看到这里,大家明白了吗?

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值