转载 [图解法结合源码]理解、记忆Handler、Looper、MessageQueue之间的关系

看了不少关于Handler、Looper、MessageQueue之间关系的文章。感觉挺枯燥的,上来就是一团代码,看着心烦。
后来我捋了捋,画了个图。先看图,我们再来谈他们间的关系:

在这里插入图片描述

在这个图中,我做了个类比:(很重要,多看几遍)

MessageQueue,流水线上的"履带";

Looper,履带的"驱动轮";

Handler,流水线上的"工人";

Message,流水线上的"包裹"。

现在让我们来解释这三者间的工作关系,首先,我们要明确一个基本法:

一个Thread只能有且只能一个Looper。一个Looper只能对应一个Message。一个Looper和MessageQueue的绑定体可以对应多个Handler。(参考上图)

1.Looper与MessageQueue

刚刚说了一个Thread只能有一个Looper。为什么只能有一个呢?让我们去看它的一个方法Looper.prepare()方法的源码:

public static final void prepare() {
if (sThreadLocal.get() != null) {
throw new RuntimeException(“Only one Looper may be created per thread”);
}
sThreadLocal.set(new Looper(true));
}
第二行,如果当前线程已有一个Looper,那么将直接抛出异常。这是基本法,没得说。

然后我们再来说说一个线程一般要怎么创建一个Looper,例子A:(下面会拿这个例子讲解)

class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
// 你的方法
}
};
}
Looper.loop();
}
首先,一个线程先Looper.prepare(),创建一个Looper,在这个prepare()方法中,回头看一眼基本法里的代码,它会在 return 中new 一个(Looper(true));其实,就是下面的代码:

private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mRun = true;
mThread = Thread.currentThread(); //绑定当前线程
}
注意第二行,它创建并绑定了一个MessageQueue,做个类比,就是给Looper这个驱动轮套上了它的履带MessageQueue,由于Looper在当前线程唯一,则其应为一一对应关系。
现在驱动轮有了,履带有了,要让这个流水线动起来,显然,还需要另外一个操作。

没错,这个操作就是例子A中倒二行的Looper.loop()。其源码为:(仅列出你需要理解的代码,其他你不需要关心)

