Loaders
从Android 3.0开始引进了loader(加载器)技术, 在activity或者fragment中,loaders可以把异步地加载数据变得更简单。Loaders具有以下特性:
他们可以提供异步加载数据的能力。
他们监视数据源,并当内容改变时传递当前最新的结果。
当他们因为配置的改变而重新连接的时候,他们会自动地重连到上一个loader的游标。因此,他们不需要重新查询数据。
Loader API汇总
在应用中使用loaders可能会涉及到多个类和接口。他们被汇总到了下表中:
| 类/接口 | 描述 |
|---|---|
LoaderManager | 一个关联到Activity或 Fragment的抽象类,用来管理一个或多个Loader实例 这样可以帮助一个应用管理那些跟Activity 或者Fragment的生命周期联系到一起的长时间运行的操作;这样的最常见的使用就是 CursorLoader,然而应用可以自由地写自己的加载器来加载其他类型的数据。 每一个activity或者fragment都只有一个 LoaderManager。但是一个LoaderManager可以拥有多个加载器(loaders)。 |
LoaderManager.LoaderCallbacks | 一个用来去为客户端和LoaderManager提供交互的回调接口。 举个例子,你可以使用onCreateLoader() 回调方法来创建一个新的loader(加载器)。 |
Loader | 一个执行异步的数据加载的抽象类。这是加载器的基类。你可以使用典型的CursorLoader,但是也可以实现你自己的子类。 当loaders被激活的时候,它们应该见识数据源并且当内容改变的时候传递最新结果。 |
AsyncTaskLoader | 提供一个AsyncTask来执行异步加载数据的抽象loader。 |
CursorLoader | AsyncTaskLoader的一个子类,查询 ContentResolver然后返回一个Cursor。这个类用标准的方式实现了 Loader的协议以此来查询cursors, AsyncTaskLoader在后台线程中执行cursor查询所以它不会阻塞应用的UI。 使用loader是从ContentProvider异步加载数据的最好的方式, 相对于通过fragment或activity的API来执行查询 |
上表中的那些类和接口都是你将会用来在应用中实现loader的极其重要的组件。你不必在创建每一个loader的时候全部使用,但是你总是需要一个 LoaderManager的引用,用来初始化一个loader Loader类的实现,类似于CursorLoader。 下面的章节将会向您展示怎样在应用中使用这些类和接口。
在应用中使用Loader
这一节描述了怎样在Android应用中使用loaders。一个典型地使用了loaders的应用应该包含以下内容:
一个
LoaderManager的实例。一个
CursorLoader来载入被ContentProvider返回的数据。 或者,你可以实现自己的Loader或者AsyncTaskLoader的子类 从而从其他的资源加载数据LoaderManager.LoaderCallbacks的一个实现 这是你创建新的loader和管理你的已经存在的loader的引用的地方一个展示loader的数据的方法,比如一个
SimpleCursorAdapter。一个数据源,比如一个
ContentProvider,当你使用一个CursorLoader的时候。
开始使用Loader
LoaderManager管理一个Activity或 Fragment范围内的一个或多个Loader实例。每一个Activity或者Fragment都只有一个 LoaderManager。
你可以典型地初始化一个 Loader,可以在activity的 onCreate()方法,也可以在fragment的onActivityCreated()方法中。你可以像下面一样做这件事:
//准备loader,无论是与一个已经存在的loader重连,//还是新建一个。getLoaderManager().initLoader(0, null, this);
initLoader()方法需要以下参数:
一个可以标识loader的唯一的ID。本例中的ID是0。
一个可选参数,当loader初始化时提供给它(在本例中是
null)。一个
LoaderManager.LoaderCallbacks的实现,将会被LoaderManager调用,用来报告loader的时间。本例中,本地类实现了LoaderManager.LoaderCallbacks借口,所以它传递了一个自身的引用this。
initLoader()方法调用确保了一个loader会被初始化以及激活 它有两种可能的后果:
如果赋予loader的ID已经存在,那么上一个被创建的loader就会被重用
如果赋予loader的ID不存在,
initLoader()就会触发LoaderManager.LoaderCallbacks的onCreateLoader()方法。 这里就是你实例化并返回一个新loader的地方。 更多的讨论,请参看onCreateLoader章节。
无论在哪一种情况中,传入的LoaderManager.LoaderCallbacks 实现都会跟loader绑定在一起,它将会在loader状态改变时被调用。如果在本次调用时,调用者处于开始状态,并且所请求的loader已经存在并产生了数据,那么系统就会立马调用 onLoadFinished() (即在initLoader()过程中), 所以你必须为这种情况做好准备。更多关于这个回调方法的讨论,请参看 onLoadFinished。
请注意,initLoader() 方法返回被创建的Loader,但是你不必保留它的引用, LoaderManager会自动管理loader的生命, LoaderManager 会在必要的时候启动和终止,以及维护loader的状态和它关联的内容。这就意味着,你几乎不用和loader进行直接交互 (寻求使用loader方法来调整loader行为的例子,请参看 LoaderThrottle实例)。 你最常用的手段是当特定事件发生时,使用LoaderManager.LoaderCallbacks方法来介入到加载过程中。 更多关于本话题的讨论,请参看Using the LoaderManager Callbacks。
重启一个Loader
当你像上面展示的那样使用initLoader()的时候, 如果有的话,它会使用已存在的带有标识ID的loader。 如果没有,它会创建一个。但是有时你会想要丢掉旧的数据,开始新的过程。
为了丢弃旧的数据,你要使用restartLoader()。 例如,SearchView.OnQueryTextListener的实现在用户查询改变时重启了, loader需要被重启从而能够使用修改过的搜索过滤进行新的查询:
public boolean onQueryTextChanged(String newText) {
//当action bar的搜索文本改变时调用。
//更新搜索过滤器,然后重启loader用当前的过滤器
//做一次新的查询。
mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
getLoaderManager().restartLoader(0, null, this);
return true;}
使用LoaderManager回调
LoaderManager.LoaderCallbacks是一个回调接口,它为客户端提供与 is a callback interfaceLoaderManager交互的能力。
Loaders,尤其是CursorLoader,大家都希望当它被停止以后仍然可以保持数据。 这样允许应用在activity或fragment的 onStop()和onStart()方法之间保持数据, 以至于当用户返回到一个应用的时候,他们不必再等待数据的重新加载。 你可以使用LoaderManager.LoaderCallbacks方法 当你知道合何时需要创建新的loader,以及高速应用何时停止使用loader的数据。
LoaderManager.LoaderCallbacks包括以下方法:
onCreateLoader()— 根据传入的ID初始化并返回一个新的Loader。在下面的章节中会更详细地描述这些方法的细节
onCreateLoader
当你试图操作一个loader的时候,(例如通过
initLoader()), 会检查被赋予唯一ID的loader是否存在。如果不存在,它会触发LoaderManager.LoaderCallbacks的onCreateLoader()方法。 这是你创建新loader的地方。一般来说被创建的都是CursorLoader,但是你可以实现你自己的Loader子类。在本例中,
onCreateLoader()回调方法创建了一个CursorLoader。你必须使用它的构造方法建造这个CursorLoader,构造方法需要向 whichContentProvider执行一次查询的完整信息作为参数。它还需要:尤其地,它还需要:例如:
//如果不是null,这就是当前的用户提供的过滤器。String mCurFilter;...public Loader<Cursor> onCreateLoader(int id, Bundle args) { //当新的Loader需要被创建的时候调用此方法。 //本例仅有一个Loader,所以不必关心ID的问题。 //首先,根据我们是否正在过滤, //选择base URI来使用。 Uri baseUri; if (mCurFilter != null) { baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(mCurFilter)); } else { baseUri = Contacts.CONTENT_URI; } //现在创建并返回一个CursorLoader, //它会创建一个用来显示数据的Cursor。 String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND (" + Contacts.HAS_PHONE_NUMBER + "=1) AND (" + Contacts.DISPLAY_NAME + " != '' ))"; return new CursorLoader(getActivity(), baseUri, CONTACTS_SUMMARY_PROJECTION, select, null, Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");}onLoadFinished
当上一个被创建的loader已经结束数据加载的时候调用此方法。这个方法被保证会在提供给这个loader的数据被释放之前调用。 这个时候,你应该移除所有旧数据的使用(因为它们马上就会被释放),但是不应该自己去释放它们,因为它们的loader会做这些事。
一旦知道了应用将不会再使用这些数据,loader就应该立即释放它们。 例如,数据是来自
CursorLoader的一个cursor, 你就不应该再调用close()。如果这个cursor正要在CursorAdapter中被替换,你应该使用swapCursor()方法使得旧的Cursor不被关闭。例如:// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter;...public void onLoadFinished(Loader<Cursor> loader, Cursor data) { //把新cursor换进来 (一旦我们返回了,框架将会管理 //关闭旧cursor的事情) mAdapter.swapCursor(data);}onLoaderReset
当之前创建的loader被重置使得数据不可用的时候,此方法被调用。这个回调方法让你弄清楚数据何时会被释放,进而你可以移除对它的引用
下面的实现调用了参数为
null的swapCursor():// This is the Adapter being used to display the list's data.SimpleCursorAdapter mAdapter;...public void onLoaderReset(Loader<Cursor> loader) { //当最后一个Cursor进入到onLoadFinished()时被调用, //Cursor将要被关闭, 我们要确保 //不会再使用到它。 mAdapter.swapCursor(null);}Example
下面的例子完整实现了一个
Fragment显示一个包含了从联系人content provider返回的查询数据的ListView的内容的功能。 它使用一个CursorLoader来管理对provider的查询。一个应用想要实现操作用户的联系人,如例子中那样,它的manifest一定要包含
READ_CONTACTS权限。public static class CursorLoaderListFragment extends ListFragment implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> { //这就是用来展示列表信息的Adapter。 SimpleCursorAdapter mAdapter; //如果不是null,这就是当前的搜索过滤器。 String mCurFilter; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); //如果没有数据,就给控件一些文本去显示。 //在真正的应用中,信息来自应用资源。 setEmptyText("No phone numbers"); //我们在action bar中显示一个菜单项。 setHasOptionsMenu(true); //创建一个新的adapter,我们将用它来显示加载的数据。 mAdapter = new SimpleCursorAdapter(getActivity(), android.R.layout.simple_list_item_2, null, new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS }, new int[] { android.R.id.text1, android.R.id.text2 }, 0); setListAdapter(mAdapter); //准备loader, 重连到一个已存在的loader, //或者启动一个新的loader。 getLoaderManager().initLoader(0, null, this); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { //放置一个action bar用于搜索。 MenuItem item = menu.add("Search"); item.setIcon(android.R.drawable.ic_menu_search); item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); SearchView sv = new SearchView(getActivity()); sv.setOnQueryTextListener(this); item.setActionView(sv); } public boolean onQueryTextChange(String newText) { //action bar上的搜索文本改变的时候被调用。 //更新搜索过滤器,并且重启loader用当前的过滤器 //来做新的查询。 mCurFilter = !TextUtils.isEmpty(newText) ? newText : null; getLoaderManager().restartLoader(0, null, this); return true; } @Override public boolean onQueryTextSubmit(String query) { //不必关心这个方法。 return true; } @Override public void onListItemClick(ListView l, View v, int position, long id) { // Insert desired behavior here. Log.i("FragmentComplexList", "Item clicked: " + id); } //我们想获取的联系人中的行数据。 static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] { Contacts._ID, Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS, Contacts.CONTACT_PRESENCE, Contacts.PHOTO_ID, Contacts.LOOKUP_KEY, }; public Loader<Cursor> onCreateLoader(int id, Bundle args) { //当需要创建一个新的loader时被调用。 //本例中仅有一个loader,所以我们不必关心ID的问题。 //首先,根据我们当前是否正在过滤, //选择base URI来使用。 Uri baseUri; if (mCurFilter != null) { baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(mCurFilter)); } else { baseUri = Contacts.CONTENT_URI; } //现在创建并返回一个CursorLoader, //它将会为被显示的数据创建一个Cursor。 String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND (" + Contacts.HAS_PHONE_NUMBER + "=1) AND (" + Contacts.DISPLAY_NAME + " != '' ))"; return new CursorLoader(getActivity(), baseUri, CONTACTS_SUMMARY_PROJECTION, select, null, Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"); } public void onLoadFinished(Loader<Cursor> loader, Cursor data) { //把新的cursor换进来。 (框架将会在我们返回的时候 //管理旧cursor的关闭事宜。) mAdapter.swapCursor(data); } public void onLoaderReset(Loader<Cursor> loader) { //当最后一个Cursor进入onLoadFinished()的时候被调用。 //cursor将要被关闭, 我们应该确保 //不再使用它。 mAdapter.swapCursor(null); }}更多实例
在ApiDemos中有一些不同的例子,它们说明了怎样使用loader:
LoaderCursor — 上面展示的片段的汇总的完整版本。
LoaderThrottle — 一个例子,用来展示当数据改变时,怎样使用throtting来减少对content provider做查询的次数
uri — 要获取的内容的URI。
projection — 返回的列组成的列表,传入
null将会返回所有列,但是效率很低。selection — 一个声明返回哪些行的过滤器,被格式化成类似SQL中WHERE子句的形式(除了没有WHERE自己)。传入
null将会返回给定URI的所有行。selectionArgs — 你可能在Selection中包含一些‘?’,他们将会被selectionArgs的值给替换掉,顺序与它们在selection中出现的顺序一致。 这些值被约束为String类型
sortOrder — 怎样给这些行排顺序,被格式化为类似SQL中ORDER BY子句的形式(除了没有ORDER自己)。传入
null将会使用默认的排序方式,可能是没有顺序。onLoaderReset()— 当一个之前创建的loader被重置的时候会调用此方法,这样会导致它的数据不可用。onLoadFinished()— 当一个之前被创建的loader已经结束加载数据的时候会调用此方法。
参考资料:http://www.android-doc.com/guide/components/loaders.html
本文详细介绍了Android中Loader(加载器)技术,自Android 3.0起引入,旨在简化Activity和Fragment中异步数据加载流程。Loader具备有效管理数据加载、监控数据源变化及自动重连等功能,通过LoaderManager、LoaderManager.LoaderCallbacks、Loader、AsyncTaskLoader和CursorLoader等关键类和接口实现。文章还提供了具体示例,展示了如何在应用中使用Loader。

398

被折叠的 条评论
为什么被折叠?



