【问题标题】:Expected the adapter to be 'fresh' while restoring state期望适配器在恢复状态时是“新鲜的”
【发布时间】:2019-06-18 10:09:39
【问题描述】:

我在 FragmentStateAdapter 中有一个带有多个片段的 viewpager2。每当我尝试打开一个新片段然后使用 viewpager2 返回到当前片段时,都会出现异常:

Expected the adapter to be 'fresh' while restoring state.

FragmentStateAdapter 似乎无法正确恢复其状态,因为它预计它是空的。

我能做些什么来解决这个问题?

【问题讨论】:

标签: android android-viewpager


【解决方案1】:

可以通过viewPager2.isSaveEnabled = false修复

【讨论】:

  • 这对我有用!我有一个 ViewPager2 页面 Fragments 每个都有自己的 RecycleView,加上顶部的 TabLayout。 ViewPager(旧版本和新版本)在旋转后没有重新创建片段。这解决了问题。
  • 这部分工作,因为我们将isSaveEnabled 设置为false,viewPager 状态不会被保存,它将始终从默认位置开始,这意味着来自后堆栈的任何片段将始终从位置 0(默认)。
  • 对我不起作用。当我尝试返回时,我只收到错误“SPAN_EXCLUSIVE_EXCLUSIVE 跨度不能有零长度”
【解决方案2】:

所以我的问题是我在我的 Fragment 类字段中创建了我的 FragmentStateAdapter,它只创建了一次。因此,当我的 onCreateView 第二次被调用时,我遇到了这个问题。如果我在每个 onCreateView 调用上重新创建适配器,它似乎可以工作。

【讨论】:

  • 这是一个非常糟糕的解决方案。结果,您将创建多个adapeter 实例,这会使您的应用程序变慢
  • @GeorgiyChebotarev 我有可能因为这个而落后的可怕经历。我的 BottomNavigationView 在一个片段内,如果它的选项卡包含一个具有 3 页的 ViewPager2,并且当我从另一个 BottomNavigationView 选项卡移动到具有 ViewPager2 的一个选项卡时,我有一个令人讨厌的滞后。 ViewPager2 的工作速度也比 ViewPager 慢
  • @Thracian 我将我的代码恢复为老式的 viewPager。对于这种情况,可以重用适配器,我在 onCreate-method 中创建它。看起来还不错。如果它对您没有帮助,请尝试对内部片段使用异步布局膨胀。
  • @GeorgiyChebotarev,谢谢。我会看看。现在,我将尝试使用 ViewPager2 和异步布局膨胀,看看它是如何工作的。恢复到ViewPager 后,您的性能是否有明显差异?在我使用 ViewPager2 移动到片段后,从 onCreteView 开始(充气之前)到 onResume 所需的时间约为 80 毫秒,这意味着丢失 5 帧
  • @Georgiy Chebotarev 最糟糕的是官方文档建议使用它来代替 fragmentPagerAdapter。主要问题是构造函数需要一个viewLifeCycle,这个viewLifeCycle只能在viewCreated之后才能获得,而它存储的Fragments无疑会在这个循环中存活下来。如果您甚至试图规避不必要的适配器/片段重新创建,则会设置 10 秒倒计时来销毁在 backStack 重入时看不到的任何片段。这是一场噩梦。
【解决方案3】:

我在使用 ViewPager2 时遇到了同样的问题。经过大量努力测试不同的方法,这对我有用:

public void onExitOfYourFragment() {
    viewPager2.setAdapter(null);
}

当你再次回到片段时:

public void onResumeOfYourFragment() {
    viewPager2.setAdapter(yourAdapter);
}

【讨论】:

  • 似乎是一个类似的解决方案,但以更合适的方式完成,这允许我们重用适配器。好发现! :)
  • 我无法让它工作。如果我在 onDestroyView 中将其设置为 null,则在恢复引发此错误的适配器状态时,mFragments 属性仍然不为空。
  • 这对我不起作用,而且 ViewPager2 产生的错误似乎表明它不想重新使用适配器 - 所以@SMGhost 的答案似乎更合适。
【解决方案4】:

在 Fragment 中使用 ViewPager2 时,我也收到了这个 java.lang.IllegalStateException: Expected the adapter to be 'fresh' while restoring state.

问题似乎是因为我在 onCreateView() 方法中执行了mViewPager2.setAdapter(mFragmentStateAdapter);

我通过将 mViewPager2.setAdapter(mMyFragmentStateAdapter); 移动到我的 onResume() 方法来修复它。

