Handler相关的学习笔记(源码解析)

一、Android的消息机制

Android的消息机制主要是指Handler的运行机制,Handler的运行需要底层的MessageQueen和Looper做支撑。MessageQueen作为消息的存储单元,Looper以无限遍历的形式查询MessageQueen中是否有新消息,Looper是与当前线程绑定的,在Handler内部获取Looper可以通过ThreadLocal获取,但是线程是默认没有Looper的,需要使用Handler就需要为线程创建Looper(UI线程除外,UI线程会默认创建初始化Looper,在ActivityThread被创建时候创建(Main方法中))

Handler相关的学习笔记(源码解析)_第1张图片
handler机制原理图解

二、Handler、MeaasgeQueen、Looper和Thread之间的绑定

  1. Handler绑定当前线程的Looper和MessageQueen
    先看一下Handler里边的源码,当我们实现下面一个这样的handler时,构造函数都做了什么。

    handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {
        super.handleMessage(msg);
        Log.d("TAG233", "handleMessage: "+msg.obj);                    
        }
    };
    

从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;
    
    //……
}

首先在Handler内部,它获取了和当前线程绑定的Looper,之后又获取了和Looper绑定的MessageQueen,这个时候会发现有一段抛出异常的代码,因为在线程创建的初期,是不会主动的去创建Looper的(UI线程除外),要创建Looper,需要调用Looper.prepare()实现(具体的源码分析会在下面写),如果没有调用这个方法,那么当前线程是不会存在这个Looper的,自然也不会存在对应的MeaasgeQueen,这个时候如果直接使用handler,系统就会抛出异常。
获取MessageQueen的方式是十分简单的,在拿到Looper之后拿Looper里的MeaasgeQueen即可。

那么Handler如何保证自己获取到的Looper是当前线程的Looper呢?
在Handler的构造方法里,有一句mLooper = Looper.myLooper(),这个myLooper里面十分简单

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

也就是说,Looper是从ThreadLocal里获取的。再看get方法

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();
}

看到代码就明白了,ThreadLocal在Handler的机制里,担当的就是一个存储Looper的任务,ThreadLocal里有一个Entry,这个Entry是 Entry(ThreadLocal k, Object v),也就是说Looper是作为value存储在ThreadLocal里的,通过当前线程t获取到ThreadLocalMap,然后通过ThreadLocalMap里的Entry,获取到Looper并返回。

到这里,Handler和Looper、MeaasgeQueen的绑定的源码看完啦~

  1. Looper和当前线程的绑定
    看完了Handler的绑定,我们知道,Handler里获取当前线程的Looper是从ThreadLocal里拿的,创建Looper又是要调用Looper.prepared()方法来创建Looper并完成和当前线程的绑定,那么现在先来看看Looper.prepared()都做了什么。
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));
}

prepare()方法里很简单,就是往ThreadLocal里添加Looper来完成绑定,当然,从代码中也能够看出来,创建Looper的前提是已经创建了线程,否则会抛出异常。

再看ThreadLocal里的set方法

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

对应前边Handler获取和当前线程绑定的get方法,思路就比较清晰了,同样通过当前线程t获取ThreadLocalMap,通过setMap实现key为当前ThreadLocal,value就是这个Looper啦。

但是这个ThreadLocal又是哪里来的?在Looper里找到了这样一句代码解释了这个问题:

static final ThreadLocal sThreadLocal = new ThreadLocal();

看完了ThreadLocal里的问题,但是还是没有解决Looper到底怎么和当前线程完成的绑定,再看Looper的构造方法:

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

这下子没问题了,原来Looper在创建的时候,在构造函数里,通过Thread.currentThread()获取到了当前线程,并用mThread引用,所以当我们在ThreadLocal里存储Looper时,这个Looper就已经和这个线程绑定好了,所以Handler在ThreadLocal里get这个Looper时,就已经是绑定了当前线程的。再看mQueue是直接new MeaasgeQueen()了,也完成了消息队列的绑定。

总结一下,Looper在Looper.prepare()时就完成了和当前线程、MeaasgeQueen的绑定,之后存储在ThreadLocal里,Handler通过ThreadLocal获取到这个Looper,完成了一系列的绑定。

三、Handler的工作原理

Handler的工作主要包含消息的发送和接收的过程,消息的发送是通过一系列的send的方法来完成的。

当我们需要发送一条消息时,最常用的方式就是使用sendMessage(Message msg)方法,接下来看一下源码:
从sendMessage开始,发送消息依次经过了sendMessage->sengMessageDelay()->sendMessageAtTime(),中间的过程十分简单,就不贴代码了,主要来看一下sendMessageAtTime()这个方法。

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);
}

这里主要做了一个对消息队列的判断,如果为空则抛出异常,重点看一下enqueueMessage都做了什么

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    msg.target = this;
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

在这段代码里,可以看到 msg.target = this; 这行代码,这行代码指定了当前这条消息是由这个handler发送的,这也就是为什么一个Handler发送消息时,其他的Handler不会接受到消息的原因。

在Looper中,Looper.loop()方法里如果检测到MessageQueen中有消息,就会取出这条消息,之后通过msg.target.dispatchMessage(msg)分发消息,由于Message中设置了target,保证了这个Handler发出的消息不会被其他的Handler接收到。也由于dispatchMessage()是在Looper中执行的,msg.target实质上就是一个handler,所以在这里,就成功的将代码逻辑切换到指定的线程执行了。

在看queue.enqueueMessage(msg, uptimeMillis),这句代码的作用其实就是向MessageQueen里插入消息了。

从上面的代码也可以看出,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()来传递消息完成最后的处理的,而handleMessage则是由我们自己重写的一个方法,也就是最后的处理消息的逻辑。

那么CallBack是什么呢?
在源码的注释中,CallBack是用来创建一个Handler实例但是并不需要派生Handler的子类,其实就是另一种使用Handler的方式。在日常的开发中,我们都是派生一个Handler的子类并且重写它的HandleMessage()方法来处理消息,当我们不想派生子类时,就可以通过 Handler handler = new Handler(callback) 来实现。

好啦,目前对Handler的分析就先告一段落~
最近在面试经常问到Handler,总觉得自己答的不够详细,希望这篇文章过后能有所进步~

你可能感兴趣的:(Handler相关的学习笔记(源码解析))