0
点赞
收藏
分享

微信扫一扫

研究Handler消息机制的成果


最近花了几天研究了一下Android的​​Handler​​机制。也阅读了网上很多的资料,于是在此写篇文章整理一下,想形成自己的体系。有一些别人总结的很好的干货我这边就不再总结了(但是会给出推荐链接),我只是把自己认为特别重要的写在这里。

小总结

Handler理解
1.功能:
①整个app运行的基础(Looper死循环)
②线程直接切换传数据的问题(子线程无法更新UI)
③发送延时任务

2.角色

  • Message
    封装了消息。主要有what、obj。传递大量数据时可以把数据装在Bundle里面通过obj传输。获取Message一般通过obtain()获取。Message内部维护了一个任务池,如果我们通过new对象的话会增大不必要的开销。
  • Handler
    Handler中主要就是发送任务send、post开头的函数和处理任务handleMesaage。这些post、send开头的发送消息的函数,但最终都会调用到Handler的enqueueMessage中,这个方法又会调用到messageQueue中的enqueueMessage中
  • MessageQueue
    messgaeQueue是一个单链表构成的优先级队列,链表这种数据结构方便根据执行时间when插入enqueueMessage就是类似单链表的插入for死循环
    next(加了synchronized关键字)函数就是从这个队列中取数据,通过一个for死循环,一直从对头取出数据
  • Looper(threadlocal)
    每个线程最多只有一个looper对象,通过threadLocal来保证的
    prepare创建loop对象创建的时候,如果当前线程已经有looper了就抛异常,没有的话就创建一个looper对象,同时会创建这个looper对应的messagequeue
    for死循环,如果有数据就通过next方法取出来,并且交给Handler的dispatchMessage处理,dispatchMessage又会调用handleMessage回调到我们写的处理逻辑中
  • Threadlocal
    threadlocalmap是Threadlocal的一个内部类,可以简单理解为维护了一个table。Thread基类中有一个ThreadlocalMap的对象叫threadlocals(名字不重要)所以每一个线程中都维护了一个独属于自己的map,当threadlocal对象进行set或者get的时候,首先获取当前执行这句话的线程,然后得到这个线程独有的那个map,并且将threadlocal对象作为key进行存取数据。

3.handler进行线程间通信的原理:

比如子线程想发送消息到主线程,则应该调用主线程的handler的send或者post方法。然后将消息存到主线程的MessageQueue中。当消息的when符合要求时,会被主线程的同一handler取出(因为发送消息的时候,message和handler实现了绑定。所以还是之前主线程发送此消息的handler去取消息),再回调handlerMessage方法或者run方法。

send开头的方法,参数是Message。post开头的方法,参数是Runnable,但后面还是会把这个Runnable封装成一个Message。如图

研究Handler消息机制的成果_handler机制

send开头的方法,最终的回调才会到hander.handleMessage(Message message)方法中供我们处理业务逻辑。post开头的方法一般都是在传进去的Runnable中的run方法中执行主线程中ui的更新相关的的代码。提交一次网络异步请求,我们会根据回调的结果切换到主线程中,这个时候就需要调用handler.postMessage()这种方式切换回主线程,然后在run方法中执行相关的主线程ui操作的代码。如果是处理message消息的,就需要在异步网络请求返回的代码,调用handler.sendMessage()方法,然后在handler的handleMessage方法中执行ui的代码。两种方案都可以。

如图,在取消息的时候,会调用queue的next方法取出消息,然后调用Handler的dispatchMessage方法去处理消息。这个方法会先判断,是否有Runnable,如果是post开头的方法提交的消息,则有Runnable,则会调用红框里的handleCallback方法去执行Runnable的run方法(传过来的Runnable就是msg.callback,handleCallback就是执行run方法)。如果是send开头的方法提交的消息,则没有Runnable,则不会调用handleCallback方法。而是执行handleMessage方法

研究Handler消息机制的成果_队列_02


