Android 应用 Loaders 全面详解及源码浅析

675 查看

1 背景

在Android中任何耗时的操作都不能放在UI主线程中,所以耗时的操作都需要使用异步实现。同样的,在ContentProvider中也可能存在耗时操作,这时也该使用异步操作,而3.0之后最推荐的异步操作就是Loader。它可以方便我们在Activity和Fragment中异步加载数据,而不是用线程或AsyncTask,他的优点如下:

  • 提供异步加载数据机制;
  • 对数据源变化进行监听,实时更新数据;
  • 在Activity配置发生变化(如横竖屏切换)时不用重复加载数据;
  • 适用于任何Activity和Fragment;

PS:由于在我们现在的多个项目中都大量的使用了Loader来处理数据加载(而且由于粗心跳过几个坑,譬如Loader ID重复导致数据逻辑异常、多线程中restartLoader导致Loader抛出异常(最后保证都在UI线程中执行即可)等),所以接下来我们进行下使用及源码浅析。

PPPS:前方高能,文章巨长,请做好心理准备(您可以选择通过左上角目录点击索引到感兴趣的章节直接查看,或者,或者,或者直接高能往下看)。

2 基础使用实例

该基础实例讲解完全来自于官方文档,详细可以点击我查看英文原文

既然接下来准备要说说他的使用强大之处了,那不妨我们先来一张图直观的感性认识下不用Loader(左)与用Loader(右)对我们开发者及代码复杂度和框架的影响吧,如下:

10-11-1

2-1 Loader API概述说明

如下是我们开发中常用的一些Loader相关接口:

Class/Interface Description
LoaderManager 一个与Activity、Fragment关联的抽象类,用于管理一个或多个Loader实例。每个Activity或Fragment只能有一个LoaderManager,而一个LoaderManager可以有多个Loader。
LoaderManager.LoaderCallbacks 用于和LoaderManager交互的回调接口。譬如,可以使用onCreateLoader()创建一个新的Loader。
AsyncTaskLoader 抽象的Loader,提供一个AsyncTask继承实现。
CursorLoader AsyncTaskLoader的子类,用于向ContentResover请求返回一个Cursor。该类以标准游标查询实现了Loader协议,使用后台线程进行查询,使用这个Loader是从ContentProvider加载异步数据最好的方式。

2-2 在应用中使用Loader

在我们开发的一个App里,使用Loader时常规的步骤包含如下一些操作需求:

  • 一个Activity或Fragment;
  • 一个LoaderManager实例;
  • 一个CursorLoader,从ContentProvider加载数据;
  • 一个LoaderManager.LoaderCallbacks实现,创建新Loader及管理已存在Loader;
  • 一个组织Loader数据的Adapter,如SimpleCursorAdapter;

下面我们看下具体流程。

2-2-1 启动一个Loader(initLoader)

一个Activity或Fragment中LoaderManager管理一个或多个Loader实例,每个Activity或Fragment只有一个LoaderManager,我们可以在Activity的onCreate()或Fragment的onActivityCreated()里初始化一个Loader。例如:

可以看见上面的initLoader()方法有三个参数:

  • 第一个参数代表当前Loader的ID;
  • 第二个参数代表提供给Loader构造函数的参数,可选;
  • 第三个参数代表LoaderManager.LoaderCallbacks的回调实现;

上面initLoader()方法的调用确保了一个Loader被初始化和激活的状态,该方法的调运有如下两种结果:

  • 如果代表该Loader的ID已经存在,则后面创建的Loader将直接复用已经存在的;
  • 如果代表该Loader的ID不存在,initLoader()会触发LoaderManager.LoaderCallbacks回调的onCreateLoader()方法创建一个Loader;

可以看见通过initLoader()方法可以将LoaderManager.LoaderCallbacks实例与Loader进行关联,且当Loader的状态变化时就被回调。所以说,如果调用者正处于其开始状态并且被请求的Loader已经存在,且已产生了数据,那么系统会立即调用onLoadFinished()(在initLoader()调用期间),所以你必须考虑到这种情况的发生。

当然了,intiLoader()会返回一个创建的Loader,但是你不用获取它的引用,因为LoadeManager会自动管理该Loader的生命周期,你只用在它回调提供的生命周期方法中做自己数据逻辑的处理即可。

2-2-2 重启一个Loader(restartLoader)

通过上面initLoader()方法介绍我们可以知道initLoader调运后要么得到一个ID已存在的Loader,要么创建一个新的Loader;但是有时我们想丢弃旧数据然后重新开始创建一个新Loader,这可怎么办呢?别担心,要丢弃旧数据调用restartLoader()即可。例如,SearchView.OnQueryTextListener的实现重启了Loader,当用户查询发生变化时Loader需要重启,如下:

上面方法的参数啥的和再上面的init方法类似,就不再罗嗦了。

2-2-3 使用LoaderManager Callbacks

LoaderManager.LoaderCallbacks是LoaderManager的回调交互接口。LoaderManager.LoaderCallbacks包含如下三个方法:

  • onCreateLoader()
    实例化并返回一个新创建给定ID的Loader对象;
  • onLoadFinished()
    当创建好的Loader完成了数据的load之后回调此方法;
  • onLoaderReset()
    当创建好的Loader被reset时调用此方法,这样保证它的数据无效;

2-2-3-1 onCreateLoader说明

当你尝试使用一个Loader(譬如通过initLoader()方法),它会检查给定Loader的ID是否存在,如果不存在就触发LoaderManager.LoaderCallbacks里的onCreateLoader()方法创建一个新Loader。创建新Loader实例典型的做法就是通过CursorLoader类创建,不过你也可以自定义一个继承自Loader的子类来实现自己的Loader。

下面的例子中我们通过onCreateLoader()回调创建一个CursorLoader实例,使用CursorLoader的构造方法创建实例时需要一些参数去查询一个ContentProvider。具体参数如下:

  • uri
    准备获取内容的URI。
  • projection
    要返回的列key list,null表示返回所有列,但是返回所有列很多时候会降低性能;
  • selection
    要返回的行过滤,也就是SQL中的WHERE语句,null代表返回uri指定的所有行;
  • selectionArgs
    用来替换上面selection中包含的“?”;
  • sortOrder
    结果的行排序,也就是SQL中的ORDER BY,传递null则无序;

2-2-3-2 onLoadFinished说明

当创建好的Loader完成数据加载时回调此方法,我们要确保该方法在Loader释放现有维持的数据之前被调用。在这里我们应该移除所有对旧数据的使用(因为旧数据不久就会被释放),但是不用释放旧数据,因为Loader会帮我们完成旧数据的释放。

Loader一旦知道App不再使用旧数据就会释放掉。例如,如果数据来自CursorLoader里的一个Cursor,我们不应该自己在代码中调用close()方法;如果一个Cursor正在被放置到一个CursorAdapter时我们应当使用swapCursor()进行新数据交换,这样正在被放置的旧的Cursor就不会被关掉,也就不会导致Adapter的加载异常。