`LoaderManager`中`initLoader`和`restartLoader`之间的区别

问题

关于526136644的initLoaderrestartLoader功能之间的差异,我完全迷失了:

  • 它们都有相同的签名。
  • restartLoader还会创建一个加载器(如果它不存在)("在此管理器中启动新的或重新启动现有的加载器")。

两种方法之间是否存在某种关系?拨打电话10420876总是致电initLoader吗?我可以拨打restartLoader而不必拨打电话initLoader吗?保存到调用initLoadertwice来刷新数据吗?我什么时候应该使用其中一个而且(重要的!)为什么?


#1 热门回答(195 赞)

要回答这个问题,你需要深入研究LoaderManager代码。虽然LoaderManager本身的文档不够清晰(或者没有这个问题),但LoaderManagerImpl(抽象LoaderManager的子类)的文档更具启发性。
initLoader>调用使用Loader初始化特定ID。如果此ID已经与其关联的Loader,则保持不变,并且先前的任何回调都将替换为新提供的回调。如果当前没有该ID的Loader,则会创建并启动一个新的。通常应在组件初始化时使用此函数,以确保创建它所依赖的Loader。这允许它重新使用现有Loader的数据(如果已有),例如,在配置更改后重新创建Activity时,不需要重新创建其加载器。
restartLoader>调用以重新创建与特定ID关联的Loader。如果当前存在与此ID关联的Loader,则将根据需要取消/停止/销毁该ID。将创建具有给定参数的新Loader,并且一旦可用,就将其数据传递给你。 [...]调用此函数后,与此ID相关联的任何先前的加载程序将被视为无效,并且你将不会从它们收到任何进一步的数据更新。

基本上有两种情况:

  • 具有id的加载器不存在:两种方法都将创建一个新的加载器,因此没有区别
  • 具有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.mCallbacks = ...),而restartLoader会停用旧加载器(当新加载器完成其工作时将被销毁),然后创建一个新加载器。

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

活动/片段生命周期与决定使用一种或另一种方法无关(并且不需要像Simon建议的那样使用一次性标记跟踪呼叫)!这个决定完全基于新装载机的"需求"。如果我们想运行相同的查询,我们使用initLoader,如果我们想运行不同的查询,我们使用restartLoader。我们总是可以使用restartLoader,但这样效率很低。在屏幕旋转之后,或者如果用户导航离开应用程序并稍后返回到同一个Activity,我们通常希望显示相同的查询结果,因此restartLoader将不必要地重新创建加载器并关闭基础(可能是昂贵的)查询结果。

理解加载的数据与加载数据的"查询"之间的区别非常重要。假设我们使用CursorLoader查询表的订单。如果向该表添加了新订单,则CursorLoader使用onContentChanged()通知ui更新并显示新订单(在这种情况下不需要使用restartLoader)。如果我们只想显示打开的订单,我们需要一个新的查询,我们将使用restartLoader返回一个反映新查询的新CursorLoader。

这两种方法之间是否存在某种关系?

他们共享代码来创建一个新的Loader,但是当加载器已经存在时,它们会做不同的事情。

调用restartLoader总是调用initLoader吗?

不,它永远不会。

我可以调用restartLoader而无需调用initLoader吗?

是。

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

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

我什么时候应该使用其中一个而且(重要的!)为什么?

在我上面的解释之后,这应该(希望)清楚。
配置更改
LoaderManager在配置更改(包括方向更改)中保留其状态,因此你可能认为我们没有什么可做的。再想一想...

首先,LoaderManager没有保留回调,所以如果你什么都不做,你就不会接到像onLoadFinished()之类的回调方法的调用,这很可能会破坏你的应用。因此,我们必须至少调用initLoader来恢复回调方法(当然也可以使用restartLoader)。 Thedocumentationstates:

如果在调用点处调用者处于启动状态,并且请求的加载器已经存在并且已经生成了它的数据,则将立即调用回调onLoadFinished(Loader,D)(在此函数内部)[...] 。

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

我们必须区分两种情况:

  • 处理配置更改本身:这是使用setRetainInstance(true)的Fragments或清单中具有相应android:configChanges标记的Activity的情况。例如,这些组件将不会收到onCreate调用。屏幕旋转,所以请记住在另一个回调方法中调用initLoader / restartLoader(例如在onActivityCreated(Bundle)中)。为了能够初始化加载器,需要存储加载器ID(例如,在列表中)。因为组件在配置更改中保留,所以我们可以遍历现有的加载器ID并调用initLoader(loaderid,...)。
  • 本身不处理配置更改:在这种情况下,可以在onCreate中初始化Loaders,但是我们需要手动保留加载器ID,否则我们将无法进行所需的initLoader / restartLoader调用。如果id存储在ArrayList中,我们将在onSaveInstanceState中执行outState.putIntegerArrayList(loaderIdsKey,loaderIdsArray),并在我们进行initLoader调用之前恢复onCreate中的id:loaderIdsArray = savedInstanceState.getIntegerArrayList(loaderIdsKey)。

#2 热门回答(43 赞)

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

CallingrestartLoader会破坏现有的Loader(以及与之关联的任何现有数据),并告诉LoaderManager调用onCreateLoader以创建新的Loader并启动新的加载。

文档也非常清楚:

  • initLoader确保Loader初始化并处于活动状态。如果加载器尚不存在,则创建一个加载器(如果活动/片段当前已启动),则启动加载器。否则,重新使用最后创建的加载器。
  • restartLoader启动一个新的或重新启动此管理器中的现有Loader,将回调注册到它,并且(如果活动/片段当前已启动)开始加载它。如果先前已启动具有相同id的加载器,则在新加载器完成其工作时将自动销毁该加载器。回调将在旧的加载器被销毁之前传递。

#3 热门回答(16 赞)

我最近遇到了多个加载程序管理器和屏​​幕方向更改的问题,并且想说经过大量的反复试验后,以下模式在我的活动和碎片中都适用:

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.

(换句话说,设置一些标志,以便onitLoaderis始终为run一次&在第二次和随后的次通过onResume上运行restartLoader)

此外,请记住为活动中的每个加载器分配不同的ID(如果你对编号不小心,这可能是该活动中碎片的一个问题)

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

TriedinitLoaderononCreatewith null args(docs说这没关系)&restartLoader(带有效的args)inonResume .... docs是错的&initLoaderthrth nullpointer异常。

TriedrestartLoaderonly ......工作了一段时间,但在第5或第6屏幕上重新定位。

TriedinitLoaderinonResume;再次工作一段时间然后吹。 (特别是"未启动时调用doRetain:"...错误)

试过以下内容:(摘自一个将加载器ID传递给构造函数的封面类)

/**
 * 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中找到了某个地方)

再次,这工作了一段时间,但仍然偶尔出现故障。

##从调试时我可以搞清楚,我认为与保存/恢复实例状态有关,要求initLoader(/ s)在生命周期的onCreate部分运行,如果它们能够在这个循环。 (我可能错了。)

如果管理器在结果从另一个管理器或任务返回之前无法启动(即无法初始化inonCreate),我只使用initLoader。 (我可能在这方面不正确,但它似乎有效。这些辅助加载器不是立即实例状态的一部分所以在这种情况下使用ininLoader可能实际上是正确的)

lifecycle

看一下图表和文档,我原以为initLoader应该在onRestart for Activities中进入onCreate&restartLoader,但是这会让Fragments使用一些不同的模式,而我没有时间调查它是否真的稳定。任何人都可以评论他们是否成功使用这种活动模式?