Android LiveData 使用及问题

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,也同时说一下我碰到的一些坑(也许是我不太会用)

最初的想法是

  1. UI层发出请求事件
  2. 通过ViewModel把事件传递到Repository(数据仓库)
  3. 通过数据仓库Repository ,再调用网络接口
  4. 网络数据返回后是以LiveData接收,通知LiveData更新
  5. 再通过LiveData的观察者模式,更新UI

这个工作流程并没有什么问题,也确实实现了但!!!!!

因为注册和登录都是管理一套User信息,所以我用了同一个数据仓库

这样问题就来了

一个LiveData绑定两个UI的时候,芜湖!!! 只要另一个UI对LiveData进行了订阅

只要LiveData中含有数据,就会立刻通知新注册的UI

数据倒灌(粘性事件)这个问题网上有很多解决方案,而我还是使用了一个暴力的方案,就是LiveDataBus

4.回到最后,LiveData这类的消息总线为什么一直是开发人员永远的痛

从最开始的广播,到eventbus,再到我们自己也尝试写过观察者模式的事件总线,现在的livedata,和livedatabus。他们到底是为了解决什么问题而出现的。

无论是套着响应式编程的皮,还是用无数方法去优化体验,通过绑定UI的生命周期来减少代码量,最终还是

  1. 单点对单点通知
  2. 单点对多点通知(广播)

再结合实际的业务场景,本人觉得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() {

                }
            });
}

你可能感兴趣的:(Android LiveData 使用及问题)