怒肝Framework通信篇合集;Handler+Binder+LiveData事件机制
Posted 初一十五啊
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了怒肝Framework通信篇合集;Handler+Binder+LiveData事件机制相关的知识,希望对你有一定的参考价值。
前言
昨天关于Handler
的总结篇里提到了十个考点,今天先来解答一下,在把Framework
整体的过一下,Handler
,Binder
,AMS
,PMS
,IMS
,WMS
,以及事件机制。
首先先回答昨天最后总结的十个考点:
1.Handler
怎么在主线程和子线程进行数据交互的原理?
主线程和子线程通过handler
交互,交互的载体是通过Message
这个对象,实际上我们在子线程发送的所有消息,都会加入到主线程的消息队列中,然后主线程分发这些消息,这个就很容易做到俩个线程信息的交互。
看到这里,你可能有疑问了,我从子线程发送的消息,怎么就加到了主线程的消息队列里呢???
大家可以看看你自己的代码,你的handler
对象是不是在主线程初始的?子线程发送消息,是不是通过这个handler
发送的?
这就很简单了,handler
只需要把发送的消息,加到自身持有的Looper
对象的MessageQueue
里面(mLooper
变量)就ok了
所以,你在哪个线程里面初始化Handler
对象,在不同的线程中,使用这个对象发送消息;都会在你初始化Handler
对象的线程里分发消息。
2.Handler
中主线程的消息队列是否有数量上限?为什么?
这问题整的有点鸡贼,可能会让你想到,是否有上限这方面?而不是直接想到到上限数量是多少?
解答:Handler
主线程的消息队列肯定是有上限的,每个线程只能实例化一个Looper
实例(上面讲了,Looper.prepare
只能使用一次),不然会抛异常,消息队列是存在Looper()
中的,且仅维护一个消息队列
重点:每个线程只能实例化一次Looper()
实例、消息队列存在Looper
中
拓展:MessageQueue
类,其实都是在维护mMessage
,只需要维护这个头结点,就能维护整个消息链表
3.Handler
中有Loop
死循环,为什么没有卡死?为什么没有发生ANR
?
先说下ANR
:5秒内无法响应屏幕触摸事件或键盘输入事件;广播的onReceive()
函数时10秒没有处理完成;前台服务20秒内,后台服务在200秒内没有执行完毕;ContentProvider
的publish
在10s内没进行完。所以大致上Loop
死循环和ANR
联系不大,问了个正确的废话,所以触发事件后,耗时操作还是要放在子线程处理,handler
将数据通讯到主线程,进行相关处理。
线程实质上是一段可运行的代码片,运行完之后,线程就会自动销毁。当然,我们肯定不希望主线程被over,所以整一个死循环让线程保活。
为什么没被卡死:在事件分发里面分析了,在获取消息的next()
方法中,如果没有消息,会触发nativePollOnce
方法进入线程休眠状态,释放CPU资源,MessageQueue
中有个原生方法nativeWake
方法,可以解除nativePollOnce
的休眠状态,ok,咱们在这俩个方法的基础上来给出答案。
-
当消息队列中消息为空时,触发
MessageQueue
中的nativePollOnce
方法,线程休眠,释放CPU资源 -
消息插入消息队列,会触发
nativeWake
唤醒方法,解除主线程的休眠状态- 当插入消息到消息队列中,为消息队列头结点的时候,会触发唤醒方法
- 当插入消息到消息队列中,在头结点之后,链中位置的时候,不会触发唤醒方法
综上:消息队列为空,会阻塞主线程,释放资源;消息队列为空,插入消息时候,会触发唤醒机制
- 这套逻辑能保证主线程最大程度利用CPU资源,且能及时休眠自身,不会造成资源浪费
本质上,主线程的运行,整体上都是以事件(Message
)为驱动的。
4.为什么不建议在子线程中更新UI?
多线程操作,在UI的绘制方法表示这不安全,不稳定。
假设一种场景:我会需要对一个圆进行改变,A线程将圆增大俩倍,B改变圆颜色。A线程增加了圆三分之一体积的时候,B线程此时,读取了圆此时的数据,进行改变颜色的操作;最后的结果,可能会导致,大小颜色都不对。。。
5.可以让自己发送的消息优先被执行吗?原理是什么?
这个问题,我感觉只能说:在有同步屏障的情况下是可以的。
同步屏障作用:在含有同步屏障的消息队列,会及时的屏蔽消息队列中所有同步消息的分发,放行异步消息的分发。
在含有同步屏障的情况,我可以将自己的消息设置为异步消息,可以起到优先被执行的效果。
6.子线程和子线程使用Handler
进行通信,存在什么弊端?
子线程和子线程使用Handler
通信,某个接受消息的子线程肯定使用实例化handler
,肯定会有Looper
操作,Looper.loop()
内部含有一个死循环,会导致线程的代码块无法被执行完,该线程始终存在。
如果在完成通信操作,我们一般可以使用: mHandler.getLooper().quit()
来结束分发操作
说明下:quit()
方法进行几项操作
- 清空消息队列(未分发的消息,不再分发了)
- 调用了原生的销毁方法
nativeDestroy
(猜测下:可能是一些资源的释放和销毁) - 拒绝新消息进入消息队列
- 它可以起到结束
loop()
死循环分发消息的操作
拓展:quitSafely()
可以确保所有未完成的事情完成后,再结束消息分发。
7.Handler
中的阻塞唤醒机制?
这个阻塞唤醒机制是基于 Linux 的 I/O 多路复用机制 epoll
实现的,它可以同时监控多个文件描述符,当某个文件描述符就绪时,会通知对应程序进行读/写操作.
MessageQueue
创建时会调用到 nativeInit
,创建新的 epoll
描述符,然后进行一些初始化并监听相应的文件描述符,调用了epoll_wait
方法后,会进入阻塞状态;nativeWake
触发对操作符的 write
方法,监听该操作符被回调,结束阻塞状态。
8.什么是IdleHandler
?什么条件下触发IdleHandler
?
IdleHandler
的本质就是接口,为了在消息分发空闲的时候,能处理一些事情而设计出来的
具体条件:消息队列为空的时候、发送延时消息的时候
9.消息处理完后,是直接销毁吗?还是被回收?如果被回收,有最大容量吗?
Handler
存在消息池的概念,处理完的消息会被重置数据,采用头插法进入消息池,取的话也直接取头结点,这样会节省时间
消息池最大容量为50,达到最大容量后,不再接受消息进入
10.不当的使用Handler
,为什么会出现内存泄漏?怎么解决?
先说明下,Looper
对象在主线程中,整个生命周期都是存在的,MessageQueue
是在Looper
对象中,也就是消息队列也是存在在整个主线程中;我们知道Message
是需要持有Handler
实例的,Handler
又是和Activity
存在强引用关系
存在某种场景:我们关闭当前Activity
的时候,当前Activity
发送的Message
,在消息队列还未被处理,Looper
间接持有当前activity
引用,因为俩者直接是强引用,无法断开,会导致当前Activity
无法被回收
思路:断开俩者之间的引用、处理完分发的消息,消息被处理后,之间的引用会被重置断开
解决:使用静态内部类弱引Activity
、清空消息队列
回答完了,正式 步入正轨😃
一丶Framework通信篇
1.1.android Binder
进程隔离:
内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据 为了保证系统的安全,用户空间和内核空间是天然隔离的 每个进程有自己的虚拟内存空间,为了安全,每个进程只能操作自己的虚拟内存空间,只有操作系统才有权限操作物理内存空间
为什么要用Binder
?
-
Android系统内核是Linux内核
-
Linux内核进程通信有:管道、内存共享、
Socket
、File
; -
对比:
Binder
的一次拷贝发生在用户空间拷贝到内核空间;
用户空间: App进程运行的内存空间;
内核空间: 系统驱动、和硬件相关的代码运行的内存空间,也就是进程ID为0的进程运行的空间;
程序局部性原则: 只加载少量代码;应用没有运行的代码放在磁盘中,运行时高速缓冲区进行加载要运行的代码;默认一次加载一个页(4K),若不够4K就用0补齐;
MMU:内存管理单元;
给CPU提供虚拟地址;
当对变量操作赋值时:
-
CPU拿着虚拟地址和值给到MMU
-
MMU用虚拟地址匹配到物理地址,MMU去物理内存中进行赋值;
物理地址: 物理内存的实际地址,并不是磁盘;
虚拟地址: MMU根据物理内存的实际地址翻译出的虚拟地址;提供给CPU使用;
页命中:CPU读取变量时,MMU在物理内存的页表中找到了这个地址;
页未命中:CPU读取变量时,MMU在物理内存的页表中没有找到了这个地址,此时会触发MMU去磁盘读取变量并存到物理内存中;
普通的二次拷贝:
应用A拷贝到服务端:coay_from_user
从服务端拷贝到应用B:coay_to_user
mmap():
-
在物理内存中开辟一段固定大小的内存空间
-
将磁盘文件与物理内存进行映射(理解为绑定)
-
MMU将物理内存地址转换为虚拟地址给到CPU(虚拟地址映射物理内存)
共享内存进程通信:
-
进程A调用
mmap()
函数会在内核空间中虚拟地址和一块同样大小的物理内存,将两者进行映射 -
得到一个虚拟地址
-
进程B调用
mmap()
函数,传参和步骤1一样的话,就会得到一个和步骤2相同的虚拟地址 -
进程A和进程B都可以用同一虚拟地址对同一块映射内存进行操作
-
进程A和进程B就实现了通信
-
没有发生拷贝,共享一块内存,不安全
Binder
通信原理:
角色:Server端A、Client端B、Binder驱动、内核空间、物理内存
-
Binder驱动在物理内存中开辟一块固定大小(1M-8K)的物理内存w,与内核空间的虚拟地址x进行映射得到
-
A的用户空间的虚拟地址ax和物理内存w进行映射
-
此时内核空间虚拟地址x和物理内存w已经进行了映射,物理内存w和Server端A的用户空间虚拟地址ax进行了映射:也就是 内核空间的虚拟地址x = 物理内存w = Server端A的用户空间虚拟地址ax
-
B发送请求:将数据按照binder协议进行打包给到Binder驱动,Binder驱动调用
coay_from_user()
将数据拷贝到内核空间的虚拟地址x -
因步骤3中的三块区域进行了映射
-
Server端A就得到了Client端B发送的数据
-
通过内存映射关系,只发生了一次拷贝
Activity
跳转时,最多携带1M-8k(1兆减去8K)的数据量;
真实数据大小为:1M内存-两页的请求头数据=1M-8K;
应用A直接将数据拷贝到应用B的物理内存空间中,数据量不能超过1M-8K;拷贝次数少了一次,少了从服务端拷贝到用户;
IPC通信机制:
-
服务注册
-
服务发现
-
服务调用
以下为简单的主进程和子进程通信:
1、服务注册: 缓存中心中有三张表(暂时理解为三个HashMap
,Binder
用的是native
的红黑树):
-
第一种:放key :String - value:类的Class;
-
第二种:放key :Class的类名 - value:类的方法集合;
-
第三种:放key :Class的类名 - value:类的对象;
类的方法集合:key-value;
key:方法签名:“方法名” 有参数时用 “方法名-参数类型-参数类型-参数类型…”;
value: 方法本身;
注册后,服务若没被调用则一直处于沉默状态,不会占用内存,这种情况只是指用户进程里自己创建的服务,不适用于AMS这种;
2、服务发现: 当被查询到时,要被初始化;
-
客户端B通过发送信息到服务端A
-
服务端解析消息,反序列化
-
通过反射得到消息里的类名,方法,从注册时的第一种、第二种表里找到Class,若对象没初始化则初始化对象,并将对象添加到第三种的表里;
3、服务调用:
-
使用了动态代理
-
客户端在服务发现时,拿到对象(其实是代理)
-
客户端调用对象方法
-
代理发送序列化数据到服务端A
-
服务端A解析消息,反序列化,得到方法进行处理,得到序列化数据结果
-
将序列化结果写入到客户端进程的容器中;
-
回调给客户端
AIDL: BpBinder
:数据发送角色 BbBinder
:数据接收角色
编译器生成的AIDL的java接口.Stub.proxy.transact()
为数据发送处;
发送的数据包含:数据+方法code+方法参数等等;
-
发送时调用了Linux的驱动
-
调用
copy_from_user()
拷贝用户发送的数据到内核空间 -
拷贝成功后又进行了一次请求头的拷贝:
copy_from_user()
-
也就是把一次的数据分为两次拷贝
请求头:包含了目的进程、大小等等参数,这些参数占了8K
编译器生成的AIDL的java接口.Stub.onTransact()
为数据接收处;
Binder中的IPC机制:
-
每个App进程启动时会在内核空间中映射一块1M-8K的内存
-
服务端A的服务注册到
ServiceManager
中:服务注册 -
客户端B想要调用服务端A的服务,就去请求
ServiceManager
-
ServiceManager
去让服务端A实例化服务:服务发现 -
返回一个用来发送数据的对象BpBinder给到客户端B
-
客户端B通过
BpBinder
发送数据到服务端A的内核的映射区域(传参时客户端会传一个reply
序列化对象,在底层会将这个地址一层一层往下传,直至传到回调客户端):这里发生了一次通信copy_from_user
:服务调用 -
服务端A通过BBBinder得到数据并处理数据
-
服务端唤醒客户端等待的线程;将返回结果写入到客户端发送请求时传的一个reply容器地址中,调用
onTransact
返回; -
客户端在
onTransac
中得到数据;通信结束;
ServiceManager
维持了Binder这套通信框架;
1.2.Android Handler
Handler、Message、MessageQueue、Looper;
以下为零散的记录,最后有总结; 内存泄露的本质:
长生命周期对象持有短生命周期对象,导致短生命周期对象销毁不掉;
持有链:
线程>>Looper>>MessageQueue>>Message>>Handler>>Activity;
Message
对象的变量target
为发送消息的Handler; MessageQueue
队列里放Message
; Looper
对象里实例化MessageQueue;
一个线程绑定一个Looper
;
为什么要有handler
? 主要目的是要解决线程切换问题,handler
里的Message
机制解决了线程间通信;
为什么有队列MessageQueue
? MessageQueue
是一个单向链表,next()
调用nativePollOnce->lunx
的epoll_wait()
等待实现阻塞时队列;
-
在单线程中一次只能执行一句代码
-
假如发送了一个大消息A
-
处理这个大的消息A
-
但是处理的太慢了
-
从而导致其他后续要发送的消息发不出去
-
因为单线程阻塞到了第3步处理那个消息A的地方
队列的出现解决了"处理消息"阻塞到"发送消息"的问题;
队列是生产者消费者模式;
而要使用队列需要至少两个线程、和一个死循环;
-
一个线程负责生产消息;
-
一个线程消费消息;
-
死循环需要取出放入队列里的消息;
为什么有Looper
?
为了循环取出队列里的消息;
一个线程有几个Looper
,为什么不会有多个?
一个线程一个Looper
,放在ThreadLocalMap
中;
假如Looper
对象由Handler
创建,每创建一个Handler
就有一个Looper
,那么调用Looper.loop()
时开启死循环;在外边调用Looper
的地方就会阻塞;
主线程中Looper
的死循环为什么没有导致系统卡死?
-
我们的UI线程主线程其实是
ActivityThread
线程,而一个线程只会有一个Looper
; -
ActivityThread.java
的main
函数是一个APP进程的入口,如果不卡死,main
函数执行完则整个应用进程就会退出; -
android是以事件为驱动的操作系统,当有事件来时,就去做对应的处理,没有时就显示静态界面;
获取当前线程:Thread.currentThread();
ThreadLocalMap
:类似于HashMap;
每个Thread
对象都有一个对应的ThreadLocalMap;
在Looper.prepare()
时,存入Looper
,存Looper
时ThreadLocalMap
的key为ThreadLocal
,value
为Looper
;
内存抖动根本的解决方式是复用;
handler.obtainMessage();
-
从
Looper
的回收池中取Message
; -
Message
是一个单向链表,Message
不是一个单纯的对象,而是一个链表集合 -
最大长度固定50个
Linux函数:
epoll_create
:App注册进红黑树中,拿到一个事件fd的值;epoll_ctl
:注册事件类型,监听fd是否改变(Linux中事件都会被写入文件中,如触摸屏幕事件会写入到:dev/input/event0
文件中),fd有改变时唤醒epoll_wait
;
epoll_wait
:有事件时就分发,没事件就阻塞
总结: handler如何做的线程切换的? 首先Handler的使用步骤:
-
调用
Looper.prepare();
-
创建Handler对象;
-
调用
Looper.Loop()
方法。 -
线程中发送消息。
在第一步时,创建一个Looper
,并放到当前线程的变量threadLocals
中;threadLocals
是一个map,key为ThreadLocal
对象本身,value为Looper;在Looper.loop()
时取出;
第二步,用户在当前线程(可能是子线程)创建Handler对象;
第三步,Looper.loop()
一直在死循环,Looper.loop()
这句代码下面的代码是不会被调用的,调用Looper.loop()
函数时,先从当前线程的map变量中取出Looper
,再从Looper
中拿到队列MessageQueue
,for循环中不断从队列中取出消息;
第四步,在其他线程调用handler
发送消息时,Message
里有个target
,就是发送消息的handler
;
在Looper.loop()
时,队列中取到消息时,调用msg.target.dispatchMessage(msg);
其实就是handler对象.dispatchMessage(msg);
所以不论在哪个线程调用发送消息,都会调用到handler自己分发消息;而handler所处的线程是创建时的“当前线程”,所以处理时也就回到了“当前线程”;实现了线程切换,和线程通信;
Looper的死循环为什么不会让主线程卡死(或ANR)? 简单版:
-
我们的UI线程主线程其实是
ActivityThread
所在的线程,而一个线程只会有一个Looper
; -
ActivityThread.java
的main
函数是一个APP进程的入口,如果不一直循环,则在main
函数执行完最后一行代码后整个应用进程就会退出; -
android是以事件为驱动的操作系统,当有事件来时,就去做对应的处理,没有时就显示静态界面;
-
ANR发生条件是:
Activity
:5 秒。应用在 5 秒内未响应用户的输入事件(如按键或者触摸)
BroadCastReceiver
:10 秒。BroadcastReceiver
未在 10 秒内完成相关的处理
Service
:20 秒(均为前台)。Service
在20 秒内无法处理完成 -
如果Handler收到以上三个相应事件在规定时间内完成了,则移除消息,不会ANR;若没完成则会超时处理,弹出ANR对话框;
详细:
-
App进程的入口为
ActivityThread.java的main()
函数,注意ActivityThread
不是一个线程; -
应用的ui主线程实际是调用
ActivityThread.java的main()
函数执行时所在的线程,而这个线程对我们不可见,但是这就是主线程;参考: -
在
ActivityThread.java
的main()
函数中,会调用Looper.prepareMainLooper();
-
Looper.prepareMainLooper()
会创建一个Looper并放到当前线程(主线程)的变量threadLocals
中进行绑定,threadLocals
是一个ThreadLocal.ThreadLocalMap;
-
在
ActivityThread.java
的main()
函数结尾,开启Looper.loop()
进行死循环,不让main函数结束,从而让App进程不会结束; -
Android系统是以事件作为驱动的操作系统,当有事件来时,就去做对应处理,没有事件时,就显示当前界面,不做其他多余操作(浪费资源);
-
在
Looper.loop()
的死循环中,不仅要取用户发的事件,还要取系统内核发的事件(如屏幕亮度改变等等); -
在调用
Looper.loop()
时,从MessageQueue.next()
中获取事件,若没有则阻塞,有则分发; -
MessageQueue
其实不是一个队列,用epoll机制实现了阻塞; -
在
Looper.prepareMainLooper()
时,调用c++函数epoll_create()
会将App注册进epoll机制的红黑树中得到fd的值,epoll_ctl()给每个App注册事件类型并监听fd值是否改变,fd有改变时唤醒epoll_wait; -
epoll_wait()
有事件时就分发,没事件就阻塞
子线程的Looper和子线程Looper有什么不同?
子线程Looper
是可以退出的,主线程不行;
1.3.LiveData事件机制
LiveDate
事件事件一共有以下内容:
LiveDate
粘性事件Framework
源码分析
Jetpack
中的状态机是如何管理生命周期
Hook
实现LiveDate
非粘性功能
LiveDate
递归调用源码是如何做容错的
1.3.1.liveData
粘性事件
粘性事件:相对比普通事件,粘性事件支持先发送事件,再去注册订阅者。一旦完成订阅动作,这个订阅者就会接收到该粘性事件。
所以粘性其实就可以理解为观察者模式的升级,让观察者与被观察者对象之间更加的粘合。
举个栗子,我们利用liveData
来做APP的全局状态管理
object GlobalState
val jcTestNumberLd: MutableLiveData<Int> = MutableLiveData<Int>()
然后在 Fragment
以及 Activity
中观察该 jcTestNumberLd
。
/** 观察 GlobalState 的 Activity */
class JcTestActivity : BaseVmDbActivity<MainViewModel, ActivityJcTestBinding>()
override fun initView()
viewBinding.incButton.setOnClickListener
GlobalState.jcTestNumberLd.value =
if (GlobalState.jcTestNumberLd.value == null)
1
else
GlobalState.jcTestNumberLd.value!!.toInt().inc()
override fun initObserve()
GlobalState.jcTestNumberLd.observe(this,
Log.e(TAG, "initObserve: jctestNumber = $it")
)
........
/** 观察 GlobalState 的 Fragment */
class EventFragment : BaseVmDbFragment<EventViewModel, FragmentEventBinding>()
override fun setObservers()
GlobalState.jcTestNumberLd.observe(viewLifecycleOwner,
Log.e(TAG, "setObservers: jctestNumber = $it", )
)
........
注意:这里例子中的 EventFragment
并不是关联到 JcTestActivity
的。用户会先进入到 JcTestActivity
,然后由用户控制进入到另一个Activity
中,加载 EventFragment
。
我们来执行一下以下五步操作,来看一下输出的日志。
- 当我们第一次进入
JcTestActivity
时,注册了观察者,没有接收到观察事件,所以也就不会执行观察动作。 - 然后我们点击自增按钮为
jcTestNumberLd
赋予新值,接收到观察事件,执行观察动作,输出 1。 - 再次点击自增按钮,有观察事件,执行观察动作,输出 2。
- 再次点击自增按钮,有观察事件,执行观察动作,输出 3。
- 然后我们到
EventFragment
中,注册新的观察者,发现直接接收到观察事件,执行观察动作,输出 3。
输出结果:
E/JcTestActivity: initObserve: jctestNumber = 1
E/JcTestActivity: initObserve: jctestNumber = 2
E/JcTestActivity: initObserve: jctestNumber = 3
E/EventFragment: setObservers: jctestNumber = 3
这就是粘性事件!所以说,LiveData是粘性的。
1.3.2.LiveData
是怎么实现粘性的呢?
在知道LiveData
是粘性后,我不经问自己:它是怎么实现粘性的呢?
这里我们先来回顾一下EventBus
粘性事件的实现原理。
EventBus
在发送粘性事件时,会将这粘性事件存到一个叫做 stickyEvents
的集合中,然后等注册订阅新的观察者对象时,会去遍历该集合中的粘性事件,如果有找到对应的粘性事件,就将该粘性事件发送给该观察者。(如果你对EventBus
粘性事件不熟悉,可以点击EventBus
源码解析(很细 很长)进一步了解学习。)
LiveData
是不是也是以同样的原理来实现粘性的呢?
public LiveData(T value)
mData = value;
mVersion = START_VERSION + 1;
/**
* Creates a LiveData with no value assigned to it.
*/
public LiveData()
mData = NOT_SET;
mVersion = START_VERSION;
从 LiveData
的构造函数中可以发现有一个 mVersion
参数,它代表着 LiveData
的版本号,每当我们进行 setValue
时,都会让 mVersion
进行自增。
另外,ObserverWrapper
这个观察者包装类中也有一个 int mLastVersion = START_VERSION
版本号。
这两个版本号分别是被观察者对象与观察者对象的版本号,那这二者之间又有什么关系呢?
在判断是否通知观察者的 considerNotify(ObserverWrapper observer)
方法中,会对这两个版本号进行比较。
private void considerNotify(ObserverWrapper observer)
...省略代码...
//如果观察者的版本号 >= LiveData的版本号,就说明该观察者已经接收过该观察事件,也就不再分发。
if (observer.mLastVersion >= mVersion)
return;
//反之,分发观察事件给该观察者,让其执行对应的观察动作,并更新观察者的版本号
observer.mLastVersion = mVersion;
observer.mObserver.onChanged((T) mData);
概括一下:根据比对观察者对象的版本号与LiveData
的版本号来判断是否分发当前版本的数据给该观察者。如果观察者对象的版本号大于等于LiveData
的版本号,也就说明该观察者已经接收过当前版本的数据了,也就不需要再次分发了(等待下一次数据更新)。反之,则分发当前版本的数据给该观察者,让其执行对应的观察动作,并更新观察者的版本号,也就是更新为LiveData
的版本号。
1.3.3.Google为何要将LiveData设计成粘性的
LiveData
是可观察的数据存储器类,这样也就意味着存储在LiveData
中的数据是会更新的,既然是会更新的,那必定就会存在状态,即最新数据状态。
所以,当数据状态发生改变时(数据发生了更新),LiveData
需要告诉所有处于活跃状态的观察者, 让其同步更新数据。这应该很好理解了,因为这就是普通事件,先注册观察者,再去更新被观察者对象,触发观察事件。
那这时,你再去新注册一个观察者对象,你认为它需不需要知道此时LiveData
最新的数据呢?
答案是:需要。
因为所有的观察者,都只需要知道LiveData中存储的数据,而且是最新数据。不管我是新注册的观察者,只要你LiveData
有了最新数据,就需要告诉我。而关于有无新数据,从代码上体现出来的就是,LiveData.mVersion > Observer.mLastVersion
。
这也就是粘性事件,先更新被观察者对象,触发观察事件,再去注册观察者,观察者会直接接收到该观察事件,执行对应的观察动作。
它的功能属性导致其只能是粘性的。
1.3.4.LiveData订阅与数据分发
使用
public class LiveDataActivity extends AppCompatActivity
MutableLiveData liveData= new MutableLiveData<String>();
void liveDataTest()
// 任何线程都可以发送数据
liveData.postValue("postValue");
// 只有主线程可以发送数据
// liveData.setValue("setValue")
void observeTest()
//订阅
liveData.observe(this, new Observer<String>()
@Override
public void onChanged(String data)
//收到数据data
);
//可以有多个订阅
liveData.observe(this, new Observer<String>()
@Override
public void onChanged(String data)
//收到数据data
);
阶段一:
postValue
:
protected void postValue(T value)
boolean postTask;
synchronized (mDataLock)
postTask = mPendingData == NOT_SET;
mPendingData = value;
if (!postTask)
return;
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
- 将用户发送的数据给到一个
mPendingData
的变量; - 切换到主线程
- 执行了一个
mPostValueRunnable
;
mPostValueRunnable
:
volatile Object mPendingData = NOT_SET;
private int mVersion;
private final Runnable mPostValueRunnable = new Runnable()
@SuppressWarnings("unchecked")
@Override
public void run()
Object newValue;
synchronized (mDataLock)
newValue = mPendingData;
mPendingData = NOT_SET;
setValue((T) newValue);
;
- 在
mPostValueRunnable
中将mPendingData
给到了新的临时变量newValue
; mPendingData
的值置为空;- 调用
setValue(newValue);
setValue
:
private volatile Object mData;
private int mVersion;
@MainThread
protected void setValue(T value)
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
- 将数据版本mVersion +1;
- 将发送的数据给到了mData;
- 调用分发数据
dispatchingValue
看到这里发现,数据其实最后存到了mData
中;若想发送订阅消息,肯定得添加订阅者;
阶段二:
添加订阅者,observe()
:
/用户使用:
//liveData.observe(this@LiveDataActivity,
//
//)
private SafeIterableMap<Observer<? super T>, ObserverWrapper> mObservers =
new SafeIterableMap<>();
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer)
assertMainThread("observe");
//如果被观察者的生命周期是DESTROYED,就不添加订阅者
if (owner.getLifecycle().getCurrentState() == DESTROYED)
// ignore
return;
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
...
owner.getLifecycle().addObserver(wrapper);
- 将有生命周期的
Activity
和订阅者Observer
传了进来; - 判断
Activity
的生命周期; - 将
Activity
和Observer
封装为一个LifecycleBoundObserver
对象; - 将
LifecycleBoundObserver
放到了mObservers
这个Map
集合中; map
的key
为观察者,value
为封装了activity
和观察者Observer
的对象LifecycleBoundObserver
;
LifecycleBoundObserver
及它的父类ObserverWrapper
:
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver
@NonNull
final LifecycleOwner mOwner;
LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer)
super(observer);
mOwner = owner;
...
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event)
Lifecycle.State currentState = mOwner.getLifecycle().getCurrentState();
if (currentState == DESTROYED)
removeObserver(mObserver);
return;
Lifecycle.State prevState = null;
while (prevState != currentState)
prevState = currentState;
activeStateChanged(shouldBeActive());
currentState = mOwner.getLifecycle().getCurrentState();
...
@Override
void detachObserver()
mOwner.getLifecycle().removeObserver(this);
//ObserverWrapper
private abstract class ObserverWrapper
//传进来的观察者放这里了
final Observer<? super T> mObserver;
boolean mActive;
int mLastVersion = START_VERSION;
ObserverWrapper(Observer<? super T> observer)
mObserver = observer;
...
void activeStateChanged(boolean newActive)
if (newActive == mActive)
return;
mActive = newActive;
changeActiveCounter(mActive ? 1 : -1);
if (mActive)
dispatchingValue(this);
- 将有生命周期的
Activity
给到了LifecycleBoundObserver
的mOwner
; - 将观察者给到了
LifecycleBoundObserver
的父类ObserverWrapper
的mObserver
; - 其实
LifecycleBoundObserver
就可以获取到Activity
和mObserver
; - 上一步,看到将封装后的
LifecycleBoundObserver
放到了mObservers
这个map
中; map
的key
为观察者,value
为封装了activity
和观察者Observer
的对象LifecycleBoundObserver
;
阶段三:
分发:
在阶段一setValue()
时,调用了dispatchingValue(null)
;
void dispatchingValue(@Nullable ObserverWrapper initiator)
if (mDispatchingValue)
mDispatchInvalidated = true;
return;
mDispatchingValue = true;
do
mDispatchInvalidated = false;
if (initiator != null)
considerNotify(initiator);
initiator = null;
else
for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
mObservers.iteratorWithAdditions(); iterator.hasNext(); )
considerNotify(iterator.next().getValue());
if (mDispatchInvalidated)
break;
while (mDispatchInvalidated);
mDispatchingValue = false;
private void considerNotify(ObserverWrapper observer)
if (!observer.mActive)
return;
...
if (observer.mLastVersion >= mVersion)
return;
observer.mLastVersion = mVersion;
observer.mObserver.onChanged((T) mData);
- 当为
null
时,走到了for
循环 - 循环取出
map
中的value
,而value
就是LifecycleBoundObserver
; LifecycleBoundObserver
中有mObserver
,mObserver
就是用户传的观察者;mObserver.onChanged((T) mData);
- 完成了“主动”分发;
以上是关于怒肝Framework通信篇合集;Handler+Binder+LiveData事件机制的主要内容,如果未能解决你的问题,请参考以下文章
硬不硬核,你说了算,怒肝Android Framework学习路线一条龙!(万字长文+视频资源)