Loaders(加载器)
在Android 3.0的推出,Loaders可以很容易在一个activity或fragment中异步加载数据。Loaders具有以下特点:
- 它们对每一个
Activity
和Fragment
都是有效的。 - 它们提供数据的异步加载。
- 它们监控其数据的来源,并在内容变化的时候传递新的结果。
- 在一个配置改变时,他们自动的重新连接到最后loader的光标。因此,它们并不需要重新查询它们的数据。
Loaders API摘要
在应用程序中使用Loaders可能有多个类和接口被涉及。摘要在此表中:
类/接口 | 描述 |
---|---|
LoaderManager | 一个抽象类与
Activity 和Fragment 相关联,用来管理一个或多个 Loader 实例。这有助于应用程序结合Activity 和Fragment 生命周期管理长时间运行的操作;最常见的用途是用 CursorLoader ,但是应用程序可以自由地写自己的装载机装载其他类型的数据。每个activity和fragment只有一个LoaderManager,但是一个LoaderManager可以有多个loaders。 |
LoaderManager.LoaderCallbacks | 为了客户端与LoaderManager交互的回调接口。 |
Loader | 一个抽象类,执行数据的异步加载。这是一个Loader的基类。您通常会使用CursorLoader ,但你也可以实现自己的子类。当Loader被激活,它们应监测其数据的来源,并在内容发生变化时提供新的结果。 |
AsyncTaskLoader | 抽象的Loader提供了一个AsyncTask 去执行工作。 |
CursorLoader | AsyncTaskLoader的一个子类去查询 ContentResolver,并 且返回一个 |
在上表中的类和接口都是你在你的应用程序中用来实现一个加载器的必要组成部分。对于你创建的Loader,你不会需要上面所有的类和接口,但你总是需要一个LoaderManager
引用,用来初始化装载器和一个Loader类的实现,如CursorLoader
。以下各节展示如何在应用程序中使用这些类和接口。
在应用程序中使用Loader
本节将介绍在Android应用程序中如何使用Loader。一个应用程序使用装载器通常包括以下内容:
- 一个
Activity
Fragment
。 一个LoaderManager实例
。- 一个
CursorLoader通过ContentProvider
加载备份数据。或者,你也可以实现自己的Loader
和AsyncTaskLoader
的子类来加载
其他来源数据。 LoaderManager.LoaderCallbacks
的实现,这里你创建新的装载器和管理现有装载器引用。- 一种显示加载数据的方法,如
SimpleCursorAdapter
。 - 一个数据源,如
ContentProvider
,当使用CursorLoader
时。
启动Loader
在一个Activity
或者 Fragment中,
LoaderManager
管理一个或多个Loader的实例。每个activity或者fragment只有一个LoaderManager
。
在activity的onCreate()方法,或者fragment的 onActivityCreated()
方法中初始化一个Loader。你应该这样做,如下所示:
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);
initLoader()
方法包括下列参数:
- 唯一的ID标识Loader。在本例中,ID为0。
- 可选参数,在构造函数中提供给Loader(在本例中为NULL)。
- 一个
LoaderManager.LoaderCallbacks
实现,其中LoaderManager
调用,用来报告Load的事件。在本例中,本地类实现LoaderManager.LoaderCallbacks
接口,因此它传递一个引用给自己,this
。
initLoader()
调用,确保Loader被初始化和激活。它有两个可能的结果:
- 如果由ID指定的装载器已经存在,最后创建的加载程序被重复使用。
- 如果由该ID所指定的加载程序不存在,
initLoader()
触发LoaderManager.LoaderCallbacks的
方法onCreateLoader()
。在这里你实现实例化的代码,并返回一个新的Loader。欲了解更多的讨论,请参见onCreateLoader。
在这两种情况下,给定的LoaderManager.LoaderCallbacks
的实现与装载器相关联,并且将被调用,在装载器状态改变时。如果在这个点调用者处在启动状态,并且请求的装载器已经存在,并且已经产生了属于它的数据,那么系统调用onLoadFinished()
立即(在initLoader()期间
),所以你必须要为此做好准备,以发生。这个回调的更多讨论见onLoadFinished。
需要注意的是initLoader()
方法返回被创建的 Loader
,但你并不需要捕获它的引用。LoaderManager
自动管理加载器的使用寿命。在需要时LoaderManager
启动和停止加载,并保持Loader和其相关联的内容的状态。因为这意味着,你很少直接与Loader交互(对于使用加载器方法去调整Loader的行为的一个例子,请看LoaderThrottle 样例)。当特定事件发生,你最常使用的LoaderManager.LoaderCallbacks
方法,在加载过程中进行干预。有关此主题的更多讨论,请参阅 Using the LoaderManager Callbacks。
重新启动Loader
当使用initLoader()时
,如上所示,如果存在,它使用一个现有的具有指定ID的装载器,如果不存在,则创建一个。但有时你要丢弃你的旧数据并重新开始。
要丢弃旧数据,可以使用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
交互。
Loader,尤其CursorLoader
,希望被停止之后,仍保留它们的数据。这允许应用程序,以保存它们的数据贯穿于activity或fragment的onStop()
和ONSTART()
方法,这样,当用户返回到一个应用程序,它们不必等待数据重新载入。在知道什么时候创建一个新的Loader时候,你可以使用LoaderManager.LoaderCallbacks
方法,并告诉应用程序时,是时候停止使用Loader的数据。
LoaderManager.LoaderCallbacks
包括以下方法:
onCreateLoader()
-为给定的ID实例化并返回一个新的Loader
。onLoadFinished()
-当先前创建的Loader已经完成了它的加载任务时被调用。onLoaderReset()
-当先前创建的Loader被重置时调用,从而使得其数据不可用。
这些方法在下面的章节中更详细地描述。
onCreateLoader
当你尝试访问Loader(例如,通过initLoader()
),它会检查是否存在由指定的ID的Loader。如果没有,它会触发LoaderManager.LoaderCallbacks
方法onCreateLoader()
。在这里你创建一个新的Loader。通常情况下,这将是一个CursorLoader
,但是你可以实现自己的Loader的子类。
在这个例子中,onCreateLoader()
回调方法创建一个CursorLoader
。你必须使用它的构造方法建立CursorLoader
,这需要ContentProvider
执行查询时所需要的很多信息。具体而言,它需要:
- URI -为内容检索的URI。
- projection -一个列表,其中列返回。传递为 null,将返回所有列,这是低效的。
- selection-过滤器声明要返回的行,格式化SQL WHERE语句(不含WHERE本身)。传递null,将返回给定的URI的所有行。
- selectionArgs -在selection中你可能包括“?”,它将会被来自于selectionArgs的值所取代,在他们出现的顺序的选择中。该值将被绑定为字符串。
- sortOrder -如何对行进行排序,格式化一个SQL ORDER BY语句(不包括ORDER BY本身)。传递为
null,
将使用默认的排序顺序,这可能是无序的。
例如:
// 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()
方法,使旧的Cursor不会被关闭。例如:
// 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()
,并带有一个为null的值:
// 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);
}
例
作为一个例子,这里是完整的实现了一个fragment,它显示了一个ListView,该ListView
包含对联系人内容提供者的查询结果。它采用CursorLoader
管理查询。
对于一个应用程序访问用户的联系人,如本例所示,其清单文件中必须包括READ_CONTACTS
权限。
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,说明如何使用Loader:
- LoaderCursor - A complete version of the snippet shown above.。
- LoaderThrottle -An example of how to use throttling to reduce the number of queries a content provider does when its data changes(一个例子,如何使用节流,以减少内容提供者其数据变化的查询的数量)。
有关下载和安装SDK样例的信息,请参阅Getting the Samples。