mCallback是什么?mCallback 是 Hanlder.Callback 类型的。在 Handler 的构造函数中我们可以传递 Hanlder.Callback 类型的对象,该对象需要实现 handleMessage 方法,假如我们在构造函数中传递了该 Callback 对象,那么我们就会让 Callback 的 handleMessage 方法来解决 Message。

也就是说,Handler 提供了三种途径解决 Message ,而且解决有前后优先级之分:首先尝试让 post() 中传递的 Runnable 的 run 方法执行,其次尝试让 Handler 构造函数中传入的 Callback 的 handleMessage 方法解决,最后才是让 Handler 自身的 handleMessage 方法解决Message。但是实际开发中,一般就选一种方法。

一.Handler机制是干啥的

我认为可以从两方面来理解

1 从侠义上来说,​​handler​​(Android消息机制的主要成员)是用来解决子线程无法访问更改UI的问题的
2 从广义上来说,所有的代码都是在​​handler​​​基础上运行的,​​handler​​​是Android的app运行的整个框架。Android系统框架内,​​Activity​​生命周期的通知等功能也是通过Handler消息机制来实现的

1好理解,因为这是​​handler​​与我们距离最近的使用。但是为什么我认为有2这么一点呢?下面我来解释下:

关于 2 的解释

我们知道,安卓应用程序作为一个控制类程序,跟Java程序类似,都是有一个入口的,而这个入口就是​​ActivityThread​​​,​​ActiviyThread​​​也有一个​​main​​​方法,这个​​main​​方法其实是安卓应用程序真正的入口。所以

①我们进入ActivityThread的main方法看一下

public static void main(String[] args) {
...
Looper.prepareMainLooper();
...
Looper.loop();

throw new RuntimeException("Main thread loop unexpectedly exited");
}

省略无关代码,我们不难发现,在​​main​​​函数这里进行了两个关于​​Looper​​​的至关重要的操作,第一个是
​​​Looper.prepareMainLooper();​

②我们追踪进入

public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}

发现这里是对​​sMainLooper​​​ 进行赋值,就是给主线程的​​looper​​​赋值(​​looper​​​是​​handler​​​机制里面一个非常重要的角色)也就是创建了主线程的​​Looper​

③然后Looper.loop(),我们追踪进入loop方法

public static void loop() {
...

for (;;) {
...
}
}

发现在​​loop​​方法里面,除了开头几行的一些赋值等等,后面就会进入一个死循环,我们整个app的启动,可以说都是在这个死循环里面进行的。可以这么理解,就是可以把我们的app比喻成人,这个死循环比喻成自然环境。只有自然环境能够一直运行下去,正常运转,那么人才能有生活的物资,才不至于灭亡。所以app就依赖这个死循环才能在你不退出的情况下一直运行。所以我说2 不过分吧?

二.Handler机制的几个角色及其相互关系

上面我们解决了两个问题,其一是​​Handler​​​机制是啥(主线程中更新UI),其二是​​Handler​​​机制的重要性(Android的​​framework​​​层的极端重要的部分)。那么下面就来详细地介绍下​​Handler​​​机制涉及的几个角色​​Handler,Looper,MessageQueue,Message​​及其相互关系

1.基本介绍

  • ​Handler​​: 发送消息和处理消息
  • ​Looper​​​: 从MessageQueue中获取​​Message​​,并将其交给 ​​Handler​​的​​dispatchMessage(Message)​​ 方法
  • ​MessageQueue​​​: 消息队列,存放​​Handler​​发送过来的消息
  • ​Message​​: 消息的载体

2.他们之间的关系

他们之间的关系,我从两个方面来分析

①其一,从基本功能的角度

首先,看这么一张图

研究Handler消息机制的成果_死循环_03


(1)​​MessageQueue​​​就像履带。它上面装载着一个个​​Message​​​ (2)​​Thread​​就像传送带的动力,对应到程序就是我们通信都是基于线程而来的。

