Android 数据存储之使用 CursorLoader 加载数据

CursorAdapter 简介

Adapter:Responsible for making a list item view for each item in data source.

正如上面所说,适配器为数据源中的每个项创建列表项视图。例如,如果我们的数据源为 ArrayList 比如由文字组成的 ArrayList,那么我们就需要使用 ArrayAdapter 来填充列表。如果数据源是一个 Cursor,比如`”cursor of pets”“(前面宠物的例子),那我们就需要使用 CursorAdapter 来填充列表。

这里写图片描述

如上图所示,ListView 刚开始是空的,我们设置一个 CursorAdapter 与我们从 PetProvider 获得的宠物 Cursor 关联起来,同时确保 CursorAdapter 知道如何为每个宠物创建列表项视图。首先 ListView 将询问适配器最终要显示在界面上的列表有多少项,接下来从位置 0 开始请求每一项,适配器将 Cursor 移至第 0 行,提取此行的值,为该宠物设置正确的列表项,然后将此列表项回传至 ListView 。ListView 继续请求位置 1 的项,CursorAdpater 将 Cursor 移至第 1 行,读取其中的值,设置相应的列表视图,然后传回到 ListView。重复上面的动作,ListView 将持续请求每个位置的项,直到 Cursor 中没有宠物或屏幕已填满。

创建列表视图的过程

这里写图片描述
假设 ListView 要求位置 0 处的列表项,那么 CursorAdapter 首先需要创建一个空白列表项,这涉及到填充一个 xml 布局,该布局包括我们想要显示的内容控件。然后我们需要使用 Cursor 中的正确数据填充我们的内容控件。由于我们感兴趣的是位置 0 处的宠物,我们将 Cursor 位置移动到 0 行,提取出需要展示的内容,将内容填充到我们的列表项

Populating a ListView with a CursorAdapter

CursorLoader 简介

主线程负责处理全部用户输入和所有绘制操作,如果 Activity 在主线程上执行时间密集型的工作,可能会使应用卡住,甚至停止对用户输入做出响应。所以不要在主线程执行任何运行时间长的操作。针对于我们的宠物例子,我们如何运用加载器(loaders)从数据库中获取宠物数据,保持主线程没有耗时操作?

我们需要 CursorLoader,因为我们的数据为 Cursor 形式。就像网络操作一样运行时间长,数据库操作也是如此,比如读取和写入 pets 表等操作。之前我们在 Activity 中执行 ContentResolver 查询方法,但是现在我们不想阻塞 UI 线程,所以使用加载器,我们就可以在后台线程上执行此操作。

loader that quires the contentResolver with a specific uri and returns a cursor

加载器需要一个特定 Uri 的 ContentResolver,并返回一个 Cursor。

Loads data on a background thread because reading and writing to a database can be an expensive operation

  • 后台线程加载数据因为读取和写入数据库可能是一个很耗时的操作。

Works well with ContentProvider because a Loader is tied to a URI

  • 与 ContentProvider 很好的配合使用,因为加载器被绑定在 URI 上

When the underlying data changes, we can automatically refresh the loader to query the data source again with the same URI.

  • 当底层数据发生更改时,我们可以自动刷新加载器,用相同的 URI 再次查询数据源。

这里写图片描述

使用 CursorLoader

加载器

列表视图

通过上面两个链接可以详细了解 CursorLoader。

这里写图片描述

将应用的 Activity 实现 LoaderCallbacks 接口,在 Activity 的 onCreate() 方法中调用 initLoader。在回调方法 onCreateLoader() 方法中定义想要从 ContentProvider 中查询的数据,创建并返回一个加载器 CursorLoader,这里的操作在后台线程完成,加载完后,onLoadFinished() 回调方法会返回包含所加载数据的 Cursor,在这里使用 swapCursor() 方法可将我们获得的 Cursor 传入 CursorAdapter。最后回调的 onLoaderReset() 方法,此方法在当前加载器被摧毁的时候,且最新提供的 Cursor 中的数据变为无效时调用,所以调用 swapCursor() 方法传入地 null

CursorLoading 的自动加载

为了避免无用的加载浪费资源,仅在 SQL 数据库中的某些数据发生变化时更新 Cursor。CursorLoader 主要解决的问题之一就是:我们想追踪数据是否已加载,如果已加载,则避免重复加载,除非有必要。

只有当我们在插入、更新或删除数据时触发 Cursor 进行更新。而插入、更新或删除数据的具体操作是在 ContentProvider 中执行的,所以我们需要在 ContentProvider 中进行设置,当数据发生变化时,告诉 CursorLoader 需要重新加载数据的地方。

在 Android 中存在一个机制能告诉 CursorLoader 与其关联的数据需要重新加载。因为每个加载器与特定数据的 URI 关联。URI 的作用是说明我们想要加载的数据是什么。Cursor 有一个属性称为 Notification URI,它指代某个特定数据点就像所有其它 URI 一样。一旦 Cursor 设置为在 URI 发生变化时接收通知。我们可以通过对 URI 调用 notifyChange 方法发出 URI 数据发生了变化的信号

下面是具体在 Provider 中的代码设置:

public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs,
                        String sortOrder){
...
// Set notification URI on the Cursor
// so we konw what content URI the Cursor was created for.
// If the data at this URI changes, then we konw we need to update the Cursor.
// 在 Cursor 上设置通知 URI,当 URI 的数据发生变化,我们就知道需要更新 Cursor
cursor.setNotificationUri(getContext().getContentResolver(), uri);
return cursor;
}

// 在 insert()、update() 和 delete() 方法中设置代码以使它们在 URI 发生变化时正确通知所有监听器

insert(){
...
// Notify all listeners that the data has changed for the pet content URI
getContext().getContentResolver().notifyChange(uri, null);
...
}

update(){
...
// Returns the number of database rows affected by the update statement
        updateCount = database.update(PetEntry.TABLE_NAME, contentValues, selection, selectionArgs);

        // notify all listeners of changes
        if (updateCount > 0) {
          getContext().getContentResolver().notifyChange(uri, null);
        }
        return updateCount;
}

delete(){
...
deleteCount = database.delete(PetEntry.TABLE_NAME, selection, selectionArgs);
if (deleteCOunt > 0) {
            getContext().getContentResolver().notifyChange(uri, null);
        }
}

这里写图片描述

首先 CatalogActivity 实现一个 LoaderManager.LoaderCallbacks<Cursor> 接口,重写默认的回调方法。在 onCreate() 方法中使用 initLoader() 方法初始化查询。后台框架将调用 onCreateLoader() 回调方法以初始化我们的 CursorLoader 对象,过程中使用 ContentResolver,找到适合的 Provider,在 PetProvider 的 query() 方法中通过 UriMatcher 找到数据库中正确的数据并获得正确的 Cursor。接下来使用 setNotificationUri() 方法,告诉我们需要监视的 URI,当数据源发生变化时它就会知道需要更新 Cursor,并将 Cursor 返回至 CursorLoader。比如表中新插入了宠物,Uri 会的通知,并转而告诉 Cursor 发生了变化通知更新,更新后的 Cursor 会被回传到 Provider 再到 ContentResolver 最后到 CursorLoader 中重新加载新信息,重新加载后将使用新的 Cursor 调用 onLoadFInishes,且 UI 得到更新。而 onLoaderReset() 用来删除旧的 Cursor,换入新的数据。

notifyChange()教程

Cursor setNotificationUri() 方法

猜你喜欢

转载自blog.csdn.net/modurookie/article/details/80905282