全局加载器(LoaderManager)可在多个Activity/Fragment中复用

16

我想要实现的目标:

我有两个不同的片段。我希望它们都以两种形式(列表和地图)显示相同的数据。我希望它们共享一个加载器(特别是 AsyncTaskLoader)。一切都很好,但加载器没有被重用。另一个被创建并且数据被加载了两次。

我的做法:

Fragment中,我使用LoaderManager lm = getActivity().getSupportLoaderManager();。在它们两个中,我实现了 LoaderCallbacks<ArrayList<Item>> 和所需的方法。在它们两个中,我都使用lm.initLoader(0, args, this);

但是,当我输出lm.toString()时,它显示这是两个不同的加载器。数据被下载了两次。

如何从与其启动的不同 Activity/Fragment 重新连接到同一个 Loader?

这应该是可能的,因为上下文在每个onCreate()时都附加到加载器上,例如在配置更改时。


你能解释一下为什么需要在Fragment和它的父Activity中都引用Loader吗?请记住,鼓励设计可重用的Fragment...如果开始交织ActivityFragment的显式行为,事情很快就会变得混乱...尽可能在每个类中单独完成尽可能多的工作,然后在适当时实现Activity回调方法。 - Alex Lockwood
话说,我会给你一个机会解释你的理由,然后再开始听起来太学究了 :P - Alex Lockwood
哦,不好意思,你误解了。它不是父Activity!它是一个非常不同的Activity。如果我问两个片段的问题,那么问题的意义将保持不变。如何在两个不同的片段中重用一个Loader?只是我在某些Activity中使用Fragment来显示列表,而在MapActivity中使用Fragment来显示地图,我认为这可能很重要,但我不确定。 - Michał Klimczak
我想再次访问这个问题,因为它并没有完全解决。过去几天里,我一直在研究LoaderManager/Loader源代码,以充分理解它的工作原理,但似乎这样的事情是不可能的。每个活动/片段都有自己的LoaderManager(它不是全局实例),LoaderManager根据需要启动/停止/销毁Loader,以便在活动/片段生命周期中管理它们。但是,您可以重用未与LoaderManager一起使用的Loader。稍后会有更多信息(新博客文章即将发布)。 - Alex Lockwood
这个评论来得有点晚了,但为什么不只加载一次数据并将其分配给全局List变量呢?或者使用接口? - Shubham
4个回答

6
如何从与其启动的不同Activity/Fragment重新连接到相同的Loader?
不应该在多个ActivityFragment之间重用由LoaderManager实例管理的Loaders。 LoaderManager将根据Activity/Fragment生命周期开始/停止这些Loaders,因此无法保证当您进入另一个Activity时这些Loaders仍然存在。
根据文档:

LoaderManager.LoaderCallbacks是一个回调接口,允许客户端与LoaderManager交互。

特别地,CursorLoader等加载器期望在被停止后保留它们的数据。这允许应用程序在activity或fragment的onStop()onStart()方法之间保持其数据,因此当用户返回应用程序时,他们不必等待数据重新加载。您在创建新的加载器并告诉应用程序何时停止使用加载器的数据时使用LoaderManager.LoaderCallbacks方法。

换句话说,通常情况下,Loaders将特定于某些Activity(或Fragment)。当您的Activity实现LoaderManager.LoaderCallbacks接口时,您的Activity将被赋予类型LoaderManager.LoaderCallbacks。每次调用initLoader(int ID, Bundle args, LoaderCallbacks<D> callback)时,LoaderManager会创建或重用一个特定于某个LoaderManager.LoaderCallbacks接口实例(在这种情况下是Activity)的Loader。这基本上将您的Activity与Loader绑定在一起,并且它的回调方法将在加载程序状态更改时被调用。
话虽如此,除非您能找到让两个单独的Activity共享相同回调方法的方法,否则我怀疑是否有干净的方法来做到这一点(即使让Activity和Fragment共享相同的回调听起来很棘手,如果不是不可能的话)。不过,我不会太担心。在我看过的所有示例代码中,我从未见过两个Activity和/或Fragment共享相同的回调方法。此外,鉴于Activity和Fragment都应该设计用于重复使用,在这种方式下共享Loader似乎并不是鼓励的事情。

1
啊哈!所以它不是绑定到Activity上,而是绑定到LoaderCallbacks接口上。我认为编写一个扩展LoaderCallbacks的外部类并非不可能。感谢您的指导!您说这并不被鼓励。但对我来说,这似乎是使用相同的数据以两种不同的方式向用户展示的完美方式。比如在我的例子中,可以在列表和地图上显示。您觉得呢?现在我使用Loader下载数据,并将其写入应用程序上下文中。这并不完美,因为如果列表中的加载器没有完成工作,并且用户切换到地图,则会再次启动相同的加载器。 - Michał Klimczak
嗯...是的,那很有道理。也许你可以尝试让你的Activity扩展一个实现了LoaderManager.LoaderCallbacks接口的类。这样,它们将继承相同的回调函数,也许你可以通过这种方式重用Loader。我从未尝试过这样做...如果你想出了什么,就告诉我,我会更新我的答案 :)。 - Alex Lockwood
1
说实话,我认为这不是你真正需要担心的事情...它可能会给你带来更多麻烦,而不值得这么做。 - Alex Lockwood
@MichałK,我最近一直在研究这个问题,得出的结论是,如果由两个不同的LoaderManager实例管理,则绝对无法共享Loader(即,如果两个Activity使用LoaderManager来管理Loader,那么它们不能共享相同的Loader)。你只能在多个活动/片段之间共享一个与LoaderManager无关的Loaders。我现在正在撰写有关此问题的博客文章 :) - Alex Lockwood
3
期待能看到您的帖子:) 顺便说一下,我放弃了之前的想法,开始使用ContentProviders解决了我的问题,虽然这不是答案。 - Michał Klimczak
显示剩余6条评论

1

是的,它对我有效。我在导航抽屉中有3个不同的片段,在其中相同的数据填充到不同的ListView中。(所有片段都是同一活动的一部分)。

我的AsyncTaskLoader:

public class MyTaskLoader extends AsyncTaskLoader<HashMap<String, Integer>> {

public MyTaskLoader(Context context) {
    super(context);
}

@Override
public HashMap<String, Integer> loadInBackground() {
...
return hashMap;
}

...
}

在所有片段中使用相同的加载器 ID。
片段1:
public class Fragment1 extends BaseFragment implements LoaderManager.LoaderCallbacks<HashMap<String, Integer>> {
@Override
public void onCreate(Bundle savedInstanceState) {

//initialize adapter

getActivity().getSupportLoaderManager().initLoader(0, null, this);

}

@Override
public Loader<HashMap<String, Integer>> onCreateLoader(int arg0, Bundle arg1) {
    // TODO Auto-generated method stub

    return new MyTaskLoader(getActivity());
}

@Override
public void onLoadFinished(Loader<HashMap<String, Integer>> arg0,
        HashMap<String, Integer> data) {
    // TODO Auto-generated method stub

    listAdapter.setData(data.keySet());

}

@Override
public void onLoaderReset(Loader<HashMap<String, Integer>> arg0) {
    // TODO Auto-generated method stub

    listAdapter.setData(null);
}
}

使用相同的ID作为Fragment2:
public class Fragment2 extends BaseFragment implements LoaderManager.LoaderCallbacks<HashMap<String, Integer>> {
@Override
public void onCreate(Bundle savedInstanceState) {

//initialize adapter

getActivity().getSupportLoaderManager().initLoader(0, null, this);

}

@Override
public Loader<HashMap<String, Integer>> onCreateLoader(int arg0, Bundle arg1) {
    // TODO Auto-generated method stub

    return new MyTaskLoader(getActivity());
}

@Override
public void onLoadFinished(Loader<HashMap<String, Integer>> arg0,
        HashMap<String, Integer> data) {
    // TODO Auto-generated method stub

    listAdapter.setData(data.keySet());

}

@Override
public void onLoaderReset(Loader<HashMap<String, Integer>> arg0) {
    // TODO Auto-generated method stub

    listAdapter.setData(null);
}
}

在初始化加载器之前,应该先初始化适配器。 目前为止还可以。 但是,这样做是否正确?有没有更好的方法来为多个Fragment使用公共加载器?


我认为这很好 - 这能够工作是因为您正在使用(常见的)Activity的supportLoaderManager以及相同的loader ID,因此相同的loader会被多次使用。我看不出任何理由它为什么不可以?而且这很简单,无需缓存或创建另一个侦听器-广播器集并管理它们。 - gkee
1
不幸的是,这并没有起作用。加载器只在每个片段的onCreate()方法中被重新创建。 数据在每次创建片段时都会被加载。 - user3316561

0

我不太确定你在阅读讨论后想要实现什么。但是有一个application.registerActivityLifecycleCallbacks()方法,可以接受全局的活动生命周期监听器(例如onActivityCreated())。


这是一个旧的讨论。当我真正理解了Android下载和缓存数据的模式(使用ContentProvider等)时,这个“重用的加载器”似乎完全没有用处。 - Michał Klimczak

0

如果您在不同的片段和活动中使用相同的加载器ID,那么我认为您将获得所需的行为。但请确保加载器ID对于要加载的数据是唯一的。例如,PhotosLoader和VideoLoader不应具有相同的ID。


因为Firebase?还是我漏掉了什么?顺便说一下,今年有这篇文章:https://medium.com/google-developers/making-loading-data-on-android-lifecycle-aware-897e12760832。 - Louis CAD
我是指Rxjava,但一般来说,Loaders还可以,但有更好的选择。 - Michał Klimczak

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接