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

时间:2013-01-21 18:36:49

标签: java android android-loadermanager

我完全迷失了initLoader的{​​{1}}和restartLoader函数之间的差异:

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

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

6 个答案:

答案 0 :(得分:200)

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

<强> initLoader

  

使用Loader调用初始化特定ID。如果这个ID已经存在   有一个与之关联的Loader,它保持不变以及之前的任何一个   回调用新提供的回调替换。如果没有   目前是ID的Loader,创建并启动了一个新的。

     

通常应在组件使用时使用此功能   初始化,以确保创建它所依赖的Loader。这个   允许它重新使用现有Loader的数据(如果有的话,   例如,当a之后重新创建一个Activity时   配置更改它不需要重新创建其加载器。

<强> restartLoader

  

调用以重新创建与特定ID关联的Loader。如果   目前有一个与此ID相关联的Loader,它将是   根据需要取消/停止/销毁。一个新的装载机   将创建给定的参数,并将其数据传递给您一次   可用。

     

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

基本上有两种情况:

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

    <强> 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建议的那样使用一次性标记来跟踪呼叫)!此决定仅基于&#34; need&#34;对于新的装载机。如果我们想运行相同的查询,我们使用initLoader,如果我们想运行不同的查询,我们使用restartLoader。 我们总是可以使用restartLoader,但这样效率很低。在屏幕旋转之后或者如果用户导航离开应用程序并稍后返回到同一个Activity,我们通常希望显示相同的查询结果,因此restartLoader将不必要地重新创建加载器并关闭基础(可能是昂贵的)查询结果

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

      

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

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

      

    调用restartLoader是否始终调用initLoader?

    不,它永远不会。

      

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

      

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

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

      

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

    在我上面的解释之后,应该(希望)明白。

    配置更改

    LoaderManager在配置更改(包括方向更改)之间保留其状态,因此您可能认为我们没有什么可做的。再想一想......

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

      

    如果在呼叫点处,呼叫者处于其启动状态,并且   请求的加载器已存在并已生成其数据   回调onLoadFinished(Loader,D)将立即被调用(内部   这个功能)[...]。

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

    我们必须区分两种情况:

    1. 处理配置更改本身:Fragments就是这种情况 使用setRetainInstance(true)或使用带有的Activity 根据清单中的android:configChanges标记。这些 在例如组件之后,组件不会收到onCreate呼叫一个 屏幕旋转,所以请记住打电话 另一个回调方法中的initLoader / restartLoader(例如,在 onActivityCreated(捆绑))。为了能够初始化装载机, 需要存储加载程序ID(例如,在列表中)。因为 我们可以在组件更改中保留组件 只需遍历现有的加载程序ID并调用initLoader(loaderid, ...)。
    2. 本身不处理配置更改:在这种情况下 可以在onCreate中初始化加载器,但我们需要手动完成 保留装载机ID或我们无法满足需要 initLoader / restartLoader调用。如果id存储在 ArrayList,我们会做一个 outState.putIntegerArrayList(loaderIdsKey,loaderIdsArray)in onSaveInstanceState并恢复onCreate中的id: loaderIdsArray = 在我们制作之前,savedInstanceState.getIntegerArrayList(loaderIdsKey) initLoader调用。

答案 1 :(得分:46)

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

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


文档也非常清楚:

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

  • restartLoader启动一个新的或重新启动此管理器中的现有Loader,将回调注册到它,并且(如果活动/片段当前已启动)开始加载它。如果先前已启动具有相同id的加载器,则在新加载器完成其工作时将自动销毁该加载器。回调将在旧的加载器被销毁之前传递。

答案 2 :(得分: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.

(换句话说,设置一些标志,以便 initLoader 始终运行一次&amp; restartLoader在 2nd&amp; later 通过 onResume

另外,请记住为Activity中的每个加载器分配不同的ID (如果您对编号不小心,可能会对该活动中的碎片产生一些问题)


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

使用null args在 onCreate 上尝试 initLoader (文档说这没关系)&amp; onResume 中的 restartLoader (有效args)....文档错误&amp; initLoader 抛出一个nullpointer异常。

仅尝试 restartLoader ...工作一段时间,但在第5或第6个屏幕上重新定位。

onResume 中尝试 initLoader ;再次工作一段时间&amp;然后吹。 (特别是“未启动时调用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 <中运行/ em>生命周期的一部分,如果他们要在周期的旋转中存活下来。 (我可能错了。)

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

lifecycle


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

答案 3 :(得分:0)

如果加载器已经存在,

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

restartLoader用于强制重新加载和更改参数。如果您要使用加载器创建登录屏幕,则每次单击按钮时只会调用restartLoader。 (由于凭据不正确等,可能会多次单击该按钮)。在登录过程中旋转屏幕的情况下,恢复活动的已保存实例状态时,您只能调用initLoader

答案 4 :(得分:-1)

如果加载器已经存在,则restartLoader将停止/取消/销毁旧的,而initLoader将使用给定的回调初始化它。我无法找出旧回调在这些情况下做了什么,但我猜他们会被抛弃。

我扫描了http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/app/LoaderManager.java,但我无法找出确切的区别,除了这些方法做了不同的事情。所以我想说,第一次使用initLoader并重新启动以下时间,虽然我不能肯定地说他们每个人会做什么。

答案 5 :(得分:-1)

首次启动时的Init loader使用loadInBackground()方法,第二次启动时将省略它。所以,我认为,更好的解决方案是:

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(...)无法正常工作。 唯一的forceLoad()允许在没有回调的情况下完成上一个加载线程(因此您将正确完成所有数据库事务)并再次启动新线程。是的,它需要一些额外的时间,但更稳定。只有最后启动的线程才会进行回调。因此,如果您想通过中断数据库事务进行测试 - 欢迎使用,请尝试使用restartLoader(...),否则使用forceLoad()。 restartLoader(...)的唯一便利是提供新的初始数据,我的意思是参数。 并且请不要忘记在这种情况下在适当的片段的onDetach()方法中销毁加载器。还要记住,有时候,当你有一个活动,让他们说,2个片段与Loader每个包含活动 - 你只会到达2个Loader管理器,所以Activity与Fragment(s)共享其LoaderManager,这是在装载期间首先显示在屏幕上。尝试LoaderManager.enableDebugLogging(true);在每种特定情况下查看详细信息。