为啥新连接的观察者会触发两次 LiveData 观察者

Posted

技术标签:

【中文标题】为啥新连接的观察者会触发两次 LiveData 观察者【英文标题】:Why LiveData observer is being triggered twice for a newly attached observer为什么新连接的观察者会触发两次 LiveData 观察者 【发布时间】:2018-10-18 14:12:56 【问题描述】:

我对@9​​87654325@的理解是,它会触发数据当前状态变化的观察者,而不是数据的一系列历史状态变化。

目前,我有一个MainFragment,它执行Room 写操作,将非垃圾数据更改为垃圾数据

我还有另一个TrashFragment,它会观察到垃圾数据

考虑以下场景。

    目前有 0 个垃圾数据MainFragment 是当前活动片段。 TrashFragment 尚未创建。 MainFragment 添加了 1 个垃圾数据。 现在,有 1 个垃圾数据 我们使用导航抽屉,将MainFragment 替换为TrashFragmentTrashFragment 的观察者将首先收到 onChanged,其中包含 0 个已丢弃数据 同样,TrashFragment 的观察者将第二次收到onChanged,其中包含 1 个已丢弃数据

出乎我意料的是,第 (6) 项不应该发生。 TrashFragment 应该只接收最新的垃圾数据,即 1。

这是我的代码


TrashFragment.java

public class TrashFragment extends Fragment 
    @Override
    public void onCreate(Bundle savedInstanceState) 
        noteViewModel = ViewModelProviders.of(getActivity()).get(NoteViewModel.class);
    

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) 
        ...

        noteViewModel.getTrashedNotesLiveData().removeObservers(this);
        noteViewModel.getTrashedNotesLiveData().observe(this, notesObserver);

MainFragment.java

public class MainFragment extends Fragment 
    @Override
    public void onCreate(Bundle savedInstanceState) 
        noteViewModel = ViewModelProviders.of(getActivity()).get(NoteViewModel.class);
    

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) 
        ...

        noteViewModel.getNotesLiveData().removeObservers(this);
        noteViewModel.getNotesLiveData().observe(this, notesObserver);

NoteViewModel .java

public class NoteViewModel extends ViewModel 
    private final LiveData<List<Note>> notesLiveData;
    private final LiveData<List<Note>> trashedNotesLiveData;

    public LiveData<List<Note>> getNotesLiveData() 
        return notesLiveData;
    

    public LiveData<List<Note>> getTrashedNotesLiveData() 
        return trashedNotesLiveData;
    

    public NoteViewModel() 
        notesLiveData = NoteplusRoomDatabase.instance().noteDao().getNotes();
        trashedNotesLiveData = NoteplusRoomDatabase.instance().noteDao().getTrashedNotes();
    


处理房间的代码

public enum NoteRepository 
    INSTANCE;

    public LiveData<List<Note>> getTrashedNotes() 
        NoteDao noteDao = NoteplusRoomDatabase.instance().noteDao();
        return noteDao.getTrashedNotes();
    

    public LiveData<List<Note>> getNotes() 
        NoteDao noteDao = NoteplusRoomDatabase.instance().noteDao();
        return noteDao.getNotes();
    


@Dao
public abstract class NoteDao 
    @Transaction
    @Query("SELECT * FROM note where trashed = 0")
    public abstract LiveData<List<Note>> getNotes();

    @Transaction
    @Query("SELECT * FROM note where trashed = 1")
    public abstract LiveData<List<Note>> getTrashedNotes();

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    public abstract long insert(Note note);


@Database(
        entities = Note.class,
        version = 1
)
public abstract class NoteplusRoomDatabase extends RoomDatabase 
    private volatile static NoteplusRoomDatabase INSTANCE;

    private static final String NAME = "noteplus";

    public abstract NoteDao noteDao();

    public static NoteplusRoomDatabase instance() 
        if (INSTANCE == null) 
            synchronized (NoteplusRoomDatabase.class) 
                if (INSTANCE == null) 
                    INSTANCE = Room.databaseBuilder(
                            NoteplusApplication.instance(),
                            NoteplusRoomDatabase.class,
                            NAME
                    ).build();
                
            
        

        return INSTANCE;
    

