加载器 - Loaders
从Android 3.0开始,Android引入loaders功能,loaders提供了在activity和fragment中异步载入数据以及监视数据源的变化的能力。Loaders的特性如下:
- 在每个Activity和Fragment都可用;
- 实现异步加载数据
- 监控源数据的变化,当数据发生变化的时候获取新的数据;
- 他们最后的装载机光标自动重新连接到配置更改后创建时。因此,他们并不需要重新查询自己的数据。
Loader API Summary
在应用程序中使用Loaders可能会用到一些类和接口,在下表中总结:
Class/Interface | Description |
LoaderManager | 和Activity和Fragment有关的抽象类,可以用来管理一个或者多个Loader实例。这可以帮助一个应用程序管理长时间运行的方法和Activity或者Fragment的生命周期结合起来。最常用的是一个CursorLoader,对于加载其他类型的数据来写入自己的装载机,应用程序都是免费的。 每个activity或者fragment只能有一个LoaderManager.但是一个LoaderManager可以有多个Loaders。 |
LoaderManager.LoaderCallbacks | 客户端和LoaderManager交互的回调接口。例如:你使用onCreateLoader()回调方法来创建一个新的Loader。 |
Loader | 执行异步加载数据的的抽象类。是loader的父类。经常使用的是CursorLoader,但是你也可以实现自己的子类。当loaders处于活动状态时,它应该监控其数据的来源,当内容改变时候显示新的结果。 |
AsyncTaskLoader | 抽象的loader提供一个AsyncTask来处理工作。 |
CursorLoader | AsyncTaskLoader的子类,查询ContentResolver返回一个游标。这个类实现了Loader协议,对于游标查询在一个标准的方式,建立在AsyncTaskLoader基础上,来执行游标查询在后台线程中,以至于不会中断应用程序的UI。从ContentProvider中实现异步数据加载,而不是通过fragment或者Activity API来执行的管理查询,是最好的方式。 |
上述表中的类和接口是你在你的应用程序中实现一个加载器的基本组件。你没有必要全部使用到他们为你创建的加载器,但是但你总是需要一个参考LoaderManager以初始化装载器和一个Loader类的实现,例如CursorLoader。以下部分显示您如何使用这些应用程序中的类和接口。
在应用程序中使用Loaders
本节介绍如何在一个Android应用程序中使用Loaders。通常使用装载器的应用程序包括以下内容:
- 一个Activity或者Fragment
- 一个LoaderManager的实例
- 一个Cousorloader通过ContentProvider加载备份数据。另外,你可以实现你自己Loader或AsyncTaskLoader的子类加载一些其他来源的数据。
- LoaderManager.LoaderCallbacks的实现,这是你创建一个新的Loader和管理已经存在的loader的参考
- 显示loader数据的方式,例如,SimpleCursorAdapter。
- 一个数据源,例如:ContentProvider,当使用CursorLoader时。
启动一个Loader
LoaderManager通过Activity或者Fragment来管理一个或者多个Loader实例。在一个Activity或者Fragment中仅仅只有一个LoaderManager。
你通常的可以通过Activity的onCreate()方法来初始化一个Loader,或者通过Fragment的onActivityCreated()方法。你可以按照下面的做:
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);
initLoader()方法需要以下几个参数:
- 一个唯一的Id标识,在这个例子中Id是0。
- 构造函数的可选参数(在这里为空)
- LoaderManager.LoaderCallbacks的实现,LoaderManager调用来报告Loader事件。在这个例子中,本类实现了LoaderManager.LoaderCallbacks接口,它传递自身的引用,this。
initLoader()调用,确保Loader被初始化,以及存在。它可能有两种结果:
- 如果通过Id标识的Loader已经存在,上次被创建的Loader将会被重用。
- 如果不存在的, 由ID标识的Loader,initLoader()方法触发 LoaderManager.LoaderCallbacks的onCreateLoader()方法 。这是你实现的代码来实例化并返回一个新的Loader。更多的讨论,请参阅节onCreateLoader。
在这两种情况下,给出的LoaderManager.LoaderCallbacks的实现类和loader有关,当loader的状态发生变化时将会被调用。如果主叫方在此调用点是在它的启动状态,请求loader已经存在并且产生了数据,这是系统会立刻调用onLoadFinished()方法。所以你应该准备好这将会发生。
重新启动一个Loader
当你使用initLoader(),它将使用指定Id的已经存在的Loader。如果没有,将会创建一个。但有时你要放弃你的旧数据,并重新开始。
想要丢弃您的旧数据,您使用restartLoader()方法。例如:当用户的查询状态改变时,实现SearchView.OnQueryTextListener的类,会重启Loader。Loader需要重新启动,以便它可以使用修改后的搜索过滤器,做一个新的查询:
public boolean onQueryTextChanged(String newText) {
// Called when the action bar search text has changed. Update
// the search filter, and restart the loader to do a new query
// with this filter.
mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
getLoaderManager().restartLoader(0, null, this);
return true;
}
使用LoaderManager回调
LoaderManager.LoaderCallbacks是一个回调接口,可以让客户端和LoaderManager交互。
Loaders,尤其是CursorLoader,当将要结束的时候,它们被要求需要保存他们的数据。这允许应用程序来保存数据贯穿于Activity或者fragment的onStop()和OnStart()方法,这样当用户返回到应用程序,他们不必须等待重新载入数据。当你想知道合适创建一个新的loader,您可以使用LoaderManager.LoaderCallbacks方法,来告诉应用程序什么时候停止使用loader的数据。
LoaderManager.LoaderCallbacks包括这些方法:
- onCreateLoader() — Instantiate and return a new Loader for the given ID.
- onLoadFinished() — Called when a previously created loader has finished its load.
- onLoaderReset() — Called when a previously created loader is being reset, thus making its data unavailable.
这些方法在下面的章节将详细介绍。
onCreateLoader
当您尝试访问一个loader(例如,通过loader中的initLoader()方法 ),它会检查是否存在由指定的ID的loader。如果不存在,它会触发的LoaderManager.LoaderCallbacks的onCreateLoader()方法。
在这个例子中,onCreateLoader() 回调方法创建一个CursorLoader。你必须使用CursorLoader的构造方法构造它,需要ContentProvider执行查询时所需要的很多信息。具体来说,它需要:
- Uri-内容检索的URI
- projection — 要返回的列元素。如果为空的话,返回所有的列,但是这效率比较低。
- selection — 声明返回行的过滤器,格式化为一个SQL WHERE子句(不含WHERE本身)。传递为空,将返回给定的URI的所有行。
- selectionArgs —你可以包含?在selection中,将会被 selectionArgs的值替代,该值将被绑定为字符串。
- SortOrder — 如何对列进行排序,格式化为SQL令(不含ORDER本身)BY子句。如果为空将使用默认的排序顺序,这可能是无序的。
例如:
// If non-null, this is the current filter the user has provided.
String mCurFilter;
...
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// This is called when a new Loader needs to be created. This
// sample only has one Loader, so we don't care about the ID.
// First, pick the base URI to use depending on whether we are
// currently filtering.
Uri baseUri;
if (mCurFilter != null) {
baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
Uri.encode(mCurFilter));
} else {
baseUri = Contacts.CONTENT_URI;
}
// Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
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的游标,你不应该自己调用close()方法就可以了。如果光标被放置在一个CursorAdapter的,你应该使用swapCursor()方法 ,以至于旧的游标没有被关闭。例如:
// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter;
...
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
mAdapter.swapCursor(data);
}
onLoaderReset
先前创建的loader被重置时这个方法将会被调用,这样使得它的数据不可用。
此实现调用 swapCursor()方法:
// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter;
...
public void onLoaderReset(Loader<Cursor> loader) {
// This is called when the last Cursor provided to onLoadFinished()
// above is about to be closed. We need to make sure we are no
// longer using it.
mAdapter.swapCursor(null);
}
例子
public static class CursorLoaderListFragment extends ListFragment
implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {
// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter;
// If non-null, this is the current filter the user has provided.
String mCurFilter;
@Override public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Give some text to display if there is no data. In a real
// application this would come from a resource.
setEmptyText("No phone numbers");
// We have a menu item to show in action bar.
setHasOptionsMenu(true);
// Create an empty adapter we will use to display the loaded data.
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);
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);
}
@Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
// Place an action bar item for searching.
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) {
// Called when the action bar search text has changed. Update
// the search filter, and restart the loader to do a new query
// with this filter.
mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
getLoaderManager().restartLoader(0, null, this);
return true;
}
@Override public boolean onQueryTextSubmit(String query) {
// Don't care about this.
return true;
}
@Override public void onListItemClick(ListView l, View v, int position, long id) {
// Insert desired behavior here.
Log.i("FragmentComplexList", "Item clicked: " + id);
}
// These are the Contacts rows that we will retrieve.
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) {
// This is called when a new Loader needs to be created. This
// sample only has one Loader, so we don't care about the ID.
// First, pick the base URI to use depending on whether we are
// currently filtering.
Uri baseUri;
if (mCurFilter != null) {
baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
Uri.encode(mCurFilter));
} else {
baseUri = Contacts.CONTENT_URI;
}
// Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
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) {
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
mAdapter.swapCursor(data);
}
public void onLoaderReset(Loader<Cursor> loader) {
// This is called when the last Cursor provided to onLoadFinished()
// above is about to be closed. We need to make sure we are no
// longer using it.
mAdapter.swapCursor(null);
}
}
更多的例子
有几个不同的例子在ApiDemos中,说明如何使用装载机: