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

关于LoaderManagerinitLoaderrestartLoader函数之间的区别我完全失去了:

  • 他们都有相同的签名。
  • restartLoader不存在(“启动一个新的或重新启动一个现有的Loader在这个pipe理器”),也会创build一个加载器。

这两种方法有一些关系吗? 调用restartLoader是否总是调用initLoader ? 我可以调用restartLoader而不必调用initLoader ? 是否保存两次调用initLoader刷新数据? 我应该什么时候使用其中的一个(重要!)为什么?

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

initLoader

使用Loader调用来初始化一个特定的ID。 如果这个ID已经有一个与之关联的Loader,它将保持不变,任何先前的callback都会被新提供的callbackreplace。 如果当前没有装载器,则创build并启动新的装载器。

通常应该在组件初始化时使用这个函数,以确保它所依赖的Loader被创build。 这使得它可以重用现有的Loader数据(如果已经存在的话),例如在configuration更改后重新创buildActivity时,不需要重新创build其装载程序。

restartLoader

调用重新创build与特定ID关联的加载程序。 如果当前有一个与此ID关联的加载器,它将被适当地取消/停止/销毁。 具有给定参数的新Loader将被创build并且其数据一旦可用就会被传送给您。

[…]调用此函数后,与此ID关联的任何以前的加载器将被视为无效,您将不会收到任何进一步的数据更新。

基本上有两种情况:

  1. 带有id的加载器不存在:两个方法都会创build一个新的加载器,所以这里没有任何区别
  2. 具有ID的加载器已经存在:initLoader将只replace作为parameter passing的callback,但不会取消或停止加载器。 对于CursorLoader来说,这意味着游标保持打开并处于活动状态(如果在initLoader调用之前是这样的话)。 另一方面,restartLoader将取消,停止并销毁加载器(并closures底层的数据源,如游标),并创build一个新的加载器(如果加载器是CursorLoader,也会创build一个新的游标并重新运行查询) 。

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

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),这两个方法将创build一个新的加载器(info = createAndInstallLoader(…))。 在加载器已经存在的情况下,initLoader只会replacecallback(info.mCallbacks = …),而restartLoader会禁用旧的加载器(当新加载器完成工作时它将被销毁),然后创build一个新加载器。

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

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

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

这两种方法有一些关系吗?

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

调用restartLoader是否总是调用initLoader?

不,从来没有。

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

是。

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

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

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

上面的解释应该(希望)清楚。

configuration更改

一个LoaderManager在configuration改变(包括方向改变)中保持状态,所以你会认为我们没有什么可做的。 再想一想…

首先一个LoaderManager不保留callback,所以如果你什么也不做,你将不会收到像onLoadFinished()之类的callback方法的调用,这很可能会破坏你的应用程序。 因此,我们必须至less调用initLoader来恢复callback方法(当然也可以使用restartLoader)。 该文件指出:

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

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

我们必须区分两种情况:

  1. 处理configuration更改本身:对于使用setRetainInstance(true)的碎片或者在清单中具有相应android:configChanges标记的Activity的情况。 这些组件在例如屏幕旋转之后将不会收到onCreate调用,因此请记住在另一个callback方法(例如,onActivityCreated(Bundle))中调用initLoader / restartLoader。 为了能够初始化加载器,加载器ID需要被存储(例如在一个列表中)。 由于组件在configuration更改中保留,因此我们可以遍历现有的装入器标识并调用initLoader(loaderid,…)。
  2. 不自己处理configuration更改:在这种情况下,可以在onCreate中初始化Loaders,但是我们需要手动保留loader ID,否则我们将无法进行所需的initLoader / restartLoader调用。 如果ID存储在一个ArrayList中,我们会做一个
    outSateInstanceState中的outState.putIntegerArrayList(loaderIdsKey,loaderIdsArray),并在进行initLoader调用之前还原onCreate:loaderIdsArray = savedInstanceState.getIntegerArrayList(loaderIdsKey)中的id。

当Loader已经被创build的时候调用initLoader (这通常发生在configuration改变之后)例如告诉LoaderManager立即将Loader的最新数据传递给onLoadFinished 。 如果Loader尚未创build(例如,当activity / fragment第一次启动时),对initLoader的调用将通知LoaderManager调用onCreateLoader来创build新的Loader。

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


