初学Android的时候总是照着葫芦画瓢,看别人怎么用handler自己也照着抄上去,发现诶可以用诶,就不管他了,后来学习的时候一直想不通这个handler发消息去哪里?为什么这个线程接收?然后面试的时候这个又是必问的,之前面百度地图的时候面试官问handler主要用来干嘛的,我说更新UI,然后就跪了。面锤子科技的时候,面试官又问主线程和其他线程里面用到要用handler的时候我需要去new一个handler吗?这些如果没有对handler的机制,或者说是Android的消息机制有全面深入的了解的话都不会回答的好。现在让我们抛弃照着葫芦画瓢吧!
- handler是到底是干嘛的?更新UI吗?
- handler的用法
- handler,MessageQueue和Looper是怎么合作的?
- 一些特殊情况
handler是到底是干嘛的?更新UI吗?
Android里面线程和线程经常我们必须要进行通信的是吧,一个线程要给另外一个线程发送消息。handler就是我们用来进行发送消息的,它是Android消息机制的上层接口,我们开发者只需要对和handler打交道就可以了。
我们知道是不能够子线程里面更新UI界面的,因为UI界面的控件不是线程安全的。所以我们只能在主线程更新UI,但是有些时候我们要更新到UI界面的内容是需要进行一些耗时操作之后才可以拿到这些要更新的内容(比如下载图片),这些耗时操作就又要放到子线程中处理了。这时候我们就需要用handler把子线程中获取到的内容发到主线程中,在主线程中更新UI,或者在子线程中获取到要更新的内容后用handler把更新UI的操作切换到主线程中执行。
所以handler只是被我们常用来更新UI的,它并不是说就是用来更新UI。
上面说到用handler把子线程中获取到的内容发到主线程中,我之前就一直迷惑用谁的handler?这个handler在哪里?然后又被发到哪里去?就到了handler怎么用了。
handler的用法
如果这个线程要用handler,我们就需要在这个线程中创建这个handler的实例(那开始的时候说到锤子科技面试那个问题怎么破?我们后面再说到),下面举个例子,在主线程中新建一个子线程,它们相互进行通信。
MainActivity{
Handler MainActivity的handle;//主线程的handler
Handler 线程的handle;//子线程的handler
//用匿名内部类的方式实例化“MainActivity的handle”,在主线程实例化handler,这个handler就是主线程的
MainActivity的handle = new Handler(){
//重写方法handleMessage方法处理这个handler收到的消息
handleMessage(msg){
···
}
线程的handle.sent(msg);//在主线程中调用子线程的handler的sent方法,往子线程发送消息
};
//子线程
new Thread(){
run(){
Looper.prepare();//下面会讲到这个是干嘛的
//实例化子线程的handler,因为在子线程中实例化,这个handler属于子线程
线程的handle = new Handler(){
//重写方法handleMessage方法处理这个handler收到的消息
handleMessage(msg){
···
}
//在子线程中调用主线程的handler的sent方法,往主线程发送消息
MainActivity的handle.sent(msg);
};
Looper.Loop();//下面会讲到这个是干嘛的
}
}.start();
}
上面有一个主线程和子线程,然后都有各自的handler,通过调用对方的handler向对方发送消息,然后自己的handler接受对方发送来的消息并处理。这样是不是明白了呢?
那上面在子线程中有个Looper.prepare()和Looper.Loop()是干嘛的?为什么在主线程中有没有这两个方法?要回答这个问题我们就要搞懂Android消息机制的原理的,就是handler,MessageQueue和Looper。
handler,MessageQueue和Looper是怎么合作的?
- handler:上面已介绍
- MessageQueue:消息队列,属于A线程的handler会把别的线程调用handler发送的消息放在A线程的消息队列里面
- Looper:负责不断得从A线程的消息队列里面读取消息,并交给A线程的handler处理。
要进行线程通信,每个线程都会有它的handler,MessageQueue和Looper,它们的作用就和上面介绍的一样,我么你再看看源码来分析一下工作原理。
Looper
public final class Looper {
final MessageQueue mQueue;
final Thread mThread;
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
···
}
我们把只看一部分,可以看到Looper里面有消息队列和线程的对象,并且它的构造器是私有的,就是说我么能不能随意创建looper,在构造器里面会新建一个消息队列,并且把当前Looper所在的线程绑定到线程对象。
就是说:我们线程的looper包含消息队列和线程的对象,并且创建looper时会新建一个消息队列指向looper的内部的消息队列对象,把looper所在的线程指向looper的内部线程对象。
Looper.prepare()
但是looper构造器是私有的,我们无法直接调用,怎么办?这时候就出现Looper.prepare()啦,我们再次进入Looper的源码看看它的prepare()方法:
public static void prepare() {
prepare(true);
}
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));
}
是不是有点看不懂?没关系,这个又涉及到了ThreadLocal的知识点,ThreadLocal这里就先不讲了。我们只需要看到new Looper(quitAllowed)这一句,是不是就给在当前线程创建了一个新looper了?是的,没错。
就是说:我们调用Looper.prepare()方法给当前线程创建一个Looper对象。
Looper.loop()
那么在子线程里的Looper.loop()有事怎么一回事呢?还记得我们说MessageQueue是一个消息队列吗(其实看了源码之后他是一个链表,不过这不影响我们理解)?Looper的作用就是不断在消息队列里面for循环里无限遍历线程的MessageQueue, 一旦发现该消息队列里收到了新的消息,就会去对消息进行处理,处理过程中会回调这个线程的handler的heandleMessage方法去处理消息。
我们再次用源码来证明:
public static void loop() {
···
for (;;) {
Message msg = queue.next(); // might block
···
msg.target.dispatchMessage(msg);
···
}
}
我们把主要的显示出来了,可以看到里面有一个for循环,queue就是这个线程的消息队列,Looper.loop()通过这个for循环不断地从消息队列里面取消息,然后调用msg.target.dispatchMessage(msg)去处理消息,这里msg.target就是我们线程的handler,通过调用handler的dispatchMessage(msg)去处理消息,我们再进去这个方法里面看看:
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
不管其他的,是不是看到handleMessage(msg)这个方法?没错,就是在这个方法里处理消息,我么你在上面不是在用匿名内部内实例化我们线程的handler的时候重写这个方法去处理消息吗?对,是的。这回是不是有点明白handler,MessageQueue和Looper是怎么合作的了吧。
一些特殊情况
好了,搞定handler的原理之后,我么你来回答一下锤子科技的那个面试问题:
主线程和其他线程里面用到要用handler的时候我需要去new一个handler吗?
在回答这个问题之前,先回答另外一个问题:
主线程和其他线程里面用到要用handler的时候我需要去都去调用Looper.prepare()吗?
- 在主线程中,系统已经初始化了一个looper对象,所以直接创建handler就可以了
- 在自己启动的子线程中,我们要自己创建looper对象,用Looper.prepare()
我们再来回答第一个问题,刚才说到主线程系统已经为我们准备好了looper对象,如果我们平时使用的话呢还是要自己创建一个handler,其实主线程也已经有一个handler了,叫ActivityThread.H,它继承自Handler,用来和ApplicationThread进行通信。
参考
疯狂Android讲义-李刚
Android艺术开发探索-任玉刚