(3)传送带的滚动需要一个开关给电机通电,那么就相当于我们的​​Looper​​的​​loop​​函数,而这个​​loop​​里面的​​for​​循环就会带着传送带不断的滚动,去轮询​​messageQueue​​上面装载的每一个​​Message​

(4)​​Message​​就是 我们的货物了。

②其二,从设计思想的角度

研究Handler消息机制的成果_死循环_04


其实构成了线程模型中的经典问题 生产者-消费者模型

生产者-消费者模型:生产者和消费者在同一时间段内共用同一个存储空间,生产者往存储空间中添加数据,消费者从存储空间中取走数据。

好处:能够保证数据生产消费的顺序(通过​​MessageQueue​​​,先进先出) 不管是生产者(子线程)还是消费者(主线程)都只依赖缓冲区(​​handler​​),生产者消费者之间不会相互持有,使他们之间没有任何耦合

③再总结下,就是下面这张图

研究Handler消息机制的成果_队列_05

三.关于Handler机制的一些必要知识点

1.​​Handler​​有哪些发送消息的方法

sendMessage(Message msg)
sendMessageDelayed(Message msg, long uptimeMillis)
post(Runnable r)
postDelayed(Runnable r, long uptimeMillis)
sendMessageAtTime(Message msg,long when)

//下面这些不怎么重要
sendEmptyMessage(int what)
sendEmptyMessageDelayed(int what, long uptimeMillis)
sendEmptyMessageAtTime(int what, long when)

2.从​​Handler​​发送消息到消息入队,其内部是如何实现的?

①首先在子线程调用​​sendMessage​​方法发送消息

public final boolean sendMessage(@NonNull Message msg) {
return sendMessageDelayed(msg, 0);
}

②追踪​​sendMessageDelayed​

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

③发现调用了​​sendMessageAtTime​​,继续追踪

public boolean sendMessageAtTime(@NonNull 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);
}

④发现调用了​​Handler​​​的​​enqueueMessage​​,继续追踪

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,long uptimeMillis) {
//将当前的Handler赋值给Message的target属性
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();

if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}

⑤发现最终调用了​​MessageQueue​​​的​​enqueueMessage​​方法,将消息入队

2.​​MessageQueue​​的必要知识

(1)基本介绍

​MessageQueue​​维护了一个优先级队列(以时间为顺序进行排列),而且是以链表的形式实现的。其他线程若想发送消息到此线程,则就要把消息发送到此线程的MessageQueue​。

(2)排序依据when的介绍

​MessageQueue​​​不仅满足先进先出,而且还有顺序。其顺序就是根据每一个消息的​​when​​​属性来排序的。还记得一开始列出来的提交信息的那些方法嘛。不管用哪个方法去提交消息,最终一般都会走到​​sendMessageDelayed​​​方法,而这个方法就可以设置​​when​​,我们进去看一下

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

可以看到这个​​when​​​值是 ​​SystemClock.uptimeMillis()​​​ 与 ​​delayMillis​​​ 之和。代表从系统启动开始再加上你给它设置的延迟时间数。很好理解的。​​when​​​就是用于表示 ​​Message​​ 期望被分发的时间,就是啥时候执行。

(3)在哪根据when来排序的

boolean enqueueMessage(Message msg, long when) {
...
synchronized (this) {
...
msg.when = when;
...
if (p == null || when == 0 || when < p.when) {
...
} else {
...
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
//***************************
//***************************
//看这里看这里
//***************************
//***************************
break;
}
if (needWake && p.isAsynchronous()) {
...
}
}
...
}

...
}
...
}

我们把相对无关的代码都省去了。在代码标注的位置,我们可以看到这个​​for​​​死循环跳出的条件有两个。其一是一直遍历走到​​MessageQueue​​​的队尾,才退出循环,其二是发现有符合要求的地方可以插入​​message​​​了。这个符合要求是啥意思。比如待插入的​​message​​​的​​when​​​是2,而​​MessageQueue​​​队列中的​​message​​​分别是​​1,3,4,5.​​​那么待插入的​​message​​​就会插入到​​when​​​为​​1​​​和​​3​​​的​​message​​​之间,新队列的​​when​​​就变成了​​1,2,3,4,5​​。很好理解吧。

