1.2.2 Loaders - 加载器
Loaders从Android 3.0引入,它使得在activity或是fragment里进行异步数据加载变得非常简单。Loaders有如下的特性:
- 它在每个 Activity 和 Fragment 里都可用。
- 它提供了异步读取数据的机制。
- 它可以监控数据相关的资源,当它们的内容发生变化时,会分发新的结果。
- 在配置发生变化引起重建后,它们会自动重新连接到最新loader的游标上。因此,不需要重新查询数据。
Loaders API Summary - 加载器API摘要
在应用里,Loaders提供了很多的类和接口,表格里是类的概要说明:
Class/Interface |
Description--描述 |
与 Activity 和 Fragment 相关联的抽象类,用于管理一个或多个 Loader 实例。结合Activity 或 Fragment的生命周期,它有助于应用管理长时间运行的操作。最常见的使用是 CursorLoader,但是,也可以写自己的加载器来读取其它类型的数据。 在每个activity或fragment里,仅仅只有一个LoaderManager,但是一个LoaderManager可以管理多个Loader。 |
|
与LoaderManager联系的客户端程序的回调接口。例如,你可以使用onCreateLoader() 回调函数来创建一个新的Loader。 |
|
实现了异步读取数据的抽象类。它是Loader的基类。默认的,你应该实现 CursorLoader,但是你也可以实现你自己的子类。当Loaders处于激活的状态,那么它就会监听它们数据的变化,并且当内容发生变化时,会分发新的结果。 |
|
抽象的loader,提供了 AsyncTask 。 |
|
AsyncTaskLoader 的子类,它会查询 ContentResolver 并返回一个 Cursor。在查询游标时,该类以标准的方式实现了 Loader 接口,在后台,它基于AsyncTaskLoader 来执行一个游标查询,因此,它不会锁住应用的UI。从 ContentProvider里异步读取数据,使用loader是最好的方法,而不是执行一个受fragmentation或activity的API管理的查询。 |
上面表格里展示的类和接口是最基本的组件,在应用时你可能会使用它们来实现一个loader。你创建的每个loader不一定都会用到它们的所有,但是你为了实例化一个loader,你必须需要一个LoaderManager的引用和Loader类的实现(例如CursorLoader)。下面的内容描述了在应用里如何使用类和接口。
Using Loaders in an Application - 在应用里使用Loader
本章节描述了如何在Android应用里使用loader。一般来说,使用loader的应用包含如下要素:
- 一个 Activity 或 Fragment 。
- 一个LoaderManager的实例。
- 由ContentProvider返回的用来读取数据的CursorLoader。你也可以实现一个Loader或AsyncTaskLoader的子类来从一些其它的资源里读取数据。
- LoaderManager.LoaderCallbacks的实现。在它的实现里,你可以创建新的loader,还可以管理已经存在的loader的引用。
- 显示loader数据的方法,例如显示SimpleCursorAdapter 数据的方法。
- 数据资源,例如在使用 CursorLoader 时的 ContentProvider 。
Starting a Loader - 开启Loader
在activity或fragment里,LoaderManager管理管理一个或多个Loader的实例。在activity或fragment里,仅仅只能有一个LoaderManager。
通常,在activity的 onCreate() 方法里,或是fragment的onActivityCreated() 方法里实例化Loader。如下面的示例所示:
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);
initLoader() 方法的参数解释如下:
- loader的唯一id,在例子中,loader的id为0。
- 参数可选,用于在构造方法给loader提供(例子中为null)。
- LoaderManager.LoaderCallbacks的实现,LoaderManager调用它来发布loader的事件。在例子中,类已经实现了LoaderManager.LoaderCallbacks接口,因此给方法传递了自己的引用--this。
调用initLoader() 方法是为了确保loader被实例化和被激活,这样做可能会有两个结果:
- 如果指定了ID的loader已经存在,那么最后被创建的loader被重用。
- 如果指定了ID的loader不存在,那么,initLoader() 方法会触发LoaderManager.LoaderCallbacks的onCreateLoader() 方法。在这个方法里,你可以实现用来实例化一个新的loader,并返回它。更多详情请参见 onCreateLoader 章节。
在这两种情况下,LoaderManager.LoaderCallbacks的实现和loader紧紧的联系着,当loader的状态发生变化时,该实现就会被调用。如果在开始状态就调用它,并且要使用的loader已经存在并生成数据了,那么系统就会立即调用 onLoadFinished() 方法(在initLoader() 期间),因此你必须为这可能发生的事件做准备。 更多详情请参见onLoadFinished 章节。
注意:initLoader() 方法返回了创建的Loader,但是你不需要获取它的引用。LoaderManager会自动管理loader的生命周期。如果有需要,LoaderManager会开始和停止读取,并且维持loader和与它并联的内容的状态。基于这样的实现,你基本上不用直接作用于loader( LoaderThrottle 示例,它使用了loader的方法来调整loader的行为)。大多数时候,当特殊事件发生时,通常会使用LoaderManager.LoaderCallbacks方法来干涉读取的进程。这个专题的大多数讨论,请参见 Using the LoaderManager Callbacks 章节。
Restarting a Loader - 重启Loader
正如上面所说的那样, 当你使用initLoader()时,如果已经有与指定的ID相对应的loader,那么方法就会使用它。如果没有的话,就会创建一个。但是有时,你也想丢弃你的旧数据并重启它。
为了丢弃旧数据,你应该使用restartLoader() 方法。在例子中,当用户的查询发生变化时,SearchView.OnQueryTextListener 会重启loader。loader需要被重启以便它可以使用修正过的搜索过滤器来进行一次新的查询。
public boolean onQueryTextChanged(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;
}
Using the LoaderManager Callbacks - 使用LoaderManager回调
LoaderManager.LoaderCallbacks回调接口可以让客户端程序与LoaderManager进行交互。
loader,尤其是CursorLoader,在停止以后仍然期望它保持着数据。也就是说,在activity或fragment的 onStop() 和onStart() 方法里允许应用保持数据,这样的话,当用户返回到activity或fragment里,他们就不必为数据的重新加载而等待。你使用LoaderManager.LoaderCallbacks方法时,知道何时创建一个新的loader,并且告诉应用,是时候停止使用loader的数据了。
LoaderManager.LoaderCallbacks包含了下面的方法:
- onCreateLoader() -- 根据ID参数实例化一个新的Loader并返回它。
- onLoadFinished() -- 当先前创建的loader已经完成了加载后调用该方法。
- onLoaderReset() -- 当先前创建的loader重置后创建它,从而使其数据不可用。
OnCreateLoader
当你试着获取一个loader时(例如,通过initLoader()方法获取),它会通过ID来检查是否拥有该ID的loader已经存在。如果不存在,它就会触发LoaderManager.LoaderCallbacks的onCreateLoader() 方法。该方法是创建一个新loader的方法。通常来说,创建的是一个CursorLoader,但是你也可以实现你自己的Loader子类。
在例子中,onCreateLoader()回调创建了一个CursorLoader。你必须使用它的构造器方法来构造CursorLoader,这需要包含完整信息的提供给ContentProvider 的查询。需要的信息通常有:
- uri - 要检索内容的URI
- projection - 要返回列的清单。如果传递null的话会返回所有的列,这样做效率是很差的。
- selection - 申明了要返回行的过滤器,格式和sql的where子句一样(不包括where本身)。传递null的话会根据所给的URI返回所有的行。
- selectionArgs - 你可能会包含多个?在selection中,在selection里的?将会被selectionArgs里的值依次按照出现的顺序所代替。这些值会被当作字符串来处理。
- sortOrder - 如何来排序结果行,格式和sql的order子句一样(不包括order本身)。传递null的话会使用默认排序,也有可能是无序的。
例子:
// 如果非空,就是用户提供的过滤器
String mCurFilter;
...
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// 当新的loader被创建时,该方法被调用
// 该例中仅仅只有一个loader,因此我们不根据ID来创建它。
// 首先,使用一个基于当前过滤器的URI。
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");
}
OnLoadFinished
当先前创建的loader完成数据加载后调用该方法。该方法要确保优先释放提供给该加载器最后的数据。这时你应该移除所有的旧数据(因为它们马上就会被释放的),但是你不要自己去释放它们,因为loader自己会处理这些的。
当loader发现应用不再使用数据时会把它们释放掉的。例如,如果数据是由CursorLoader提供的,你就不要自己去调用close() 方法。如果游标被放在CursorAdapter 里,你应该使用 swapCursor() 方法来使旧的Cursor 不被关闭。例如:
// 用来显示列表数据的Adapter
SimpleCursorAdapter mAdapter;
...
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// 用新的游标进行交换。(一旦我们返回了旧游标,框架会关闭旧游标的)
mAdapter.swapCursor(data);
}
OnLoaderReset
当先前创建的loader被重置后调用该方法,这样就会使得它的数据不可用了。该回调方法会让你知道什么时候数据将被释放,你就可以在该方法里移除引用。
实现里调用了 swapCursor()方法,传递参数null:
// 用来显示列表数据的Adapter
SimpleCursorAdapter mAdapter;
...
public void onLoaderReset(Loader<Cursor> loader) {
// 当提供给onLoadFinished()的Cursor即将被关闭时调用该方法。我们要做的是确保不再使用它。
mAdapter.swapCursor(null);
}
Example - 示例
在下面的例子中,有Fragment完整实现,在Fragment里,用一个ListView 来显示从联系人内容提供器里获取的数据。这使用CursorLoader来管理基于提供器的查询。
要像下面的例子一样来获取用户的联系人,应用的mainfest文件里必须包含READ_CONTACTS 权限。
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");
// 在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已经存在,重新连接;如果不存在,创建一个新的
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) {
// Don't care about this.
return true;
}
@Override public void onListItemClick(ListView l, View v, int position, long id) {
// 在这里打印行为描述的日志
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。
// 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<Cursor> 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<Cursor> 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);
}
}
More Examples - 更多示例
在ApiDemos里有其它的示例来说明如何使用loader:
- LoaderCursor -- 上面显示的代码片断的完整版本。
- LoaderThrottle -- 当数据发生变化时,如何通过节流来控制查询内容提供者的数量。
1.2.2 Loaders - 加载器的更多相关文章
- Webpack 常见静态资源处理 - 模块加载器(Loaders)+ExtractTextPlugin插件
Webpack 常见静态资源处理 - 模块加载器(Loaders)+ExtractTextPlugin插件 webpack系列目录 webpack 系列 一:模块系统的演进 webpack 系列 二: ...
- webpack加载器(Loaders)
加载器(Loaders) loader 是对应用程序中资源文件进行转换.它们是(运行在 Node.js 中的)函数,可以将资源文件作为参数的来源,然后返回新的资源文件. 示例 例如,你可以使用 loa ...
- KnockoutJS 3.X API 第六章 组件(5) 高级应用组件加载器
无论何时使用组件绑定或自定义元素注入组件,Knockout都将使用一个或多个组件装载器获取该组件的模板和视图模型. 组件加载器的任务是异步提供任何给定组件名称的模板/视图模型对. 本节目录 默认组件加 ...
- Webpack模块加载器
一.介绍 Webpack是德国开发者 Tobias Koppers 开发的模块加载器,它能把所有的资源文件(JS.JSX.CSS.CoffeeScript.Less.Sass.Image等)都作为模块 ...
- Webpack的加载器
一.什么是加载器(loaders)loaders 用于转换应用程序的资源文件,他们是运行在nodejs下的函数 使用参数来获取一个资源的来源并且返回一个新的来源(资源的位置),例如:你可以使用load ...
- webpack进阶构建项目(一):1.理解webpack加载器
1.理解webpack加载器 webpack的设计理念,所有资源都是“模块”,webpack内部实现了一套资源加载机制,这与Requirejs.Sea.js.Browserify等实现有所不同. We ...
- 使用webpack loader加载器
了解webpack请移步webpack初识! 什么是loader loaders 用于转换应用程序的资源文件,他们是运行在nodejs下的函数 使用参数来获取一个资源的来源并且返回一个新的来源(资源的 ...
- vue-loader 调用了cssLoaders方法配置了css加载器属性。
module: { loaders: [ // 这里也是相应的配置,test就是匹配文件,loader是加载器, { test: /\.vue$/, loader: 'vue' }, { test: ...
- 实现一个类 RequireJS 的模块加载器 (二)
2017 新年好 ! 新年第一天对我来说真是悲伤 ,早上兴冲冲地爬起来背着书包跑去实验室,结果今天大家都休息 .回宿舍的时候发现书包湿了,原来盒子装的牛奶盖子松了,泼了一书包,电脑风扇口和USB口都进 ...
随机推荐
- fc游戏反编译流程
最近打算玩一下nes游戏,重拾一下6502汇编. 摸索了几天,觉得下面这个反汇编的流程比较好: 用 fceux 载入游戏,选择debug菜单Code/Data Logger,点击Start,稍微玩一下 ...
- Solidworks如何添加齿轮 运动副
建立下面的齿轮装配关系(注意装配体不要先拖入齿轮,因为我们第一个齿轮是要手动让他转的,所以不能固定) 分别在两个齿轮中绘制两条直线,一个从圆心到齿顶圆,一个从圆心到齿根圆(在零件中绘图完成之后要退 ...
- 微博轻量级RPC框架Motan正式开源:支撑千亿调用
支撑微博千亿调用的轻量级 RPC 框架 Motan 正式开源了,项目地址为https://github.com/weibocom/motan. 微博轻量级RPC框架Motan正式开源 Motan 是微 ...
- SpringBoot环境属性占位符解析和类型转换
前提 前面写过一篇关于Environment属性加载的源码分析和扩展,里面提到属性的占位符解析和类型转换是相对复杂的,这篇文章就是要分析和解读这两个复杂的问题.关于这两个问题,选用一个比较复杂的参数处 ...
- input输入框禁止显示历史记录
有时我们在设计网页时不想让表单保存用户输入历史记录,比如一些隐私数据 <input name="test" type="text" id="te ...
- Android开发之应用程序更新实现
近期给项目app做升级.对Android应用程序更新稍有研究,分享一下我的心得. 既然是更新,那么一定是要联网和下载的.所以联网和存储訪问权限时一定要有的: <!-- 权限申请 --> ...
- 使用ipmitool 命令添加IPMI 界面的SMTP邮件服务器地址
目前要通过ipmitool工具在IPMI的界面上添加邮件服务器地址,该脚本如下 SMTP.sh #!/bin/bash ipmitool raw 0x32 0x78 0x01 0x01 0x00 0x ...
- 在go中使用leveldb --levi
github上有个比较好用的leveldb go wrapperlevigo, 安装之前需现在机器上安装leveldb 当前版本的LevelDB没有带安装脚本,需自行编译安装,过程如下: instal ...
- Oracle SQL Developer出现错误 【ora-28002:the password will expire within 7 days】的解决办法
启动 Oracle SQL Developer的时候,点击用户system进行连接并输入密码后(下图左),会出现(下图右)提示信息: 即:[ora-28002:the password will ex ...
- 点滴记录:input的value不能放值
以前我写登录框交互的时候,总是在focus和blur时,把input的value值为空或显示,也一直认为对的没有争议.可是,今天,后台同学告诉我这个不好使了?!我一时没听明白,后来他亲自演示后,我才 ...