第三部分:Android 应用程序接口指南---第一节:应用程序组件---第一章1-2.Loaders(装载机)
第1-2章 Loaders(装载机)
在Android 3.0中引入装载机,使得很容易异步加载Activity或Fragment中的数据。装载机有以下特点:
1. 他们使每个Activity 和Fragment可用。
2. 他们提供异步加载的数据。
3. 当内容发生改变时他们监测数据的来源并传递新的结果。
4. 当配置更改后被重新创建时,他们会自动重新连接到最近一次装载机的Cursor。因此,他们不需要重新查询自己的数据。
1-2.1 装载机的API概述
他们有可在一个应用程序中参与使用装载机的多个类和接口,如表格3-1所示:
Class/Interface |
描述 |
LoaderManager |
一个与Activity或Fragment相关的抽象类,用于管理一个或多个Loader实例。这有助于应用程序在Activity或Fragment生命周期上管理长时间运行的操作;经常使用的是CursorLoader类,但是应用程序可以根据需要载入的其它类型数据来自由的写入他们自己的装载机。 每个Activity或Fragment只有一个LoaderManager。但一个LoaderManager却可以有多个装载机。
|
LoaderManager.LoaderCsllbacks |
一个客户端与LoaderManager交互的回调接口。例如,你可以使用onCreateLoader()回调方法,来创建一个新的装载机。 |
Loader |
一个执行加载异步数据的抽象类。这是一个装载机的基类。你通常会使用CursorLoader,但你也能在自己的子类实现它。然而当内容发生改变时,装载器会积极的监测数据的来源并传递新的结果。 |
AsyncTaskLoader |
一个抽象的载入器,用于提供一个AsyncTask来做这项工作。 |
CursorLoader |
一个AsyncTaskLoader的子类,用于查询ContentResolver并返回一个Cursor。这个类实现了以一种标准方式查询Cursor的装载机协议,它建立在AsyncTaskLoader上,用来在后台线程上执行Cursor查询,这样它不会阻塞应用程序的UI。使用这种装载机的最佳方式是从一个ContentProvider中异步加载数据而不是通过Fragment或Activity的API来执行一种托管查询数据。 |
表格3-1 装载机的API概述
上述表中的类和接口是在你的应用程序里用来实现装载机必不可少的组成部分。你所创建的每个装载机并不需要全部都用到他们,但你为了初始化装载机和一个如CursorLoader的Loader类的实现将需要一个引用的LoaderManager。以下部分展示你在一个应用程序中是如何使用这些类和接口的。
1-2.2 在应用程序中使用装载机
本节介绍在一个Android应用程序中如何使用装载机。通常使用装载机的应用程序包含以下内容:
(1). 一个Activity或Fragment。
(2). LoaderMananger的一个实例。
(3). 一个CursorLoader加载的ContentProvider备份数据。另外,你可以实现自己的子类装载机或者用AsyncTaskLoader加载一些其它来源的数据。
(4). 一个LoaderManager.LoaderCallbacks的实现。可以创造新的装载机并且管理现有装载机的引用。
(5). 显示装载机数据的一种方法,如一个SimpleCursorAdapter。
(6). 一个数据源,如使用CursorLoader时的一个ContentProvider。
1. 启动一个装载机
LoaderManager管理Activity或Fragment内的一个或多个Loader实例,每个Activity或Fragment只有一个LoaderManager。
你通常会在Activity的onCreate()方法或Fragment的onActivityCreated()方法上初始化一个装载机。
如代码清单3-1所示:
// 准备装载机,要么重新链接已经存在的一个装载机要么开始一个新的 getLoaderManager().initLoader(0, null, this);
代码清单 3-1
initLoader()方法采用以下参数:
(1). 一个识别装载机的唯一ID。在这个例子中,ID为0.。
(2). 在构造中提供装载机的可选参数(在这例子中为null)。
(3). 一个LoaderManager.LoaderCallbacks的实现,其中LoaderManager调用报告装载机事件。在此例中,局部类实现LoaderManager.LoaderCallbacks的接口,所以它给自己传递一个引用,this.
调用initLoader()是确保一个Loader的初始化并有效的。它有两种可能的结果:
(1).如果指定装载机的ID已经存在,最后创建的装载机就会被重用。
(2). 如果制定装载机的ID不存在,initLoader()会触发LoaderManager中的onCreateLoader()方法。这个方法让你实现代码实例化并返回一个新的装载机。
在这两种情况下,给定LoaderManager.LoaderCallbacks实现与装载机相关,装载机状态发生变化时,它将被调用。如果调用者在它的启动状态调用,并要求装载机已经存在,也已产生了其数据,然后系统会马上调用onLoadFinished()(在initLoader()期间),所以你必须为此做好准备。
请注意,initLoader()方法返回被创建的装载机,你并不需要捕获它的引用。LoaderManager自动地管理装载机的寿命。当必要时该LoaderManager会启动和停止加载,并保持装载机的状态和其相关的内容。这意味着,你很少与装载机直接互动(尽管这个例子中使用的装载机方法调整了装载机的行为)。在装载过程中,当特定事件发生时你最常使用LoaderManager.LoaderCallbacks的方法来干涉载入过程。
2. 重新启动一个装载机
当你使用如上述所示的initLoader()时,如果有一个现成的装载机,它会根据一个指定ID使用现有的装载机。如果没有,它就创建一个。但有时你要放弃你的旧数据,并重新开始。你使用restartLoader的()来放弃旧数据。例如,当用户查询变化时,就实现这一SearchView.OnQueryTextListener来重新启动装载机。装载机需要重新启动,这样它就可以使用修订后的搜索过滤器来执行一个新的查询。如代码清单3-2所示:
public boolean onQueryTextChanged(String newText) { // 当action bar搜索文本改变时被调用.更新search filter并重新启动装载机使用这个filter执行一个新的查询 mCurFilter = !TextUtils.isEmpty(newText) ? newText : null; getLoaderManager().restartLoader(0, null, this); return true; }
代码清单 3-2
3. 使用LoaderManager回调
LoaderManager.LoaderCallbacks是一个让客户端与LoaderManager交互的回调接口。
尤其是CursorLoader这一类的装载机,当被阻止后会保留他们的数据。这允许应用程序在整个Activity或Fragment的onStop()和onStart中()方法中保留他们的数据,这样当用户返回到应用程序,他们不要等待重新载入数据。当知道何时创建一个新的装载机并且知道何时停止使用装载机数据时,你可以使用LoaderManager.LoaderCallbacks方法来创建一个新的载入器,并告知应用程序是时候停止使用一个载入器的数据。LoaderManager.LoaderCallbacks包括这些方法:
(1). onCreateLoader() - 实例化并返回一个新给定ID的Loader。
(2). onLoadFinished() - 当先前创建的装载机已完成了它的载入时调用。
(3). onLoaderReset() - 当先前创建的装载机被重置,其数据不可用时调用。
下面让我们详细介绍这些方法
(1). onCreateLoader()
当你尝试访问一个装载机(例如,通过initLoader()),它会检查是否存在由ID指定的装载机。如果不是这样,它会在你创建新装载机的onCreateLoader()上触发LoaderManager.LoaderCallbacks方法。通常,这将是一个CursorLoader,但你可以根据它实现自己的装载机子类。在这个例子中,onCreateLoader()回调方法创建一个CursorLoader。你必须使用它的构造方法来建立CursorLoader,这需要一套完整的执行ContentProvider查询后所需的信息。具体来说,它需要:
◆ uri:检索内容的URI。
◆ projection:一个返回列的清单。传递null,将返回所有列,但这是低效率的。
◆ selection:一个声明返回行的过滤器,格式化为一个SQL WHERE子句(不含WHERE本身)。传递null,将返回所有指定URI的行。
◆ selectionArgs:您可能包括一个在选择中按出现顺序的selectionArgs值所取代的选择。该值将被绑定为字符串。
◆ sortOrder:如何整理行数,格式化为SQL ORDER BY子句(不含ORDER本身)。传递null将使用默认的排序顺序,这可能是无序的。
综上所示下面让我们看下代码清单3-3:
// 如果不为null,当前 filter会赋值一个字符串 String mCurFilter; ... public Loader<Cursor> onCreateLoader(int id, Bundle args) { // 当一个新的装载机被创建时,会调用此方法。这是一个很简单的装载机,我们不需要关心ID。 // 首先我们需要根据当前filte是否为null来挑选base URI Uri baseUri; if (mCurFilter != null) { baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(mCurFilter)); } else { baseUri = Contacts.CONTENT_URI; } //现在创建并返回一个 CursorLoader,这个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"); }
代码清单 3-3
(2). onLoadFinished()
当先前创建的装载机已完成了它的载入时这个方法会被调用。此方法保证在被调用之前释放此装载机提供的最近一个数据。在这一点上,你应该删除所有使用的旧数据(因为它会很快被释放),但不应该在装载机拥有需要保护的数据时释放你的数据。
一旦应用程序不再使用装载机,它将释放数据。例如,如果数据是来自于CursorLoader的一个cursor,你不应该自行调用close()。如果cursor 被放置在CursorAdapter,你应该使用swapCursor()方法使旧的cursor不被关闭。如代码清单3-4所示:
// 这个Adapter被用来显示 list的数据 SimpleCursorAdapter mAdapter; ... public void onLoadFinished(Loader<Cursor> loader, Cursor data) { // 交换新的cursor mAdapter.swapCursor(data); }
代码清单 3-4
(3). onLoaderReset()
当先前创建的装载机被重置时这种方法会被回调,因此其数据不可用。这个回调,可以让你知道数据何时将被释放,这样你就可以在这里移除你的引用。
在实现的方法中,使用swapCursor()传入了一个null值。如代码清单3-5所示:
//这个Adapter被用来显示 list的数据. SimpleCursorAdapter mAdapter; ... public void onLoaderReset(Loader<Cursor> loader) { mAdapter.swapCursor(null); }
代码清单 3-5
1-2.3 代码示例
举一个例子,这有一个关于Fragment完整的实现,它显示一个ListView,这个ListView中包含针对联系人content provider的查询结果。它采用了CursorLoader来管理provider上的查询。如这个例子所示,应用程序访问用户联系人,所以需要在manifest 中包括的READ_CONTACTS权限。如代码清单3-6所示:
public static class CursorLoaderListFragment extends ListFragment implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> { SimpleCursorAdapter mAdapter; String mCurFilter; @Override public void onActivityCreated(Bundle savedInstanceState) { super.onActivityCreated(savedInstanceState); // 如果没有数据,显示一个提示文本 setEmptyText("No phone numbers"); // 我们有一个菜单item来显示在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); getLoaderManager().initLoader(0, null, this); } @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { // 放置一个 action bar item 用于搜索 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; 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) { // 这里可以插入想要的行为 Log.i("FragmentComplexList", "Item clicked: " + id); } //这些Contacts rows用于检索 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) { Uri baseUri; if (mCurFilter != null) { baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(mCurFilter)); } else { baseUri = Contacts.CONTENT_URI; } 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) { mAdapter.swapCursor(data); } public void onLoaderReset(Loader<Cursor> loader) { mAdapter.swapCursor(null); } }
代码清单 3-6
本文来自jy02432443,QQ78117253。是本人辛辛苦苦一个个字码出来的,转载请保留出处,并保留追究法律责任的权利