一、Android的消息机制
Android的消息机制主要是指Handler的运行机制,Handler的运行需要底层的MessageQueen和Looper做支撑。MessageQueen作为消息的存储单元,Looper以无限遍历的形式查询MessageQueen中是否有新消息,Looper是与当前线程绑定的,在Handler内部获取Looper可以通过ThreadLocal获取,但是线程是默认没有Looper的,需要使用Handler就需要为线程创建Looper(UI线程除外,UI线程会默认创建初始化Looper,在ActivityThread被创建时候创建(Main方法中))
二、Handler、MeaasgeQueen、Looper和Thread之间的绑定
-
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的绑定的源码看完啦~
- 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<Looper> sThreadLocal = new ThreadLocal<Looper>();
看完了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,总觉得自己答的不够详细,希望这篇文章过后能有所进步~