要回答这个问题,您需要深入研究LoaderManager
代码。虽然 LoaderManager 本身的文档不够清晰(或者不会有这个问题),但抽象 LoaderManager 的子类 LoaderManagerImpl 的文档更具启发性。
初始化加载器
调用以使用 Loader 初始化特定 ID。如果这个 ID 已经有一个与之关联的 Loader,它保持不变,并且任何以前的回调都替换为新提供的回调。如果当前没有该 ID 的 Loader,则会创建并启动一个新的 Loader。
这个函数一般应该在组件初始化时使用,以确保创建它所依赖的 Loader。这允许它重新使用现有加载器的数据(如果已经存在),因此例如在配置更改后重新创建 Activity 时,它不需要重新创建其加载器。
重启加载器
调用以重新创建与特定 ID 关联的加载程序。如果当前有与此 ID 关联的 Loader,它将酌情取消/停止/销毁。将创建一个具有给定参数的新加载器,并且一旦可用,它的数据就会交付给您。
[...] 调用此函数后,任何与此 ID 关联的先前加载程序都将被视为无效,您将不会收到来自它们的进一步数据更新。
基本上有两种情况:
- id 的加载器不存在:两种方法都会创建一个新的加载器,所以没有区别
- id 的加载器已经存在:
initLoader
只会替换作为参数传递的回调,但不会取消或停止加载器。对于CursorLoader
这意味着光标保持打开和活动状态(如果在调用之前是这种情况initLoader
)。另一方面,`restartLoader 将取消、停止和销毁加载器(并像游标一样关闭底层数据源)并创建一个新的加载器(如果加载器是,它也会创建一个新的游标并重新运行查询游标加载器)。
以下是这两种方法的简化代码:
初始化加载器
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;
}
重启加载器
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
.
Activity/Fragment 生命周期与使用一种或另一种方法的决定无关(并且没有必要像 Simon 建议的那样使用一次性标志来跟踪调用)!该决定完全基于对新装载机的“需求”。如果我们想运行我们使用的相同查询initLoader
,如果我们想运行我们使用的不同查询restartLoader
。
我们总是可以使用restartLoader
,但这将是低效的。在屏幕旋转之后,或者如果用户离开应用程序并稍后返回到同一个 Activity,我们通常希望显示相同的查询结果,因此restartLoader
会不必要地重新创建加载程序并关闭底层(可能很昂贵)的查询结果。
了解加载的数据与加载该数据的“查询”之间的区别非常重要。假设我们使用 CursorLoader 查询订单表。restartLoader
如果向该表添加新订单,CursorLoader 使用 onContentChanged() 通知 UI 更新并显示新订单(在这种情况下无需使用)。如果我们只想显示未结订单,我们需要一个新查询,我们将使用restartLoader
它返回一个反映新查询的新 CursorLoader。
这两种方法之间有什么关系吗?
他们共享代码来创建一个新的加载器,但是当加载器已经存在时,他们会做不同的事情。
打电话restartLoader
总是打电话initLoader
吗?
不,它永远不会。
我可以restartLoader
不打电话就打电话initLoader
吗?
是的。
调用initLoader
两次刷新数据是否安全?
调用initLoader
两次是安全的,但不会刷新数据。
我什么时候应该使用两者之一,为什么?
在我上面的解释之后,这应该(希望)很清楚。
配置更改
LoaderManager 在配置更改(包括方向更改)时保持其状态,因此您会认为我们无事可做。再想想...
首先,LoaderManager 不保留回调,因此如果您什么都不做,您将不会收到对您的回调方法onLoadFinished()
之类的调用,这很可能会破坏您的应用程序。
因此,我们至少必须调用initLoader
来恢复回调方法(restartLoader
当然,a 也是可能的)。该文档指出:
如果在调用点调用者处于启动状态,并且请求的加载器已经存在并且已经生成了它的数据,那么回调onLoadFinished(Loader, D)
将立即被调用(在这个函数内部)[...]。
这意味着如果我们initLoader
在方向更改后调用,我们将立即收到onLoadFinished
调用,因为数据已经加载(假设是更改之前的情况)。虽然这听起来很简单,但它可能很棘手(我们不都喜欢 Android...)。
我们必须区分两种情况:
- 处理配置本身的变化:对于使用 setRetainInstance(true) 的 Fragment 或
android:configChanges
清单中具有相应标签的 Activity 就是这种情况。这些组件在屏幕旋转后不会收到 onCreate 调用,因此请记住调用
initLoader/restartLoader
另一个回调方法(例如 in
onActivityCreated(Bundle)
)。为了能够初始化加载器,加载器ID需要被存储(例如在一个列表中)。因为组件在配置更改中保留,所以我们可以遍历现有的加载器 id 并调用initLoader(loaderid,
...)
.
- 本身不处理配置更改:在这种情况下,可以在 onCreate 中初始化加载器,但我们需要手动保留加载器 ID,否则我们将无法进行所需的 initLoader/restartLoader 调用。如果 id 存储在 ArrayList 中,我们将
outState.putIntegerArrayList(loaderIdsKey, loaderIdsArray)
在 onSaveInstanceState 中执行并在 onCreate: 中恢复 id:
loaderIdsArray =
savedInstanceState.getIntegerArrayList(loaderIdsKey)
在我们进行 initLoader 调用之前。