Osheep

时光不回头,当下最重要。

自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇

前言:接上一篇intent讲,或许有人会问,为什么是这个顺序来讲呢?我愿意,要你管!

开个玩笑,其实,是有原因的,其实,我是从理解难易度分顺序的,首先是广播,因为广播,我们通过名字就能不言而知,intent用于传递主线程的东西(一般都是主线程,不涉及多线程),但是handler涉及消息队列,多线程。相对难度而言,handler的难度要大一点,当然,你都理解了,就会发现,他们都很简单。

介绍handler之前,我们先聊点和handler相关的一些东西

Handler消息机制

就应用程序而言,Android系统中Java的应用程序和其他系统上相同,都是靠消息驱动来工作的,它们大致的工作原理如下:

有一个消息队列,可以往这个消息队列中投递消息。

有一个消息循环,不断从消息队列中获取消息,然后处理。

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

事件源把需要处理的消息(message)加入到消息队列中,一般是放到队列的队尾,或者安装优先级来放置消息。

处理线程不断从消息队列中提取消息处理(叫handler的家伙负责处理)。

在Android系统中,这个消息机制是有Looper(包含一个MessageQueue)和Handler来完成的。(可以称之为消息三剑客,名字我取的)

Looper类,用于封装消息循环,并且包含一个消息队列MessageQueue。

Handler类,封装了消息的投递和消息的处理。

看图,再看解释,handler消息机制的模型现在是清楚了吧,ok,下面再细讲局部细节

事件源这东西,这里就不讲了,你可以理解为一个按钮,点了这个按钮就开始了后面的事情

Message(消息)

消息机制,最基础的应该就是这个载体——消息。Android上使用Message类表示一个消息,这个Message可以通过Handler传递,通过Looper进行分发,通过MessageQueue进行排列组队,最后通过Handler进行处理

Message的基本成员,我们在使用中经常会用到:

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

后两个,这里不讲,如果想知道,自行百度。

消息机制中重要的成员:

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

并且,Message内部保存了一个缓存的消息池,我们可以用obtain从缓存池获得一个消息,Message使用完后系统会调用recycle回收。

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

有些同学会想知道缓存和回收的事情,(其实,这个东西,系统自己帮我们做了,不过还是了解一下吧)

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

通过obtain获取缓存池链表的第一个Message返回,将第一个Message的next的Message作为第一个。其实就是查看缓存池是否有对象,有就返回缓存的对象,没有就创建新对象。

再看看回收是怎么样的吧

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

回收Message的时候,如果缓存池没满的话,将Message的成员清空之后放入缓存池。

注:由于Message具有缓存能力,所以我们在创建Message对象的时候最好使用obtain。

ok,再来介绍下一位成员 Looper

Looper

消息Message通过Handler传递给Looper,Looper通过封装一个消息队列MessageQueue来进行消息存放和分发。

Looper类主要是通过prepare方法进行准备和loop方法进行循环获取消息队列的消息进行分发处理。

prepare:

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

通过prepare方法,为当前调用线程生成一个Looper对象,这个对象保存ThreadLocal中。通过这种方式使Looper和调用线程(最终的处理线程)进行关联,保证每个调用线程只会有一个Looper对象

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

Looper在构成方法中,新建一个消息队列和获取当前线程。构成方法的参数quitAllowed,是给MessageQueue使用的,用来标记这个消息队列(同时也是这个Looper)能否终止运行。

loop:


《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

loop将进行无限循环,在消息队列MessageQueue的next进行退出,next方法可能堵塞(休眠),MessageQueue的休眠和唤醒机制是通过Native层完成,是一种epoll机制。

好了,looper就介绍到这里,接着聊MessageQueue

MessageQueue

MessageQueue类是Looper类分装消息列表的类,包含一个消息列表,这个列表通过Looper进行消息分发。

Message不是直接添加到MessageQueue的,而是通过Handler将它与Looper关联的。

具体的过程是这样的:

1添加消息(enqueueMessage)

向队列中添加消息,是通过Handler的sendMessage等方法,然后执行Handler的enqueueMessage方法调用MessageQueue的enqueueMessage。enqueueMessage方法会更加Message的when添加到合适的队列的相应的位置。

