使用Loader,是为了方便在Activity或者Fragment中异步加载数据及监听数据源的变化,Loader的一些特征:
每一个Activity或者Fragment都能使用Loader
Loader提供异步加载数据的数据
Loader可以监听数据资源,有数据内容改变时Loader可以提供新的数据
当配置改变导致Activity或者Fragment重新创建后,Loader可以自动连接上次的游标(cursor),这样就不用重新执行查询操作了
在一个应用程序中使用Loader可能会用到的类文件(class)和接口(interface)如下:
LoaderManager:用来管理Loader的实例,通常和CursorLoader结合起来用,一个Activity或者Fragment中只能有一个LoaderManager,但是一个LoaderManager可以有多个Loaders
LoaderManager.LoaderCallbacks:用来和LoaderManager进行交互的回调接口
Loader:抽象类,用于执行异步加载数据,通常使用其子类CursorLoader,但也可以自定义一个子类使用
AsyncTaskLoader:Loader的子类,提供一个异步任务(AsyncTask)进行相关处理
- CursorLoader:是AsycnTaskLoader的子类,用于查询ContentResolver并且返回一个Cursor,常用于从ContentProvider中异步加载数据
一个使用Loader的应用程序的组成:
在一个Activity或者Fragment中
定义LoaderManager的实例
定义一个CursorLoader的实例用以从ContentProvider中加载数据,或者自定义一个Loader或者AsyncTaskLoader的子类去执行这个操作
实现LoaderManager.LoaderCallbacks接口,后面暂时略过。。。
接下来就是展示加载到的数据了,可以使用SimpleCursorAdapter等
- 当然,最重要的是要有数据源,比如一个ContentProvider提供的数据源
启动一个Loader
- 使用LoaderManager,在Activity的onCreate()或者在Fragment的onActivityCreated()方法中初始化Loader:
getLoaderManager.initLoader(0,null,this);
initLoader中的参数如下:
一个唯一识别Loader的id
构造方法的可选参数
- LoaderManager.LoaderCallbacks的实现,用以报告Loader的事件(先写着,再改)
initLoader方法用来确保Loader初始化完成并且处于活动状态,调用此方法的结果如下:
如果通过id标识的这个Loader已经存在了,上次创建的Loader将会被重用
如果指定id标识的Loader不存在,则通过LoaderManager.LoaderCallbacks的onCreateLoader()方法回调得到一个新的Loader实例
通常,Loader的生命周期由LoaderManager管理,所以我们很少直接与Loader交互
重新启动一个Loader
使用initLoader()方法,如果一个Loader已经存在了,则会重新使用这个存在的,所以当我不想使用这个存在的,就需要重新启动一个Loader,这时就可以使用restartLoader()方法
使用LoaderManager的回调方法
使用LoaderManager.LoaderCallbacks实现与LoaderManager的交互
Loader特别是CursorLoader,通常会在停止后仍然保留数据,这样就可以在Activity或者Fragment的onStop()和onStart()方法中保留应用程序的数据,这样,当用户返回到当前应用程序时,无需等待数据重新加载(对这句话不太理解)
LoaderManager.LoaderCallbacks包含如下回调方法:
onCreateLoader():通过Id进行实例化并返回一个新的Loader,通常在使用initLoader()时,如果指定Id的Loader不存在,就会新建一个Loader,就会用到这个回调方法(下面的实例中会说明其返回的Cursor的组成)
onLoadFinished():当之前创建的Loader加载完成时调用
- onLoaderReset():当之前创建的Loader被重置时调用,这样,之前的Loader中的数据就不可用了
下面是官方文档的实例,通过实例能更好的理解怎样使用Loader:
public class CursorLoaderListFragment extends ListFragment
implements SearchView.OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {
// 这是用于显示列表数据的Adapter
SimpleCursorAdapter mAdapter;
// 如果非null,这是当前的搜索过虑条件
String mCurFilter;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// 如果列表中没有数据,就给控件一些文字去显示.在一个真正的应用
// 中这应用资源中取得.
setEmptyText("No phone numbers");
// 设置在顶部栏中有菜单项.也就是允许Fragment使用菜单,这在之前的Fragment中有笔记
setHasOptionsMenu(true);
// 创建一个空的adapter,将用它显示加载后的数据
mAdapter = new SimpleCursorAdapter(getActivity(),
android.R.layout.simple_list_item_2, null,
new String[]{ContactsContract.Contacts.DISPLAY_NAME, ContactsContract.Contacts.CONTACT_STATUS},
new int[]{android.R.id.text1, android.R.id.text2}, 0);
//填充ListView
setListAdapter(mAdapter);
// 准备loader.可能是重连到一个已存在的或开始一个新的
getLoaderManager().initLoader(0, null, this);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
// 顶部栏的菜单项,用于点击出现搜索框
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) {
//获取搜索过滤条件(也就是变化是如果输入了文字就获取到)
mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
//并重启loader来执行一个新的查询
getLoaderManager().restartLoader(0, null, this);
return true;
}
@Override
public boolean onQueryTextSubmit(String query) {
// 我们不关心这个方法
return true;
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
// ListView中的点击事件,写入你想写的代码
Log.i("FragmentComplexList", "Item clicked: " + id);
}
// 这是想获取的联系人中一行的数据.
static final String[] CONTACTS_SUMMARY_PROJECTION = new String[]{
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.CONTACT_STATUS,
ContactsContract.Contacts.CONTACT_PRESENCE,
ContactsContract.Contacts.PHOTO_ID,
ContactsContract.Contacts.LOOKUP_KEY,
};
/**
* 当一个新的loader需被创建时调用.本例仅有一个Loader,所以不需要ID.首先设置baseUri,URI指向的是联系人
*/
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Uri baseUri;
//如果有搜索条件,就过滤出条件匹配的数据,如果没有搜索条件,就直接获取所有的数据
if (mCurFilter != null) {
baseUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_FILTER_URI,
Uri.encode(mCurFilter));
} else {
baseUri = ContactsContract.Contacts.CONTENT_URI;
}
// 现在创建并返回一个CursorLoader,它将负责创建一个Cursor用于显示数据
String select = "((" + ContactsContract.Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+ ContactsContract.Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+ ContactsContract.Contacts.DISPLAY_NAME + " != '' ))";
return new CursorLoader(getActivity(), baseUri,
CONTACTS_SUMMARY_PROJECTION, select, null,
ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
//CursorLoader的参数解释:CursorLoader(Context ctx,Uri uri,String[] projection,String selection,String[] selectionArgs,String sortOrder)
// ctx:为上下文,不多说
// uri:需要搜索的内容的uri
// projection:指定需要返回的列的集合,如果要返回所有列,使用null,但这样效率比较低
// selection:过滤需要返回的行,就像是SQL中的where语句后面的条件,使用null返回所有行
// selectArgs:过滤的条件,可以在上面的selection中使用"?"隔开来设置条件,也可以在这里写下条件,效果是一样的
// sortOrder:对列进行排序,就像是SQL中的“order by”后面的内容
}
/**
* 当之前创建的Loader加载完成时调用,将新的cursor换进来
*/
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
mAdapter.swapCursor(data);
}
/**
* 在最后一个Cursor准备进入上面的onLoadFinished()之前,Cursor要被关闭了,需要确保不再使用它
*/
public void onLoaderReset(Loader<Cursor> loader) {
mAdapter.swapCursor(null);
}
}