LiveData may lost Data

声明: 本文翻译自Medium 文章

LiveData可能会丢失数据。在你说“没关系”之前,请检查所有的情况。

LiveData是谷歌的Android架构中最引人注目的明星组件之一。基本上,它是一个可观察的数据持有者,并在观察者处于活动状态时将数据更改发送给观察者。在Android应用程序中,视图通常附加到生命周期感知组件,如片段或活动。然而,生命周期感知组件可能被杀死、重新创建或分离(片段),这使得访问视图变得危险和复杂。为了使视图层操作更加友好,LiveData向它的观察者引入了生命周期感知。每个观察者可以与一个LifecycleOwner相关联,通常是一个片段或活动,并防止在其LifecycleOwner不活动时将数据更改发送给观察者。此行为可以确保在执行不是“必要的”情况下,观察者中的逻辑不会被执行。例如,如果一个片段正在分离,它的视图将很快被销毁。在分离过程中,这个片段视图上的任何视图层操作都是不可见的,也是不必要的。相反,LiveData缓冲最后的数据更改,并在片段重新连接和重新创建视图时通知观察者,这可以将新视图实例“恢复”到最新状态,并防止潜在的NPE或内存泄漏。
但是,由于LiveData在主线程上分发数据,并且只缓冲一个挂起的数据更改,所以您的观察者很少会错过或没有接收到数据更改。尽管谷歌的文档从未将LiveData定义为类似于rxjava的流框架,也没有建议使用LiveData,只是在视图和VM之间建立一个薄薄的粘合层,但我们仍然有必要研究所有可能的数据丢失场景,并避免生产中可能出现的意外行为。在下一节中,我们将通过示例讨论三种数据丢失情况。

将数据发送到活动状态之外

LiveData是一个支持生命周期的组件。因此,只有在其LifecycleOwner处于活动状态时,才能将数据更改交付给观察者。如下图所示,当一个LifecycleOwner切换到“STARTED”或“resume”时,它是活动的。因此,LiveData观察者只能在其LifecycleOwner“启动”或“恢复”时接收数据更改更新。如果数据源将数据发送到活动状态之外的LiveData,则观察者将不会接收发送的数据。

图片

假设我们有一个活动,它在其生命周期回调中将数据写入MutableLiveData。一个观察者正在观察MutableLiveData并打印出接收到的数据。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43

public class ActiveGapActivity extends BaseActivity {

private static MutableLiveData<String> mutableLiveData = new MutableLiveData<>();

@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mutableLiveData.observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable String string) {
System.out.println("ActiveGapActivity Observed: " + string);
}
});
mutableLiveData.setValue("onCreate");
}

@Override
public void onResume() {
super.onResume();
mutableLiveData.setValue("onResume");

}

@Override
public void onPause() {
super.onPause();
mutableLiveData.setValue("onPause");
}

@Override
public void onStop() {
super.onStop();
System.out.println("ActiveGapActivity onStop called");
mutableLiveData.setValue("onStop");
}

@Override
public void onDestroy() {
super.onDestroy();
mutableLiveData.setValue("onDestroy");
}
}

如果我们旋转app并触发Activity recreation,会打印如下日志:

1
2
3
4
5
6
7
8
9
10
ActiveGapActivity Observed: onCreate
ActiveGapActivity Observed: onResume
ActiveGapActivity Observed: onPause
ActiveGapActivity onStop called <== onStop() is called
ActiveGapActivity Observed: onCreate
ActiveGapActivity Observed: onResume
ActiveGapActivity Observed: onPause
ActiveGapActivity onStop called
ActiveGapActivity Observed: onCreate
ActiveGapActivity Observed: onResume

如日志所示,观察者没有接收到“onStop”,这肯定是被调用的,因为“ActiveGapActivity onStop调用”被打印出来了。这种行为是由两个LiveData礼:

  • LiveData可以缓冲一个数据实例;
  • LiveData在其生命周期中不会通知观察者,而cleowner则处于“非活动”状态。

当我们旋转屏幕时,活动将被重新创建。将顺序调用当前活动实例的onStop()和onDestroy()。但是,由于活动不再活动,发出的数据将不会传递给观察者。而是在内部缓冲最新的数据实例,即“onDestroy”。当活动被重新创建时,第二个活动实例将发出“onCreate”,并覆盖之前的缓冲数据“onDestroy”。一旦活动(即LifecycleOwner)恢复活动,观察者将只接收缓冲的“onCreate”。onStop和onDestroy都丢失了。
下图显示了在第二个活动的onCreate()中调用LiveData的setValue函数时的调试信息。显然,新的数据实例“onCreate”覆盖了先前由第一个实例的onDestroy()发出的缓冲数据实例(“mData”)。

图片

从后台线程发出数据