2获取消息(next)

loop每次从MessageQueue取出一个Message(通过next方法),分发,在Handler中处理消息,之后进行回收。

ok,前面铺垫了这么多,重要要开始说今天的主角了:handler

Handler

还是先说点题外话:handler是什么?为什么会有这玩意?怎么用等等等等,,,

handler是什么?为什么会出现这东西?

任何一个东西,他的存在,都是有其意义的,说到handler,不得不讲一个小故事了:

当app启动时,Android首先会开启一个主线程 (也就是UI线程) , 主线程为管理界面中的UI控件, 进行事件分发, 比如说, 你要是点击一个 Button ,Android会分发事件到Button上,来响应你的操作。  如果此时需要一个耗时的操作,例如: 联网读取数据, 你不能把这些操作放在主线程中,如果你放在主线程中的话,界面会出现假死现象, 如果5秒钟还没有完成的话,会收到Android系统的一个错误提示  “强制关闭”(传说中的ANR)。  更新UI只能在主线程中更新,子线程中操作是危险的。 这个时候,Handler就出现了。,来解决这个复杂的问题 ,由于Handler运行在主线程中(UI线程中),  它与子线程可以通过Message对象来传递数据, 这个时候,Handler就承担着接受子线程传过来的(子线程用sedMessage()方法传弟)Message对象,(里面包含数据)  , 把这些消息放入主线程队列中,配合主线程进行更新UI。

补充一个常识:耗时操作会产生ANR。至于为什么?记住就行,反正你也不写底层系统(至少现在)。

ok,现在再来说说handler的几个作用,或者叫功能

handler都干了什么事?(说白了,handler的功能)

构造:

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

通过构造方法设置Looper,Callback,async。其中async是设置Message的setAsynchronous。

Looper:

Handler必须需要使用Looper,不过使用带有Looper的构成方法的时候,是使用当前线程的Looper。

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

Callback:

这里消息时候的回调,在处理消息的时候使用

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

所以,在创建Handler的时候,为了实现处理消息,除了可以使用自己实现Handler的handleMessage方法的形式,还可以使用一个Callback来完成。

消息投递:

消息的投递有几种方式,sendMessage、sendEmptyMessage、sendEmptyMessageDelayed…、post等,最终都是调用enqueueMessage。只是不同的方法,生成不同形式的Message。例如post方法,就是生成(obtain)一个Message,然后设置Message的callback。

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

消息处理:

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

在Looper的loop调用Handler的dispatchMessage进行消息的处理。如果回调mCallback存在的话,callback处理,如果不存在的话使用handleMessage处理。

ok,handler的理论,就介绍这么多,还有一些深入的东西,这里就不介绍了。下面我们讲讲实战应用的东西。

handler应用

实在不知道怎么归纳这部分,就叫应用吧(仿大学的取名方式,xx应用)

既然是应用,我们就从创建开始,一步步去应用这个东西:

handler创建

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

这里,展示了在主线程和子线程创建handler的场景。然后发现,主线程创建没问题,但子线程就崩了,原因是这个handler没有looper.prepare.

这是为什么呢?别急,现在给你解释和解决:

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

我们走进handler的构造方法,看到了吧,这个异常吗,是因为这个mLooper为null了,然后我们再往里面走看看这个Looper.myLooper()怎么获取looper的:

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

是不是,觉得太简单了吧,就是一个get方法,好吧,那就找set方法吧,看从哪里set的。

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

ok,找到了,就是这个方法,他设置了looper。那么现在知道问题了,也找到答案了,解决方案自然就出来了,很简单,调用这个方法prepare

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

那么问题来了,为什么主线程的没有抛异常呢?难道是主线程自己设置了这个looper吗?,看源码吧:我们继续之前往里走

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

ok,看到这里,谜底揭晓。(先说一下,一个app启动的入口,就是这个activitiyThread的main方法,这里其实和java的main方法是一个意思:入口)。所以,现在知道为什么,主线程不需要我们写那句话了吧,就是因为,在app启动的时候(主线程中),就已经给了我们looper了。

handler 发送消息(对应前面的投递消息)