知道如何防止收到onChanged 两次,对于相同的数据?


演示

我创建了一个演示项目来演示这个问题。

如您所见,在我在MainFragment 中执行写入操作(单击添加已删除注释 按钮)后,当我切换到TrashFragment 时,我希望TrashFragment 中的onChanged只会被调用一次。但是,它被调用了两次。

Demo工程可从https://github.com/yccheok/live-data-problem下载

【问题讨论】:

LiveData 立即提供最后一个值(如果之前已发布过值)以及未来的更改。 getTrashedNotesLiveData()的实现是什么?您要从 Room 中返回 LiveData 吗?你在使用 RxJava 和 LiveDataReactiveStreams 吗?这是自定义的LiveData 实现吗?还有什么? 只是来自 Room 的简单 LiveData。我更新了我的问题以提供更多信息。因为在我们切换到TrashFragment 之前,数据库应该写入了 11 个废弃数据。我不确定为什么 TrashFragment 中的观察者会首先收到 10 个已丢弃数据(旧快照),然后是 11 个已丢弃数据(最新快照) 我们可以通过将 ViewModel 修改为 gist.github.com/yccheok/4bb6539c93fa39cf7dc7f08f0752d232 来“防止”这种情况发生。每当在TrashFragmentMainFragment 之间切换之前,我们都会在ViewModel 中调用init。但是,在我们了解问题之前,我们还不想走那条路。 嗨@CommonsWare,我创建了一个演示项目来演示这个问题。不确定您是否愿意指出,代码的哪一部分出错了?谢谢。 您可能会将此示例应用程序提出问题作为可能存在问题的证据。我会摆脱removeObservers() 调用,因为它们在现实世界中不应该是必需的,并且绝对不是重现问题所必需的。 IOW,将演示削减到最低限度以说明问题。感觉就像您最初获得的是缓存值,然后是实际值,尽管我不知道为什么。 【参考方案1】:

我只对您的代码进行了一项更改:

noteViewModel = ViewModelProviders.of(this).get(NoteViewModel.class);

代替:

noteViewModel = ViewModelProviders.of(getActivity()).get(NoteViewModel.class);

FragmentonCreate(Bundle) 方法中。现在它可以无缝运行了。

在您的版本中,您获得了两个片段(来自 Activity)共有的 NoteViewModel 的引用。 ViewModel 在之前的 Fragment 中注册了 Observer,我想。因此LiveData 保留了对Observer 的引用(在MainFragmentTrashFragment 中)并调用了这两个值。

所以我猜结论可能是,你应该从ViewModelProviders 获得ViewModel 来自:

FragmentFragment ActivityActivity

顺便说一句。

noteViewModel.getTrashedNotesLiveData().removeObservers(this);

在 Fragments 中不是必需的,但我建议将其放入 onStop

【讨论】:

但是如果你看官方教程,developer.android.com/topic/libraries/architecture/… 他们建议使用ViewModelProviders.of 的活动和observe 的片段。 Android 官方文档提供了理想的情况,有时只是错误的。我并不是说这是否是一个错误,但有助于防止这种情况发生。 建议的解决方案是错误的,只要您会收到不想要的onChanged() 事件。假设您已经进入过TrashFragment 一次,现在您已经导航到HomeFragment。一旦您启动另一个数据库更改,就会调用TrashFragment 中的订阅,因为您提供了错误的LifecycleOwner(活动仍然存在,而片段不是),因此您违反了@ 的总体目的987654348@ - 当屏幕实际处于活动状态时获取数据。 Cheok Yan Cheng 指出的文档实际上暗示了在片段之间共享视图模型的选项。但目的不同。正如我在回答中指出的那样,我认为两步加载不是要解决的错误,而是解决数据库连接速度慢和轮换等问题的功能。 谢谢兄弟。寻找解决方案很多天。【参考方案2】:

