Loader 机制

概述

加载器 Loader 支持在 ActivityFragment 中异步加载数据,具有如下几个特点:

  • 可用于每个 ActivityFragment
  • 支持异步加载数据
  • 监控其数据源并在内容变化时传递新结果
  • 在某一配置更改后重建 Loader 时,会自动重新连接上一个 LoaderCursor,因此它们无需重新查询其数据

API 简介

  • LoaderManager
    ActivityFragment 相关联的的抽象类,用于管理一个或多个 Loader 实例。这有助于应用管理与 ActivityFragment 生命周期相关联的、运行时间较长的操作。用来管理实例初始化,获取,重启一个 Loader
  • LoaderManager.LoaderCallbacks
    回调接口,用于 UILoaderManager 进行交互。执行 Loader 回调,绑定分发 Loader,完成加载,重置数据等。
  • Loader
    执行异步数据加载的抽象类,基类。运行异步操作,有开始,完成,后台加载等接口实现。常用 CursorLoader 或自己定义的子类。
  • AsyncTaskLoader
    提供 AsyncTask 来执行工作的抽象加载器。
  • CursorLoader
    AsyncTaskLoader 的子类,它将查询 ContentResolver 并返回一个 Cursor。此类采用标准方式为查询游标实现 Loader 协议。它是以 AsyncTaskLoader 为基础而构建,在后台线程中执行 Cursor 查询,以免阻塞应用的 UI 。使用此加载器是从 ContentProvider 异步加载数据的最佳方式,而不用通过 FragementActivityAPI 来执行托管查询。

使用加载器

LoaderManager 可在 ActivityFragment 内管理一个或多个 Loader 实例;但是每个 ActivityFragment 中只有一个 LoaderManager 。常用接口:

1
2
3
4
5
6
public abstract <D> Loader<D> initLoader(int id, Bundle args,
LoaderManager.LoaderCallbacks<D> callback);
public abstract <D> Loader<D> restartLoader(int id, Bundle args,
LoaderManager.LoaderCallbacks<D> callback);
public abstract void destroyLoader(int id);
public abstract <D> Loader<D> getLoader(int id);

启动加载器

通常在 ActivityonCreate() 方法或 FragmentonActivityCreated() 方法内初始化 Loader ,如:

1
2
3
4
5
6
7
// Prepare the loader.  Either re-connect with an existing one,
// or start a new one.
getLoaderManager().initLoader(0, null, this);

// initLoader的详细定义
public abstract <D> Loader<D> initLoader(int id, Bundle args,
LoaderManager.LoaderCallbacks<D> callback);

initLoader() 方法参数解析:

  • id
    用于标识加载器的唯一 ID,此示例中为 0
  • args
    在构建时提供给加载器的可选参数,可以保存任何数据,示例中为 null
  • callback
    LoaderManager.LoaderCallbacks 的实现,LoaderManager 将调用此实现来报告加载器事件

注意initLoader() 方法将返回已创建的 Loader,但不必使用该引用, LoaderManager 将自动管理加载器的生命周期,根据需要启动和停止加载,并维护加载器的状态及其相关内容。

重启加载器

使用 initLoader() 时,将使用含有指定 ID 的现有加载器。如果没有则它会创建一个。但是如果需要舍弃这些旧数据并重新开始,可以重启加载器:

1
2
3
4
5
6
// 重启加载器,ID必须和初始化时相同
getLoaderManager().restartLoader(0, null, this);

// restartLoader的详细定义
public abstract <D> Loader<D> restartLoader(int id, Bundle args,
LoaderManager.LoaderCallbacks<D> callback);

使用加载器回调

LoaderManager.LoaderCallbacks 是交互的回调接口,都是在主线程中执行,包括以下方法:

1
2
3
4
5
public interface LoaderCallbacks<D> {
public Loader<D> onCreateLoader(int id, Bundle args);
public void onLoadFinished(Loader<D> loader, D data);
public void onLoaderReset(Loader<D> loader);
}
  • onCreateLoader()
    针对指定的 ID 进行实例化并返回新的 Loader
  • onLoadFinished()
    在先前创建的加载器完成加载时调用
  • onLoaderReset()
    在先前创建的加载器重置且其数据因此不可用时调用

onCreateLoader 详解

initLoader() 方法检查是否已存在由该 ID 指定的加载器。如果没有它将触发LoaderManager.LoaderCallbacks 方法 onCreateLoader() 。在此方法中,返回新建的加载器。比如:返回 CursorLoaderContentProvider 执行查询时所需的一系列完整信息,查询是异步执行的。

onLoadFinished 详解

当先前创建的加载器完成加载时调用此方法。
一般情况下会使用 CursorLoaderCursorAdapter 组合的方式异步更新数据源。其中 CursorLoader 异步加载 Cursor 后,通过 CursorAdapterswapCursor() 方法来更新数据,该方法会返回之前旧的 Cursor ,但是我们不需要手动去关闭这个旧游标,CursorLoader 会在 onLoadFinished 执行完后自动关闭这个旧游标。示例及源码分析:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
// 1. 示例
// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter;
...
// 每次数据源都有更新的时候,就会回调这个方法,然后update 我们的 UI 了
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);
}

// 2. 游标管理源码分析
// CursorLoader.java
/* Runs on the UI thread */
@Override
public void deliverResult(Cursor cursor) {
if (isReset()) {
// An async query came in while the loader is stopped
if (cursor != null) {
cursor.close();
}
return;
}
Cursor oldCursor = mCursor;
mCursor = cursor;

if (isStarted()) {
// 这里会执行回调函数 onLoadFinished
// 回调中一般都是数据交换:mAdapter.swapCursor(data);
// 返回的旧游标如果需要做数据处理,需要在回调函数中执行
super.deliverResult(cursor);
}

// 执行完 onLoadFinished 后,关闭旧游标
// 如果UI线程中后续再操作该旧游标会抛出异常 android.database.StaleDataException
// : Attempted to access a cursor after it has been closed.
if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
oldCursor.close();
}
}

onLoaderReset 详解

此方法将在先前创建的加载器重置且其数据因此不可用时调用。通过此回调可以了解何时将释放数据,因而能够及时移除其引用。使用 null 来实现:
mAdapter.swapCursor(null);

常用 Loader

类定义:
public class Loader<D> {...}

CursorLoader

类定义及构造函数

1
2
3
4
5
6
// 类定义
public class CursorLoader extends AsyncTaskLoader<Cursor> {...}

// 除了 context 之外,其他参数都是 ContentProvider 用来查询数据的条件
public CursorLoader(Context context, Uri uri, String[] projection,
String selection, String[] selectionArgs, String sortOrder) {...}

AsyncTaskLoader

public abstract class AsyncTaskLoader<D> extends Loader<D> {...}

自定义 Loader

问题: ????数据是需要两次 cursor 拼接才能完成,该如何实现?

0%