LoaderManager 中 initLoader 和 restartLoader 的区别

我完全不明白 LoaderManagerinitLoaderrestartLoader功能的区别:

  • 他们的签名都一样。
  • 如果加载程序不存在,则 restartLoader也会创建一个加载程序(“在此管理器中启动一个新的或重新启动一个现有的加载程序”)。

这两种方法之间有什么联系吗?调用 restartLoader总是调用 initLoader吗?我可以调用 restartLoader而不必调用 initLoader吗?调用 initLoader两次刷新数据安全吗?我应该什么时候使用其中的一个和 为什么

34083 次浏览

如果加载程序已经存在,restartLoader 将停止/取消/销毁旧的加载程序,而 initLoader 将使用给定的回调对其进行初始化。我无法了解旧的回调函数在这些情况下会做什么,但我猜测它们将被放弃。

我通过 http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/app/LoaderManager.java扫描,但我不能找出确切的差异是什么,除了方法做不同的事情。因此,我会说,第一次使用 initLoader,然后在接下来的时间里重新启动,尽管我不能肯定地说它们每一个具体会做什么。

第一次启动时的初始化加载程序使用 loadInBack ()方法,第二次启动时将省略该方法。所以,我认为,更好的解决办法是:

Loader<?> loa;
try {
loa = getLoaderManager().getLoader(0);
} catch (Exception e) {
loa = null;
}
if (loa == null) {
getLoaderManager().initLoader(0, null, this);
} else {
loa.forceLoad();
}

///////////////////////////////////////////////////////////////////////////

protected SimpleCursorAdapter mAdapter;


private abstract class SimpleCursorAdapterLoader
extends AsyncTaskLoader <Cursor> {


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


@Override
protected void onStartLoading() {
if (takeContentChanged() || mAdapter.isEmpty()) {
forceLoad();
}
}


@Override
protected void onStopLoading() {
cancelLoad();
}


@Override
protected void onReset() {
super.onReset();
onStopLoading();
}
}

我花了很多时间来找到这个解决方案—— restartLoader (...)在我的情况下不能正常工作。 唯一的 forcLoad ()允许在不回调的情况下完成上一个加载线程(这样就可以正确地完成所有的 db 事务) ,并重新启动新线程。是的,它需要一些额外的时间,但更稳定。只有最后一个启动的线程将接受回调。因此,如果希望使用中断数据库事务进行测试——这是欢迎的,请尝试重新启动 Loader (...) ,否则执行 forcLoad ()。RestartLoader (...)的唯一方便之处是提供新的初始数据,我指的是参数。 在这种情况下,请不要忘记在 onDetach ()方法中销毁适当片段的加载程序。还要记住,有时候,当你有一个活动,并且,让他们说,两个片段与 Loader 每一个包含的活动-你将只到达2个 Loader 管理器,所以 Activity 共享它的 LoaderManager 与片段,这是显示在屏幕上第一次加载。尝试 LoaderManager.ableleDebugLogging (true) ; 查看每个特定情况下的详细信息。

我最近遇到了一个多个加载程序管理器和屏幕方向改变的问题,我想说的是,经过大量的反复试验之后,下面的模式对我来说在活动和片段中都适用:

onCreate: call initLoader(s)
set a one-shot flag
onResume: call restartLoader (or later, as applicable) if the one-shot is not set.
unset the one-shot in either case.

(换句话说,设置一些标志,使 InitLoader一直都是运行一次,并在 第二及其后通过 继续时运行 restartLoader)

另外,请记住为活动中的每个加载程序分配不同的 id (如果您不注意编号,那么该活动中的片段可能会有一点问题)


我尝试只使用 InitLoader... ... 似乎没有效果。

OnCreate上使用 null args (docs 说这没问题)和 RestartLoader(使用有效 args)在 继续上尝试了 InitLoader... . docs 是错误的,InitLoader抛出一个 nullpoint 异常。

尝试 RestartLoader只... 工作了一段时间,但吹在第5或第6屏幕重新定位。

继续中尝试了 InitLoader; 再次工作了一段时间,然后就爆炸了。(特别是“未启动时调用 doRetain:”... error)

尝试以下操作: (摘自一个将加载程序 ID 传递给构造函数的 cover 类)

/**
* start or restart the loader (why bother with 2 separate functions ?) (now I know why)
*
* @param manager
* @param args
* @deprecated use {@link #restart(LoaderManager, Bundle)} in onResume (as appropriate) and {@link #initialise(LoaderManager, Bundle)} in onCreate
*/
@Deprecated
public void start(LoaderManager manager, Bundle args) {
if (manager.getLoader(this.id) == null) {
manager.initLoader(this.id, args, this);
} else {
manager.restartLoader(this.id, args, this);
}
}

