期望适配器在恢复状态时是“新鲜的”

SMG*_*ost 17 android android-viewpager

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

Expected the adapter to be 'fresh' while restoring state.
Run Code Online (Sandbox Code Playgroud)

FragmentStateAdapter 似乎无法正确恢复其状态,因为它期望它为空。

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

Pho*_*ton 8

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

public void onExitOfYourFragment() {
    viewPager2.setAdapter(null);
}
Run Code Online (Sandbox Code Playgroud)

当你再次回到片段时:

public void onResumeOfYourFragment() {
    viewPager2.setAdapter(yourAdapter);
}
Run Code Online (Sandbox Code Playgroud)

  • 我无法让它发挥作用。如果我在 onDestroyView 中将其设置为 null,则在恢复引发此错误的适配器状态时,“mFragments”属性仍然不为空。 (6认同)
  • 这对我来说不起作用,而且 ViewPager2 产生的错误似乎表明它不想重新使用适配器 - 所以 @SMGhost 的答案似乎更合适。 (2认同)

小智 8

它可以通过 viewPager2.isSaveEnabled = false


SMG*_*ost 6

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

  • 这是非常糟糕的解决方案。在结果中,您将创建多个 adapeter 实例,这会使您的应用程序变慢 (2认同)
  • @Thracian,我将代码恢复为老式 viewPager。对于这种情况,适配器可以重用,我在 onCreate 方法中创建它。看起来不错。如果它对您没有帮助,请尝试对内部片段使用异步布局膨胀。 (2认同)

Del*_*ark 5

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

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

球队设置了很多休息点来防止这种情况发生,只有天知道为什么......

他们本可以使用一个自分离的生命周期观察者,这种能力已经在其代码中预见到了,但在 Android 架构中没有任何地方使用这种能力......

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

其次...即使这样做了,viewPager 本身是建立在 recyclerView 之上的,这一事实使得处理您所期望的 Fragment 行为变得极其困难,这是一种保存状态,一次性实例化,以及明确定义的生命周期(可控/预期销毁)。

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

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

假设所有其他变量已经考虑到正确的 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.");
        }
.....
}
Run Code Online (Sandbox Code Playgroud)

第二次休息:

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

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

第三:在重新进入(到视图,后台堆栈)时放置一个片段垃圾收集器,该收集器在 10 秒后延迟,试图销毁屏幕外片段,导致所有屏幕外页面上的内存泄漏,因为它杀死了控制其各自的 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);
}
Run Code Online (Sandbox Code Playgroud)

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

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