从Android 3.0开始引进了loader(加载器)技术, 在activity或者fragment中,loaders可以把异步地加载数据变得更简单。Loaders具有以下特性:
他们对于每一个Activity
和Fragment
都是有效的。
他们可以提供异步加载数据的能力。
他们监视数据源,并当内容改变时传递当前最新的结果。
当他们因为配置的改变而重新连接的时候,他们会自动地重连到上一个loader的游标。因此,他们不需要重新查询数据。
在应用中使用loaders可能会涉及到多个类和接口。他们被汇总到了下表中:
类/接口 | 描述 |
---|---|
LoaderManager |
一个关联到Activity 或 Fragment 的抽象类,用来管理一个或多个Loader 实例 这样可以帮助一个应用管理那些跟Activity 或者Fragment 的生命周期联系到一起的长时间运行的操作;这样的最常见的使用就是 CursorLoader ,然而应用可以自由地写自己的加载器来加载其他类型的数据。 每一个activity或者fragment都只有一个 LoaderManager 。但是一个LoaderManager 可以拥有多个加载器(loaders)。 |
LoaderManager.LoaderCallbacks |
一个用来去为客户端和LoaderManager 提供交互的回调接口。 举个例子,你可以使用onCreateLoader() 回调方法来创建一个新的loader(加载器)。 |
Loader |
一个执行异步的数据加载的抽象类。这是加载器的基类。你可以使用典型的CursorLoader ,但是也可以实现你自己的子类。 当loaders被激活的时候,它们应该见识数据源并且当内容改变的时候传递最新结果。 |
AsyncTaskLoader |
提供一个AsyncTask 来执行异步加载数据的抽象loader。 |
CursorLoader |
AsyncTaskLoader 的一个子类,查询 ContentResolver 然后返回一个Cursor 。这个类用标准的方式实现了 Loader 的协议以此来查询cursors, AsyncTaskLoader 在后台线程中执行cursor查询所以它不会阻塞应用的UI。 使用loader是从ContentProvider 异步加载数据的最好的方式, 相对于通过fragment或activity的API来执行查询 |
上表中的那些类和接口都是你将会用来在应用中实现loader的极其重要的组件。你不必在创建每一个loader的时候全部使用,但是你总是需要一个 LoaderManager
的引用,用来初始化一个loader Loader
类的实现,类似于CursorLoader
。 下面的章节将会向您展示怎样在应用中使用这些类和接口。
这一节描述了怎样在Android应用中使用loaders。一个典型地使用了loaders的应用应该包含以下内容:
一个Activity
或者Fragment
。
一个LoaderManager
的实例。
一个CursorLoader
来载入被ContentProvider
返回的数据。 或者,你可以实现自己的Loader
或者AsyncTaskLoader
的子类 从而从其他的资源加载数据
LoaderManager.LoaderCallbacks
的一个实现 这是你创建新的loader和管理你的已经存在的loader的引用的地方
一个展示loader的数据的方法,比如一个SimpleCursorAdapter
。
一个数据源,比如一个ContentProvider
,当你使用一个 CursorLoader
的时候。
LoaderManager
管理一个Activity
或 Fragment
范围内的一个或多个Loader
实例。每一个Activity或者Fragment都只有一个 LoaderManager
。
你可以典型地初始化一个 Loader
,可以在activity的 onCreate()
方法,也可以在fragment的onActivityCreated()
方法中。你可以像下面一样做这件事:
//准备loader,无论是与一个已经存在的loader重连,//还是新建一个。getLoaderManager().initLoader(0, null, this);
initLoader()
方法需要以下参数:
一个可以标识loader的唯一的ID。本例中的ID是0。
一个可选参数,当loader初始化时提供给它(在本例中是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状态改变时被调用。如果在本次调用时,调用者处于开始状态,并且所请求的loader已经存在并产生了数据,那么系统就会立马调用 onLoadFinished()
(即在initLoader()
过程中), 所以你必须为这种情况做好准备。更多关于这个回调方法的讨论,请参看 onLoadFinished。
请注意,initLoader()
方法返回被创建的Loader
,但是你不必保留它的引用, LoaderManager
会自动管理loader的生命, LoaderManager
会在必要的时候启动和终止,以及维护loader的状态和它关联的内容。这就意味着,你几乎不用和loader进行直接交互 (寻求使用loader方法来调整loader行为的例子,请参看 LoaderThrottle实例)。 你最常用的手段是当特定事件发生时,使用LoaderManager.LoaderCallbacks
方法来介入到加载过程中。 更多关于本话题的讨论,请参看Using the LoaderManager Callbacks。
当你像上面展示的那样使用initLoader()
的时候, 如果有的话,它会使用已存在的带有标识ID的loader。 如果没有,它会创建一个。但是有时你会想要丢掉旧的数据,开始新的过程。
为了丢弃旧的数据,你要使用restartLoader()
。 例如,SearchView.OnQueryTextListener
的实现在用户查询改变时重启了, loader需要被重启从而能够使用修改过的搜索过滤进行新的查询:
public boolean onQueryTextChanged(String newText) { //当action bar的搜索文本改变时调用。 //更新搜索过滤器,然后重启loader用当前的过滤器 //做一次新的查询。 mCurFilter = !TextUtils.isEmpty(newText) ? newText : null; getLoaderManager().restartLoader(0, null, this); return true;}
LoaderManager.LoaderCallbacks
是一个回调接口,它为客户端提供与 is a callback interfaceLoaderManager
交互的能力。
Loaders,尤其是CursorLoader
,大家都希望当它被停止以后仍然可以保持数据。 这样允许应用在activity或fragment的 onStop()
和onStart()
方法之间保持数据, 以至于当用户返回到一个应用的时候,他们不必再等待数据的重新加载。 你可以使用LoaderManager.LoaderCallbacks
方法 当你知道合何时需要创建新的loader,以及高速应用何时停止使用loader的数据。
LoaderManager.LoaderCallbacks
包括以下方法:
onCreateLoader()
— 根据传入的ID初始化并返回一个新的Loader
。
在下面的章节中会更详细地描述这些方法的细节
当你试图操作一个loader的时候,(例如通过initLoader()
), 会检查被赋予唯一ID的loader是否存在。如果不存在,它会触发LoaderManager.LoaderCallbacks
的onCreateLoader()
方法。 这是你创建新loader的地方。一般来说被创建的都是CursorLoader
,但是你可以实现你自己的Loader
子类。
在本例中,onCreateLoader()
回调方法创建了一个CursorLoader
。你必须使用它的构造方法建造这个CursorLoader
,构造方法需要向 which ContentProvider
执行一次查询的完整信息作为参数。它还需要:尤其地,它还需要:
例如:
//如果不是null,这就是当前的用户提供的过滤器。String mCurFilter;...public Loader<Cursor> onCreateLoader(int id, Bundle args) { //当新的Loader需要被创建的时候调用此方法。 //本例仅有一个Loader,所以不必关心ID的问题。 //首先,根据我们是否正在过滤, //选择base URI来使用。 Uri baseUri; if (mCurFilter != null) { baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(mCurFilter)); } else { baseUri = Contacts.CONTENT_URI; } //现在创建并返回一个CursorLoader, //它会创建一个用来显示数据的Cursor。 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");}
当上一个被创建的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) { //把新cursor换进来 (一旦我们返回了,框架将会管理 //关闭旧cursor的事情) mAdapter.swapCursor(data);}
当之前创建的loader被重置使得数据不可用的时候,此方法被调用。这个回调方法让你弄清楚数据何时会被释放,进而你可以移除对它的引用
下面的实现调用了参数为null
的 swapCursor()
:
// This is the Adapter being used to display the list's data.SimpleCursorAdapter mAdapter;...public void onLoaderReset(Loader<Cursor> loader) { //当最后一个Cursor进入到onLoadFinished()时被调用, //Cursor将要被关闭, 我们要确保 //不会再使用到它。 mAdapter.swapCursor(null);}
下面的例子完整实现了一个Fragment
显示一个包含了从联系人content provider返回的查询数据的ListView
的内容的功能。 它使用一个CursorLoader
来管理对provider的查询。
一个应用想要实现操作用户的联系人,如例子中那样,它的manifest一定要包含 READ_CONTACTS
权限。
public static class CursorLoaderListFragment extends ListFragment implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> { //这就是用来展示列表信息的Adapter。 SimpleCursorAdapter mAdapter; //如果不是null,这就是当前的搜索过滤器。 String mCurFilter; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); //如果没有数据,就给控件一些文本去显示。 //在真正的应用中,信息来自应用资源。 setEmptyText("No phone numbers"); //我们在action bar中显示一个菜单项。 setHasOptionsMenu(true); //创建一个新的adapter,我们将用它来显示加载的数据。 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); //准备loader, 重连到一个已存在的loader, //或者启动一个新的loader。 getLoaderManager().initLoader(0, null, this); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { //放置一个action bar用于搜索。 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) { //action bar上的搜索文本改变的时候被调用。 //更新搜索过滤器,并且重启loader用当前的过滤器 //来做新的查询。 mCurFilter = !TextUtils.isEmpty(newText) ? newText : null; 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) { // Insert desired behavior here. Log.i("FragmentComplexList", "Item clicked: " + id); } //我们想获取的联系人中的行数据。 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) { //当需要创建一个新的loader时被调用。 //本例中仅有一个loader,所以我们不必关心ID的问题。 //首先,根据我们当前是否正在过滤, //选择base URI来使用。 Uri baseUri; if (mCurFilter != null) { baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(mCurFilter)); } else { baseUri = Contacts.CONTENT_URI; } //现在创建并返回一个CursorLoader, //它将会为被显示的数据创建一个Cursor。 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) { //把新的cursor换进来。 (框架将会在我们返回的时候 //管理旧cursor的关闭事宜。) mAdapter.swapCursor(data); } public void onLoaderReset(Loader<Cursor> loader) { //当最后一个Cursor进入onLoadFinished()的时候被调用。 //cursor将要被关闭, 我们应该确保 //不再使用它。 mAdapter.swapCursor(null); }}
在ApiDemos中有一些不同的例子,它们说明了怎样使用loader:
LoaderCursor — 上面展示的片段的汇总的完整版本。
LoaderThrottle — 一个例子,用来展示当数据改变时,怎样使用throtting来减少对content provider做查询的次数
uri — 要获取的内容的URI。
projection — 返回的列组成的列表,传入null
将会返回所有列,但是效率很低。
selection — 一个声明返回哪些行的过滤器,被格式化成类似SQL中WHERE子句的形式(除了没有WHERE自己)。传入null
将会返回给定URI的所有行。
selectionArgs — 你可能在Selection中包含一些‘?’,他们将会被selectionArgs的值给替换掉,顺序与它们在selection中出现的顺序一致。 这些值被约束为String类型
sortOrder — 怎样给这些行排顺序,被格式化为类似SQL中ORDER BY子句的形式(除了没有ORDER自己)。传入null
将会使用默认的排序方式,可能是没有顺序。
onLoaderReset()
— 当一个之前创建的loader被重置的时候会调用此方法,这样会导致它的数据不可用。
onLoadFinished()
— 当一个之前被创建的loader已经结束加载数据的时候会调用此方法。
参考资料:http://www.android-doc.com/guide/components/loaders.html