关于这个问题,网上很多,有一篇文章还详细列举了几种情况,写的非常直观:https://www.jianshu.com/p/aa24dd9123a1

我写的此文章比较多的个人想法,需要自己思考一下。

我碰到的实际情况是:

使用阿里RTC实时音视频服务,我把音视频操作和回调都写在了ViewModel中,在同一房间内,已经有人的情况下,在自己加入房间时,会触发阿里SDK事件通知回调onRemoteUserOnLineNotify,告诉我当前房间存在的人,因为回调都是在非主线程里,然后我通过LiveData.postValue通知到UI有人加入,我在recyclerView的adapter将数据add进去,有几个人回调几次此方法。在不止一人的情况下,就会几乎同时的多次调用LiveData.postValue,从而导致我只观察到了最后一个postValue。

我不想先讲这个问题的解决办法,我想先谈谈:为什么会出现这个问题?

据我猜测,碰到这个问题的大多数使用情况应该和我上面的差不多,都是获取数据,并且将数据添加到列表中。不知道猜的对不对?

那么出现这个问题的原因,是你对于LiveData的认知,是LiveData的概念问题。在你而言,LiveData是用于事件通知呢,还是一个activity的数据持有类。LiveData的正确使用方式是:

作为可以被观察的数据持有类

 

在MVP架构中,假如增加了功能,那么首先接口层需要增加一个方法定义,View层需要实现其方法,Presenter层调用此方法,把数据回调到UI界面上,其中需要判断activity是否被销毁。这样做能明显看出有2点不足,一是方法定义变多,改动的地方增多,而且实现接口从代码来看,不够直观、二是需要手动控制Presenter的生命周期。

那么在MVVM中,LiveData很好的解决了这个问题,我们不需要写一个接口文件,把方法提前定义好;也不需要自己判断数据更新时UI是否存在。只需要将需要的数据类型包裹在MutableLiveData中,生成它,在activity中观察:

viewModel.liveData.observe(this, new Observer() {    @Override    public void onChanged(xxx s) {        在这边实现数据的使用    }}); 

这个时候,对于我来说,概念上的偏差就来了,我是将它作为MVP中V和P之间交互的替代品,那么它就是作为一个数据通知功能。把它当成一种事件传递,数据通知的工具会出现什么问题呢?

接下来看一个简单的例子,来看看它作为界面数据持有的功能,功能很简单:界面上一个TextView,两个Button,一个按钮旋转屏幕,让activity重建,一个按钮生成String数据,并将数据设置到TextView上:

上代码,代码中使用了封装的框架,这个框架源自github上的一个项目,我拿来修改重新封装更适合自己使用,功能上大致能猜个八九不离十,之前想写这个框架博客的,但是太忙了。

首先界面 layout:

<?xml version="1.0" encoding="utf-8"?>                                    

当然,LiveData也支持android DataBinding,写成android:text="@{viewModel.testLiveData}",只是为了更直观的观察它调用情况,不使用它。

ViewModel类:

public class TestViewModel extends BaseViewModel {    public MutableLiveData testLiveData = new MutableLiveData<>();    public TestViewModel(@NonNull Application application) {        super(application);    }    public void setTestString(String str) {        Handler handler = new Handler();        handler.postDelayed(new Runnable() {            @Override            public void run() {                testLiveData.postValue(str);            }        }, 1000);    }    @Override    public void onCreate() {        Log.d("TEST--activity", "onCreate");    }    @Override    public void onResume() {        Log.d("TEST--activity", "onResume");    }    @Override    public void onPause() {        Log.d("activity", "onPause");    }    @Override    public void onDestroy() {        Log.d("TEST--activity", "onDestroy");    }}

因为我实现了LifecycleObserver接口方法,所以可以直接重写onCreate这些方法。然后setTestString模拟网络延时数据。

 

Acitivity类:

public class TestActivity extends BaseActivity {    @Override    protected void initData() {    }    @Override    protected void initViewObservable() {        viewModel.testLiveData.observe(this, new Observer() {            @Override            public void onChanged(String s) {                Log.d("TEST--liveData", "---观察到了数据改变---");                binding.tvTest.setText(s);            }        });        binding.btSetText.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                //binding.tvTest.setText("直接设置文字");                viewModel.setTestString("设置liveData数据,观察该LiveData,在其改变时,更新UI");            }        });        binding.btChange.setOnClickListener(new View.OnClickListener() {            @SuppressLint("SourceLockedOrientationActivity")            @Override            public void onClick(View v) {                if (getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {                    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);                } else {                    setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);                }            }        });    }    @Override    public TestViewModel initViewModel() {        return new ViewModelProvider(this,                new ViewModelProvider.AndroidViewModelFactory(getApplication()))                .get(TestViewModel.class);    }    @Override    public int initContentView() {        return R.layout.activity_test;    }    @Override    public int initVariableId() {        return BR.viewModel;    }}

这个比较简单,我连Model都没放,单看initViewObservable方法,初始化了按钮的点击方法,观察了ViewModel的testLiveData,应该都比较简单。接下来看log,我的操作是,打开Activity,点击设置文字,再点击旋转屏幕

D/TEST--activity: onCreate  ①D/TEST--activity: onResume  ②D/TEST--liveData: ---观察到了数据改变---  ③D/TEST--activity: onDestroy  ④D/TEST--activity: onCreate   ⑤D/TEST--liveData: ---观察到了数据改变---  ⑥D/TEST--activity: onResume   ⑦我标了个小圆圈数字,比较好说明一下,1和2是在activity启动触发的;3是点击了设置文字按钮后,触发了LiveData观察到的;4和5是屏幕旋转activity被重建;6是在重建时自动触发了LiveData观察7就不说明了

附图:

            

 

可以看出来,LiveData在activity重建时,会把数据重新赋予一次,这就是它本质的功能可以被观察的数据持有类,它持有着界面上的数据,那么在界面重建时,会把数据恢复。

那么再看,假如不用LiveData呢,现在把那个设置文字按钮点击事件更换一下:

binding.btSetText.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                binding.tvTest.setText("直接设置文字");                //viewModel.setTestString("设置liveData数据,观察该LiveData,在其改变时,更新UI");            }        });

    

可以看到,旋转屏幕时,数据丢失了。

因此,LiveData并不是简单的用于事件通知和数据回调。假设像上面RTC例子中我没碰到数据丢失的情况,他们进房间都是一个一个进的,就不会有问题,但是RTC实时音视频界面被重建了,这个时候,LiveData恢复的数据,肯定只有最后进房间的那个人的数据。同样的道理,假如把LiveData当做比如RecyclerView加载更多的数据回调,在界面重建时,恢复的也是部分数据。

这个合理吗?我觉得是合理的,LiveData所持有的数据,就是界面上要展示的数据,最后一次postValue就是你界面上应该展示的数据,所以中间的数据都没发送出去。看看它的源码:

protected void postValue(T value) {        boolean postTask;        synchronized (mDataLock) {            postTask = mPendingData == NOT_SET;            mPendingData = value;        }        if (!postTask) {            return;        }        ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);    }

在这个runnable未被执行前,多次调用postValue,mPendingData就会被多次赋值,所以只有最后一次数据被发送出去了。

 

解决办法:

1、使用setValue,setValue不会造成数据丢失,它每次都会调用,但是这样会有界面重建时数据丢失的隐患。(去除这个隐患的话,就getValue 然后获取到的数据,add,再setValue)

2、保证数据不会一起进来,大部分数据应该都不会同时进来的,所以碰到这种问题的比较小众。

(不能使用getValue获取列表项再add数据,然后postValue的方法,getValue获取的数据是setValue后的mData,在还没被调用到setValue时,你getValue出来的数据,都是在postValue之前的数据)

更多相关文章

  1. “罗永浩抖音首秀”销售数据的可视化大屏是怎么做出来的呢?
  2. Nginx系列教程(三)| 一文带你读懂Nginx的负载均衡
  3. 不吹不黑!GitHub 上帮助人们学习编码的 12 个资源,错过血亏...
  4. [sg] Android(安卓)6.0 添加对Home键的拦截
  5. [android]获取各应用的启动次数和运行时间
  6. 【Android】安卓AVD无法上网解决方案
  7. Android(安卓)Camera API2中采用CameraMetadata用于从APP到HAL的
  8. android事件拦截处理机制图解
  9. android TIPS小结

随机推荐

  1. Android栗子の双击事件
  2. viewModel与recyclerView结合
  3. Android(安卓)adb monkey 测试命令
  4. android kernel content
  5. Android中的数据库操作(保证线程安全)
  6. android:与USB设备通信
  7. compile ffmpeg for android
  8. 使用HttpURLConnection请求数据、上传文
  9. android_service_totoal
  10. Android之ConnectivityManager