(我在 Stack-Overflow 找到的)

同样,这种方法虽然有效了一段时间,但仍然偶尔会出现一些小故障。


根据我在调试过程中得出的结论,我认为存储/恢复实例状态需要在生命周期的 OnCreate部分运行 InitLoader(/s) ,这样它们才能在循环中存活下来。(我可能错了)

在经理的情况下,不能启动,直到结果从另一个经理或任务返回(即。不能在 OnCreate中初始化) ,我只使用 InitLoader。(在这一点上我可能不正确,但它似乎是有效的。这些辅助加载程序不是直接实例状态的一部分,因此在这种情况下使用 InitLoader实际上可能是正确的)

lifecycle


看看这些图表和文档, 我本来以为 initLoader 应该在 onRestart for Activity 中加入 onCreate & restartLoader,但是那样会使片段使用一些不同的模式,我没有时间去研究这是否真的是稳定的。其他人可以评论一下他们是否在这个活动模式上取得了成功吗?

当 Loader 已经创建时调用 initLoader(例如,这通常发生在配置更改之后)告诉 LoaderManager 立即将 Loader 的最新数据交付给 onLoadFinished。如果 Loader 尚未创建(例如,当活动/片段首次启动时) ,对 initLoader的调用将告诉 LoaderManager 调用 onCreateLoader来创建新的 Loader。

调用 restartLoader会销毁一个已经存在的 Loader (以及与之相关的任何现有数据) ,并告诉 LoaderManager 调用 onCreateLoader来创建新的 Loader 并启动一个新的加载。


有关这一点的文档也非常清楚:

  • initLoader确保 Loader 已初始化并处于活动状态。如果加载程序不存在,则创建一个加载程序并(如果活动/片段当前已启动)启动加载程序。否则,将重用最后创建的加载程序。

  • restartLoader在这个管理器中启动一个新的或重新启动一个现有的 Loader,注册它的回调,然后(如果当前启动了活动/片段)开始加载它。如果以前启动过具有相同 id 的加载程序,那么当新加载程序完成其工作时,它将自动被销毁。在销毁旧加载程序之前,将传递回调函数。

要回答这个问题,您需要深入研究 LoaderManager代码。 虽然 LoaderManager 本身的文档不够清楚(或者不会有这个问题) ,但是 LoaderManagerImpl (抽象 LoaderManager 的一个子类)的文档更具启发性。

InitLoader

调用以使用 Loader 初始化特定的 ID 有一个与之关联的 Loader,它将保持不变 用新提供的回调替换。如果没有 当前为 ID 创建一个 Loader,创建并启动一个新的 Loader。

此函数通常应在组件为 初始化,以确保创建它所依赖的 Loader 允许它重用现有的 Loader 数据,如果已经有一个, 例如,当一个活动在一个 配置更改不需要重新创建其加载程序。

RestartLoader

调用以重新创建与特定 ID 关联的 Loader 当前有一个与此 ID 关联的 Loader,它将是 取消/停止/销毁 将创建给定的参数并将其数据一次性传递给您 有空。

[ ... ]调用此函数后,与此 ID 关联的任何以前的 Loader 将被视为无效,您将不会收到进一步的数据 他们的最新消息。

基本上有两种情况:

  1. 带 id 的加载程序不存在: 两个方法都会创建一个新的加载程序,所以没有区别
  2. 具有 id 的加载程序已经存在: initLoader只会替换作为参数传递的回调,但不会取消或停止加载程序。对于 CursorLoader,这意味着游标保持打开和活动(如果在 initLoader调用之前是这种情况)。另一方面,restartLoader 将取消、停止和销毁加载程序(并像关闭游标一样关闭底层数据源) ,并创建一个新的加载程序(如果加载程序是 CursorLoader,它也将创建一个新的游标并重新运行查询)。

下面是这两种方法的简化代码:

InitLoader

LoaderInfo info = mLoaders.get(id);
if (info == null) {
// Loader doesn't already exist -> create new one
info = createAndInstallLoader(id, args, LoaderManager.LoaderCallbacks<Object>)callback);
} else {
// Loader exists -> only replace callbacks
info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
}

RestartLoader

LoaderInfo info = mLoaders.get(id);
if (info != null) {
LoaderInfo inactive = mInactiveLoaders.get(id);
if (inactive != null) {
// does a lot of stuff to deal with already inactive loaders
} else {
// Keep track of the previous instance of this loader so we can destroy
// it when the new one completes.
info.mLoader.abandon();
mInactiveLoaders.put(id, info);
}
}
info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);

如我们所见,在加载程序不存在的情况下(info = = null) ,两个方法都将创建一个新的加载程序(info = createAndInstallLoader (...))。 在加载程序已经存在的情况下,initLoader只替换回调函数(info.mCallback = ...) ,而 restartLoader使旧加载程序失活(当新加载程序完成其工作时,旧加载程序将被销毁) ,然后创建一个新加载程序。

