【问题标题】:Restoring an Adapter on Fragment Recreation在 Fragment Recreation 上恢复适配器
【发布时间】:2018-12-12 15:34:49
【问题描述】:

我遇到了崩溃,如果 android 操作系统杀死一个进程,当应用程序尝试从保存的实例中重建活动和片段时,我的 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

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

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

现在,我的最终用户遇到了崩溃,如果他们将此活动置于后台,并且操作系统决定终止此活动所在的进程,则它无法正确恢复自身。据我所见,除了适配器为空外,一切都在恢复中。然后代码尝试访问适配器接口并与 NPE 一起崩溃。

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

让回收器frag将自己存储在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");
        ...
    }
}

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

接下来,我尝试保存在父活动中创建的片段以用于 viewPager 实现,然后尝试在父活动 onCreate 中恢复这些片段。父类如下所示:

@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,它提供了最简单的方法来覆盖 get it 运行。据我了解,这正是您应该做的,父 FragmentStatePagerAdapter 将为您处理其余的恢复过程。这个假设我错了吗?

我非常感谢您就此事提供任何意见。我试图提供尽可能多的信息,但是,如果您认为您可以在提供更多信息的情况下帮助我,如果您让我知道,我很乐意更新我的帖子。谢谢!

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

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

【问题讨论】:

    标签: android android-fragments android-recyclerview android-savedstate


    【解决方案1】:

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

    这些回收器片段中的每一个都是完全相同的片段 除了,当父活动创建它们的新实例时,我 调用设置类型适配器的公开设置方法 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;
    }
    

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

    希望这至少有点帮助。

    【讨论】:

    • 事实证明,我正在努力的解决方案是将父类中的任何这些片段保存到片段管理器中,在 onCreate 中从管理器中获取它们,再次设置适配器,然后继续前进。鉴于我必须在任何地方都这样做,这不是一个务实的解决方案。我最终选择了您上述解决方案的混合体。有时我的 viewpager 会包含其他片段,所以我在片段中创建了一个枚举,并要求它作为 newInstance() 的参数。总而言之,它就像一个魅力,并保持代码正交。感谢您的建议!
    猜你喜欢
    • 1970-01-01
    • 2014-01-11
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2014-03-22
    • 2019-07-28
    • 2019-12-27
    • 2017-10-22
    相关资源
    最近更新 更多