我分叉了您的项目并对其进行了一些测试。据我所知,你发现了一个严重的错误。

为了使复制和调查更容易,我对您的项目进行了一些编辑。你可以在这里找到更新的项目:https://github.com/techyourchance/live-data-problem。我还向您的仓库打开了一个拉取请求。

为确保不会被忽视,我还在 Google 的问题跟踪器中opened an issue:

重现步骤:

    确保在 MainFragment 中将 REPRODUCE_BUG 设置为 true 安装应用程序 点击“添加垃圾笔记”按钮 切换到 TrashFragment 请注意,只有一个通知表单 LiveData 具有正确的值 切换到 MainFragment 点击“添加垃圾笔记”按钮 切换到 TrashFragment 请注意,LiveData 有两个通知,第一个通知的值不正确

请注意,如果您将 REPRODUCE_BUG 设置为 false,则该错误不会 复制。它演示了订阅 LiveData 在 MainFragment 改变了 TrashFragment 中的行为。

预期结果:在任何情况下都只有一个具有正确值的通知。 由于以前的订阅,行为没有变化。

更多信息:我查看了一下来源,看起来像 由于 LiveData 激活和新功能而触发的通知 观察者订阅。可能与 ComputableLiveData 的方式有关 将 onActive() 计算卸载到 Executor。

【讨论】:

谢谢。您的修改使错误更加明显。注意第7步,如果我们把Click on "add trashed note" button改成Click on "add trashed note" button 1 or multiple times,效果会更明显。 刚看到你对票的评论。在提交另一张票之前,我只查看了未解决的问题,因为我从来没有想过像这样严重的事情可以这么容易地被驳回。你做得很好,你的项目清楚地证明了这个问题。我不知道为什么他们甚至懒得检查。 一年过去了,我发现使用奥利奥仍然存在这个问题。【参考方案3】:

原因是在您的 .observe() 方法中,您传递了一个片段作为生命周期所有者。应该传递的是片段的viewLifecycleOwner对象

viewModel.livedata.observe(viewLifecycleOwner, Observer 
        // Do your routine here
    )

【讨论】:

那是我的问题。当我返回上一个片段时,我再次观察视图,该片段没有被破坏。同样有效的解决方案是在 onViewDestory 中删除观察者 @murt viewLifecycleOwner 使 Observer/LiveData 知道生命周期,因此 Observer 将被自动注销,这避免了在 onDestroyView 方法中手动注销的需要【参考方案4】:

这不是错误,而是一项功能。了解原因!

观察者方法void onChanged(@Nullable T t)被调用了两次。没关系。

第一次在启动时调用。 Room 加载数据后第二次调用它。因此,在第一次调用时,LiveData 对象仍然是空的。这样做是有充分理由的。

第二次通话

让我们从第二个电话开始,你的第 7 点。Room 的文档说:

Room 生成更新 LiveData 对象所需的所有代码 当数据库更新时。生成的代码运行查询 需要时在后台线程上异步执行。

生成的代码是其他帖子中提到的ComputableLiveData 类的对象。它管理一个MutableLiveData 对象。在这个LiveData 对象上,它调用LiveData::postValue(T value),然后调用LiveData::setValue(T value)

LiveData::setValue(T value) 呼叫LiveData::dispatchingValue(@Nullable ObserverWrapper initiator)。这将调用 LiveData::considerNotify(ObserverWrapper observer) 并将观察者包装器作为参数。这最终以加载的数据作为参数在观察者上调用onChanged()

第一次通话

现在是第一个电话,您的第 6 点。

你在onCreateView()钩子方法中设置你的观察者。在这一点之后,生命周期将其状态更改为两次可见,on starton resume。内部类LiveData::LifecycleBoundObserver 会在此类状态更改时收到通知,因为它实现了GenericLifecycleObserver 接口,该接口包含一个名为void onStateChanged(LifecycleOwner source, Lifecycle.Event event); 的方法。