因此,现在已经很清楚什么时候使用 initLoader,什么时候使用 restartLoader,以及为什么使用这两种方法是有意义的。 initLoader用于确保有一个初始化的加载程序。如果不存在,则创建一个新的,如果已经存在,则重新使用。我们总是使用这种方法 UNLESS 我们需要一个新的加载程序,因为查询运行已经改变(不是底层数据,而是实际的查询,如 SQL 语句中的 CursorLoader) ,在这种情况下,我们将调用 restartLoader

活动/碎片生命周期与决定使用一种或另一种方法没有任何关系(也没有必要像 Simon 建议的那样使用一次性标志来跟踪调用) !这个决定完全是基于对新加载程序的“需要”而做出的。如果我们想运行同样的查询,我们使用 initLoader,如果我们想运行不同的查询,我们使用 restartLoader

我们总是可以使用 restartLoader,但是效率不高。在一个屏幕旋转之后,或者如果用户从应用导航离开并返回到相同的活动,我们通常希望显示相同的查询结果,因此 restartLoader将不必要地重新创建加载程序并取消底层的(可能代价高昂的)查询结果。

理解加载的数据和加载该数据的“查询”之间的区别非常重要。假设我们使用 CursorLoader 查询表中的订单。如果向该表添加了新订单,CursorLoader 将使用 onContentChanged ()通知 UI 更新并显示新订单(在本例中不需要使用 restartLoader)。如果我们只想显示打开的订单,我们需要一个新的查询,我们将使用 restartLoader返回一个新的 CursorLoader 反映新的查询。


这两种方法之间有什么联系吗?

它们共享创建新 Loader 的代码,但是当加载程序已经存在时,它们执行不同的操作。

调用 restartLoader总是调用 initLoader吗?

不,从来没有。

我可以调用 restartLoader而不必调用 initLoader吗?

是的。

调用 initLoader两次刷新数据是否安全?

调用 initLoader两次是安全的,但不会刷新任何数据。

我什么时候应该使用其中的一个和 为什么


在我上面的解释之后,这应该(希望)是清楚的。

配置改变

LoaderManager 通过配置更改(包括方向更改)保持其状态,因此您可能会认为我们没有什么可做的了。再想想..。

首先,LoaderManager 不保留回调函数,所以如果什么都不做,就不会接收到对 onLoadFinished()等回调方法的调用,这很可能会破坏应用程序。

因此,我们必须至少调用 initLoader来恢复回调方法(当然,也可以调用 restartLoader)。文件指出:

如果在调用时调用方处于其启动状态,并且 请求的加载程序已经存在并已生成其数据,则 回调 onLoadFinished(Loader, D)将被立即调用(在 [ ... ].

这意味着如果我们在方向改变之后调用 initLoader,我们将立即得到 onLoadFinished调用,因为数据已经加载(假设在方向改变之前是这种情况)。 虽然这听起来直截了当,但它可能很棘手(我们不都喜欢 Android 吗... ...)。

我们必须区分两种情况:

  1. 处理配置更改本身: 片段就是这种情况 使用 setRetainInstance (true)或者对于在清单中具有相应 android:configChanges标记的活动 组件不会接收 onCreate 调用 屏幕旋转,所以记住要调用 在另一个回调方法中(例如,在 为了能够初始化加载程序, 加载程序 id 需要被存储(例如在 List 中) 组件在我们可以进行的配置更改中被保留 只需循环现有的加载程序 id 并调用 < code > initLoader (loaderid, ...) .
  2. 不处理配置更改本身: 在这种情况下, 加载程序可以在 onCreate 中初始化,但是我们需要手动进行初始化 保留加载程序 ID,否则我们将无法制作所需的 InitLoader/restartLoader 调用 数组列表,我们会做一个
    进入 outState.putIntegerArrayList(loaderIdsKey, loaderIdsArray) OnSaveInstanceState 并恢复 onCreate 中的 id: LoaderIdsArray = GetIntegerArrayList (loaderIdsKey) InitLoader 调用。

如果加载程序已经存在,initLoader将重用相同的参数。如果已经加载了旧数据,即使使用新参数调用它,它也会立即返回。理想情况下,加载程序应该自动通知新数据的活动。如果屏幕旋转,将再次调用 initLoader并立即显示旧数据。

restartLoader是用于当您想要强制重新加载和更改参数以及。如果要使用加载程序创建登录屏幕,那么每次单击按钮时都只调用 restartLoader。(由于不正确的凭证等原因,按钮可能被多次点击)。只有在登录过程中屏幕旋转的情况下恢复活动保存的实例状态时才会调用 initLoader