用于在多个活动/片段中重用的全局加载器 (LoaderManager)

Posted

技术标签:

【中文标题】用于在多个活动/片段中重用的全局加载器 (LoaderManager)【英文标题】:Global Loader (LoaderManager) for reuse in multiple Activities / Fragments 【发布时间】:2012-04-20 04:37:57 【问题描述】:

我想要达到的目标:

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

我的工作:

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

但是当我输出lm.toString() 时,它们似乎是两个不同的加载器。并且数据被下载了两次。

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

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

【问题讨论】:

您能解释一下为什么需要在Fragment 及其父Activity 中引用Loader 吗?请记住,鼓励设计您的Fragments 以重用...如果您开始将Activitys 和Fragments 的显式行为交织在一起,事情会很快变得混乱...尝试在每个类中分别做尽可能多的工作,然后在适当的时候实现Activity 回调方法。 话虽如此,在我开始听起来太迂腐之前,我会给你一个机会来解释你的推理:P 哦不,你误会了。这不是它的父 Activity!这是一个非常不同的活动。如果我问两个片段,问题的意义将保持不变。 如何在两个不同的片段中重用一个加载器? 只是我在某个 Activity 中为列表使用片段,为地图使用 MapActivity,我认为这可能很重要,但我不这么想。 我想我会再次访问这个问题,因为它没有完全解决。过去几天我一直在研究 LoaderManager/Loader 源代码,只是为了完全掌握它的工作原理,而且似乎这样的事情是不可能的。每个活动/片段都有自己的 LoaderManager(它不是全局实例),并且 LoaderManager 根据需要启动/停止/销毁加载器,以便在整个活动/片段生命周期中管理它们。但是,您可以重用与 LoaderManager 一起使用的 的加载器。稍后会详细介绍(即将发布新博文) 此评论为时已晚,但为什么不只加载一次数据并将其分配给全局 List 变量呢?还是使用接口? 【参考方案1】:

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

不应在多个Activitys 和Fragments 之间重复使用由LoaderManager 实例管理的Loaders。

LoaderManager 将在 Activity/Fragment 生命周期中启动/停止那些 Loaders,因此无法保证一旦您在另一个 @ 中这些 Loaders 将存在987654330@.

来自文档:

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

加载器,尤其是 CursorLoader,应该保留它们的数据 被阻止后。这允许应用程序保留其数据 跨活动或片段的 onStop() 和 onStart() 方法,所以 当用户返回应用程序时,他们不必等待 要重新加载的数据。您使用 LoaderManager.LoaderCallbacks 方法 什么时候知道什么时候创建一个新的加载器,并告诉应用程序 何时停止使用加载程序的数据。

换句话说,您的Loaders 通常会特定于某个活动(或片段)。当您的Activity 实现LoaderManager.LoaderCallbacks 接口时,您的活动将被赋予类型LoaderManager.LoaderCallbacks。每次调用initLoader(int ID, Bundle args, LoaderCallbacks<D> callback) 时,LoaderManager 都会创建或重用Loader,该Loader 特定于LoaderManager.LoaderCallbacks 接口的某个实例(在本例中是您的Activity 实例)。这实质上是将你的 Activity 与一个 Loader 绑定在一起,并且它的回调方法会随着 loader 状态的变化而被调用。

话虽如此,除非你能找到一种方法让你的两个单独的 Activity 共享相同的回调方法,否则我怀疑是否有一种干净的方法可以做到这一点(即让 Activity 和 Fragment 共享相同的回调听起来像会很棘手,如果不是不可能的话)。不过我不会太担心。在我见过的所有示例代码中,我从未见过两个 Activity 和/或 Fragments 共享相同的回调方法。此外,鉴于Activitys 和Fragments 都应该是为重用而设计的,以这种方式共享Loaders 似乎并不值得鼓励。

【讨论】:

啊哈!所以它不是绑定到Activity,而是绑定到LoaderCallbacks。我不认为编写一个扩展 LoaderCallbacks 的外部类是不可能的。感谢那!你说不鼓励。但对我来说,使用相同的数据以两种不同的方式向用户展示它似乎是一种完美的方式。在我的示例中,在列表和地图上。你不觉得吗?现在我使用 Loader 下载它并将其写入应用程序上下文。这并不完美,因为如果列表中的加载器没有完成它的工作并且用户切换到地图,那么相同的加载器会再次启动。 "...找到一种方法让您的两个单独的 Activity 共享相同的回调方法,我怀疑是否有一种干净的方法可以做到这一点" - 如果可能的话,我希望有人向我们解释.从我的想法来看,它是因为它是绑定到活动/片段生命周期的 LoaderManager,而不是 LoaderCallbacks。但也许我错了 嗯...是的,这是有道理的。也许您可以尝试让您的Activitys 扩展一个实现LoaderManager.LoaderCallbacks 的类。这样,它们都将继承相同的回调,也许您可​​以以这种方式重用 Loader。我从来没有真正尝试过这样做......如果你想出办法,请告诉我,我会更新我的答案:)。 老实说,我不认为这是你真正需要担心的事情......它可能最终会给你带来更多的麻烦而不是它的价值。 期待看到这个帖子:) 顺便说一句,我放弃了这个想法并开始使用 ContentProviders,它没有回答问题但解决了我的问题。【参考方案2】:

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

我的 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);


对 Fragment2 使用相同的 Id:

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 加上相同的加载程序 ID,所以同一个加载程序只会被多次使用。我看不出有什么不正常的理由?而且它非常简单,无需缓存或创建另一个侦听器-广播器集和管理它们。 不幸的是,这不起作用。加载器仅在每个 Fragment 的 onCreate() 方法中重新创建。每次创建 Fragment 时都会加载数据。【参考方案3】:

在完成讨论后,我不太确定您想要归档什么。但是有一个application.registerActivityLifecycleCallbacks() 方法,它接受全局活动生命周期监听器(如onActivityCreated())。

【讨论】:

这是一个古老的讨论。当我真正理解了下载和缓存数据(使用 ContentProvider 等)的 android 模式时,这个“重用加载器”似乎完全没用了。【参考方案4】:

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

【讨论】:

因为 Firebase?还是我错过了什么? BTW,今年有这篇文章:medium.com/google-developers/… 我的意思是 Rxjava,但总的来说,Loaders 还可以,但还有更好的选择。

以上是关于用于在多个活动/片段中重用的全局加载器 (LoaderManager)的主要内容,如果未能解决你的问题,请参考以下文章

片段存储和重用:使用TabView的多个子片段

如何保存和加载 Android 活动的预设?

如何在活动和片段之间传递对象

如何从片段到活动而不会干扰片段的可重用性

用于数据加载的 Android 活动/片段职责

用于 onActivityCreated 中通用图像加载器的片段中进度条的 NullPointerException