在Android3.0中介绍的,载入器(Loader)可以让从activity或者fragment中异步载入数据变得更简单。载入器有下面这些特点:
● 对任意activity和fragment都有效
● 提供异步下载数据功能
● 当内容(content)改变的时候,它们监控数据的来源并且提交新的结果
● 当配置改变导致重新创建(activity/fragment)的时候,它们自动重新连接到最后一个载入器的指针(the last loader’s cursor)。因此,它们不需要重新提交它们的数据
Loader API Summary
有很多类和接口可能包含和使用载入器。它们总结在下面的表格中。
Class/Interface | Description |
LoaderManager | 这个是与Activity或Fragment管理一个或多个Loader实例有关的抽象类。它帮助app管理在activity或fragment生命周期中的长时间操作。最常见使用该类操作是联合CursorLoader使用的,app还可以自由定义这两个类来下载其他类型的数据。 在每个activity或fragment中,只有一个LoaderManager。但是每个LoaderManager中可以有多个loader. |
LoaderManager. LoaderCallbacks | 客户端的回调接口可以与LoaderManager相互作用。例如,可以使用onCreateLoader()回调方法创建一个新的loader |
Loader | 提供异步下载数据的抽象类。对于载入器来说,这是最进本的类。你可以使用最常见的CursorLoader,你也可以实施自己自定义的子类。当载入器活动的时候,它应该监控数据来源并在content改变的时候发送新结果。 |
AsyncTaskLoader | 抽象载入器,提供一个AsyncTask对象。 |
CursorLoader | 是AsyncTaskLoader的父类,查询ContentResolver同时返回一个Cursor。这个类用一种标准方式查询cursor来实现载入器协议,它创建一个AsyncTaskLoader在后台运行线程来查找cursor列表,并不在app的UI上显示出来。使用这个载入器从ContentProvider上异步下载数据是最佳方法。 |
Using Loaders in an Application
这个部分说明如何在android app中使用载入器。典型的在app中使用载入器包括下面的:
● 一个activity或fragment
● 一个LoaderManager实例
● 一个CursorLoader下载由ContentProvider支持的数据。你可以实现自定义的Loader子类或实现AsyncTaskLoader从其他资源处下载数据。
● 一个LoaderManager. LoaderCallbacks的实现。在这个实现中你创建一个新的载入器,同时也在其中管理已有载入器的引用。(manage your references to existing loaders)
● 展示载入器数据的一种方式,例如一个SimpleCursorAdapter
● A data source, such as a ContentProvider,when using aCursorLoader.
Starting a Loader
LoaderManager管理存在于activity或fragment中的一个或多个Loader实例。每个Activity/Fragment中只有一个LoaderManager。
典型初始化一个Loader是在activity的onCreate()方法中,或者在fragment的onActivityCreated()方法中。你应该如下初始化:
//Prepare the loader. Either re-connect with anexisting one, or start a new one
getLoaderManager ().initLoader (0, null, this);
在initLoader()方法中需要如下参数:
● 一个独立的ID声明这个loader,在例子中ID是0
● 在构造函数中提供可选参数,在例子中是null
● 一个LoaderManager. LoaderCallbacks的实现,LoaderManager会调用这个实现报告loader事件。在例子中,该类实现了LoaderManager. LoaderCallbacks的接口,所以它传递了一个引用给自身,this
方法initLoader()确保一个loader会被初始化且是活动的。该方法有两个可能性:
● 如果loader通过已经存在的ID声明,则最后被创建的loader会被重复利用
● 如果loader通过没有存在的ID声明,initLoader()会触发LoaderManager. LoaderCallbacks中的onCreateLoader()方法。这个方法是你创建实例且返回一个新的loader的地方。更多信息可以参见onCreateLoader部分。
在两个情况下,LoaderManager. LoaderCallbacks都与loader有关系,并且在loader状态改变的时候都会被调用。如果在调用这个方法的时候,调用者处在其started状态,同时被请求的loader已经存在且已经生成了它的数据,则系统会直接调用方法onLoadFinished()(在initLoader()期间),所以你应该对此做好准备。可以参见文档SeeLoadFinished获取更多该回调方法的信息。
注意initLoader()方法会返回一个创建好的Loader,你不需要特意去捕获它,LoaderManager会自动控制这个loader的生命周期。LoaderManager会在需要的时候开始或停止下载,同时保持loader的状态和与它相关的content。这就意味着,你极少需要与loader直接交互(通过LoaderThrottle例子可以查看怎样使用loader的方法调整其行为)当特殊情况发生的时候,通常使用LoaderManager. LoaderCallbacks中的方法干涉下载线程。更多信息请查看Using the LoaderManager Callbacks.
Restarting a Loader
当你如下使用initLoader()的时候,它会使用一个带有已经声明ID的已经存在的loader,如果不存在这样一个loader,则这个方法会创建一个。但是有时候你会想抛弃掉旧的数据重新开始。
使用restartLoader()可以丢弃旧的数据。例如,SearView.OnQueryTextListener的实现在用户查询改变的时候重启了loader。载入器需要重启,这样它才可以用修改过的搜索过滤器执行一个新的查询。
public boolean onQueryTextChanged (StringnewText) {
//called whenthe action bar search text has changed. Update the search //filter, and restartthe 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 .LoaderCallbacks是一个可以让客户端与LoaderManager进行交互的回调接口。
载入器,特别是CursorLoader,被期望能在停止后还能保存它们的数据。这样使得app可以通过activity或fragment的onStop()和onStart()来保持数据。所以当用户返回一个app时,他们不需要等到数据的重新载入。在知道什么时候创建一个新的loader时,你可以使用LoaderManager. LoaderCallbacks方法告诉app何时停止使用载入器的数据。
LoaderManager.LoaderCallbacks包括下面这些方法:
● onCreateLoader()——实例化并返回一个新的loader for the given ID
● onLoadFinished()——当前一个已经被创建好的loader完成下载时调用
● onLoaderReset()——当前一个已经被创建好的loader被重置调用,这样使得其数据不可用
这些方法的具体使用会在下面描述
OnCreateLoader
当你尝试通过一个loader(例如通过initLoader()),它会检查loader是否被一个已经存在的ID指定。如果没有,会触发LoaderManager. LoaderCallbacks的方法onCreateLoader()。这是你创建新的loader的地方。典型的loader是CursorLoader,但是你也可以创建自定义的Loader子类。
在下面例子中,onCreateLoader()回调方法创建了一个CursorLoader。你必须创建一个CursorLoader使用它的构造方法,该构造方法需要完整的设备信息以便从ContentProvider中查询。特别的,它需要:
● uri——检索内容所需的URI
● projection——A list of which columns to return. 传递null会返回所有的列,但这样效率很低
● selection——Afilter declaring which rows to return. 类似SQL的WHERE语句的格式化(排除“WHERE“这个词)。传递null则返回所给URI的所有的行。
● selectionArgs——你可能在selection中使用多个“?”,可以使用这个参数依次代替selection中出现的“?”。值一定是String。
● sortOrder——类似SQL中ORDERBY语句的格式化(排除“ORDER BY”这个词)。传递null则使用默认的分类指令,有时候默认分类指令就是不分类。
例如:
//If non-null, this is the current filter the usehas provided.
String mCurFilter;
… …
public Loader<Cursor> onCreateLoader (intid, Bundle args) {
//This iscalled when a new Loader needs to be created. This sample only has //oneLoader, 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 createand return a CursorLoader that will take care of creating a //Cursor for thedata being displayed.
Stringselect= “((”+Contacts.DISPLAY_NAME +”NOTNULL) AND (” +
Contacts.HAS_PHONE_NUMBER+”=1)AND (”+Contacts.DISPLAY_NAME+ “!=’’ ))”;
return newCursorLoader(getActivity(), baseUri,
CONTACTS_SUMMARY_PROJECTION,select, null,
Contacts.DISPLAY_NAME+”COLLATE LOCALIZED ASC”);
}
OnLoadFinished
当前一个已经创建的loader完成它的下载任务时会调用这个方法。这个方法在释放为loader服务的最后的资源前一定会被调用。此时你应该移除所有旧数据的使用(因为它将很快被释放),但是你不需要自己动手释放数据,因为loader自己会处理好这一切。
当loader知道app将不再使用某些数据时,它会释放它们。例如,如果数据是来自CursorLoader的一个Cursor,你不需要为它调用close()。如果这个cursor将被存放在CursorAdapter,你应该使用swapCursor()方法使旧的cursor不会关闭。例如:
//This is the Adapter being used to display thelist’s data
SimpleCursorAdapter mAdapter;
… …
public void onLoadFinished (Loader <Cursor>loader, Cursor data) {
//swap thenew cursor in. (The framework will take care of closing the old //cursor oncewe return)
mAdapter.swapCursor(data);
}
OnLoaderReset
当前一个被创建的loader被重置的时候会调用这个方法,此时loader的数据是不可用的。这个回调方法使可以让你知道数据会在什么时候将被释放,然后你可以删除你对它的引用。
// This is the Adapter being used to display thelist’ data.
SimpleCursorAdapter mAdapter;
… …
public void onLoaderReset (Loader<Cursor>loader) {
//This iscalled when the last cursor provided to onLoadFinished () above is //about tobe closed. We need to make sure we are no longer using it.
mAdapter.swapCursor(null);
}
Example
下面是一个fragment显示listview的完整的实现。其中,ListView中包含的结果是从联系人的content provider中查询到的。这里使用CursorLoader管理从provider的查询。
如下所示,一个app想要获得用户的联系人,它的manifest必须包含READ_CONTACT的许可。
public static class CursorLoaderListFragmentextends ListFragment
ImplementsonQueryTextListener, LoaderManager.LoaderCallbacks<Cursor>{
//This is the Adapter being used to display the list’s data
SimpleCursorAdaptermAdapter;
//Ifnon-null, this is the current filter the user has provided.
StringmCurFilter;
@Override
publicvoid onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Give sometext to display if there is no data. In a real application this
// would comefrom a resource.
setEmptyText(“Nophone numbers”);
// We have amenu item to show in action bar
setHasOptionsMenu(true);
//Create anempty adapter we will use to display the loader 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});
setListAdapter(mAdapter);
//Prepare theloader. Either re-connect with an existing one, or start a //new one
getLoaderManager().initLoader(0,null, this);
}
@Override
public voidonCreateOptionsMenu(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=newSearchView(getActivity());
sv.setOnQueryTextListener(this);
item.setActionView(sv);
}
@Override
publicboolean onQueryTextChange(String newText) {
//Called whenthe action bar search text has changed. Update the //search filter, and restartthe loader to do a new query with this filter
mCurFilter =!TextUtils.isEmpty(newText)?newText:null;
getLoaderManager().restartLoader(0,null, this);
return true;
}
@Override
publicboolean onQueryTextSubmit (String query){
//Don’t care about this
return true;
}
@Override
public voidonListItemClick (ListView l, View v, int position, long id) {
//Insert desired behavior here
Log.i(“FragmentComplexList”, “Itemclicked: ”+id);
}
//These arethe Contacts rows that we will retrieve
static finalString[] CONTACT_SUMMARY_PROJECTION= new String[] {
Contacts._ID,
Contacts.DISPLAY_NAME,
Contacts.CONTACT_STATUS,
Contacts.CONTACT_PRESENCE,
Contacts.PHOTO_ID,
Contacts.LOOKUP_KEY,
};
publicLoader<Cursor> onCreateLoader(int id, Bundle args){
//This iscalled when a new Loader needs to be created. This sample only //has oneLoader, so we don’t care about the ID. First, pick the base URI //to usedepending on whether we are currently filtering.
Uri baseUri;
if(mCurlFilter != null ){
baseUri =Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI),
Uri.encode(mCurFilter));
} else {
baseUri=Contacts.CONTENT_URI;
}
//Now createand return a CursorLoader that will take care of creating a Cursor for the databeing displayed.
Stringselect= “((”+Contacts.DISPLAY_NAME +”NOTNULL) AND (” +
Contacts.HAS_PHONE_NUMBER+”=1)AND (”+Contacts.DISPLAY_NAME+ “!=’’ ))”;
return newCursorLoader(getActivity(), baseUri,
CONTACTS_SUMMARY_PROJECTION,select, null,
Contacts.DISPLAY_NAME+”COLLATE LOCALIZED ASC”);
}
public voidonLoadFinished() {
//swap thenew cursor in. (The framework will take care of closing the old //cursor oncewe return)
mAdapter.swapCursor(data);
}
public onLoaderReset(Loader<Cursor>loader){
//This iscalled when the last Cursor provided to onLoadFinished() above is //about to beclosed. We need to make sure we are no longer using it.
mAdapter.swapCursor(null);
}
}
广播!!尝试过上述Contact在较新版本的API中不可用!!上述例子不可用!!!
More Examples
下面有一些的不同例子在ApiDemos中显示如何使用下面的例子:
LoaderCursor/LoaderThrottle: http://developer.android.com/samples/index.html
其中LoaderThrottle显示如何在数据改变的时候减少从content provider中的查询。