不使用 ContentProvider 的 CursorLoader 用法

Android SDK 文档说 startManagingCursor()方法已经过时了:

不推荐使用此方法。使用新的 CursorLoader 类和 LoaderManager; 这也可以通过 Android 兼容性包在较旧的平台上使用。此方法允许活动根据活动的生命周期为您管理给定的 Cursor 的生命周期。也就是说,当活动停止时,它会自动调用给定的游标的反激活() ,当它稍后重新启动时,它会为您调用 request ()。销毁活动后,将自动关闭所有托管游标。如果您的目标是蜂巢或更高版本,可以考虑改用通过 getLoaderManager ()提供的 LoaderManager

所以我想使用 CursorLoader。但是,当我需要在 CursorLoader的构造函数中使用 URI 时,如何在没有 ContentProvider的情况下与自定义 CursorAdapter一起使用它呢?

47150 次浏览

Write your own loader that uses your database class instead of a content provider. The easiest way is just to take the source of the CursorLoader class from the compatibility library, and replace provider queries with queries to your own db helper class.

I wrote a simple CursorLoader that does not need a content provider:

import android.content.Context;
import android.database.Cursor;
import android.support.v4.content.AsyncTaskLoader;


/**
* Used to write apps that run on platforms prior to Android 3.0. When running
* on Android 3.0 or above, this implementation is still used; it does not try
* to switch to the framework's implementation. See the framework SDK
* documentation for a class overview.
*
* This was based on the CursorLoader class
*/
public abstract class SimpleCursorLoader extends AsyncTaskLoader<Cursor> {
private Cursor mCursor;


public SimpleCursorLoader(Context context) {
super(context);
}


/* Runs on a worker thread */
@Override
public abstract Cursor loadInBackground();


/* 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()) {
super.deliverResult(cursor);
}


if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
oldCursor.close();
}
}


/**
* Starts an asynchronous load of the contacts list data. When the result is ready the callbacks
* will be called on the UI thread. If a previous load has been completed and is still valid
* the result may be passed to the callbacks immediately.
* <p/>
* Must be called from the UI thread
*/
@Override
protected void onStartLoading() {
if (mCursor != null) {
deliverResult(mCursor);
}
if (takeContentChanged() || mCursor == null) {
forceLoad();
}
}


/**
* Must be called from the UI thread
*/
@Override
protected void onStopLoading() {
// Attempt to cancel the current load task if possible.
cancelLoad();
}


@Override
public void onCanceled(Cursor cursor) {
if (cursor != null && !cursor.isClosed()) {
cursor.close();
}
}


@Override
protected void onReset() {
super.onReset();


// Ensure the loader is stopped
onStopLoading();


if (mCursor != null && !mCursor.isClosed()) {
mCursor.close();
}
mCursor = null;
}
}

It only needs the AsyncTaskLoader class. Either the one in Android 3.0 or higher, or the one that comes with the compatibility package.

I also wrote a ListLoader which is compatible with the LoadManager and is used to retrieve a generic java.util.List collection.

The SimpleCursorLoader is a simple solution, however it doesn't support updating the loader when the data changes. CommonsWare has a loaderex library that adds a SQLiteCursorLoader and supports re-query on data changes.

https://github.com/commonsguy/cwac-loaderex

A third option would be to simply override loadInBackground:

public class CustomCursorLoader extends CursorLoader {
private final ForceLoadContentObserver mObserver = new ForceLoadContentObserver();


@Override
public Cursor loadInBackground() {
Cursor cursor = ... // get your cursor from wherever you like


if (cursor != null) {
// Ensure the cursor window is filled
cursor.getCount();
cursor.registerContentObserver(mObserver);
}


return cursor;
}
};

This will also take care of re-querying your cursor when the database changes.

Only caveat: You'll have to define another observer, since Google in it's infinite wisdom decided to make theirs package private. If you put the class into the same package as the original one (or the compat one) you can actually use the original observer. The observer is a very lightweight object and isn't used anywhere else, so this doesn't make much of a difference.

The third option proposed by Timo Ohr, together with the comments by Yeung, provide the simplest answer (Occam's razor). Below is an example of a complete class that works for me. There are two rules for using this class.

  1. Extend this abstract class and implement methods getCursor() and getContentUri().
  2. Any time that the underlying database changes (e.g., after an insert or delete), make sure to call

    getContentResolver().notifyChange(myUri, null);
    

    where myUri is the same one returned from your implementation of method getContentUri().

Here is the code for the class that I used:

package com.example.project;


import android.content.Context;
import android.database.Cursor;
import android.content.CursorLoader;
import android.content.Loader;


public abstract class AbstractCustomCursorLoader extends CursorLoader
{
private final Loader.ForceLoadContentObserver mObserver = new Loader.ForceLoadContentObserver();


public AbstractCustomCursorLoader(Context context)
{
super(context);
}


@Override
public Cursor loadInBackground()
{
Cursor cursor = getCursor();


if (cursor != null)
{
// Ensure the cursor window is filled
cursor.getCount();
cursor.registerContentObserver(mObserver);
}


cursor.setNotificationUri(getContext().getContentResolver(), getContentUri());
return cursor;
}


protected abstract Cursor getCursor();
protected abstract Uri getContentUri();
}