Loader在android 3.0 被引进,使得在activity或fragment异步下载数据更简单。具备一下特点:
1. 对每个activity和fragment都适合。
2. 提供异步下载数据
3. 与原数据保持同步,随时更新。
4. 当配置变化导致重新创建时,自动与最近的loader的连接。这样就不需要重新查找数据。
Loader API Summary (Loader API 总结)
下面列出有可能在使用Loader用到的类和接口。
上面列出的这些类是用于实现Loader重要的组建。对于你创建的每个Loader,你不需要所有上面的逐渐,但你必须有一个LoaderManager的引用以便用来初始化一个Loader,还必须有一个实现Loader的类,比如CursorLoader。下面演示如何使用这些类和接口。
Using Loaders in an Application (在一个应用中使用Loader)
这部分讲解在android应用中如何使用Loader,一个使用Loader的应用包含下面几个部分:
一个activity或fragment
一个LoaderManager实例
一个从ContentProvider下载数据的CursorLoader。你也可以自己实现Loader或者AsyncTaskLoader接口从其它数据源下载数据。
一个用来显示Loader的数据,通常使用SimpleCursorAdapter。
一个数据源,比如当使用CursorLoader时,通常数据源是ContentProvider。
Starting a Loader (启动一个Loader)
在一个activity或fragment中,LoaderManager管理一个或多个Loader实例。但在一个activity或fragment中,只有一个LoaderManager。
通常在activity的onCreate()函数中初始化一个Loader,或者在fragment的onActivityCreate()函数中:
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);
初始化函数initLoader()有以下几个参数:
1. 一个用来标识Loader的标识符,比如上面例子的ID是0。
2. 可选参数,用与Loader创建,在上面例子为null。
3. 实现LoaderManager.LoaderCallbacks的类,LoaderManager调用这个函数来报告相应的loader事件,这上面例子中,这个activity类实现了 LoaderManager.LoaderCallback()接口,所以使用this作为参数。
初始化函数initLoader()保证了一个loader被初始化并被激活,调用这个函数后,通常有几下两个可能的结果:
如果指定ID的Loader存在,最后创建的Loader会被重新使用。
如果制定ID的Loader不存在,那么initLoader()将会触发LoaderManager.LoaderCallbacks的onCreateLoader()函数。在这个函数中你必须实例化一个Loader并返回这个 Loader。详见onCreateLoader。
无论那种情况,给定的LoadManager.LoaderCallbacks的实现都和Loader相关,当Loader的状态变化是,里面的相应的函数会被调用。如果请求的Loader已经存在并有自己的数据,那么系统将会马上调用onLoadFInished()函数,所以你必须考虑到这种情况,详见onLoaderFinished()。
注意到函数initLoader()返回了创建的Loader,但是你不必创建一个这个Loader的引用。LoaderManager能够自动的管理Loader的生命过程。LoaderManager在必要的情况下会启动或停止下载,维持Loader的状态和它关联的Content。也就是说你很少会直接和Loader交互(除了使用Loader中的函数来微调Loader的行为,参见LoaderThrottle),大多数情况你都是使用LoaderManager.LoaderCallbacks的函数 来干预下载过程。更多信息参照Using the LoaderManager Callbacks。
Restarting a Loader (重启一个Loader)
正如前面所示,当你用通过initLoader()来创建Loader时,如果制定的Loader ID存在,那么将使用这个已经存在的Loader,如果不存在,再创建一个。当有时候你相想丢弃旧的数据并 从新开始。
通过函数restartLoader()来丢弃旧的数据。比如下面的例子中,通过在函数SearchView.OnQueryTextListener检查用户查找的内容是否变化,如果变了,那么重新启动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;
}
Using the LoaderManager Callbacks (使用LoaderManager)
LoaderManager.LoaderCallbacks是一个回调接口,用户通过它与LoaderManager.Loaders(一般是CursorLoader)交互,这些Loader会在fragment或activity保存数据,即在activity或fragment的onStop()和onStart()间还是会保存数据,使得当用户返回到应用的使用不用等待数据下载。使用LoaderManager.LoaderCallbacks可以直到创建一个新的Loader,并告诉应用什么时候停止使用一个Loader的数据。
LoaderManager.LoaderCallbacks包括下面的函数:
onCreateLoader():实例化一个新的指定ID的LoaderManager并返回这个Loader。
onLoadFinished():当前一个创建的Loader完成下载时被调用
onLoaderReset():当前一个创建的Loader被重置时被调用,使得之前的数据无效。
下面详细描述这些函数的用法:
onCreateLoader
当你试图访问一个Loader时(比如通过initLoader()),这个函数会检查指定ID的Loader是否存在,如果没有,会触发LoaderManager.LoaderCallbacks中的函数onCreateLoader()。在这个函数里会创建一个新的Loader,通常是CursorLoader,但你也可以通过继承Loader来创建自己的Loader。
在下面的例子中,onCreateLoader()创建一个CursorLoader。在CursorLoader的构造函数中,你必须提供完整的信息以便在ContentProvider进行查找。需要下列信息:
uri:从制定的uri获取数据。
projection:一个列表,包含需要返回的列,如果为空,那么将返回所有的列。
selection:过滤器,用来过滤想要返回的行,用SQL WHERE语句(包含WHERE关键字),如果为空,将返回所有的列。
selectionArgs:你可以在selection包含?,这个?将会被selectionArgs取代,顺序跟出现在selection的顺序一样。这个值只能是字符串。
sortOrder:指定如何对返回的行排序,用SQL ORDERBY语句(包括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");
}
onLoaderFinished
当之前创建的loader完成下载时调用这个函数。这个函数必须在释放有这个loader提供的数据之前被调用。在这个函数中,你必须移除所有对所有的旧数据的使用(因为这些数据待会要被释放),但不是移除数据(仅仅移除对旧数据的使用),因为数据放在loader中。
一旦直到应用不再使用这些数据,loader将会移除这些数据,比如,如果数据是在CursorLoader中,那么你不能调用CursorLoader的close()函数,如果此时这个cursor关联到CursorAdapter中,那么你只能调用swapCursor()来却换Cursor而不能直接关闭这个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包含从contact content provider查询的数据,其中使用了CursorLoader来对provider进行查询。
对于想访问用户contact的应用,必须在它的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);
}
}
More Example (更多例子)
在ApiDemos中还有更多关于如何使用的例子
LoaderCursor:上面例子的完整版
LoaderThrottle:一个用来说明如何使用throttling来减少当ContentProvider数据变化时的查询次数。