特殊情况,如果​​when​​相同,则按代码执行时间先后顺序插入。

(4)​​MessageQueue​​​核心算法​​enqueueMessage​​的完整分析

上面我们简简单单地分析了下​​enqueueMessage​​,下面我们就较为完整地分析下这个算法

boolean enqueueMessage(Message msg, long when) {
// Hanlder为空则抛异常
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
//当前消息如果已经已经被执行则抛异常
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}

// 对MessageQueue进行加锁
synchronized (this) {
// 判断目标thread是否已经死亡
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
// 标记Message正在被执行,以及需要被执行的时间
msg.markInUse();
msg.when = when;
// p是MessageQueue的链表头
Message p = mMessages;
// 判断是否需要唤醒MessageQueue
boolean needWake;
// 如果有新的队头,同时MessageQueue处于阻塞状态则需要唤醒队列
if (p == null || when == 0 || when < p.when) {
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
//...
// 根据时间找到插入的位置
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
//...
}
msg.next = p;
prev.next = msg;
}

// 如果需要则唤醒队列
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}

需要注意的是:当新插入的​​Message​​​在链表头时,如果​​messageQueue​​​是空的或者正在等待下个延迟消息,换句话说就是处于阻塞状态的时候,则需要唤醒​​MessageQueue​

3.​​Looper​​的必要知识

(1)简单介绍

​Looper​​​是个什么角色呢?从一个简单的例子来说吧,我们在子线程创建​​Handler​​​实例的时候,必须要先创建一个​​Lopper​​​,并开启循环读取消息。在主线程中我们​​ActivityThread​​​已经帮我们创建了(在本文最开始的地方就有介绍),所以我们不需要再次创建。但是在子线程中新建​​Handler​​的时候就需要创建咯!比如这样

public static class MyThread extends Thread {
public Handler mHandler;

public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(@NonNull Message msg) {
//处理接收的消息
}
};
Looper.loop();
}
}

一个线程对应一个​​Looper​​​,但是可以有多个​​Handler​​​。​​Looper​​​是消息轮询的基础。比如本文最开始的传送带图片,没有​​Looper​​​那么整个​​Handler​​​机制就无法运转,而且​​Looper​​还不能多,否则传送带就会崩掉,违背单线操作的概念,造成不合适的并发操作。

  • 值得注意的是,我们看下​​Looper​​​的构造方法,发现我们在创建​​Looper​​​的时候,也会捎带着创建​​MessageQueue​

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

(2)​​Looper​​的工作介绍(消息是怎么被读取的)

刚刚说了用​​Loop​​​来轮询消息,那么具体是怎么个询法呢?没错,就是​​Looper​​​的​​loop​​​方法。我们要想搞懂​​Looper​​​的消息轮询机制,就要从​​loop​​​方法开始。(本文开始也有一丢丢对​​loop​​方法的分析,不过这里才是更全面的)

public static void loop() {
...
for (;;) {
Message msg = queue.next(); // might block
...
}
}

我们可以看到,在开启死循环后,获取每一个消息的方式是调用​​queue​​​的​​next​​​方法,而且值得注意的是,在源码的注释中给了两个单词:​​might block​​,即有可能是阻塞的。好,下面追踪​​queue​​​的​​next​​方法看看

