感知生命周期的数据 -- LiveData
零. 前言
上篇文章《万物基于Lifecycle》 介绍了整个Lifecycle体系的基石,今天这篇文章咱们来看看Jetpack给我们带来的活着的数据——LiveData。
大纲
- LiveData 是什么?
- 为什么要用LiveData?
- How to use LiveData?
- LiveData的生命感知能力从何而来,是如何与Lifecycle结合的?
一. LiveData 是什么?
LiveData 简单来说,就是普通数据对象的一个包装类,这个包装类中帮助你主动管理了数据的版本号,基于观察者模式,让普通的数据对象能够感知所属宿主(Activity、Fragment)的生命周期。这种感知能力就能够保证只有宿主活跃(Resumed、Started)时,数据的观察者才能受到数据变化的消息。
上面这短短的一段话,却有着重大的意义,举一个case:
有一个页面需要加载一个列表,我们需要在后台线程去服务器请求对应的数据,请求数据成功并经过解析后post消息通知UI,UI再渲染请求来的数据。等等!假若此时的网络很慢,而刚好用户此时按home键退出了应用,那么在此时UI是不应该被渲染的,而是应该等用户重新进入应用时才开始渲染。
从下面的演示代码就诠释了上面的说所的case
class MainActivity extends AppcompactActivity{
public void onCreate(Bundle bundle){
Handler handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
//无论页面可见不可见,都会去执行页面刷新,IO。更有甚者弹出对话框
}
};
//1.无论当前页面是否可见,这条消息都会被分发。----消耗资源
//2.无论当前宿主是否还存活,这条消息都会被分发。---内存泄漏
handler.sendMessage(msg)
liveData.observer(this,new Observer<User>){
void onChanged(User user){
}
}
//1.减少资源占用--- 页面不可见时不会派发消息
//2.确保页面始终保持最新状态---页面可见时,会立刻派发最新的一条消息给所有观察者--保证页面最新状态
//3.不再需要手动处理生命周期---避免NPE
//4.可以打造一款不用反注册,不会内存泄漏的消息总线---取代eventbus
liveData.postValue(data);
}
}
有人说,我可以在处理消息时,根据当前页面时是否可见来具体处理对应逻辑。是的,没错,确实可以这样,就像下面这样。
Handler handler = new Handler(){
@Override
public void handleMessage(@NonNull Message msg) {
if (isActivityValid()) {
// updateUI...
} else {
// dosomething...
}
}
};
我再拿上面的例子说一下这种问题:
- 需要自行判断宿主活跃状态,防止生命周期越界。
- 如果Activity不可见,此时不更新UI,那么就需要复写onResume方法去更新UI,手工管理生命周期,增加了代码的复杂性。
二. 为什么要使用LiveData?
上面的例子已经很好地诠释了LiveData的强大,当然不仅限于此,它的优势如下:
- 确保界面符合数据状态LiveData 遵循观察者模式。当生命周期状态发生变化时,LiveData 会通知 Observer 对象。观察者可以在onChanged事件时更新界面,而不是在每次数据发生更改时立即更新界面。
- 不会发生内存泄漏观察者会绑定到 Lifecycle 对象,并在其关联的生命周期遭到销毁后进行自我清理。
- 不会因 Activity 停止而导致崩溃如果观察者的生命周期处于非活跃状态(如返回栈中的 Activity),则它不会接收任何 LiveData 事件。
- 不再需要手动处理生命周期界面组件只是观察相关数据,不会停止或恢复观察。LiveData 将自动管理所有这些操作,因为它在观察时可以感知相关的生命周期状态变化。
- 数据始终保持最新状态如果生命周期变为非活跃状态,它会在再次变为活跃状态时接收最新的数据。例如,曾经在后台的 Activity 会在返回前台后立即接收最新的数据。
- 适当的配置更改如果由于配置更改(如设备旋转)而重新创建了 Activity 或 Fragment,它会立即接收最新的可用数据。
- 共享资源可以使用单一实例模式扩展 LiveData 对象以封装系统服务,以便在应用中共享它们。LiveData 对象连接到系统服务一次,然后需要相应资源的任何观察者只需观察 LiveData 对象
- 支持黏性事件的分发
即先发送一条数据,后注册一个观察者,默认是能够收到之前发送的那条数据的
三. How to use LiveData ?
step1: 添加依赖:
step2: 在ViewModel中创建 LiveData
实例 (ViewModel组件会在下期讲到,将LiveData存储至viewModel中,是为了符合MVVM架构思想,V层仅负责展示,而VM层负责数据逻辑)
public class ViewModelTest extends AndroidViewModel {
public final MutableLiveData<String> name = new MutableLiveData<>();
...
}
step3: 在Activity或Fragment中对LiveData进行添加观察者进行监听
public class ActivityTest extends AppCompatActivity {
...
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
viewModel.name.observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable String name) {
// dosomething...
}
});
}
}
我们可以根据LiveData值的变化来做对应的事情,且不用担心生命周期越界的问题。
LiveData核心方法
方法名 | 作用 |
observe(LifecycleOwner owner,Observer observer) | 注册和宿主生命周期关联的观察者 |
observeForever(Observer observer) | 注册观察者,不会反注册,需自行维护 |
setValue(T data) | 发送数据,没有活跃的观察者时不分发。只能在主线程。 |
postValue(T data) | 和setValue一样。不受线程环境限制, |
onActive | 当且仅当有一个活跃的观察者时会触发 |
inActive | 不存在活跃的观察者时会触发 |
LiveData的衍生类及功能
MutableLiveData
该类十分简单,主要开放了LiveData的发消息接口
public class MutableLiveData<T> extends LiveData<T> {
@Override
public void postValue(T value) {
super.postValue(value);
}
@Override
public void setValue(T value) {
super.setValue(value);
}
}
设计初衷:考虑单一开闭原则,LiveData只能接受消息,避免拿到LiveData对象既能发消息也能收消息的混乱使用。
MediatorLiveData
合并多个LiveData, 即一对多统一观察,一个经典的场景是:在向服务器请求数据时,优先展示本地数据库的数据,然后由请求的响应决定是否要更新数据库,如下图所示:
// ResultType: Type for the Resource data.
// RequestType: Type for the API response.
public abstract class NetworkBoundResource<ResultType, RequestType> {
// MediatorLiveData 数据组合者
private final MediatorLiveData<Resource<ResultType>> result = new MediatorLiveData<>();
private Executor executor;
@MainThread
protected NetworkBoundResource(Executor mExecutor) {
this.executor = mExecutor;
// 首先初始化一个Loading的status 空result
result.setValue(Resource.loading(null));
// 然后从数据库中获取持久化数据
LiveData<ResultType> dbSource = loadFromDb();
// 数据组合者监听数据库中的数据
result.addSource(dbSource, data -> {
// dbSource第一次回调,用来判断数据有效期,此时取消监听
result.removeSource(dbSource);
// 业务自行定义是否需要fetch最新的数据
if (shouldFetch(data)) {
fetchFromNetwork(dbSource);
} else {
// 数据有效,重新观察一次,观察者会立马收到一次回调(LiveData粘性事件机制)
result.addSource(dbSource, newData -> result.setValue(Resource.success(newData)));
}
});
}
private void fetchFromNetwork(final LiveData<ResultType> dbSource) {
LiveData<ApiResponse<RequestType>> apiResponse = createCall();
// 这里数据虽无效,但是可以先给UI展示
result.addSource(dbSource, newData -> setValue(Resource.loading(newData)));
result.addSource(apiResponse, response -> {
result.removeSource(apiResponse);
result.removeSource(dbSource);
if (response != null) {
if (response.isSuccessful()) {
executor.execute(() -> {
saveCallResult(processResponse(response));
executor.execute(() ->
// 这里我们拿到的最新的数据需要主动通知监听者,以拿到从服务端拿到的最新数据
result.addSource(loadFromDb(),
newData -> setValue(Resource.success(newData)))
);
});
} else {
onFetchFailed();
result.addSource(dbSource,
newData -> setValue(Resource.error(response.errorMessage, newData)));
}
} else {
result.addSource(dbSource,
newData -> setValue(Resource.error("Request failed, server didn't response", newData)));
}
});
}
}
上面的例子MediatorLiveData同时监听了数据库中的LiveData和服务端的LiveData
Transformations
这是一个数据转化工具类,共两个主要方法:
- 静态转化 -- map(@NonNull LiveData source, @NonNull final Function<X, Y> mapFunction)
MutableLiveData<Integer> data = new MutableLiveData<>();
//数据转换
LiveData<String> transformData = Transformations.map(data, input -> String.valueOf(input));
//使用转换后生成的transformData去观察数据
transformData.observe( this, output -> {
});
//使用原始的livedata发送数据
data.setValue(10);
- 动态转化 -- LiveData switchMap(
@NonNull LiveData source,
@NonNull final Function<X, LiveData> switchMapFunction)
MutableLiveData userIdLiveData = ...;
// 用户数据和用户id紧密相关,当我们改变userId的liveData的同时还会主动通知userLiveData更新
LiveData userLiveData = Transformations.switchMap(userIdLiveData, id ->
repository.getUserById(id));
void setUserId(String userId) {
this.userIdLiveData.setValue(userId);
}
// 不要像下面这么做
private LiveData getUserLiveData(String userId) {
// DON'T DO THIS
return repository.getUserById(userId);
}
四. LiveData的实现机制
LiveData注册观察者触发消息分发流程:
- observe 注册时,可以主动跟宿主生命周期绑定,不用反注册:
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<? super T> observer) {
//1. 首先来个断言,这个方法只能在主线程调用,observeForever也是。
assertMainThread("observe");
//2.其次把注册进来的observer包装成 一个具有生命周边边界的观察者
//它能监听宿主被销毁的事件,从而主动的把自己反注册,避免内存泄漏
//此时观察者是否处于活跃状态就等于宿主是否可见
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
//3.接着会判断该观察是否已经注册过了,如果是则抛异常,所以要注意,不允许重复注册
ObserverWrapper existing = mObservers.putIfAbsent(observer, wrapper);
if (existing != null && !existing.isAttachedTo(owner)) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
//4.这一步才是关键
//利用Lifecycle,把观察者注册到进去,才能监听到宿主生命周期状态的变化,对不对?
//根据Lifecycle文章中的分析,一旦一个新的观察者被添加,Lifecycle也会同步它的状态和宿主一致对不对?此时会触发观察者的onStateChanged方法
owner.getLifecycle().addObserver(wrapper);
}
- LifecycleBoundObserver 监听宿主的生命周期(这里我们还记得之前在Lifecycle解析中提到addObserver时也会对observer进行包装,这时一样的),并且宿主不可见时不分发任何数据:
class LifecycleBoundObserver extends ObserverWrapper implements LifecycleEventObserver {
LifecycleBoundObserver(@NonNull LifecycleOwner owner, Observer<? super T> observer) {
super(observer);
}
@Override
boolean shouldBeActive() {
//使用observer方法注册的观察者都会被包装成LifecycleBoundObserver
//观察者是否活跃就等于宿主 的状态是否大于等于STARTED,
//如果页面当前不可见,你发送了一条消息,此时是不会被分发的,可以避免后台任务抢占资源,当页面恢复可见才会分发。
//注意:如果使用observerForever注册的观察者,
//会被包装成AlwaysActiveObserver,它的shouldBeActive一致返回true.即便在页面不可见也能收到数据
return mOwner.getLifecycle().getCurrentState().isAtLeast(STARTED);
}
@Override
public void onStateChanged(@NonNull LifecycleOwner source,
@NonNull Lifecycle.Event event) {
//在这里如果监听到宿主被销毁了,则主动地把自己从livedata的观察者中移除掉
if (mOwner.getLifecycle().getCurrentState() == DESTROYED) {
removeObserver(mObserver);
return;
}
//否则说明宿主的状态发生了变化,此时会判断宿主是否处于活跃状态
activeStateChanged(shouldBeActive());
}
}
- ObserverWrapper 状态变更后,如果观察者处于活跃状态会触发数据的分发流程:
abstract class ObserverWrapper{
final Observer<? super T> mObserver;
boolean mActive;
int mLastVersion = START_VERSION//这里就等于-1,没有主动和LiveData的mVersion对齐,为黏性事件埋下了伏笔
void activeStateChanged(boolean newActive) {
if (newActive == mActive) {
return;
}
//更改观察者的状态
mActive = newActive;
boolean wasInactive = LiveData.this.mActiveCount == 0;
//如果此时有且只有一个活跃的观察者则触发onActive
LiveData.this.mActiveCount += mActive ? 1 : -1;
if (wasInactive && mActive) {
onActive();
}
//没有任何一个活跃的观察者则触发onInactive
//利用这个方法被触发的时机,可以做很多事,比如懒加载,资源释放等
if (LiveData.this.mActiveCount == 0 && !mActive) {
onInactive();
}
//如果此时观察者处于活跃状态,下面就开始分发数据了
//请注意,这里传递了this = observer
if (mActive) {
dispatchingValue(this);
}
}
}
- dispatchingValue 数据分发流程控制:
void dispatchingValue(@Nullable ObserverWrapper initiator) {
if (mDispatchingValue) {
mDispatchInvalidated = true;
return;
}
mDispatchingValue = true;
do {
mDispatchInvalidated = false;
if (initiator != null) {
//如果传递的观察者不为空,则把数据分发给他自己。这个流程是新注册观察者的时候会被触发
considerNotify(initiator);
initiator = null;
} else {
//否则遍历集合中所有已注册的的观察者,逐个调用considerNotify,分发数据
for (Iterator<Map.Entry<Observer<? super T>, ObserverWrapper>> iterator =
mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
considerNotify(iterator.next().getValue());
if (mDispatchInvalidated) {
break;
}
}
}
} while (mDispatchInvalidated);
mDispatchingValue = false;
}
- considerNotify 数据真正分发的地方,需要满足三个套件:
private void considerNotify(ObserverWrapper observer) {
//观察者当前状态不活跃不分发
if (!observer.mActive) {
return;
}
//观察者所在宿主是否处于活跃状态,否则不分发,并且更改观察者的状态为false
if (!observer.shouldBeActive()) {
observer.activeStateChanged(false);
return;
}
//此处判断观察者接收消息的次数是否大于等于 发送消息的次数
//但是observer被创建之初verison=-1
//如果此时LiveData已经发送过数据了。这里就不满足了,就出现黏性事件了,后注册的观察者收到了前面发送的消息。
if (observer.mLastVersion >= mVersion) {
return;
}
//每分发一次消息,则把观察者和LiveData的version对齐,防止重复发送
observer.mLastVersion = mVersion;
//最后的数据传递
observer.mObserver.onChanged((T) mData);
}
普通消息分发流程。即调用 postValue,setValue 才会触发消息的分发:
五. 总结
Android开发大部分主要的工作就是从服务器获取数据,将其转为渲染为UI展示给用户。本质上我们所做的逻辑都是“数据驱动”,所有的View都是数据的一种状态,数据映射的过程中我们需要去考虑生命周期的限制--即数据的活跃性。LiveData结合Lifecycle帮我们规范了这个流程,完美诠释了observer、lifecycle-aware、data holder 这个铁三角,开发者在遵循这个开发流程的过程中,便完成了UI -> ViewModel -> Data的单项依赖。