LiveData在主线程上调度数据。如果源从后台线程发出数据,则下游使用者不会立即收到更新。相反,发出的数据将在LiveData中进行缓冲,并等待主线程稍后进行调度。但是,由于LiveData只缓冲一个数据实例,如果数据源发出的数据比主线程分派的数据“更快”。新数据实例可能会覆盖已缓存的数据实例,从而导致数据丢失。
让我们来看看下面的例子。在onResume()中启动一个AsyncTask,并向LiveData发送三个数据实例;在“onCreate”中初始化输出数据的观察者。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
public class PostValueSampleActivity extends BaseActivity {

private MutableLiveData<Integer> source;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

source = new MutableLiveData<>();
source.observe(this, new Observer<Integer>() {
@Override
public void onChanged(@Nullable Integer integer) {
append(integer.toString());
}
});
}

@Override
protected void onResume() {
super.onResume();


AsyncTask.execute(new Runnable() {
@Override
public void run() {
source.postValue(1);
source.postValue(2);
source.postValue(3);
}
});
}
}

示例代码将只输出“3”,这是AsyncTask发出的最后一个数据。这个输出背后的魔力可以在LiveData的源代码中找到。

1
2
3
4
5
6
7
8
9
10
11
protected void postValue(T value) {
boolean postTask;
synchronized (mDataLock) {
postTask = mPendingData == NOT_SET;
mPendingData = value;
}
if (!postTask) {
return;
}
ArchTaskExecutor.getInstance().postToMainThread(mPostValueRunnable);
}

当一个后台线程向LiveData发送数据时,LiveData会将一个标记、postTask标记为true;发布一个runnable到主线程,将数据更改发送给观察者。如果后台线程发出数据“太频繁”(在主线程可运行完成之前),新发布的数据将覆盖“mPendingData”。当主thead执行runnable并尝试分发数据更改时,它将获得最后的数据,即“mPendingData”保存的数据。

在观察者中发射数据

由于LiveData在主线程上调度数据,所以在调度已经启动时,所有的事情都是(某种程度上)同步的。所有观察者将被迭代,并依次接收更新。但是,如果在观察者内部设置新的数据更改,则新的数据更改(我们称之为“B”)将覆盖当前正在调度的数据更改(我们称之为“A”)并终止当前调度周期。一些观察者可能不会永远得到“A”。
假设有两个观察者。观察者2打印接收到的内容,但是如果接收到的数字是偶数,观察者1将把新值设置回LiveData。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
public class SetValueSampleActivity extends BaseActivity {

private MutableLiveData<Integer> source;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

source = new MutableLiveData<>();

source.observe(this, new Observer<Integer>() {
@Override
public void onChanged(@Nullable Integer integer) {
append("Receiver 1: " + integer.toString());
if (integer % 2 == 0) {
source.setValue(integer * 10);
source.setValue(integer * 10 + 1);
}
}
});
source.observe(this, new Observer<Integer>() {
@Override
public void onChanged(@Nullable Integer integer) {
append("Receiver 2: " + integer.toString());
}
});
}

@Override
protected void onResume() {
super.onResume();

source.setValue(1);
source.setValue(2);
source.setValue(3);
}
}

此示例的输出如下图所示。观察者1收到1 2 21 3,观察者2收到1 21 3。

图片

这种行为背后的原因有点复杂。让我们检查LiveData中的“dispatchingValue()”函数。从第2行到第6行可以看出,如果调度已经启动,任何新的数据集都不会启动另一个调度循环。相反,新的数据更改将覆盖挂起的数据(在setValue()函数中),将“mDispatchInvalidated”设置为true并跳过(从第2行到第5行)。在我们的示例中,当观察者1接收到“2”时,它将“20”和“21”设置回LiveData。因为“mDispatchingValue”为真,所以“20”将不会被分派,而是被“21”覆盖。观察者1的逻辑完成后,由于“mDispatchInvalidated”为真,它将中断当前调度循环(从第13行到第19行,并停止观察者2接收“2”),并使外部while循环再运行一次(从第7行到第21行)。在这个调度循环中,所有的观察者接收最后的数据“21”。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
private void dispatchingValue(@Nullable ObserverWrapper initiator) {
if (mDispatchingValue) {
mDispatchInvalidated = true;
return;
}
mDispatchingValue = true;
do {
mDispatchInvalidated = false;
if (initiator != null) {
considerNotify(initiator);
initiator = null;
} else {
for (Iterator<Map.Entry<Observer<T>, ObserverWrapper>> iterator =
mObservers.iteratorWithAdditions(); iterator.hasNext(); ) {
considerNotify(iterator.next().getValue());
if (mDispatchInvalidated) {
break;
}
}
}
} while (mDispatchInvalidated);
mDispatchingValue = false;
}

github code↓↓
https://github.com/hanyuliu/livedata_may_lose_data

============ END ============