Android Loader官方使用说明
PS:对于Loader并不是很常用,但是在google的mvp示例中有使用loader加载数据的demo,本人觉得Loader有一定的尝试价值,v4有提供低版本支持不用担心API版本兼容问题。
前言
Loader API让你从ContentProvider或者其他数据源读取数据展示在FragmentActivity或者Fragment上。如果你不理解你为什么要用Loader API执行看似平凡的操作,那么首先要考虑没有loader的情况下可能遇到这些问题:
- 如果你直接在activity或fragment中获取数据,由于在UI线程执行查询过慢,用户将遇到缺乏响应。
- 如果你从另一个线程获取数据,也许是AsyncTask,那么你将负责通过各个activity或fragment生命周期
管理线程和UI线程,例如onDestroy()和configurations变更。
Loader解决了这些问题,包括其他好处。例如:
- Loader单独运行在隔离的线程放置janky或UI无响应
- 当事件发生时,Loader提供回调方法简化线程管理
- 当configuration变更时,Loader保留并缓存结果,防止重复查询
- Loader能实现一个Oberver监听底层数据变化。例如,CursorLoader自动注册一个ContentObserver,当数据变化时触发重新加载
Loader API摘要
在app中使用Loader是可能涉及多个类和接口。
LoaderManager
一个与FragmentActivity和Fragment相关的抽象类,管理一个或多个Loader实例。 每个activity或fragment中只有一个LoaderManager,但是一个LoaderManager能管理多个Loader。
Loader调用initLoader()或restartLoader()开始读取数据。系统自动确定具有相同ID的Loader是否存在,并将创建新的Loader或者重新使用现用的Loader。
LoaderManager.LoaderCallbacks
这个接口包含回调Loader事件触发时回调的方法。接口定义三个回调方法:
- onCreateLoader(int,Bundle) - 当需要一个新Loader被创建时调用此方法。代码应该创建一个Loader对象并返回给系统。
- onLoadFinished(Loader<D>,D) - 当Loader加载数据完成后调用此方法。通常,应该展示数据给用户。
- onLoaderReset(Loader<D>) - 当一个已经创建的Loader被重置是(当你调用destroyLoader(int))或activity(或fragment)已经销毁时调用此方法。 代码应该移除任何对Loader的数据的引用。
此接口通常通过activity或fragment实现并注册,当调用initLoader()或restartLoader()。
Loader
Loader执行加载数据。该类是一个抽象的,作为基础类服务与所有Loader。你可以直接使用子类Loeader或者使用内置子类之一来简单实现。
- AsyncTaskLoader - 一个抽象Loader,提供一个AsyncTask在单独的线程执行加载操作。
- Cursoroader - 一个AsyncTaskLoader用于异步加载来自ContentProvider的数据。它请求一个ContentResolver并返回一个Cursor。
应用中使用Loader
app使用Loader通常包括以下内容:
- FragmentActiivty或fragment
- 一个LoaderManager实例
- 一个CursorLoader通过ContentProvider加载数据。或者,你可以实现你的子类Loader或AsyncTaskLoader加载其他来源的数据。
- 一个LoaderManager.LoaderCallbacks的实现。在你创建一个新Loader和管理已经存在Loader引用的地方。
- 一个数据源,例如ContentProvider,当使用一个CursorLoader
开启Loader
一个FragmentActivity或Fragmentyou只有一个LoaderManager管理一个或多个Loader实例。通常在Activity的onCreate()方法或者在fragment的onActivityCreateed()方法中初始化Loader,如下:
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
getSupportLoaderManager().initLoader(0, null, this);
initLoader()方法参数如下:
- 标识Loader的唯一的ID,这里ID是0
- 提供给Loader的可选参数,这里传null
- LoaderManager.LoaderCallbacks的实现对象,LoaderManager会调用报告给Loader事件
initLoader()调用确保Loader被初始化和活跃。他有两种可能:
- 如果ID指定的Loader已经存在,则使用最后创建的Loader
- 如果ID指定的Loader不存在,initLoader()触发LoaderManager.LoaderCallbacks的onCreateLoader()方法。这是实现代码来实例化并返回Loader的地方。
在任何一种情况下,给的LoaderManager.LoaderCallbacks实现都与Loader相关联,在Loader状态改变时被调用。如果调用的时候调用者处于开始状态,请求已经存在的Loader并生成了它的数据,系统会立马调用onLoadFinished()方法(在iniLoader()之间),你必须准备好这些的发生。
注意initLoader()方法返回的被创建Loader,但是你不需要捕获它的引用。Loadermanager自动管理Loader的生命周期。必要时LoaderManager开始和停止加载数据,可以维持Loader相关内容的状态。这意味着,你很少和Loader直接交互(尽管使用Loader方法来微调Loader的行为)。
重启Loader
当你使用initLoader(),如上所示,如果ID指定的Loader已经存在,用这个。如果不是就创建一个。但是有时候你想丢弃老数据并重新开始。
要丢弃你的老数据,你用restartLoader()。例如,当用得查询队列变更时,这个SearchView.OnQueryTextListenerde实现会重启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;
getSupportLoaderManager().restartLoader(0, null, this);
return true;
}
LoaderManager.LoaderCallbacks的使用
LoaderManager.LoaderCallbacks是一个回调接口,让客户端通过LoaderManager与之交互。
CursorLoader预计Loader在停止之后将保留其数据。允许应用保持activity或fragment的onStop()和onStart()方法相关的数据,当用户返回应用程序时他们不必等待数据重载。你可以再LoaderManager.LoaderCallbacks方法合适使用这些方法创建一个新的Loader,并告诉应用合适停止使用Loader的数据。
- onCreateLoader() - 实例化并Loader为给定的ID 返回一个新的。
- onLoadFinished() - 当以前创建的加载程序完成加载时调用。
- onLoaderReset() - 当以前创建的加载程序正在重置时调用,从而使其数据不可用。
这些方法在下面的章节中有更详细的描述。
onCreateLoader
当你试图访问一个Loader(例如通过initLoader())时,它会检查该ID指定的Loader是否存在。如果没有,则触发LoaderManager.LoaderCallbacks的onCreateLoader()方法。这是你创建一个新的Loader的地方。通常将是CursorLoader(),但你能实现Loader的子类。
在这个例子中,onCreateLoader()回调方法创建一个CursorLoader。你必须用CursorLoader的构造方法创建CursorLoader,这需要完整地信息集来执行查询ContentProvider。具体来说,它需要:
- uri - 药检所的内容的URI
- projection - 要返回的列的列表。传递null将返回所有列,这是低效的。
- selection - 过滤器,声明返回哪些行,格式化为SQL WHERE子句(不包括WHERE本身)。传递null将返回给定URI的所有行。
- selectionArgs - 你可以在选择中包括 '?s' ,它们将按照它们出现在选择中的顺序由selectionArgs中的值替换。这些值将被绑定为字符串。
- sortOrder - 如何对行进行排序,格式为SQL ORDER BY子句(不包括ORDER BY本身)。传递null将使用默认的排序顺序,可能是无序的。
例如:
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的一个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) {
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
mAdapter.swapCursor(data);
}
onLoaderReset
当已经创建的Loader正在重置是调用这个方法,从而使数据不可用。这个回调让你找出数据何时被释放,这样你就可以移除它的引用。
// 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,包含ContentProvider查询联系人内容的查询结果。它使用了CursorLoader管理provider的查询。
对于访问用户联系人的应用,如本例所示,manifest必须包含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);
}
}
更多的例子
以下示例说明如何使用装载机:
- LoaderCursor - 上面显示的片段的完整版本。
- 检索联系人列表 - 使用CursorLoader从演示文稿提供程序检索数据的演练。
- LoaderThrottle - 如何使用限制来减少内容提供者在其数据更改时执行的查询数的示例。
- AsyncTaskLoader- 使用AsyncTaskLoader 从包管理器加载当前安装的应用程序的示例。