public static void loop() {
final Looper me = myLooper();//获取当前线程的Looper
if (me == null) { //如果没有为当前线程进行Looper.prepare(),跳出异常
throw new RuntimeException(“No Looper; Looper.prepare() wasn’t called on this thread.”);
}
final MessageQueue queue = me.mQueue; //获得当前Looper me所绑定的MessageQueue
for (;😉 { //无限循环
Message msg = queue.next(); // 通过MessageQueue的next()方法从队首取一个消息
if (msg == null) {
//如果消息队列为空,就退出这个无限循环
return;
}
msg.target.dispatchMessage(msg); //分发消息,msg.target其实就是个handler,你先记着不要管
msg.recycle(); //通过MessageQueue的recycle()方法回收资源,让"履带"转动取来
}
}
Tips:这里需要注意一个事情,观察到loop()方法里有个无限循环。在例子A中,如果你在Looper.loop()后面写代码,IDE会判断这些代码是不会执行的,因此会报错。也就说,loop()方法必须在该线程需要执行的内容的最后一行写!!!

上面的loop()中我给出了详细的注解,你稍微花两分钟就能看明白。第8行,从MessageQueue队列中取走第一个消息,然后在第13行,调用msg.target的disspachMessage()方法,分发这个消息。其实msg.target就是个handler,至于为什么,我会在Handler的部分进行讲解,你先这个记着。

Looper部分总结一句话:

在线程的开头,为这个线程准备一个Looper(Looper.prepare()),这个Looper会自动绑定上一个MessageQueue,然后在线程的最后,通过Looper.loop()方法,让这个MessageQueue转动起来,传送这个MessageQueue上的消息对象Message。每次Message被传送到队首,这个Message会被disspatchMessage()方法分发出去,分配到管理它的Handler(流水线工人)那里进行处理。

2.Message

我们在图中做了一个类比,把Message当成了一个包裹,那么它里面到底包裹了啥?我们来看一下它的源码:

public final class Message implements Parcelable {
public int what;
public int arg1;
public int arg2;
public Object obj;//以上是一些数据类型,无视
public Messenger replyTo;
Bundle data;//一般用于封装数据的包
Handler target;//注意看这里,是个Handler
Runnable callback;//注意这个回调

}
看到那个target了么,就是个Handler。现在回去看我的那个图,你会看到我在Handler(“流水线工人”)那里写了个盖章二字,是什么意思呢?
一个handler通过post方法(我们下面再说,总是就是线程把这个消息Message丢给了Handler)拿到一个Message之后,这个Handler并不是马上处理这个消息,而是先盖上一个章:

msg.target=this;
然后把这个消息丢向流水线的"履带"——MessageQueue,让它排队去,等它排到队首之后,再通过msg.target.dispatchMessage()方法分到刚刚送它进流水线的“工人”手里进行处理。
有的同学可能会问了,Handler拿到Message为什么不马上处理呢?

原因是:本身Handler并没有提供并发解决机制,但MessageQueue的next()提供了并发解决机制。稍微理解一下这句话,很容易理解的。要知道Handler不只能接收到本线程丢过来的Msg包,还能接到其他线程(一般是子线程)丢过来的包(参考第一幅图),你不搞个基本法,排队来解决,这个程序怕是药丸。

这里还要注意一下那个Runnable callback的回调。现在先别管,总之先记着,我挖的坑我肯定会填的。

Message要注意的只有两种用法,一个是把信息封装,一个是解包提取信息;

Message msg = Message.obtain();//尽量不要一直new Message(),原因很简单,省内存。
Bundle b=new Bundle();//信息封包
b.putInt(“Yidong”,10086);
msg.setData(b);
//msg.sendToTarget();
myHandler.post(msg);//先丢给流水线工人盖章、入列
Bundle b=msg.getData();//在handlerMessage()方法中解包提取信息。至于这个方法是啥,马上就要说到了。
int a=b.getInt(“Yidong”);//a=10086
一句话总结,Message就是个"包裹",里面装了你需要传递信息,然后被丢来丢去(╯‵□′)╯︵,最后被分配到它的工人那里拆包进行处理。

3.Handler

终于,这篇文章要写完了。QAQ

接下来,我们要说我们的主角了——Handler了。大部分时候,你不需要关心MessageQueue和Looper是怎么工作的,但是Handler是你时时刻刻都必须打招呼的家伙。

首先是Handler究竟是个什么家伙。我把它类比为流水线上"工人" ,它即负责把收到的消息入列,也负责处理队首属于自己的那一份Message。

它的构造函数里没有啥东西,你只需要知道,它会先获取当前线程的Looper并绑定,然后从这个Looper获取到它的MessageQueue。然后,Handler与Looper、MessageQueue的关系就建立起来了。另外,他还创建了一个mCallback。这个待会儿再说。

刚刚我们一直在说msg.target.dispatchMessage()方法(其实也就是msg绑定的handler的disspatchMessage()),那么这个方法到底是个啥东西呢?看源码:

public void dispatchMessage(Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
第2行,看到那个callback了没有?是不是!很!熟!悉!我刚刚让你注意了有木有?还记得它是啥么?它其是个Runnable。
这一整段的代码就是告诉你:要分发这个代码,先看这个包裹的callback有没有绑定上啥东西(一个Runable);如果有,直接交由这个Runable进行处理,如果没有,查看当前handler的mCallback有没有绑定上啥东西;如果有,交由这个mCallback处理,如果没有,那么就调用当前handler的handleMessage()方法处理。

你可能要问,这个mCallback到底是个啥玩意?其实就是这个玩意:

public interface Callback {
public boolean handleMessage(Message msg);
}
讲白了就是这个handler"工人"的上一个"工人","包裹"Message被丢来丢去地传递,可能它真正要处理它的工人并不是当前给它盖章的工人,而是上一个…不过,一般情况下我们并不会遇到这种情况。
现在要说的是handleMessage()方法,它的源码:

public void handleMessage(Message msg) {
//填入你自己的方法
}
卧槽?源码里是空的?
当然了,这个handleMessage()方法就是这个"包裹"Message被丢来丢去后最后要被进行处理的地方(被丢给Runable处理的不算),你当然要重写这个方法,给出你自己的处理方法了。

比如,把我上面写的那个解包提取信息的语句写进去…

一句话总结:

Handler是整个工作流水线的"传递者"和Message"包裹"的"分发者"(比如甩给Runnable)、“处理者”,是流水线的"工人们"。

好像,都写完了?

其实并没有,似乎,我们还有个Handler的post()方法没说。这个方法,就是线程把"包裹"丢给Handler,让Handler送其入列的方法。

4.Handler.post()

这个post到底有哪些方法呢?

public final boolean sendMessage(Message msg);//丢包法1,不吵吵直接丢
public final boolean sendEmptyMessageDelayed(int what, long delayMillis);//丢包法2,延迟一段时间后丢一个空包,只含一个空的数据what=0,告诉Looper:“嘿哥们你还没空,继续转~”
public final boolean sendMessageDelayed(Message msg, long delayMillis);//丢包法3,延迟一段时间后丢
public boolean sendMessageAtTime(Message msg, long uptimeMillis);//丢包法4,在指定的时间丢
下面我会给出他们的源码,当然你要没啥兴趣读,也没关系,你可以直接看我给的结论,那就是
丢包法1中其实最后调用了丢包法2,2中调3,3中调4,4中调用了enqueueMessage(queue, msg, uptimeMillis),终于把这个包丢进了流水线"履带"MessageQueue。在enqueueMessage()方法中,会进行msg.target=this的操作,也就是我们刚刚说的"盖章"。

源码:

public final boolean sendMessage(Message msg){
return sendMessageDelayed(msg, 0);
}
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis){
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
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);
}
这就是线程把信息"包裹"Message靠"工人"Message送入列的方法了。