此方法调用ObserverWrapper::activeStateChanged(boolean newActive)LifecycleBoundObserver 扩展ObserverWrapper。方法activeStateChanged 调用dispatchingValue(),后者又调用LiveData::considerNotify(ObserverWrapper observer),并将观察者包装器作为参数。这最终会在观察者身上调用onChanged()

所有这些都是在特定条件下发生的。我承认我没有调查方法链中的所有条件。有两次状态变化,但onChanged() 只触发一次,因为条件会检查这样的事情。

这里的底线是,有一个方法链,在生命周期的变化时触发。这负责第一次调用。

底线

我认为您的代码没有任何问题。很好,观察者被要求创造。所以它可以用视图模型的初始数据填充自己。这就是观察者应该做的,即使视图模型的数据库部分在第一次通知时仍然是空的。

用法

第一个通知基本上表明视图模型已准备好显示,尽管它仍未加载来自底层数据库的数据。第二个通知表明,该数据已准备就绪。

当您想到缓慢的数据库连接时,这是一种合理的方法。您可能希望从通知触发的视图模型中检索和显示其他数据,这些数据不是来自数据库。

Android 有一个指南如何处理缓慢的数据库加载。他们建议使用占位符。在这个例子中,差距是如此之短,没有理由进行如此扩展。

附录

两个片段都使用自己的ComputableLiveData 对象,这就是为什么第二个对象没有从第一个片段中预加载的原因。

还要考虑旋转的情况。视图模型的数据不会改变。它不会触发通知。仅生命周期的状态变化就会触发新视图的通知。

【讨论】:

It's just fine, that the observer is called upon creation. So it can fill itself with the initial data of the view model. That's what an observer should do Imo,如果我订阅了一个数据源,我应该得到它的最新状态。您的陈述表明,可以得到一些垃圾(实际上与现实不相符的东西),只需忽略该垃圾即可。我认为这应该被视为故障。 您询问对onChanged() 的第一个通知的调用是否提供了一个空列表。如果 Room 尚未加载,实际上可能就是这种情况。然后在初始通知时视图模型仍然是空的,并且您显示一个空列表。我不会称那是垃圾。这只是它的实施方式。一旦 Room 完成它的工作,它就会触发它的通知并更新列表。如果您观看演示,更新速度很快,您甚至看不到空列表的通知。 LiveData 背后的概念显然是,在数据变化和生命周期状态变化时两次通知您。 诚实吗?我认为可以使用 MediatorLiveDtaa 覆盖内置行为。不知道我写了能不能得到500的声望,哈哈。 大声笑。宽限期在 7 小时后结束。但坦率地说,我认为它做得很好。对我来说没有任何问题,在数据库仍在加载时收到早期通知。很容易检查它是否仍然是空的。【参考方案5】:

我抢走了 Vasiliy 的 fork of your fork of the fork,并进行了一些实际调试,看看会发生什么。

可能与 ComputableLiveData 将 onActive() 计算卸载到 Executor 的方式有关。

关闭。 Room 的 LiveData&lt;List&lt;T&gt;&gt; 暴露的工作方式是它创建一个 ComputableLiveData,它会跟踪您的数据集是否已在 Room 下方失效。

trashedNotesLiveData = NoteplusRoomDatabase.instance().noteDao().getTrashedNotes();

因此,当写入note 表时,绑定到LiveData 的InvalidationTracker 将在发生写入时调用invalidate()

  @Override
  public LiveData<List<Note>> getNotes() 
    final String _sql = "SELECT * FROM note where trashed = 0";
    final RoomSQLiteQuery _statement = RoomSQLiteQuery.acquire(_sql, 0);
    return new ComputableLiveData<List<Note>>() 
      private Observer _observer;

      @Override
      protected List<Note> compute() 
        if (_observer == null) 
          _observer = new Observer("note") 
            @Override
            public void onInvalidated(@NonNull Set<String> tables) 
              invalidate();
            
          ;
          __db.getInvalidationTracker().addWeakObserver(_observer);
        

现在我们需要知道的是,如果 LiveData 处于活动状态ComputableLiveDatainvalidate()实际上刷新数据集。