Message next() {
// Return here if the message loop has already quit and been disposed.
// 源码中的注释表示:如果looper已经退出了,这里就返回null
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
//...
// 定义阻塞时间赋值为0
int nextPollTimeoutMillis = 0;
//死循环
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
// 阻塞对应时间 这个方法最终会调用到linux的epoll机制
nativePollOnce(ptr, nextPollTimeoutMillis);
// 对MessageQueue进行加锁,保证线程安全
synchronized (this) {
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//...
if (msg != null) {
if (now < msg.when) {
// 将要获取的下一个消息(也就是队头)还没开始,计算等待时间
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 获得消息且现在要执行,标记MessageQueue为非阻塞
mBlocked = false;
// 链表操作
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
msg.markInUse();
return msg;
}
} else {
// 没有消息,进入阻塞状态
nextPollTimeoutMillis = -1;
}
//退出
if (mQuitting) {
dispose();
return null;
}

}
}

总结下:先看下​​Looper​​​是否已经退出(怎么退出后面讲),然后进入死循环,首先判断是否要进行阻塞(判断是依据是上一次循环得到的某个值:​​nextPollTimeoutMillis​​​ ),阻塞最终会调用到​​linux​​​的​​epoll​​​(​​pipe/epoll​​)机制,阻塞结束后会进入中,得到队首的​​Message​​​对象,然后进入​​if​​​判断,如果有消息,即把队头消息取出,然后进行判断,决定是当下立即返回还是​​delay​​一下再返回。如果没有消息,则无限期进入阻塞状态,直到有消息进入。

  • ​nextPollTimeoutMillis​​​表示阻塞的时间,其中,​​-1​​​表示无限时间,直到有消息传入为止,​​0​​表示不阻塞。
(3)​​Looper​​的退出

刚刚说了​​Looper​​​的工作原理,那么工作完了怎么退出呢?
​​​Looper​​​提供了​​quit​​​和​​quitSafely​​​两个方法来退出一个​​Looper​​​,二者的区别是: ​​quit​​​会直接退出​​Looper​​​,而​​quitSafely​​只是设定一个标记,然后把消息队列中的已有消息处理完毕后才安全退出。这个好理解。

public void quit() {
mQueue.quit(false);
}

public void quitSafely() {
mQueue.quit(true);
}

其实两个方法最终都是调用​​mQueue​​​的​​quit​​方法。让我们来分析一下这个方法

// 最终都是调用到了这个方法
void quit(boolean safe) {
// 如果!mQuitAllowed为true即不能退出则抛出异常。
if (!mQuitAllowed) {
throw new IllegalStateException("Main thread not allowed to quit.");
}

synchronized (this) {
if (mQuitting) {
return;
}
mQuitting = true;
// 执行不同的退出逻辑方法
if (safe) {
removeAllFutureMessagesLocked();
} else {
removeAllMessagesLocked();
}
// 唤醒MessageQueue
nativeWake(mPtr);
}
}

这个方法首先根据​​mQuitAllowed​​参数判断是否能退出,如果可以退出则进入中执行退出逻辑。如果​​mQuitting==true​​​,那么这里会直接​​return​​​掉,而且​​mQuitting​​​这个变量只有在这里被执行了赋值,所以一旦​​looper​​​退出,则无法再次运行了。也就是说​​Looper​​​只能创建一次,一旦​​quit​​之后此线程就无法再复活了。

  • 而且同一个线程,只能创建一个​​Looper​​​。因为​​Looper​​​的创建是通过​​Looper.prepare​​​方法实现的,而在​​prepare​​​方法中就判断了,当前线程是否存在​​Looper​​​对象,如果已经存在则抛出异常。所以同一个线程,只能创建一个​​Looper​​,多次创建会报错。

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

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

之后执行不同的退出逻辑,然后唤醒​​MessageQueue​​​,之后​​MessageQueue​​​的​​next​​​方法会退出,​​Looper​​​的​​loop​​方法也会跟着退出,那么线程也就停止了。

(4)关于Looper的几个骚操作
①如何获取当前线程的looper

Looper.myLooper()

内部原理就是通过 ​​ThreadLocal<Looper>​​​ 的​​sThreadLocal​​​对象的​​get​​​方法来获取 ​​Looper​​​​sThreadLocal​​对象在整个app中只有一个,因为它是由​​static final​​修饰的

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

