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

jgi*_*les 13 android android-fragments android-viewpager android-loadermanager android-loader

基于android开发人员示例,我有一个使用ActionBar选项卡滑动选项卡的活动.

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

问题是,如果您点击一个标签移动2个标签/页面,而您要离开的标签的片段(旧片段)正在加载结果,则该结果将传递到您移动到的标签的片段(新片段).在我的情况下,这会导致ClassCastException,因为预期的结果是不兼容的类型.

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

装载机:

public class FooLoader extends AsyncTaskLoader<Foo>
public class BarLoader extends AsyncTaskLoader<Bar>
Run Code Online (Sandbox Code Playgroud)

片段:

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(); }
    ...
}
Run Code Online (Sandbox Code Playgroud)

标签管理代码与上述示例中的相同.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)
...
Run Code Online (Sandbox Code Playgroud)

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

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

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

完整代码:我创建了一个演示错误示例应用程序.它包含一个强制错误的monkeyrunner脚本(尽管它可能不适用于所有屏幕大小).

ale*_*lex 10

不要使用0作为您的ID.据我LoaderManager所知,他们的意思是他同样的装载机.

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

<item type="id" name="loader_foo" />
<item type="id" name="loader_bar" />
Run Code Online (Sandbox Code Playgroud)

并从R访问它们

loaderManager.initLoader(R.id.loader_foo, null, new LoaderCallbacks(){});
Run Code Online (Sandbox Code Playgroud)

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(){});
}
Run Code Online (Sandbox Code Playgroud)


jgi*_*les 2

您可以通过调用而不是 ininitLoader来避免此问题- 正如Alex Lockwood在问题评论中指出的那样。修改后的代码如下。onActivityCreatedonCreate

更正的片段:

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(); }
    ...
}
Run Code Online (Sandbox Code Playgroud)

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