// invalidation check always happens on the main thread
@VisibleForTesting
final Runnable mInvalidationRunnable = new Runnable() 
    @MainThread
    @Override
    public void run() 
        boolean isActive = mLiveData.hasActiveObservers();
        if (mInvalid.compareAndSet(false, true)) 
            if (isActive)  // <-- this check here is what's causing you headaches
                mExecutor.execute(mRefreshRunnable);
            
        
    
;

liveData.hasActiveObservers() 在哪里:

public boolean hasActiveObservers() 
    return mActiveCount > 0;

所以refreshRunnable 实际上只有在有一个活跃的观察者时才会运行(afaik 意味着生命周期至少已经开始,并观察实时数据)。



这意味着当您订阅 TrashFragment 时,您的 LiveData 会存储在 Activity 中,因此即使 TrashFragment 消失,它也会保持活动状态,并保留以前的值。

但是,当您打开 TrashFragment 时,TrashFragment 订阅,LiveData 变为活动状态,ComputableLiveData 检查失效(这是真的,因为实时数据未激活,因此从未重新计算过),在后台线程上异步计算它,并且完成后,将发布值。

所以你得到两个回调,因为:

1.) 第一个“onChanged”调用是之前在 Activity 的 ViewModel 中保持活动状态的 LiveData 的保留值

2.) 第二个“onChanged”调用是您数据库中新评估的结果集,其中计算是由 Room 中的实时数据变为活动状态触发的。


所以从技术上讲,这是设计使然。如果您想确保只获得“最新和最大”的值,那么您应该使用片段范围的 ViewModel。

您可能还想在 onCreateView() 中开始观察,并使用 viewLifecycle 作为 LiveData 的生命周期(这是一个新增功能,因此您无需删除 onDestroyView() 中的观察者。

如果 Fragment 看到最新值很重要,即使 Fragment 未处于活动状态且未观察它,那么由于 ViewModel 是 Activity 范围的,您可能还希望在 Activity 中注册一个观察者以确保您的 LiveData 上有一个活跃的观察者。

【讨论】:

我不认为 SO 是进行此讨论的地方,但官方问题跟踪器也不是。所以,我会写在这里。我觉得你下结论太快了。 1)某事物是by design 的事实并不意味着它不是错误 2)它已经在onCreateView 中观察到(并不重要) 3)viewLifecycle 与它无关 4)你的最后一段基本上是一个错误的解决方法 5) OP,我和@CommonsWare 怀疑这是一个错误,所以我打开了 googlers 的票来检查它。 - 在这种特定情况下,您的输入是不成熟的恕我直言 不,viewLifecycle 真的与它无关。只有当至少有一个活跃的观察者时,才会重新评估 Room 的 LiveData。 “如果一棵树倒在森林里,没有人听到,它会发出声音吗?”在 ComputableLiveData 的情况下,显然不是。您通常希望在 RecyclerView + ListAdapter 中显示数据,因此它会使用 submitList 相应地处理更改。 我们没有必要争论它是否是错误。我看不出带有错误状态的虚假通知除了是一个严重的错误之外还有什么意义,但让我们让谷歌员工彻底检查这一点,并让社区做出反应。 ^ 创建一个额外的 LiveData,由 MutableLiveData 支持,用于跟踪操作的状态。将其值设置为“完成”(无论您想表示该状态),然后将其状态设置为“不相关”(无论您想表示该状态,我通常使用 null 并且观察者只是忽略 null),然后在订阅观察者时初始值将是“无关紧要的”。 这是我面临的 100% 问题,它导致我从中保存的数据与从活动中重新加载的 onResume 数据发生冲突。简而言之,这个feature 导致了一个竞争条件,我不得不忽略保存这个回调的结果,而是让它运行两次。它设置了两次 UI,但听起来现在只是 the way it is【参考方案6】:

我的答案不是对这个问题描述的解决方案,而是对问题标题的解决方案。只是标题。

