「Jetpack - LiveData解析」
一、概览
LiveData-属于
Android Jetpack
的一部分,是一种可观察的数据存储容器类。它的强大之处在于其具有生命周期感知能力,也不会发生内存泄漏问题。当然现在如果可以还是建议转化到Kotlin
的Flow
,谷歌虽然没有明确表示放弃LiveData,但是Kotlin毕竟是趋势。
使用LiveData还是具有一些优势的:
LiveData
具有生命周期感知能力,不需要手动管理生命周期。- 数据驱动模型,由数据来驱动
UI
的变更。 - 生命周期自动感知,可以很好的避免内存泄漏问题。
- 可以用来数据共享,例如同一
Activity
内多个Fragment
共享同一个LiveData
,当然这需要配合ViewModel
,其实本质是ViewModel的共享。
二、使用
依赖引入
def lifecycle_version = "2.5.0-alpha01"
//LiveData
implementation "androidx.lifecycle:lifecycle-livedata-ktx:$lifecycle_version"
//ViewModel
implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:$lifecycle_version"
在ViewModel中使用LiveData,例如获取一个UserVO
对象则可以:
class UserViewModel : ViewModel() {
val userVO: MutableLiveData<UserVO> by lazy {
MutableLiveData<UserVO>()
}
//Repository to get value and update
}
//在Activity中观察数据变更
class Activity : AppCompatActivity() {
private val model: UserViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val userObserver = Observer<UserVO> { userVo ->
//do something
}
//(当然为了简洁也可以使用内部类的形式)
model.userVO.observe(this, userObserver)
}
}
通过更新ViewModel中的LiveData来更新UI上的展示,将数据(Data)与页面(UI)区分管理,数据驱动,层级更加清晰。但是需要注意的是,observe订阅操作是需要在主线程Main中执行的。其次,在使用过程一般的流程都是首先订阅observe
当数据需要变更是调用setValue()或者postValue()来实现UI
的刷新,但是如果先调用setValue,然后在注册observe,那样还能收到数据嘛?答案是可以的,先看一段测试代码:
private void testLiveData() {
MutableLiveData<String> data = new MutableLiveData<>();
Log.d("LiveData", "set live data value");
data.setValue("Sai");
new Thread(() -> {
try {
Log.d("LiveData", "Thread sleep start");
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d("LiveData", "Thread sleep end");
runOnUiThread(() -> data.observe(Activity.this, s -> Log.d("LiveData", "s = " + s)));
}).start();
}
}
打印的Log信息:
D/LiveData: set live data value
D/LiveData: Thread sleep start
D/LiveData: Thread sleep end
D/LiveData: s = Sai
在setValue之后延迟了3s再执行了订阅操作,但是结果仍然是可以接收到了数据,这说明LiveData数据是带有粘性属性的,那么具体的原因只有分析了源码实现才知道具体是怎样的机制。
三、LiveData源码分析
1.基础参数
LiveData作为抽象类,其内部实现不是很复杂,一般作为开发在使用过程中都是使用其实现类MutableLiveData,实现类只对外抛出了构造方法与setValue(T value)、postValue(T value),分别对应在主线程中更新数据Data
和子线程中更新操作。
-
MutableLiveData源码实现
-
LiveData成员参数
-
LiveData
成员参数比较少,需要重点关注的有START_VERSION、mVersion,这个两个版本号的对比决定了是否对数据data的分发。而mPostValueRunnable,顾名思义,我们知道可以通过postValue在子线程中更新数据,那势必会使用handler机制来切换线程,mPostValueRunnable也恰恰是这么做的。具体来一步步分析实现。
2.从setValue入手
根据上述的例子,这里第一步调用的是setValue,然后延迟3s才完成绑定的操作,但结果是依然收到了数据显示。那么这里来看看这个过程到底是如何进行数据分发的:
@MainThread
protected void setValue(T value) {
assertMainThread("setValue");
mVersion++;
mData = value;
dispatchingValue(null);
}
LiveData
作为抽象类,使用的是其实现类MutableLiveData
其构造函数为:
public MutableLiveData() {
super();
}
而在LiveData中无参的构造函数为:
static final int START_VERSION = -1;
public LiveData() {
mData = NOT_SET;
mVersion = START_VERSION;
}
这里可以发现mVersion在构造之初的值是 -1,回到 setValue() 方法之中,mVersion进行了自增操作此时结果为:
mVersion = 0;
mData = value;
赋值操作完成以后紧接着进行数据Data的分发-通过dispatchingValue(@Nullable ObserverWrapper initiator):
-
以首次set为例简化分析,此时传入参数为null也即是dispatchingValue(null),首先判断数据是否被分发过,这里显示是
false
,且initiator的值也为null,继而执行到for循环当中。mObservers此时的size为零(测试代码中延迟3s才执行订阅操作)。最终结果,相当于没有将数据分发出去。那么此时就需要分析订阅observe具体实现了。 -
observe方法具体实现:
observe主要分为四个执行流程,具体分析之前先看看LifecycleBoundObserver是个啥:
LifecycleBoundObserver即是ObserverWrapper的具体实现,之前分析的dispatchingValue分发的“对象”就是ObserverWrapper。总结一下这个类有什么作用,关键注意 onStateChanged() 方法:
1.通过订阅observe将当前组件的生命周期与LifecycleBoundObserver绑定。
2.监听宿主的生命周期,根据生命周期的变化处理资源的反注册,解绑。
3.判断是否是活跃状态而决定是否进行数据的分发。
回到observe方法总结一下整个执行的流程:
1.主线程的检查,判断是否在主线程中订阅,这个是强制在主线程中执行的。
2.状态检查,如果是DESTROYED状态则直接返回。
3.通过订阅传入的上文参数owner,内部实例化一个LifecycleBoundObserver与之生命周期相绑定,并将其添加到内部维护的Map容器mObservers中。同时判断是否已经存在相同的对象,这里主要是防止同一个observer被不同的组件所持有。
4.通过addObserver操作将wrapper添加到当前组件内部,其实就是LifecycleRegistry中的具体实现。
到这里似乎还是不能解释测试代码中LiveData的“粘性”问题。接着查看LifecycleRegistry中addObserver,看看这个“之前”的数据到底是如何能够被接收到的。LifecycleRegistry#addObserver:
注意到这个dispatchEvent()方法,其为LifecycleRegistry的内部方法,具体实现:
void dispatchEvent(LifecycleOwner owner, Event event) {
State newState = event.getTargetState();
mState = min(mState, newState);
mLifecycleObserver.onStateChanged(owner, event);
mState = newState;
}
//LifecycleEventObserver mLifecycleObserver;
回忆LiveData中LifecycleBoundObserver的具体实现,就实现了LifecycleEventObserver接口,那么调用observe绕了一圈,最终还是回到了LiveData中LifecycleBoundObserver的onStateChanged方法之中:
@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();
}
}
本质就是对生命周期状态的监听,当宿主的生命周期已经到达末期DESTROYED则解绑此mObserver,那么最终会回调到哪个方法呢?还是走到了dispatchingValue方法中。但是区别在于此时initiator已经不为null了,即执行到considerNotify(initiator):
if (initiator != null) {
considerNotify(initiator);
initiator = null;
}
private void considerNotify(ObserverWrapper observer) {
if (!observer.mActive) {
return;
}
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
//版本号对比
if (observer.mLastVersion >= mVersion) {
return;
}
observer.mLastVersion = mVersion;
observer.mObserver.onChanged((T) mData);
}
//int mLastVersion = START_VERSION;
关键看这个mLastVersion,其初始值为START_VERSION = -1,而mVersion在setValue调用时作了自增操作也即是mVersion = 0,很显然mLastVersion < mVersion,往后执行数据被更新通过调用observer.mObserver.onChanged((T) mData);那么此时UI则可以根据数据来刷新。到这里为什么首先调用setValue然后observe时同样可以收到数据,也即是LiveData的粘性。总结一下(先设置数据,再订阅observe):
-
实例化MutableLiveData对象时,将mVersion的值修改为START_VERSION,也即是 -1。
-
调用setValue时,mVersion进行了自增操作,此时mVersion的值为0,因为此时还没有订阅者订阅(observe),mDispatchingValue的值不变依然为false。
-
延迟调用observe进行订阅,内部对传入的owner的上下文进行包裹,将宿主的生命周期与LifecycleBoundObserver进行绑定操作。而LifecycleBoundObserver继承自ObserverWrapper并实现了接口LifecycleEventObserver,检查判断同一个observer是否被绑定了不同的生命周期组件;这是不允许的。最终将LifecycleBoundObserver添加到LifecycleRegistry中。
-
LifecycleRegistry中通过dispatchEvent将状态抛出,LifecycleEventObserver#onStateChanged(),而之前提到的LifecycleBoundObserver本身实现了LifecycleEventObserver接口。方法最终走到LiveData#dispatchingValue,此时initiator已经不为空了,自然就执行到了considerNotify。
-
considerNotify对两个版本号mLastVersion、mVersion作了对比,而经过之前的分析mLastVersion是小于mVersion的,那么最终完成了数据的分发,通知到了UI上,即是observe订阅操作是延迟后的。
3.postValue线程切换
但凡Android的现程切换,首先想到的就是利用Handler机制,那么这里的postValue是否也是一样符合猜想呢?
protected void postValue(T value) {
boolean postTask;
synchronized (mDataLock) {
postTask = mPendingData == NOT_SET;
mPendingData = value;
}
if (!postTask) {
return;
}
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}
ArchTaskExecutor继承自抽象类TaskExecutor,方法比较简单:
具体实现在TaskExecutor的默认实现DefaultTaskExecutor,关键方法postToMainThread:
//ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
//最终执行就是通过handler来将任务Task加入到主线程中
public void postToMainThread(Runnable runnable) {
if (mMainHandler == null) {
synchronized (mLock) {
if (mMainHandler == null) {
mMainHandler = createAsync(Looper.getMainLooper());
}
}
}
//noinspection ConstantConditions
mMainHandler.post(runnable);
}
当调用postValue内部还是通过handler机制进行了线程的切换,最终完成数据的分发。
四、小结
- 结合源码的实现分析,即使先调用setValue然后订阅observe,依然会收到数据;实际开发过程中要根据实际情况决定要不要使用粘性的数据。
- 内部的版本号mVersion可以根据实际情况来区分对“事件”还是“状态”,事件一般消费完就标志着结束,而“状态”可以多次消费。
- postValue的线程切换还是利用到了Handler机制。
- Kotlin作为趋势,并且Flow也不存在LiveData的粘性缺点,当然还是跟着管方推荐方式构建。