首页 > 解决方案 > 从 FragmentManager 检索的 Fragment 有时会将其 RecyclerView Adapter 设置为 null

问题描述

在我的应用程序中,我有一个Fragment负责显示新闻项目列表的应用程序。它需要一个String参数来确定从哪个 url 提取数据。

Fragment用这个代码设置:

private void setFragment(String pageToLoad, NewsFeedFragment newsFeedFragment) {

    FragmentTransaction transaction = getSupportFragmentManager().beginTransaction();
    if(newsFeedFragment == null) {
        transaction.replace(R.id.container, NewsFeedFragment.newInstance(pageToLoad), pageToLoad);
    }
    else {
        transaction.replace(R.id.container, newsFeedFragment, pageToLoad);
    }
    mPageToLoad = pageToLoad;

}

在我的父母Activity中,我跟踪当前正在查看哪个“页面”:

protected void onSaveInstanceState(@NonNull Bundle outState) {
    if(mPageToLoad != null) {
        outState.putString("pageToLoad", mPageToLoad);
    }
    super.onSaveInstanceState(outState);
}

在我的父Activity onCreate方法中,我检查是否已创建 NewsFeedFragment 的实例并将其添加到FragmentManager如下:

protected void onCreate(Bundle savedInstanceState) {
    if (savedInstanceState != null) {
        if (savedInstanceState.containsKey("pageToLoad")) {
            String pageToLoad = savedInstanceState.getString("pageToLoad");
            if(pageToLoad != null) {
                NewsFeedFragment newsFeedFragment = (NewsFeedFragment) getSupportFragmentManager().findFragmentByTag(pageToLoad);
                if(newsFeedFragment != null) {
                    setFragment(pageToLoad, newsFeedFragment);
                }
                else {
                    setFragment(pageToLoad, null);
                }
            }
        }
    }
}

这在 99% 的情况下运行良好,应用程序正确恢复并显示NewsFeedFragment添加的最后一个实例。但是,我有一个问题似乎是随机发生的RecyclerView AdapterNewsFeedFragment有时null在从FragmentManager使用该findFragmentByTag(pageToLoad)方法检索片段时出现。

NewsFeedFragmentRecyclerView Adapter一个class变量:

public NewsPageAdapter mNewsPageAdapter;

NewsFeedFragment的onActivityCreated方法如下:

public void onActivityCreated(Bundle savedInstanceState) {

    if(mNewsPageAdapter == null) {
        Log.i(TAG, "mNewsPageAdapter is null");  // This is logged when issue occurs
    }
    if(savedInstanceState == null || mNewsPageAdapter == null) {
        new LoadFirstPageTask().execute();  // Fetches news items from web service, creates mNewsPageAdapter, and then calls setupRecyclerView() method
    }
    else {
        setupRecyclerView(savedInstanceState);
    }

}

最后,这是 NewsFeedFragment setupRecyclerView 方法:

private void setupRecyclerView(Bundle savedInstanceState) {
    mRecyclerView.setLayoutManager(mLayoutManager);
    mRecyclerView.setAdapter(mNewsPageAdapter);
}

根据我所描述的内容,任何人都可以提供任何见解,说明为什么NewsPageAdapter有时从 ThenullFragment检索FragmentManager?

谢谢

标签: androidandroid-fragmentsandroid-recyclerview

解决方案


好的,所以你这样做new LoadFirstPageTask().execute();了,我猜这最终会调用 setupRecyclerView?

我认为您在这里使解决方案复杂化了。

所有这些条件、业务逻辑和决策都在一个可变组件(片段)中做出,其生命周期非常复杂,并不总是您所期望的;您还将异步数据的维护与此结构相结合,这会产生难以确定和追踪的意外副作用。

与为所述适配器获取、处理和生成数据相比,创建适配器“便宜”。

您似乎没有在任何地方提及 ViewModel,您使用的是 viewModel 吗?或者任何其他类型的模式,比如 Presenter、Interactors、useCases?

AsyncTasks已弃用,虽然我不主张每次弃用类时都跑到“重构”山上,但我认为如果你将 AsyncTask 抽象为协程,你可以获得更好、更稳定、可测试和可读的解决方案(例如,所有这些都由您的 ViewModel 管理)。

换句话说,您的 Fragment 和 Activity 不应该处理有关“我是否需要加载此数据”的逻辑;这是别人的责任。

关于你的代码。

好的,既然我已经对您的工作方式进行了咆哮,让我们更深入地研究您现有的 Java 代码。

当使用 findFragmentByTag(pageToLoad) 方法从 FragmentManager 检索 Fragment 时,NewsFeedFragment 中的 RecyclerView Adapter 有时为 null。

每当我们在崩溃中看到“有时”时,第一个嫌疑人应该是计时/线程。同步代码可能会失败,但它通常比异步代码更可预测几个数量级。

如果它“有时”为空,则负责更改此行为的任务并不总是在需要时准备好;或此任务运行所需的条件,在检查它时并不总是您所期望的,依此类推。

首先将您的想法重新架构成一个单独的组件。

让 Fragment 尽快创建它的适配器(可以是空的数据),无论是否有数据。

让片段向另一个组件请求数据。当数据可用时,将其发送到 Fragment,Fragment 将依次将其设置在适配器中。如果数据在您请求时已经存在(因为您“缓存”了它),您就不必等待。

我还将“最后查看的页面”存储在同一组件中,因此您无需保存状态并将其传递给片段。相反,片段要求“当前数据”并且组件已经知道它是什么。

总而言之,将您的所有部分放在一起有点困难,因为我们,读者,没有所有的代码,也没有您的要求导致您找到这个解决方案。


推荐阅读