如果 LiveData 的观察者被多次调用,则意味着您多次调用 livedata.observe(...)。 这发生在我做 livedata 时.observe(...) 并在用户执行某些操作时调用此方法,从而再次观察 liveData。为了解决这个问题,我将 livedata.observe(...) 移至 onCreate() 生命周期方法。

场景是什么? 该应用程序有一个色板。当用户选择一种颜色时,我必须调用 API 来获取该颜色的产品图像。进行 API 调用并在 onColorChanged() 中观察 livedata 也是如此。当用户选择一种新颜色时,onColorChanged() 将再次被调用,从而再次观察 livedata 的变化。

编辑:另一个问题可能是在注册 LiveData Observer 时传递 this 而不是 viewLifecycleOwner在下面的另一个答案中。观察 Fragments 中的 LiveData 时始终使用 viewLifecycleOwner

【讨论】:

【参考方案7】:

这就是幕后发生的事情:

ViewModelProviders.of(getActivity())

当您使用 getActivity() 时,这会保留您的 NoteViewModel,而 MainActivity 的范围仍然存在,因此您的垃圾NotesLiveData 也是如此。

当您第一次打开您的 TrashFragment 房间时,会查询数据库,并且您的 trampedNotesLiveData 填充了已删除的值(在第一次打开时,只有一个 onChange() 调用)。所以这个值被缓存在了回收的NotesLiveData中。

然后你来到主片段添加一些废弃的音符,然后再次进入 TrashFragment。这次你首先得到的是缓存值 房间进行异步查询时,已丢弃NotesLiveData。当查询完成时,您是 带来了最新的价值。这就是你得到两个 onChange() 调用的原因。

所以解决方案是你需要在打开之前清理掉垃圾的NotesLiveData 垃圾片段。这可以在您的 getTrashedNotesLiveData() 方法中完成。

public LiveData<List<Note>> getTrashedNotesLiveData() 
    return NoteplusRoomDatabase.instance().noteDao().getTrashedNotes();

或者你可以使用类似SingleLiveEvent

或者您可以使用 MediatorLiveData 拦截 Room 生成的数据并仅返回不同的值。

final MediatorLiveData<T> distinctLiveData = new MediatorLiveData<>();
    distinctLiveData.addSource(liveData, new Observer<T>() 
        private boolean initialized = false;
        private T lastObject = null;

        @Override
        public void onChanged(@Nullable T t) 
            if (!initialized) 
                initialized = true;
                lastObject = t;
                distinctLiveData.postValue(lastObject);
             else if (t != null && !t.equals(lastObject)) 
                lastObject = t;
                distinctLiveData.postValue(lastObject);
            

        
    );

【讨论】:

【参考方案8】:

永远不要将观察者放在循环中/任何它被注册两次的地方。观察者应该放在 onViewCreated / onCreate / 任何只被调用一次的地方。只观察一次!

这里是错误方式的一个例子:

for(int i=0;i<5;i++)
//THIS IS WRONG, DONT PUT IT INSIDE A LOOP / FUNCTION CALL
    yourviewModel.getYourLiveData().observe(getViewLifecycleOwner(), new Observer<Boolean>() 
            @Override
            public void onChanged(Boolean sBoolean) 
                 //SOME CODE 
            
 );

将其置于多次调用的函数下是错误的,例如:

@Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) 
        super.onViewCreated(view, savedInstanceState);
observeMyViewModel();
observeMyViewModel();//THIS IS WRONG, CALLING IT MORE THAN ONCE


private void observeMyViewModel()
  yourviewModel.getYourLiveData().observe(getViewLifecycleOwner(), new Observer<Boolean>() 
            @Override
            public void onChanged(Boolean sBoolean) 
                 //SOME CODE 
            
 );


【讨论】:

【参考方案9】:

我使用了SingleLiveEvent 并且工作正常。当片段/活动被恢复或重新创建时 SingleLiveEvent 不抛出事件,仅当显式更改时

【讨论】:

你是最棒的。我快死了。【参考方案10】:

我特别发现了它为什么会这样。观察到的行为是垃圾片段中的 onChanged() 会在您第一次在删除便笺后激活该片段时调用一次(在新的应用程序启动时),并在随后在删除便笺后激活片段时调用两次。

两次调用发生的原因是:

调用 #1:片段在其生命周期中在 STOPPED 和 STARTED 之间转换,这会导致将通知设置到 LiveData 对象(毕竟它是一个生命周期观察者!)。 LiveData 代码调用 onChanged() 处理程序,因为它认为需要更新观察者的数据版本(稍后会详细介绍)。注意:此时对数据的实际更新可能仍处于挂起状态,导致 onChange() 被陈旧数据调用。

调用 #2:作为查询设置 LiveData(正常路径)的结果而发生。 LiveData 对象再次认为观察者的数据版本是陈旧的。

现在为什么 onChanged() 只在应用启动后第一次激活视图时被调用一次?这是因为第一次 LiveData 版本检查代码由于 STOPPED->STARTED 转换而执行,实时数据从未设置为任何内容,因此 LiveData 跳过通知观察者。通过此代码路径的后续调用(参见 LiveData.java 中的考虑Notify())在数据至少设置一次后执行。

LiveData 通过保留一个版本号来确定观察者是否有过时的数据,该版本号指示数据已设置了多少次。它还记录最后发送给客户端的版本号。当设置新数据时,LiveData 可以比较这些版本以确定是否需要调用 onChange()。

这是 4 次调用 LiveData 版本检查代码调用期间的版本号:

   Ver. Last Seen  Ver. of the     OnChanged()
   by Observer     LiveData        Called?
  --------------   --------------- -----------
1  -1 (never set)  -1 (never set)  N
2  -1              0               Y
3  -1              0               Y
4   0              1               Y

如果您想知道为什么调用 3 中的观察者上次看到的版本是 -1,即使 onChanged() 被第二次调用,因为调用 1/2 中的观察者与调用中的观察者不同3/4(观察者在用户返回主片段时被销毁的片段中)。

避免混淆由于生命周期转换而发生的虚假调用的一种简单方法是将片段中的标志保持为 false,以指示片段是否已完全恢复。在 onResume() 处理程序中将该标志设置为 true,然后检查该标志在您的 onChanged() 处理程序中是否为 true。这样您就可以确定您正在响应发生的事件,因为数据是真实设置的。

【讨论】:

【参考方案11】:

如果您正在寻找一种解决方案来避免从目标片段弹出返回堆栈到原始片段的多个触发器

我的解决方案是在 Fragment 生命周期的 onCreate() 处观察 LiveData 生命周期所有者为 Activity 并在 Fragment生命周期的onDestroy()

【讨论】:

是的,将实时数据范围限定为活动帮助我解决了这个自动触发器【参考方案12】:

我不确定这个问题是否仍然存在。

但主要肇事者是片段生命周期所有者内部的一个错误,该错误在视图被销毁时没有被清除。

以前您必须实现自己的 lyfecycle 所有者,在调用 onDestroyView 时将状态移动到 destroyed

如果您至少使用 API 28 进行定位和编译,则不应再出现这种情况

【讨论】:

【参考方案13】:

我的解决方案只是在需要时开始观察数据,并在它检索到数据后立即移除观察者。这样你不会得到双重触发。

【讨论】:

我在这里有两个回调的类似情况:***.com/questions/58790802/…。关于如何忽略或绕过第一个回调的存储值的任何想法?

以上是关于为啥新连接的观察者会触发两次 LiveData 观察者的主要内容,如果未能解决你的问题,请参考以下文章

为啥用 viewLifecycleOwner 观察到的 LiveData 在 onDestroyView 之后会得到回调?

为啥刷新浏览器时 Application_BeginRequest() 会触发两次?

Android知识点:LiveData为啥连续postValue两次,第一次的值会丢失?

Room:来自 Dao 的 LiveData 将在每次更新时触发 Observer.onChanged,即使 LiveData 值没有变化

未调用 LiveData 观察者

为啥观察 LiveData 时不调用 onChanged()