别急,别忘了,handler其实还有个"分发者"的身份,把信息丢给Runnable处理的方法把?那么是怎么做的呢?其实一个Handler还有这个方法:

public final boolean post(Runnable r)
public final boolean postDelayed(Runnable r, long delayMillis)
public final boolean sendMessage(Message msg)
public final boolean sendEmptyMessage(int what)
是的,其实也就是把mCallback的值变成这个Runnable而已罢了…
你可能要问,我一个好好的Runnable,怎么到了你Handler就变成一个Message了呢?

其实吧,Hander内部提供了getPostMessage方法把Runnable对象转化为Message:

private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
好的,到了这里,我们把这个利用Looper、MessageQueue、Handler进行消息传递的流程总结一下:

一个线程,首先进行Looper.prepare(),就可以创建出一个绑定了MessageQueue"履带"的[唯一]的Looper+MessageQueue"流水线";然后线程可以实例化出几个Handler"工人"。线程有要处理的信息"包裹"Message了,丢给对应的Handler"工人";这个工人判断一下这个到底是个Runnable还是Message,如果是Runnable就包装成一个Message,再"盖章",然后丢向流水线,让它排队;不过不是,"盖完章"不多bb直接甩进流水线。一个Message"包裹"到了流水线的队首,就要被拿出来,根据刚刚盖的章,各找各妈各回各家,该上哪上哪,然后进行msg.target.diapatchMessage()->msg.target.handleMessage()拆包处理。

作者:身披白袍
来源:CSDN
原文:https://blog.csdn.net/shenpibaipao/article/details/70214927
版权声明:本文为博主原创文章,转载请附上博文链接!

展开阅读全文

没有更多推荐了,返回首页