Answers:
要回答这个问题,您需要深入研究LoaderManager
代码。尽管LoaderManager本身的文档不够清楚(或者不会有这个问题),但抽象LoaderManager的子类LoaderManagerImpl的文档却更具启发性。
初始化加载器
调用以使用Loader初始化特定的ID。如果此ID已经有一个与之关联的Loader,则将其保持不变,并且所有以前的回调都将被新提供的回调代替。如果当前没有用于该ID的加载程序,则会创建并启动一个新的ID。
通常,在初始化组件时应使用此功能,以确保创建了依赖它的Loader。这样,如果已有的装载程序数据已经存在,它便可以重新使用它,例如,当在更改配置后重新创建活动时,不需要重新创建其装载程序。
restartLoader
调用以重新创建与特定ID关联的Loader。如果当前有一个与此ID关联的装载程序,则将视情况取消/停止/销毁它。将创建一个具有给定参数的新Loader,并将其数据传递给您。
[...]调用此函数后,与该ID关联的所有以前的装载程序都将被视为无效,并且您将不会再从中收到任何数据更新。
基本上有两种情况:
initLoader
将仅替换作为参数传递的回调,而不会取消或停止加载程序。对于a CursorLoader
表示光标保持打开状态并处于活动状态(如果在initLoader
调用之前是这种情况)。另一方面,`restartLoader将取消,停止和销毁加载器(并像游标一样关闭底层数据源)并创建新的加载器(如果加载器为一个CursorLoader)。这是这两种方法的简化代码:
初始化加载器
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
。
该活动/片段生命周期无关的决定使用一种或另一种方法(和有没有必要使用一次性标志来跟踪调用的西蒙建议)!该决定仅基于对新装载机的“需求”来做出。如果我们要运行使用的相同查询initLoader
,如果我们要运行不同的查询,则使用restartLoader
。
我们可以一直使用,restartLoader
但是那样效率很低。屏幕旋转后,或者如果用户离开应用程序并稍后返回相同的Activity,我们通常希望显示相同的查询结果,因此这restartLoader
将不必要地重新创建加载程序并取消基础(可能昂贵)的查询结果。
了解加载的数据与加载该数据的“查询”之间的区别非常重要。假设我们使用CursorLoader在表中查询订单。如果向该表添加了新订单,则CursorLoader使用onContentChanged()通知UI更新并显示新订单(restartLoader
在这种情况下无需使用)。如果我们只想显示未结订单,则需要一个新查询,并使用restartLoader
它返回一个反映新查询的新CursorLoader。
两种方法之间有关系吗?
他们共享代码来创建新的Loader,但是当已有Loader时,它们会做不同的事情。
打电话
restartLoader
总是打initLoader
吗?
不,永远不会。
我可以打电话
restartLoader
而不必打电话initLoader
吗?
是。
拨打
initLoader
两次刷新数据是否安全?
可以安全拨打initLoader
两次,但不会刷新任何数据。
什么时候应该使用两者之一,为什么?
经过我上面的解释,这(希望)应该清楚。
配置变更
LoaderManager会在配置更改(包括方向更改)中保留其状态,因此您会认为我们已无事可做。再想一想...
首先,LoaderManager不会保留回调,因此,如果您不执行任何操作,将不会收到对诸如like onLoadFinished()
之类的回调方法的调用,这很可能会破坏您的应用程序。
因此,我们必须至少调用initLoader
以恢复回调方法(restartLoader
当然,a 也可以)。该文档指出:
如果在调用时调用者处于启动状态,并且所请求的加载程序已经存在并已生成其数据,则将
onLoadFinished(Loader, D)
立即(在此函数内部)调用回调。[...]
这意味着,如果我们initLoader
在方向更改后致电,我们将立即onLoadFinished
接到电话,因为数据已经加载(假设更改之前就是这种情况)。虽然听起来很简单,但可能会有些棘手(我们都不要喜欢Android吗?)。
我们必须区分两种情况:
android:configChanges
清单中具有相应标记的Activity就是这种情况。这些组件在屏幕旋转后将不会收到onCreate调用,因此请记住要调用
initLoader/restartLoader
另一个回调方法(例如在中
onActivityCreated(Bundle)
)。为了能够初始化加载程序,需要存储加载程序ID(例如,在列表中)。由于组件是在配置更改中保留的,因此我们可以循环访问现有的加载器ID和call initLoader(loaderid,
...)
。outState.putIntegerArrayList(loaderIdsKey, loaderIdsArray)
在onSaveInstanceState中进行操作并在onCreate:中恢复ID
loaderIdsArray =
savedInstanceState.getIntegerArrayList(loaderIdsKey)
。initLoader
在已经创建Loader时调用(例如,通常在配置更改后发生),它告诉LoaderManager立即将Loader的最新数据传送到onLoadFinished
。如果尚未创建Loader(例如,在首次启动活动/片段时),则的调用会initLoader
告诉LoaderManager调用onCreateLoader
以创建新的Loader。
调用restartLoader
会销毁一个已经存在的Loader(以及与之关联的任何现有数据),并告诉LoaderManager调用onCreateLoader
以创建新的Loader并启动新的加载。
文档对此也很清楚:
initLoader
确保加载程序已初始化且处于活动状态。如果该加载器尚不存在,则会创建一个并(如果当前正在启动活动/片段)启动该加载器。否则,将重新使用最后创建的加载程序。
restartLoader
在此管理器中启动新程序或重新启动现有的Loader,向其注册回调,并且(如果活动/片段当前已启动)开始加载它。如果先前已启动了具有相同ID的装载程序,则在新的装载程序完成工作时,它将自动销毁。回调将在销毁旧加载程序之前传递。
initLoader()
在onCreate()
/中使用onActivityCreated()
。这样,当用户首次打开活动时,将首次创建加载程序...但是在随后的配置更改中,必须销毁整个活动/片段,以下调用initLoader()
将仅返回旧的Loader
而不是创建一个新的。通常restartLoader()
在需要更改Loader
查询时使用(例如,您要获取过滤/排序的数据等)。
我最近遇到了多个加载程序管理器和屏幕方向更改的问题,并且想说,经过反复试验,以下模式在“活动”和“片段”中均适用:
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是在运行运行第二&随后通过通行证的onResume)
另外,请记住为一个活动中的每个加载程序分配不同的ID(如果您不小心编号,则该活动中的片段可能会有点问题)
我尝试仅使用initLoader ....似乎无法有效工作。
试图initLoader上的onCreate与空ARGS(文档说这是确定)restartLoader(标有有效参数)的onResume ....文档是错误的&initLoader抛出一个空指针异常。
仅尝试过restartLoader ...可以工作一段时间,但在第5或第6屏幕重新定向时会失败。
在onResume中尝试过initLoader ; 再次工作一会儿再吹。(特别是“未启动时称为doRetain:” ...错误)
尝试了以下操作:(摘自具有loader 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。(我可能在这方面是不正确的,但似乎可以正常工作。这些辅助加载程序不是即时实例状态的一部分,因此在这种情况下使用initLoader实际上可能是正确的)
查看图表和文档,我会认为initLoader应该进入onRestart for Activities中的onCreate&restartLoader中,但是这会使Fragment使用某些不同的模式,而且我还没有时间研究这种情况是否稳定。任何人都可以评论这种模式是否成功吗?
如果加载程序已经存在,则restartLoader将停止/取消/销毁旧的加载程序,而initLoader只会使用给定的回调对其进行初始化。在这些情况下,我无法找到旧的回调函数,但我想它们将被放弃。
我通过http://grepcode.com/file/repository.grepcode.com/java/ext/com.google.android/android/4.0.1_r1/android/app/LoaderManager.java进行了扫描,但我找不到确切的信息区别在于,这些方法做的事情不同。因此,我想说的是,第一次使用initLoader并在随后的几次重新启动,尽管我不能确定地说它们中的每一个将做什么。
initLoader
在这种情况下该怎么办?
首次启动时的初始化加载程序使用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()可以完成没有回调的先前加载线程(因此您将正确完成所有数据库事务)并再次启动新线程。是的,它需要一些额外的时间,但是更稳定。只有最后一个启动的线程才会进行回调。因此,如果要通过中断数据库事务进行测试(欢迎使用),请尝试重新启动Loader(...),否则请尝试forceLoad()。restartLoader(...)的唯一便利是传递新的初始数据,我的意思是参数。并且在这种情况下,请不要忘记在适当的Fragment的onDetach()方法中破坏加载程序。还请记住,有时候,当您进行一项活动时,让他们说,每个Loader活动包含2个带有Loader的片段-您将仅到达2个Loader Manager,因此Activity与片段共享其LoaderManager,片段在加载过程中首先显示在屏幕上。尝试LoaderManager.enableDebugLogging(true); 查看每种情况下的详细信息。
getLoader(0)
中try { ... } catch (Exception e) { ... }
。
initLoader
在轮换后使用(并且所有回调均已完成,Loader处于空闲状态),您将不会获得onLoadFinished
回调,但是如果使用,restartLoader
您将获得回调吗?