这里就要用到我们的载体Message。new出一个Message对象,然后可以使用setData()方法或arg参数等方式为消息携带一些数据,再借助Handler将消息发送出去就可以了:

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

其实,看这一部分代码,相信,都是看得懂的。不过,我们要知其然还要知其所以然:老样子,一步步往下走,才能知道谜底。

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

ok,终于走到终点了,那么现在来稍微解释一下:

sendMessageAtTime()方法接收两个参数,其中msg参数就是我们发送的Message对象,而uptimeMillis参数则表示发送消息的时间,它的值等于自系统开机到当前时间的毫秒数再加上延迟时间,如果你调用的不是sendMessageDelayed()方法,延迟时间就为0,然后将这两个参数都传递到MessageQueue的enqueueMessage()方法中。这个MessageQueue又是什么东西呢?其实从名字上就可以看出了,它是一个消息队列,用于将所有收到的消息以队列的形式进行排列,并提供入队和出队的方法。这个类是在Looper的构造函数中创建的,因此一个Looper也就对应了一个MessageQueue

嗯,现在大家应该知道,这个sendMessage是怎么回事了吧。不过,这里还有一个操作,就是最后一句,这是干嘛呢?不放翻译一下,其实就是加入队列的意思。好吧,都走到这里了,继续往里走吧:

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

首先你要知道,MessageQueue并没有使用一个集合把所有的消息都保存起来,它只使用了一个mMessages对象表示当前待处理的消息。然后观察上面的可以看出,所谓的入队其实就是将所有的消息按时间来进行排序,这个时间当然就是我们刚才介绍的uptimeMillis参数(这里就是when)。具体的操作方法就根据时间的顺序调用msg.next,从而为每一个消息指定它的下一个消息是什么。当然如果你是通过sendMessageAtFrontOfQueue()方法来发送消息的,它也会调用enqueueMessage()来让消息入队,只不过时间为0,这时会把mMessages赋值为新入队的这条消息,然后将这条消息的next指定为刚才的mMessages,这样也就完成了添加消息到队列头部的操作。

ok,入队,我们看了,现在我们要看一下怎么循环出对(为什么说循环出对呢?请看一下我最前面的那张图):

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

可以看到,有一个死循环,然后不断地调用的MessageQueue的next()方法,我想你已经猜到了,这个next()方法就是消息队列的出队方法。它的简单逻辑就是如果当前MessageQueue中存在mMessages(即待处理消息),就将这个消息出队,然后让下一条消息成为mMessages,否则就进入一个阻塞状态,一直等到有新的消息入队。每当有一个消息出队,就将它传递到msg.target的dispatchMessage()方法中,那这里msg.target又是什么呢?其实就是Handler啦,你观察一下上面sendMessageAtTime()方法可以看出来了。接下来当然就要看一看Handler中dispatchMessage()方法的源码了:

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

如果mCallback不为空,则调用mCallback的handleMessage()方法,否则直接调用Handler的handleMessage()方法,并将消息对象作为参数传递过去。这样我相信大家就都明白了为什么handleMessage()方法中可以获取到之前发送的消息了吧!

ok,这个时候,细心的同学,可能会问,那为什么我主线程不需要写Looper.prepare()我知道了,可是,我好像也没有写Looper.loop呀?这难道不会报错吗?别急,再仔细看看源码:这是之前的入口方法。

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

看到了,系统已经帮我都解决了。所以主线程,什么都不需要考虑。

下面介绍一种最常规的写法:

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

ok,handler的东西,差不多就说这么多了,下面再介绍几种好用的子线程到UI线程的方法:
就不解释,他们的原理了,想了解的话,按照我上面的解密步骤,自己就能看懂

Handler的post()方法


《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

Handler的postDelayed()方法

《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

Activity中的runOnUiThread()

此方法仅限activity或者持有activity者可以调用


《自我提升(基础技术篇)——消息传递模块(广播,intent,handler)handler篇》

小结:handler这玩意,是个好东西呀,不是说,我们平时怎么用他,而是学习他的这种处理问题的思想。其实,很多的异步框架,他们都是源自与handler机制的,所以,学好这个东西还是很有必要的。

至此,消息传递模块,算是总结完了。

点赞