【讨论】:

    【解决方案5】:

    此适配器/视图可用作 FragmentStatePagerAdapter 的替代品。

    如果您寻求的是在从 Backstack 重新进入时保留 Fragments,而使用此适配器很难实现。

    为了防止这种情况,团队设置了许多休息时间,只有上帝知道为什么......

    他们本可以使用自分离 lifeCycle 观察者,该功能已在其代码中预见到,但在 android 架构中没有任何地方使用该功能....

    他们应该使用这个未完成的组件来监听全局 Fragments 生命周期而不是它的 viewLifeCycle,从这里开始,可以将监听从 Fragment 扩展到 viewLifeCycle。 (附加/分离 viewLifeCycle 观察者 ON_START/ON_STOP)

    其次...即使这样做了,viewPager 本身是建立在 recyclerView 之上的事实使得处理您对 Fragment 行为的期望变得非常困难,这是一种保存状态,一个时间实例化和明确定义的生命周期(可控/预期破坏)。

    这个适配器在功能上是矛盾的,它检查 viewPager 是否已经被提供了 Fragment,同时在重入时仍然需要一个“新”的适配器。

    它在退出到 backStack 时保留 Fragments,同时期望在重新进入时重新创建所有片段。

    假设 所有 其他变量已经考虑到正确的 viewLifeCycle 处理(注册/注销和设置和重置参数),防止字段实例化适配器的地方是:

    @Override
        public final void restoreState(@NonNull Parcelable savedState) {
            if (!mSavedStates.isEmpty() || !mFragments.isEmpty()) {
                throw new IllegalStateException(
                        "Expected the adapter to be 'fresh' while restoring state.");
            }
    .....
    }
    

    第二次休息:

    @CallSuper
    @Override
    public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
        checkArgument(mFragmentMaxLifecycleEnforcer == null);
        mFragmentMaxLifecycleEnforcer = new FragmentMaxLifecycleEnforcer();
        mFragmentMaxLifecycleEnforcer.register(recyclerView);
    }
    

    mFragmentMaxLifecycleEnforcer 在重入时必须为 == null,否则会在 checkArgument() 中引发异常。

    第三: Fragment 垃圾收集器在重入时放置(视图,后台堆栈),它在 10 秒后延迟,试图销毁屏幕外的 Fragment,导致内存泄漏所有屏幕外页面,因为它会杀死控制其生命周期的各自 FragmentManager。

    private void scheduleGracePeriodEnd() {
        final Handler handler = new Handler(Looper.getMainLooper());
        final Runnable runnable = new Runnable() {
            @Override
            public void run() {
                mIsInGracePeriod = false;
                gcFragments(); // good opportunity to GC
            }
        };
    
        mLifecycle.addObserver(new LifecycleEventObserver() {
            @Override
            public void onStateChanged(@NonNull LifecycleOwner source,
                    @NonNull Lifecycle.Event event) {
                if (event == Lifecycle.Event.ON_DESTROY) {
                    handler.removeCallbacks(runnable);
                    source.getLifecycle().removeObserver(this);
                }
            }
        });
    
        handler.postDelayed(runnable, GRACE_WINDOW_TIME_MS);
    }
    

    所有这些都是因为它的罪魁祸首:构造函数:

    public FragmentStateAdapter(@NonNull FragmentManager fragmentManager,
            @NonNull Lifecycle lifecycle) {
        mFragmentManager = fragmentManager;
        mLifecycle = lifecycle;
        super.setHasStableIds(true);
    }
    

    【讨论】:

    • 我想你就在这里,这是一笔不划算的交易。在宿主片段的视图被破坏并且看起来不可能之后,我还花了一些时间试图“返回”到我正在查看的 viewpager2。
    【解决方案6】:

    我也遇到过同样的问题。

    经过一番研究,我发现它与Adapter 的实例有关。当它被创建为 Fragment 的惰性属性时,它会因该错误而崩溃。

    所以在Fragment::onViewCreated 中创建Adapter 可以解决它。

    【讨论】:

      【解决方案7】:

      我通过测试是否等于 null 解决了这个问题

      if(recyclerView.adapter == null) {recyclerView.adapter = myAdapter}
      

      【讨论】:

        【解决方案8】:

        我一直在努力解决这个问题,以前的答案都没有帮助。

        这可能不适用于所有可能的情况,但在我的情况下,包含 ViewPager2 的片段是固定的并且很少,我通过使用 FragmentTransactionshow()hide() 方法进行片段切换解决了这个问题,而不是 @ 987654324@ 通常为此推荐。将show() 应用于活动片段,将hide() 应用于所有其他片段。这样可以避免重新创建视图和恢复触发问题的状态等操作。

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 2022-11-22
          • 1970-01-01
          • 2018-10-17
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2023-01-31
          相关资源
          最近更新 更多