如果你没有听说过或者使用过Handler,那么你一定不是一个合格的Android开发。Handler作为我们最常使用的跨线程UI工具,你可能只知道它的使用,却没有了解过它的实现原理。可能有人会想,原理这种东西理解与不理解它,并不影响我们对它的使用。话是没错,可能原理这种东西对于大部分人最直接的作用就是面试了,对于Handler,它可是面试中的常客,与之相关提问最多的就是Handler的原理了。有人会说,这我熟悉啊,然后张口就来:
“Handler机制由四部分组成,分别是Handler、Looper、MessageQueue、Message。Handler负责消息的发送和处理,Looper负责不断从MessageQueue中取出消息,MessageQueue负责Message的存储与管理,Message是消息的载体,可以承载需要的数据”
这要是搁几年前面试的时候,你这么说没什么问题,毕竟那时移动端很火,各行各业急需懂得开发App的人才,标准和要求没那么高,可能面试你的人都不懂它的原理甚至都不懂技术。但是,随着市场对移动端人才需求的不断饱和与移动开发市场的降温,这样的回答在今天可就不再那么管用了,一是对人才标准和要求方面的提高,二是如今的面试官不可能再出现像几年前那样的情况。当再往下问深点时,可能就触到你的知识盲区,GG了。
所以,就算是为了面试需要,请你务必搞懂这些原理。对于想在技术上有所进阶的人,就更该了。
那么本篇博文就带着以下对于Handler的疑问进行展开(下面都是针对Handler原理问题的变种,本质上还是离不开原理)
1、Handler如何与线程相关联?
2、消息是如何存储和管理的?
3、消息又是如何分发与处理的?
4、如果一个线程存在多个Handler,那么消息是怎样分发到对应的Handler而不出错的?
5、Handler处理消息的方式都有哪些?
6、线程的切换是怎么回事?
上面这些问题可能是你在使用Handler的过程中会产生的疑问,又或是在面试的时候被提问过,今天我们就Handler的使用过程搞懂这些问题背后的原理。
一个Handler是如何使用的呢?
class MyHandler:Handler(){
override fun handleMessage(msg: Message?) {
super.handleMessage(msg)
/.../
}
}
myHandler.sendEmptyMessage()
myHandler.post()
继承Handler,复写handleMessage方法处理消息,再调用send系列或post系列的方法发送一个消息,这应该是Handler最常见的使用方式了,然而你会发现看不到Looper、MessageQueue的身影,因为这些细节都被Handler封装了起来,让我们只需关注它如何使用而不需要知道背后的细节,今天我们就反其道而行之,从它的创建入手,看看Handler机制是怎么实现的。
一、Handler的创建
Handler一共有七个构造方法,但是最后都会指向其中两个,一个带Looper参数的,一个不带的。
那么我们先看看不带Looper参数的那个
public Handler(Callback callback, boolean async) {
/.../
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
可以看到,在这个构造方法内通过Looper.myLooper()方法获得一个Looper对象,对其进行为空判断,并抛出一个异常提示当前线程不能创建Handler,因为在此之前没有调用Looper.prepare()方法。
那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));
}
prepare()方法调用了私有的prepare方法并转递了一个true参数,这个参数表示当前Looper是否可退出,在这个私有的prepare()方法内它首先去当前线程的本地存储区去获得一个Looper对象,如果对象不为空,则提示异常并告知一个线程只能存在一个Looper对象,如果为空则创建一个Looper对象并设置到当前线程的本地存储区。
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Looper的构造方法里创建了一个MessaQueue并标记了当前线程。
那么再来看看Handler带Looper参数的构造函数
public Handler(Looper looper, Callback callback, boolean async) {
mLooper = looper;
mQueue = looper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
这个构造方法只是把传进来的参数赋值给本地,并没有什么特别的。
ok,通过Handler的构造函数可以得知,Handler的构建离不开Looper,而Looper是与线程绑定且一个线程只能拥用有一个Looper,所以Handler与线程是通过Looper来关联的,Looper在那个线程创建,Handler就在哪个线程执行。
二、消息的存储与管理
通常我们使用Handler发送一个消息,会调用send系列或post系列的方法,但其实它们最后调用的都是同一个方法。
而这个enqueueMessage()方法又会调用MessageQueue的enqueueMessage()方法将消息入队。
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
到这里,显而易见消息存储与管理是通过MessageQueue来完成。
三、消息的分发与处理
在子线程中使用Handler不仅需要Looper.prepa(),还需调用Looper.loop()方法才能正常使用。
public static void loop() {
final Looper me = myLooper();
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
/.../
for (;;) {
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
/.../
try {
msg.target.dispatchMessage(msg);
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
/.../
msg.recycleUnchecked();
}
}
loop()方法内部开始了一个死循环,并不断从MessageQueue中取出消息,没有时则阻塞,当Looper调用quit()方法时,MessageQueue的queue.next()返回为null,loop()方法结束死循环,Looper退出。
当有Message时,尝试执行msg.target.dispatchMessage(msg)方法,msg.target是什么?记不记得前面Handler的enqueueMessage方法,其实msg.target就是Handler对象本身
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
//将Handler引用指向msg.target
msg.target = this;
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
这样Message就被分发到对应的Handler的dispatchMessage进行处理
public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
咦?这msg.callback和mCallBack又是什么,消息不是被handleMessage()处理就完了么,怎么还有其他方法会处理?别慌,其实这些东西你也见过,而且还用过。
msg.callback其实就是handler.post()方法里的Runnable,在使用post系列方法的时候,Runnable会经getPostMessage(Runnable r)
封装成一个Message对象
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
handleCallback()方法则是在Handler绑定的线程执行Runnable的run方法
private static void handleCallback(Message message) {
message.callback.run();
}
那mCallback呢?mCallback是一个接口,内有一个handleMessage方法,你会注意到,这个方法有一个布尔返回值,如果不需dispatchMessage方法再往下执行时返回ture。这个接口通过Handler的构造方法传入
public interface Callback {
public boolean handleMessage(Message msg);
}
最后就是Handler的handleMessage()方法了,通常继承Handler时需要复写这个方法。
上面就是Handler处理Message的三种实现了,你会发现,这相当于三个方法拥有不同的优先执行权,Runnable最高,CallBack接口次之,最后是handleMessage()。对于这个优先级可以干什么,有兴趣的可以自行谷哥度娘。
扩展
1、Handler的内存泄漏
其实在Handler的构造方法里,就明确表示过Handler应该是静态的,否则可能会发生内存泄漏
Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
klass.getCanonicalName());
比如我要通过Handler的消息设置进度条
companion object{
class MyHandler(activity: MainActivity) : Handler() {
private var mWeakReference =WeakReference(activity)
override fun handleMessage(msg: Message) {
super.handleMessage(msg)
val activity = mWeakReference.get()
activity?.let { it.progress_bar.progress = msg.what }
}
}
}
2、Message的最佳获取方式?
因为我们需要频繁的使用Message,所以频繁的创建会过多消耗资源,所以Message内部维护着一个消息池供我们使用,它会循环利用Message,这个消息池的大小为50。
private static final int MAX_POOL_SIZE = 50;
我们可以通过Handler.obtainMessage()或Message.obtain()方法来获取一个Message。
3、子线程吐司的正确方式
正如我们在子线程使用Handler一样,在吐司前后调用Looper.preare()、Looper.loop()方法即可(这个我没研究过,貌似是Toast的底层也是通过Handler实现的,如果有知道的可以评论告诉我)
最后
相信看完这篇文章,以后再有人问起你Handler的问题,基本不会再出现触及知识盲区的情况而卡壳了(除非真的很底层的东西),如果有人在面试遇到过关于Handler的其他问题,希望可以留言评论,和大家分享一下。