在 Fragment Recreation 上恢复适配器

Restoring an Adapter on Fragment Recreation

我 运行 正在崩溃,如果 android OS 终止了一个进程,当应用程序试图从已保存的实例,我的 recyclerview 的适配器始终为空。

这是我的层次结构:

Parent Activity    // destroyed when process is killed
  Nested View Pager Fragment // view pager containing four recycler view fragments
    Recycler Frag 1
    Recycler Frag 2
    Recycler Frag 3
    Recycler Frag 4

这些回收器片段中的每一个都是完全相同的片段,除了当父 activity 创建它们的新实例时,我调用一个公开的 setter 方法来设置类型为 RecyclerView.Adapter(原因是这些碎片中的每一个都可能是一个分页回收器,但它显示的内容以及用户与之交互的方式可能会有所不同)示例代码来自父 activity:

RecyclerFrag feedFrag = RecyclerFrag.newInstance();
feedFrag.setAdapter(new myCustomAdapter());

现在,我的最终用户 运行 如果他们有这个 activity 后台,并且 OS 决定终止这个 activity 继续存在,它无法正常恢复自身。据我所见,还原时一切都在那里,除了适配器为空。然后代码尝试访问适配器接口并因 NPE 而崩溃。

我不明白为什么会这样。我也在努力全神贯注于恢复此类事件中的活动和片段。我已经阅读了一堆 posts 和关于状态恢复的文档,但仍然找不到解决方案。这是我尝试过的解决方案示例,也许有人可以指出我逻辑中的缺陷/差距。

将回收器碎片存储在 fragmentManager 中,并尝试在 onActivityCreated 方法调用中读取它。

@Override
public void onSaveInstanceState(@NonNull Bundle outState) {
    super.onSaveInstanceState(outState);
    getFragmentManager().putFragment(outState, "myFrag", this);
    ...
}

然后:

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    if(savedInstanceState != null) {
        getFragmentManager().getFragment(savedInstanceState, "myFrag");
        ...
    }
}

对我来说,这感觉就像我错过了标记。片段获取对自身的引用然后尝试从中设置自身是没有意义的。更糟糕的是,片段 it returns 有一个空适配器。生活和学习。

接下来我尝试保存我在父 activity 中为 viewPager 实现创建的片段,然后尝试在父 onCreate 中恢复这些片段。父级 class 如下所示:

@Override
public void onSaveInstanceState(Bundle outState) {
    super.onSaveInstanceState(outState);
    getSupportFragmentManager().putFragment(outState, "frag1", frag1);
    ...
}

然后:

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if(savedInstanceState != null) { 
        getSupportFragmentManager.getFragment(savedInstanceState, "myFrag);
        ...
    }
}

关于此方法的有趣之处在于,当我在 onSaveInstance 方法中将片段保存到管理器时,我的 adpater 不为 null,这是有道理的。当我在 onCreate 方法中获取它时,该片段适配器现在为空。

这让我最后想到,也许恢复适配器效率不高,不应该这样做。再说一次,在 Recycler Fragment 中创建适配器对我来说没有意义,因为我希望此片段采用 RecyclerView.Adapter 类型的任何适配器实现。对于进一步的上下文,viewPager 实现的类型为 FragmentStatePagerAdapter,它提供了最基本的方法来覆盖将其获取到 运行。据我了解,这正是您应该做的,父 FragmentStatePagerAdapter 将为您处理其余的恢复过程。我这个假设错了吗?

对于此事的任何意见,我将不胜感激。我尽力提供了尽可能多的信息,但是,如果您觉得在提供更多信息的情况下可以帮助我,我很乐意更新我的 post,只要您告诉我。谢谢!

最终编辑: 我最终采用了与标记为此 post 答案的解决方案类似的解决方案。我将在下面提供我的实际解决方案,以便将来如果有人发现此 post 对他们的情况有帮助。

  1. 删除 Recycler Fragments 界面中的 public setter 方法。鉴于我总是在初始化片段后立即调用 setter,这种方法有问题是有道理的,并且当未来的开发人员必须在代码中工作时容易出错。
  2. 创建枚举类型作为回收器中对象的数据抽象。鉴于某些隐含类型的数据需要不同的适配器,这似乎也是合适的。我的意思是我可能有一个汽车对象,但取决于我在用户旅程中所处的位置,该汽车对象可能需要一个不同的适配器,用 CAR_PREVIEW_SEARCHCAR_PREVIEW_SELECTABLE 等枚举反映出来onCreateView
  3. 期间

如果不查看更多代码库,我无法自信地解决您的实际问题,但是下面这行有点令人担忧;

Each one of these recycler fragments is the exact same fragment except, when the parent activity creates a new instance of them, I call an exposed setter method that sets an adapter of type RecyclerView.Adapter

我可以建议另一种设置类型的方法吗?

在您的 FragmentStatePagerAdapter 中创建各种片段类型的枚举;

public enum Page {
    PAGE_ONE,
    PAGE_TWO,
    PAGE_THREE
}

那么在您的 getItem(int position) 实施中您将拥有;

@Override
public Fragment getItem(int position) {
    CustomFragment fragment = CustomFragment();

    Bundle bundle = new Bundle();
    bundle.putInt(CustomFragment.INDEX, position); // You could swap this to whatever serialised information you need

    fragment.setArguments(bundle);

    return fragment;
}

在 CustomFragment 的 onCreateView 实现中,您可以像这样访问这个包;

@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
    View view = inflater.inflate(R.layout.custom_fragment, container, false);

    RecyclerView.Adapter adapter;

    if(getArguments() != null) {
        switch (FragmentStatePagerAdapter.Page.values()[getArguments().getInt(CustomFragment.INDEX)]) {
            // Assign whatever adapter/values ect in here
        }
    } else {
        // setup various default states here
        adapter = SomeDefaultCustomFragmentAdapter()
    }

    // set adapters etc

    return view;
}

以上内容确保您仅在确定拥有视图时才操作适配器。当系统重新创建片段时,一切都应该正常工作。

希望这至少能有所帮助。