特性:
Available For Every Activity and Fragment
Asynchronous loading data
monitor source data and deliever new results when the content changes
reconnnect to the last loader's cursor.
Loader API Summary
LoaderManager, 抽象类,关联到Activity(or Fragment), 用于管理LoaderManager的实例。每个Activity或者Fragmente只拥有一个LoaderManager,但一个LoadManager可拥有多个loaders, 常用的有CursoLoader
LoaderManager.LoaderCallbacks, 接口类,用于Client与LoaderManager交互
Loader, 抽象类, 执行异步loading data, 具体类CursorLoader, 它会监视源文件,并在内容变化时传递变化结果
AsyncTaskLoader, 抽象Loader, 提供一个AsyncTask 以便执行任务。
CursorLoader, AsyncTaskLoader子类,查询ContentResolver 并返回Cursor。实现了Loader协议,并使用AsyncTaskLoader将任务放至后台进行处理。最好的异步加载数据 from ContentResolver的解决方案
Starting a Loader
1. onCreate()
getLoaderManaer().initLoader(int id, Bundle args, LoaderCallbacks callback)
其中
id设为0,标示loader, 若该id已被使用,则直接连接上使用该id的loader,否则create one.
args设为null, 无配置数据
callback 设为this, loadmanager将汇报loader event。
当检索条件改变,希望重新查询的时候,使用getLoaderManager().restartLoader(0, null, this);
具体initLoader所做工作,可看源码追踪的时序图。
2. Using the LoaderManager Callbacks
在实现LoaderManager.LoaderCallbacks接口时,三个方法必须实现:
- onCreateLoader() — 根据所给id 实例化loader and 返回一个新的loader
- onLoadFinished() — 数据加载完成时, 参数data即是返回的结果。
- onLoaderReset() — 当数据源更新时。
可在onLoadFinished方法中,发送EVENT至Handler,然后在Handler中处理数据刷新,更新UI等工作。
Tracking Source Code
当开发者在实现LoaderCallbacks接口的onCreateLoader()方法,返回CursorLoader实例时,
在LoadManager.LoadManagerImpl.LoaderInfo.start()方法中会调用
mLoader = mCallbacks.onCreateLoader(mId, mArgs);
这样就将我们CursorLoader的实例传入了LoaderInfo的实例中。
其中mCallbacks在LoaderInfo的构造方法中进行赋值,也就是传入我们实现的LoaderCallbacks接口的实例。
public LoaderInfo(int id, Bundle args, LoaderManager.LoaderCallbacks callbacks)
接下来会调用
mLoader.startLoading()
而Loader类的startLoading()方法会调用onStartLoading(),它是一个空方法。
CursorLoader 作为Loader的子类,重写了onStartLoading()方法,代码如下:
Java@Override
protected void onStartLoading() {
if (mCursor != null) {
deliverResult(mCursor);
}
if (takeContentChanged() || mCursor == null) {
forceLoad();
}
}
可见,当mCursor为空时,或者contentChanged时,都将调用forceLoad()。也即数据源更新时,CursorLoader会自动调用onLoadComplete(),所以开发者只需在此处发送事件给Handler,让其更新数据即可。
当mCursor不为空,则直接传递结果,此处将会调用onLoadFinish()方法,将数据传递给客户端onLoadComplete()。
具体代码流程如图所示。
对于数据源更新,如何自动调用onLoadComplete()方法,简单来说,就是在进行onCreateLoader()方法中,会注册观察者,使其在数据更新时,将takeContentChanged()置为true。不再进行详细展开。
Example
Javapublic static class CursorLoaderListFragment extends ListFragment
implements OnQueryTextListener, LoaderManager.LoaderCallbacks {
// 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 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 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 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);
}
}