Android:LiveData postValue导致数据丢失问题,及其原因
關于這個問題,網上很多,有一篇文章還詳細列舉了幾種情況,寫的非常直觀: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<String>() {@Overridepublic void onChanged(xxx s) {在這邊實現數據的使用} });?這個時候,對于我來說,概念上的偏差就來了,我是將它作為MVP中V和P之間交互的替代品,那么它就是作為一個數據通知功能。把它當成一種事件傳遞,數據通知的工具會出現什么問題呢?
接下來看一個簡單的例子,來看看它作為界面數據持有的功能,功能很簡單:界面上一個TextView,兩個Button,一個按鈕旋轉屏幕,讓activity重建,一個按鈕生成String數據,并將數據設置到TextView上:
上代碼,代碼中使用了封裝的框架,這個框架源自github上的一個項目,我拿來修改重新封裝更適合自己使用,功能上大致能猜個八九不離十,之前想寫這個框架博客的,但是太忙了。
首先界面 layout:
<?xml version="1.0" encoding="utf-8"?> <layout xmlns:android="http://schemas.android.com/apk/res/android"xmlns:app="http://schemas.android.com/apk/res-auto"xmlns:tools="http://schemas.android.com/tools"><data><variablename="viewModel"type="com.zh.mvvmui.viewmodel.TestViewModel" /></data><androidx.constraintlayout.widget.ConstraintLayoutandroid:layout_width="match_parent"android:layout_height="match_parent"tools:context=".mvvmui.activity.TestActivity"><TextViewandroid:id="@+id/tv_test"android:layout_width="wrap_content"android:layout_height="wrap_content"android:textSize="15sp"android:textColor="@color/black"android:layout_marginTop="20dp"app:layout_constraintStart_toStartOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintBottom_toTopOf="@+id/bt_change"/><Buttonandroid:id="@+id/bt_change"android:layout_width="120dp"android:layout_height="40dp"android:text="旋轉屏幕"app:layout_constraintStart_toStartOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintTop_toTopOf="parent"app:layout_constraintBottom_toBottomOf="parent" /><Buttonandroid:id="@+id/bt_set_text"android:layout_width="120dp"android:layout_height="40dp"android:text="設置文字"app:layout_constraintStart_toStartOf="parent"app:layout_constraintEnd_toEndOf="parent"app:layout_constraintTop_toBottomOf="@+id/bt_change"app:layout_constraintBottom_toBottomOf="parent" /></androidx.constraintlayout.widget.ConstraintLayout> </layout>當然,LiveData也支持android DataBinding,寫成android:text="@{viewModel.testLiveData}",只是為了更直觀的觀察它調用情況,不使用它。
ViewModel類:
public class TestViewModel extends BaseViewModel {public MutableLiveData<String> testLiveData = new MutableLiveData<>();public TestViewModel(@NonNull Application application) {super(application);}public void setTestString(String str) {Handler handler = new Handler();handler.postDelayed(new Runnable() {@Overridepublic void run() {testLiveData.postValue(str);}}, 1000);}@Overridepublic void onCreate() {Log.d("TEST--activity", "onCreate");}@Overridepublic void onResume() {Log.d("TEST--activity", "onResume");}@Overridepublic void onPause() {Log.d("activity", "onPause");}@Overridepublic void onDestroy() {Log.d("TEST--activity", "onDestroy");} }因為我實現了LifecycleObserver接口方法,所以可以直接重寫onCreate這些方法。然后setTestString模擬網絡延時數據。
?
Acitivity類:
public class TestActivity extends BaseActivity<ActivityTestBinding, TestViewModel> {@Overrideprotected void initData() {}@Overrideprotected void initViewObservable() {viewModel.testLiveData.observe(this, new Observer<String>() {@Overridepublic void onChanged(String s) {Log.d("TEST--liveData", "---觀察到了數據改變---");binding.tvTest.setText(s);}});binding.btSetText.setOnClickListener(new View.OnClickListener() {@Overridepublic void onClick(View v) {//binding.tvTest.setText("直接設置文字");viewModel.setTestString("設置liveData數據,觀察該LiveData,在其改變時,更新UI");}});binding.btChange.setOnClickListener(new View.OnClickListener() {@SuppressLint("SourceLockedOrientationActivity")@Overridepublic void onClick(View v) {if (getRequestedOrientation() == ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE) {setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT);} else {setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);}}});}@Overridepublic TestViewModel initViewModel() {return new ViewModelProvider(this,new ViewModelProvider.AndroidViewModelFactory(getApplication())).get(TestViewModel.class);}@Overridepublic int initContentView() {return R.layout.activity_test;}@Overridepublic 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() {@Overridepublic 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之前的數據)
總結
以上是生活随笔為你收集整理的Android:LiveData postValue导致数据丢失问题,及其原因的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 关于人脸识别,Taylor Swift是
- 下一篇: Java:爬取代理ip,并使用代理IP刷