②如果在任意线程获取主线程的 ​​Looper​

Looper.getMainLooper()

③如何判断当前线程是不是主线程

方法一:

Looper.myLooper() == Looper.getMainLooper()

方法二:

Looper.getMainLooper().getThread() == Thread.currentThread()

方法三: 方法二的简化版

Looper.getMainLooper().isCurrentThread()

4.关于​​ThreadLocal​

​​这里我发现了一个讲的比较清晰的up主的视频,推荐给大家看​​​​大佬文章1​​​​大佬文章2​​​​ThreadLocal​​是​​Java​​中一个用于线程内部存储数据的工具类,并不是Android所特有的。
值得注意的是,在不同的线程中访问同一个​​threadLocal​​对象,但是它们获取的值却是不一样的,这就是​​ThreadLocal​​的奇妙之处。那么问题来了,原理是什么?
我们进入其​​set​​方法看一下

public void set(T value) {
//得到当前线程
Thread t = Thread.currentThread();
//得到当前线程的ThreadLocalMap
ThreadLocalMap map = getMap(t);
if (map != null)
//将当前的ThreadLocal变量作为key,传进来的泛型作为value进行存储
map.set(this, value);
else
//没有map则创建,并且将当前的ThreadLocal变量作为key,传进来的泛型作为value进行存储
createMap(t, value);
}

我们可以发现,每一个线程都维护着一个​​Map​​​集合,其名字为​​ThreadLocalMap​​​ 。我们可以通过
​​​getMap​​​方法来获得。此集合是以键值对的方式来存储数据的。键为​​ThreadLocal​​​对象,也就是调用​​set​​​方法的那个​​ThreadLocal​​​对象。这个值就是​​set()​​​里面传入的参数了。每一个线程都有一个​​ThreadLocalMap​​ 。虽然键相同,但是取出来的值是不相同的。

  • 我们可以这样类比,就是​​ThreadLocal​​​是一个人,不同的线程即不同的​​Thread​​​和不同的​​ThreadLocalMap​​​,是不同的环境。相同的一个人(​​key​​​),在学校(学校的​​Thread​​​,里面有学校特有的​​ThreadLocalMap​​​)里面他的身份(​​value​​​)就是学生,在家庭里面他的身份(​​value​​​)可能是父亲,在社会上他的身份(​​value​​)就是中国公民。即同一个人在不同的环境下有不同的身份,同一个ThreadLocal在不同的线程下可能映射着不同的值,就是这个道理。
  • 前面在​​Looper​​​中提到的的​​ThreadLocal<Looper>​​​ 的​​sThreadLocal​​​,它的​​get​​​方法就是返回每个线程所特有的​​Looper​​​。其也是从​​ThreadLocalMap​​​中取值,虽然整个App中​​sThreadLocal​​​是唯一的,但是每个线程​​ThreadLocalMap​​​是不同的,所以不同的线程能得到不同的​​Looper​​。

5.同步屏障

在​​Handler​​机制中,有三种消息类型:

①同步消息。也就是普通的消息。
②异步消息。通过​​​setAsynchronous(true)​​​设置的消息。
③同步屏障消息。通过​​​postSyncBarrier​​​方法添加的消息,特点是​​target​​​为空,也就是没有对应的​​handler​​。

同步屏障是什么意思?可以这么理解,比如救护车,在正常情况下,没有拉病人的情况下是可以和其他车一样在公路上正常行驶的,但是如果拉上病人就不一样了。不仅需要加急,不用等红灯,而且前面还有一辆警车帮忙带路。其实这个救护车就可以理解成异步消息,警车就可以理解成同步屏障消息。在后方有承载着病人的救护车的时候,其他车辆都会被警车“屏障”,救护车优先通行。这就是同步屏障机制存在的意义。当有要紧的异步消息需要立马被执行的时候,同步屏障消息就来了。具体过程我们可以看一下源码

