LiveData
由于最近在写一个新项目,所以突发奇想,是不是能用Jetpack来组合一个项目呢
这里只介绍LiveData部分,剩余的一些组件库后续还会介绍。
首先,用这个组件库的前提是几个问题:
1.为什么要用LiveData
2.有什么库可以替代它,或者它能替代什么现有的库
3.优点以及缺点
4.如果我来实现这个框架能不能更完美
1.为什么要使用LiveData
在google的Jetpack demo上有这样一句话 LiveData-构建数据对象,这些数据对象在基础数据库更改时通知视图
而在官方的文档里 : LiveData
是一种可观察的数据存储器类。与常规的可观察类不同,LiveData 具有生命周期感知能力,意指它遵循其他应用组件(如 Activity、Fragment 或 Service)的生命周期。这种感知能力可确保 LiveData 仅更新处于活跃生命周期状态的应用组件观察者。
中,可以看到它是这样使用的
// PlantListFragment.kt
viewModel.plants.observe(viewLifecycleOwner) { plants ->
adapter.submitList(plants) // 这里是数据更新操作
}
// PlantListViewModel.kt
private val growZone: MutableStateFlow = MutableStateFlow(
savedStateHandle.get(GROW_ZONE_SAVED_STATE_KEY) ?: NO_GROW_ZONE
) // 这里使用的是 kotlinx.coroutines.flow 的功能 以后再说明
//这里的含义是 当“growZone”更改时,将新值存储在“savedStateHandle”中
val plants: LiveData> = growZone.flatMapLatest { zone ->
if (zone == NO_GROW_ZONE) {
plantRepository.getPlants() // 这里是通过room返回的一个Flow>
} else {
plantRepository.getPlantsWithGrowZoneNumber(zone)
}
}.asLiveData()
viewModelScope.launch {// 这里是kotlin的协程
growZone.collect { newGrowZone -> // 如果熟悉 RxJava 的话,则可以理解为 collect() 对应subscribe(),而 emit() 对应onNext()
savedStateHandle.set(GROW_ZONE_SAVED_STATE_KEY, newGrowZone)
}
}
// PlantDao
@Query("SELECT * FROM plants ORDER BY name")
fun getPlants(): Flow> // 这是room里的方法
由上方可以看出plants是一个LiveData>, 它使用observe绑定了当前UI的生命周期的同时,当数据变化时,将通过Observer通知UI进行响应。
2.有什么库可以替代它,或者说它能替代什么库
LiveData 官方罗列了7大优点
1.确保界面符合数据状态 (观察者模式)
2.不会发生内存泄漏 (它是绑定了Lifecycle的,但是!!!要注意它是会被回收的)
3.不会因 Activity 停止而导致崩溃(这也是绑定生命周期的优势,如果被挂起,它是不会收到消息的)
4.不再需要手动处理生命周期(基于UI的生命周期变化,实现一个动态的数据管理)
5.数据始终保持最新状态
6.适当的配置更改(屏幕旋转时,重构UI,会收到最新的消息)
7.共享资源(单例模式的使用)
从以上优点,我们可以看到,它其实就很类似Vue中的Store,是一个连通Data和View工具。更多的是专门为android优化了view的生命周期管理,看上去像是经历过之前Fragment和Activity生命周期混乱年代的老码农们的新世纪福音。
从对UI生命周期的天然适配,看上去确实是有一点亲儿子的优势,但是我EventBus其实也可以通过注册和释放实现大致相同的功能(但它不够优雅)
3.优点已经说过了,现在聊聊缺点
抛开应用场景聊框架(工具)就没有任何意义:
下面是一个最简单的注册+登录的demo,也同时说一下我碰到的一些坑(也许是我不太会用)
最初的想法是
- UI层发出请求事件
- 通过ViewModel把事件传递到Repository(数据仓库)
- 通过数据仓库Repository ,再调用网络接口
- 网络数据返回后是以LiveData接收,通知LiveData更新
- 再通过LiveData的观察者模式,更新UI
这个工作流程并没有什么问题,也确实实现了但!!!!!
因为注册和登录都是管理一套User信息,所以我用了同一个数据仓库
这样问题就来了
一个LiveData绑定两个UI的时候,芜湖!!! 只要另一个UI对LiveData进行了订阅
只要LiveData中含有数据,就会立刻通知新注册的UI
数据倒灌(粘性事件)这个问题网上有很多解决方案,而我还是使用了一个暴力的方案,就是LiveDataBus
4.回到最后,LiveData这类的消息总线为什么一直是开发人员永远的痛
从最开始的广播,到eventbus,再到我们自己也尝试写过观察者模式的事件总线,现在的livedata,和livedatabus。他们到底是为了解决什么问题而出现的。
无论是套着响应式编程的皮,还是用无数方法去优化体验,通过绑定UI的生命周期来减少代码量,最终还是
- 单点对单点通知
- 单点对多点通知(广播)
再结合实际的业务场景,本人觉得LiveData(ViewModel 和 View 的双向绑定) + LiveDataBus (数据广播)再同时绑定UI生命周期是现阶段的一个解决方案 下面上一部分代码
// RegisterActivity.java
// 注册页面状态更变通知 这里使用的是LiveData
mViewModel.getRegisterState().observe(this, state -> {
switch (state) {
case ERROR_CUSTOMER_SUCCESS_PASS:
mLoading.getObj().show(); // 通过校验 开始网络请求
break;
case ERROR_CUSTOMER_PASSWORD_ERROR: // 账号错误
case ERROR_CUSTOMER_USERNAME_ERROR: // 密码错误
case ERROR_CUSTOMER_REPEAT_ERROR: // 账号密码不一致
ToastUtil.showToast(this, TCErrorConstants.getErrorInfo(state));
break;
}
});
// 注册接口回调通知 这里使用的是LiveEventBus
LiveEventBus.get(RequestTags.REGISTER_REQ, BaseResponBean.class)
.observe(this, bean -> {
Optional.ofNullable(mLoading).ifPresent(builder -> mLoading.getObj().dismiss()); // 取消 Loading
if (bean != null && bean.getCode() == 200) { // 注册成功 就开始自动登录
ToastUtil.showToast(RegisterActivity.this, "注册成功!");
mLoading.setMessage(getString(R.string.login_loading_text)).create().show(); // 显示登录中的loading
mViewModel.login(mDataBinding.registerUserNameEdt.getText().toString().trim() // 注册成功后 进行登录请求
, mDataBinding.registerPasswordEdt.getText().toString().trim());
} else {
ToastUtil.showToast(RegisterActivity.this, "注册失败:" + TCErrorConstants.getErrorInfo(bean.getCode()));
}
});
// 点击注册按钮
RxView.clicks(mDataBinding.submitBtn)
.to(AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(this)))
.subscribe(unit -> mViewModel.register(mDataBinding.registerUserNameEdt.getText().toString().trim()
, mDataBinding.registerPasswordEdt.getText().toString().trim()
, mDataBinding.repeatRegisterPasswordEdt.getText().toString().trim()));// 调用注册接口
// RegisterViewModel.java
private final MutableLiveData registerState = new MutableLiveData<>(); // 注册状态
public void register(String userName, String passWord, String repeatPassword){
if(checkInfo(userName, passWord, repeatPassword)){ // 通过格式校验
loginRepository.registerReq(lifecycleOwner, userName, passWord);
registerState.postValue(ERROR_CUSTOMER_SUCCESS_PASS); // 通过校验
}
}
// LoginRepository.java 登录和注册共用一套数据仓库
public void registerReq(LifecycleOwner lifecycleOwner,String username, String password) {
LoginRequestBuilder.registerFlowable(username, password)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.to(AutoDispose.autoDisposable(AndroidLifecycleScopeProvider.from(lifecycleOwner)))
.subscribe(new DisposableSubscriber() {
@Override
public void onNext(BaseResponBean registerBean) {
if (registerBean != null) {
LiveEventBus.get(RequestTags.REGISTER_REQ, BaseResponBean.class)
.post(new BaseResponBean<>(registerBean.getCode(), registerBean.getMessage())); // 页面要处理的逻辑(登录返回)
}
}
@Override
public void onError(Throwable t) {
}
@Override
public void onComplete() {
}
});
}