Loader将结果传递给错误的片段

时间:2013-03-19 03:17:20

标签: android android-fragments android-viewpager android-loadermanager android-loader

我有一项活动,根据the android developer example使用ActionBar标签滑动标签。

每个标签显示一个片段,每个片段(实际上是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(否则,为什么我在正常情况下可以在不同的片段中使用相同ID的加载器?)

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

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

3 个答案:

答案 0 :(得分:10)

不要使用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(){});

LoaderManager的文档说“标识符的范围限定为特定的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(){});
}

答案 1 :(得分:2)

您可以通过initLoader中的onActivityCreated而不是onCreate中的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(); } ... } 来解决此问题,正如问题评论中Alex Lockwood所述。修改后的代码。

更正的碎片:

{{1}}

答案 2 :(得分:0)

最近我使用SimpleCursorAdapter和加载器来显示sql lite表中的数据。我花了很长时间调试错误&#34;没有这样的专栏&#39; _id&#39;。&#34;发生在我的第二个片段的onLoadFinished()中。我的第一张桌子上有&#39; _id&#39;列,但我的第二个表没有,这让我相信Loader正在传递错误的片段。所以,如果您做了类似的事情,请确保您的sql表具有&#39; _id&#39;第一列。希望这可以帮助其他与我有同样问题的人。