文档也很清楚:

  • initLoader确保Loader被初始化和激活。 如果加载器尚不存在,则创build一个加载器(如果活动/片段当前已启动)将启动加载器。 否则,重新使用最后创build的加载器。

  • restartLoader启动一个新的或者重新启动这个pipe理器中的一个现有的Loader,注册callback给它,并且(如果活动/片段当前已经启动)开始加载它。 如果之前已经启动了一个具有相同ID的加载器,那么当新加载器完成其工作时,它将自动被销毁。 旧的加载器被销毁之前,callback将被交付。

最近我遇到了一个关于多个loader loader和屏幕方向变化的问题,我想在经过大量的反复试验之后,下面的模式适用于我的Activity和Fragments:

 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 总是运行一次,并且在第二次以及随后经过onResume时运行restartLoader)

另外,请记住为一个Activity中的每个加载器分配不同的id(如果您不太注意编号,那么在该活动中可能存在片段问题)


我只尝试使用initLoader ….似乎没有有效的工作。

onCreate尝试initLoader与空参数(文档说这是好的)& 重新装载机 (与有效的参数)在onResume …. docs是错误的& initLoader抛出一个空指针exception。

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

onResume中尝试initLoader ; 再工作一段时间,然后吹。 (具体是“未启动时调用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); } } 

(我发现堆栈溢出中的某个地方)

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


从debugging的时候我可以看出,我认为有一点需要保存/恢复实例状态,如果initLoader (/ s)在生命周期的onCreate部分运行。 (我可能是错的。)

在pipe理器的情况下,直到结果从其他pipe理器或任务返回(即无法在onCreate中初始化)才能启动,我只使用initLoader 。 (我可能不是正确的,但它似乎工作。这些辅助装载机不是立即实例状态的一部分,所以在这种情况下使用initLoader实际上可能是正确的)

生命周期


看图和文档,我会认为initLoader应该在onRestart for Activities中的onCreate&restartLoader中,但是使用一些不同的模式留下碎片,我没有时间去调查它是否实际上是稳定的。 任何人都可以评论,如果他们有这种模式的成功活动?

如果加载器已经存在,那么restartLoader会停止/取消/销毁旧的,而initLoader只会用给定的callback来初始化它。 我找不到这些旧callback在这些情况下做了什么,但我想他们会被放弃。

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

初次启动时使用loadInBackground()方法,在第二次启动时它将被省略。 所以,我认为,更好的解决scheme是:

 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(); } } 

我花了很多时间来find这个解决scheme – restartLoader(…)在我的情况下不能正常工作。 唯一的forceLoad()让我们在没有callback的情况下完成前面的加载线程(这样你就可以正确地完成所有的db事务)并重新启动新的线程。 是的,它需要一些额外的时间,但更稳定。 只有最后一个启动的线程会callback。 因此,如果你想中断你的数据库事务进行testing – 欢迎你,尝试重新启动加载器(…),否则forceLoad()。 重新启动加载程序(…)的唯一便利是提供新的初始数据,我的意思是参数。 在这种情况下,请不要忘记在合适的Fragment的onDetach()方法中销毁loader。 另外请记住,有些时候,当你有一个活动,并让他们说,与Loader每个包容性活动2个片段 – 你将达到只有2个Loaderpipe理器,所以活动分享它的LoaderManager(s),这是在装载过程中首先显示在屏幕上。 试试LoaderManager.enableDebugLogging(true); 在每个特定情况下查看细节。

如果加载器已经存在, initLoader将重新使用相同的参数。 如果旧数据已经加载,它会立即返回,即使您使用新参数调用它。 加载器应该自动通知新数据的活动。 如果屏幕旋转, initLoader将被再次调用,旧数据将立即显示。

当你想强制重载并改变参数的时候, restartLoader是用来做的。 如果您要使用加载程序创buildlogin屏幕, restartLoader每次单击该button时都只会调用restartLoader 。 (由于凭证不正确等原因可能会多次点击该button)。 如果在login过程中旋转屏幕的情况下恢复活动保存的实例状态,则只会调用initLoader