Loaders从Android 3.0引入,它使得在activity或是fragment里进行异步数据加载变得非常简单。Loaders有如下的特性:

  • 它在每个 Activity  和 Fragment 里都可用。
  • 它提供了异步读取数据的机制。
  • 它可以监控数据相关的资源,当它们的内容发生变化时,会分发新的结果。
  • 在配置发生变化引起重建后,它们会自动重新连接到最新loader的游标上。因此,不需要重新查询数据。

Loaders API Summary - 加载器API摘要

在应用里,Loaders提供了很多的类和接口,表格里是类的概要说明:

Class/Interface

Description--描述

LoaderManager

与 Activity 和 Fragment 相关联的抽象类,用于管理一个或多个 Loader 实例。结合Activity 或 Fragment的生命周期,它有助于应用管理长时间运行的操作。最常见的使用是 CursorLoader,但是,也可以写自己的加载器来读取其它类型的数据。

在每个activity或fragment里,仅仅只有一个LoaderManager,但是一个LoaderManager可以管理多个Loader。

LoaderManager.LoaderCallbacks

LoaderManager联系的客户端程序的回调接口。例如,你可以使用onCreateLoader() 回调函数来创建一个新的Loader。

Loader

实现了异步读取数据的抽象类。它是Loader的基类。默认的,你应该实现 CursorLoader,但是你也可以实现你自己的子类。当Loaders处于激活的状态,那么它就会监听它们数据的变化,并且当内容发生变化时,会分发新的结果。

AsyncTaskLoader

抽象的loader,提供了 AsyncTask 。

CursorLoader

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的应用包含如下要素:

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() 方法的参数解释如下:

调用initLoader() 方法是为了确保loader被实例化和被激活,这样做可能会有两个结果:

  • 如果指定了ID的loader已经存在,那么最后被创建的loader被重用。
  • 如果指定了ID的loader不存在,那么,initLoader() 方法会触发LoaderManager.LoaderCallbacksonCreateLoader()      方法。在这个方法里,你可以实现用来实例化一个新的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

当你试着获取一个loader时(例如,通过initLoader()方法获取),它会通过ID来检查是否拥有该ID的loader已经存在。如果不存在,它就会触发LoaderManager.LoaderCallbacksonCreateLoader() 方法。该方法是创建一个新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 - 加载器的更多相关文章

  1. Webpack 常见静态资源处理 - 模块加载器(Loaders)+ExtractTextPlugin插件

    Webpack 常见静态资源处理 - 模块加载器(Loaders)+ExtractTextPlugin插件 webpack系列目录 webpack 系列 一:模块系统的演进 webpack 系列 二: ...

  2. webpack加载器(Loaders)

    加载器(Loaders) loader 是对应用程序中资源文件进行转换.它们是(运行在 Node.js 中的)函数,可以将资源文件作为参数的来源,然后返回新的资源文件. 示例 例如,你可以使用 loa ...

  3. KnockoutJS 3.X API 第六章 组件(5) 高级应用组件加载器

    无论何时使用组件绑定或自定义元素注入组件,Knockout都将使用一个或多个组件装载器获取该组件的模板和视图模型. 组件加载器的任务是异步提供任何给定组件名称的模板/视图模型对. 本节目录 默认组件加 ...

  4. Webpack模块加载器

    一.介绍 Webpack是德国开发者 Tobias Koppers 开发的模块加载器,它能把所有的资源文件(JS.JSX.CSS.CoffeeScript.Less.Sass.Image等)都作为模块 ...

  5. Webpack的加载器

    一.什么是加载器(loaders)loaders 用于转换应用程序的资源文件,他们是运行在nodejs下的函数 使用参数来获取一个资源的来源并且返回一个新的来源(资源的位置),例如:你可以使用load ...

  6. webpack进阶构建项目(一):1.理解webpack加载器

    1.理解webpack加载器 webpack的设计理念,所有资源都是“模块”,webpack内部实现了一套资源加载机制,这与Requirejs.Sea.js.Browserify等实现有所不同. We ...

  7. 使用webpack loader加载器

    了解webpack请移步webpack初识! 什么是loader loaders 用于转换应用程序的资源文件,他们是运行在nodejs下的函数 使用参数来获取一个资源的来源并且返回一个新的来源(资源的 ...

  8. vue-loader 调用了cssLoaders方法配置了css加载器属性。

    module: { loaders: [ // 这里也是相应的配置,test就是匹配文件,loader是加载器, { test: /\.vue$/, loader: 'vue' }, { test: ...

  9. 实现一个类 RequireJS 的模块加载器 (二)

    2017 新年好 ! 新年第一天对我来说真是悲伤 ,早上兴冲冲地爬起来背着书包跑去实验室,结果今天大家都休息 .回宿舍的时候发现书包湿了,原来盒子装的牛奶盖子松了,泼了一书包,电脑风扇口和USB口都进 ...

随机推荐

  1. fiddler在ios10.3系统抓包https失败原因解决

    一直是按照以往的设置抓包,设置代理ip,通过Safari下载安装证书,抓包https怎么显示证书无效呢?难道证书被apple设为黑名单了?google后发现,IOS10.3以后,安装了证书不是默认启用 ...

  2. go语言基础之常量

    1.常量 示例: package main //必须有一个main包 import "fmt" func main() { //变量:程序运行期间,可以改变的量, 变量声明需要va ...

  3. 阅读ARm芯片手册 阅读方法

    一 overview 1 table of contents -*** 2 product overview -***  芯片概述 3 feature  ***    每一个功能的特点 4 block ...

  4. ORA-04030

    ORA-04030: 在尝试分配...字节(...)时进程内存不足的原因分析及解决办法 正在使用的oracle 11g数据库,前天在用一段时间后(开始要较长时间才出现,后来较短时间就出现),频繁报OR ...

  5. CF(D. Fibonacci Sums)dp计数

    题目链接:http://codeforces.com/contest/126/problem/D 题意:一个数能够有多种由互不同样的斐波那契数组成的情况: 解法:dp,easy证明:每一个数通过贪心能 ...

  6. Strategy Pattern(策略模式)

    Head First定义: 策略模式定义了算法族,分别封装起来,让他们之间可以互相替换,此模式让算法的变化独立于使用算法的客户. 策略模式的设计原则主要有三个: 找出应用中可能需要变化的部分,把它们独 ...

  7. 建站笔记1:centos6.5下安装mysql

    近期买了个域名,想要玩玩自己建站点:接下来遇到的问题都会一次记录下来.以备自己以后复习查看: 首先建站方案选择: wordPress +centos6.5 +mysql; server买的:搬瓦工最低 ...

  8. org.eclipse.e4.core.di.InjectionException:org.eclipse.swt.SWTException: Widget is disposed

    org.eclipse.e4.core.di.InjectionException:org.eclipse.swt.SWTException: Widget is disposed 开发环境为ecli ...

  9. 【Statistics】CAP曲线

    功能描述 CAP曲线(Cumulative Accuracy Profile)/Power Curve(准确率/AR)是描述整个评级结果下,累计违约客户比例与累计客户比例的关系. 在完美的模型下,CA ...

  10. windows开了远程控制访问老提示密码账号不正确

    今天搞windows忽然遇到一件诡异的事情 今天想开一个远程控制,点吧点吧该做的事情做了,服务也开了,防火墙都直接关了,然后用另一台电脑访问,总是提示凭证(密码账户)不对,这账号信息我输的是百分之一百 ...