加载器将结果传递给错误的片段

Posted

技术标签:

【中文标题】加载器将结果传递给错误的片段【英文标题】:Loader delivers result to wrong fragment 【发布时间】:2013-03-07 14:53:38 【问题描述】:

我有一个使用 ActionBar 选项卡滑动选项卡的活动,基于 the android developer example。

每个选项卡显示一个 Fragment,每个 Fragment(实际上是一个 SherlockFragment)通过自定义 AsyncTaskLoader 加载不同类型的远程 api 请求。

问题在于,如果您在要离开的选项卡的片段(旧片段)正在加载结果时点击一个选项卡以移动 2 个选项卡/页面,则该结果将传递给您移动的选项卡的片段到(新片段)。在我的例子中,这会导致 ClassCastException,因为预期的结果是不兼容的类型。

在代码中,情况的要点是:

装载机:

public class FooLoader extends AsyncTaskLoader<Foo>
public class BarLoader extends AsyncTaskLoader<Bar>

片段:

public class FooFragment extends Fragment implements LoaderManager.LoaderCallbacks<Foo> 
...
    @Override
    public void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        getLoaderManager().initLoader(0, null, this);
    
    public Loader<Foo> onCreateLoader(int id, Bundle args)  return new FooLoader(); 
...

public class BarFragment extends Fragment implements LoaderManager.LoaderCallbacks<Bar> 
    ...
    @Override
    public void onCreate(Bundle savedInstanceState) 
        super.onCreate(savedInstanceState);
        getLoaderManager().initLoader(0, null, this);
    
    public Loader<Bar> onCreateLoader(int id, Bundle args)  return new BarLoader(); 
    ...

标签管理代码如the aforementioned example。在 Foo 和 Bar 选项卡之间有第三个选项卡(称为 Baz)。当我们在 FooFragment 在其 LoaderManager 上调用 initLoader 之后但在调用 FooFragment.onLoadFinished 之前通过点击 Bar 选项卡从 Foo 选项卡跳到 Bar 选项卡时,我们最终会在调用 BarFragment.onLoadFinished 时出现 ClassCastException:

java.lang.ClassCastException: com.example.Foo cannot be cast to com.example.Bar
at com.example.BarFragment.onLoadFinished(BarFragment.java:1)
at android.support.v4.app.LoaderManagerImpl$LoaderInfo.callOnLoadFinished(LoaderManager.java:427)
at android.support.v4.app.LoaderManagerImpl.initLoader(LoaderManager.java:562)
at com.example.BarFragment.onCreate(BarFragment.java:36)
at android.support.v4.app.Fragment.performCreate(Fragment.java:1437)
...

为什么会发生这种情况,如何预防?从调试日志来看,Bar 片段中重复使用了相同的 LoaderManager(尽管 Baz 片段有自己的),但我不知道为什么会发生这种情况。

更新:在每个片段中使用不同的加载程序 ID 确实可以消除崩溃(或者似乎 - 我真的不知道为什么),但我宁愿不这样做。在其中一个片段中,我实际上是动态创建 ID,并且不想假设不会发生冲突。另外,这个解决方案对我来说很奇怪——加载器 ID 应该是每个片段的本地(否则,为什么在正常情况下我可以在不同片段中拥有具有相同 ID 的加载器?)

看来我也可以通过在 ViewPager 上调用 setOffscreenPageLimit(2) 来消除崩溃,这样当我们切换到 Bar 视图时,Foo 视图不会被丢弃。但这是一种解决方法,而不是通用解决方案。

完整代码:我创建了一个example application demonstrating the error。它包含一个用于强制错误的 monkeyrunner 脚本(尽管它可能不适用于所有屏幕尺寸)。

【问题讨论】:

能否请您发布其中一个加载器的完整代码。 尝试将您的呼叫转移到 initLoaderonActivityCreated 而不是 onCreate 这看起来解决了问题。谢谢! 【参考方案1】:

不要使用 0 作为您的 ID。据LoaderManager 所知,他们应该是同一个装载机。

您可以在 XML 资源文件中定义唯一 ID

<item type="id" name="loader_foo" />
<item type="id" name="loader_bar" />

并从 R 访问它们。

loaderManager.initLoader(R.id.loader_foo, null, new LoaderCallbacks());

@987654321@ 的文档说“标识符的作用域是特定的 LoaderManager 实例”。 LoaderMananger 的实例与 Activities 相关联。

为了生成唯一 ID,您可以手动为它们分配与它们之间存在较大差距的 ID。

private static final int LOADER_FOO = 1;
private static final int LOADER_BAR = 100;

for(int i = 0; i < 10; ++i)
    loaderManager.initLoader(LOADER_FOO + i, null, new LoaderCallbacks());

【讨论】:

我可以通过在每个片段中使用不同的 ID 来消除崩溃,但不应该这样做也不想这样做。 (请参阅对原始问题的更新)。 我生成的 ID 没有任何特定的界限,因此我无法假设间隙大小足够大。另外,这仍然是一种解决方法。 在这种情况下,您可以使用单个加载程序并重复调用交付结果。只要不是同一个对象 onLoadFinished 就会被调用多次。 在同一个LoaderManager 上为不同的LoaderCallbacks 使用不同的ID 对我来说很有意义。解决我的问题的最干净的方法。谢谢 不要使用 0 作为您的 ID。这救了我!谢谢:)【参考方案2】:

您可以通过在onActivityCreated 而不是onCreate 中调用initLoader 来避免此问题 - 如问题 cmets 中的Alex Lockwood 所述。修改后的代码如下。

更正的片段:

public class FooFragment extends Fragment implements LoaderManager.LoaderCallbacks<Foo> 
...
    @Override
    public void onActivityCreated(Bundle savedInstanceState) 
        super.onActivityCreated(savedInstanceState);
        getLoaderManager().initLoader(0, null, this);
    
    public Loader<Foo> onCreateLoader(int id, Bundle args)  return new FooLoader(); 
...

public class BarFragment extends Fragment implements LoaderManager.LoaderCallbacks<Bar> 
    ...
    @Override
    public void onActivityCreated(Bundle savedInstanceState) 
        super.onActivityCreated(savedInstanceState);
        getLoaderManager().initLoader(0, null, this);
    
    public Loader<Bar> onCreateLoader(int id, Bundle args)  return new BarLoader(); 
    ...

【讨论】:

我认为如果您有一个带有相同类型片段的浏览器,这将不起作用。在这种情况下,viewpager 将销毁片段并在您向后滑动后重新创建它,此时片段将永远不会收到 onActivityCreated 事件,而只会收到 onAttach()。恕我直言,您可能需要确保加载程序 ID 不同。如果您在同一个活动中多次使用同一个片段,则需要传递参数以确保加载器 ID 不会发生冲突。【参考方案3】:

最近我正在使用 SimpleCursorAdapter 和加载器来显示来自 sql lite 表的数据。我花了很长时间调试一个错误“没有这样的列'_id'”。这发生在我的第二个片段的 onLoadFinished() 中。我的第一个表有 '_id' 列,但我的第二个表没有,这让我相信 Loader 正在传送到错误的片段。因此,以防万一您做了类似的事情,请确保您的 sql 表首先具有“_id”列。希望这对与我有同样问题的其他人有所帮助。

【讨论】:

以上是关于加载器将结果传递给错误的片段的主要内容,如果未能解决你的问题,请参考以下文章

如何将图像视图从改造加载的片段传递给子片段?

活动结果片段索引超出范围:0x20001

如何使用警报管理器将数据从片段传递到广播接收器

作为由相对路径加载的绝对路径传递给链接器的动态库?

s-s-rS 2008 报告订阅电子邮件随机接收传递扩展加载错误

如果我异步加载图像,Firefox 选项卡加载微调器将永远运行