Message next() {
...
for (;;) {
...
synchronized (this) {
Message msg = mMessages;
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
...
}
...
}
}

我们可以看到,在​​next​​​方法,每次取出消息的时候,会先进行一个​​if​​​判断,即​​if (msg != null && msg.target == null)​​​ ,就是如果某个​​msg​​​不为​​null​​​而且它的​​target​​​为​​null​​​,那么它就是一个​​同步屏障消息​​​,那我们就要进入这个​​if​​​。此时会往后逐个遍历​​MessageQueue​​中的消息,当发现下一个异步消息的时候,就退出循环,得到这个异步消息然后加急进行处理。

  • 值得注意的是:同步屏障消息只是一个标志,就像我一开始说的警车一样,它上面并没有承载病人,真正承载病人的是后面的救护车,也就是有重要信息的异步消息。
  • 而且并不是所有的异步消息都是需要加急处理的,正常情况下异步消息和同步消息都是可以根据​​when​​来决定处理顺序的,当然只是正常情况下,毕竟异步消息设计出来就是要被加急处理的,就像救护车的职能就是紧急救援病人的。

用张图表示就是这样

研究Handler消息机制的成果_死循环_06

那么有什么加急消息呢?比如UI的更新,这就是一个加急消息。相信不用我解释大家都能理解咯。

6.​​HandlerThread​

​HandlerThread​​​是啥?看名字就知道,其实它继承了​​Thread​​​,本质上也是一个​​Thread​​​。但是它的​​run​​​函数中已经帮我们完成了​​Looper​​的创建,不信你看

public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}

所以它第一个好处就是方便我们使用,也就是我们使用子线程创建​​Handler​​​的时候就不用再显示地创建​​Looper​​​了。
但是,光知道这个还是不够的,万一在​​​HandlerThread​​​还没有创建好​​Looper​​​的时候就调用​​getLooper​​​方法怎么办呢?谷歌工程师早就想到了这个线程安全问题,所以我们看​​getLooper​​方法

public Looper getLooper() {
...
// If the thread has been started, wait until the looper has been created.
//意思是如果线程已经启动了,那么等looper创建了之后再返回
synchronized (this) {
while (isAlive() && mLooper == null) {
try {
wait();
} catch (InterruptedException e) {
}
}
}
return mLooper;
}

从这个方法可以看出,人家​​getLooper​​​方法并不是无脑给你返回​​looper​​​,因为有可能还没有创建好,所以先​​wait​​​,等前面​​run​​​方法创建了​​looper​​​之后​​notifyAll​​​,这里​​getLooper​​​才给你返回​​looper​​。这就是第二个好处,线程安全

7.​​IntentService​

首先看源码

public abstract class IntentService extends Service {
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}

@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}
@Override
public void onCreate() {
super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();

mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}

@Override
public void onStart(@Nullable Intent intent, int startId) {
Message msg = mServiceHandler.obtainMessage();
msg.arg1 = startId;
msg.obj = intent;
mServiceHandler.sendMessage(msg);
}

首先它是一个服务,可以借助子线程​​HandlerThread​​​ ,将消息一个接一个地执行,而且消息队列的特点是前面的消息执行完后面的消息才能执行,所以就可以让子任务在子线程中有条不紊地一个接一个地执行。而且消息执行完毕之后服务自动关闭​​stopSelf​​​。实现了内存释放。
我们的耗时任务的逻辑就是重写​​​onHandleIntent​​​方法,在执行耗时任务的时候就会回调我们重写的​​onHandleIntent​​​方法。
简单来说,这就是一个可以在子线程进行耗时任务,并且在任务有序执行后自动停止的Service

8.​​Handler​​内存泄漏

​​小题大做 | 内存泄漏简单问,你能答对吗​​(强烈建议大家学习此文)

参考文章


​​​你真的懂Handler吗?Handler问答​​​​"一篇就够"系列: Handler消息机制完全解析​​

举报

相关推荐

0 条评论