没有ContentProvider的CursorLoader用法


107

Android SDK文档说该startManagingCursor()方法已被弃用:

不建议使用此方法。而是将新的CursorLoader类与LoaderManager一起使用;在旧版平台上,也可以通过Android兼容性软件包获得此功能。此方法使活动可以根据活动的生命周期来为您管理给定的Cursor生命周期。也就是说,当活动停止时,它将自动在给定的Cursor上调用deactivate(),而在稍后重新启动时,它将为您调用requery()。活动销毁后,所有托管游标将自动关闭。如果您的目标是HONEYCOMB或更高版本,请考虑改用LoaderManager(可通过getLoaderManager()获得)

所以我想用CursorLoader。但是,当我在的构造函数中需要URI时,如何在有自定义CursorAdapter和不存在的情况下使用它?ContentProviderCursorLoader


@Alex洛克伍德为什么我们使用的CursorAdapter没有ContentProvider的请建议我stackoverflow.com/questions/20419278/...

为什么我们使用的CursorAdapter没有ContentProvider的请建议我stackoverflow.com/questions/20419278/...

Answers:


155

我编写了一个简单的CursorLoader,不需要内容提供程序:

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;
    }
}

它只需要AsyncTaskLoader类。要么是Android 3.0或更高版本中的一种,要么是兼容软件包随附的一种。

我还写了ListLoader与兼容的LoadManager,用于检索通用java.util.List集合。


13
找到了一个使用它的不错的代码示例-bitbucket.org/ssutee/418496_mobileapp/src/fc5ee705a2fd/demo/…-发现它非常有用!
Shushu 2012年

@Cristian感谢您的示例。与您的课程相关的许可证是什么?如何重用?
codinguser

2
许可证是Apache 2.0;您可以随时随地重复使用它。让我知道您是否有任何改进。
克里斯蒂安

14
好东西!用户应该意识到一个限制,那就是它没有刷新数据更改的机制(就像
加载程序

1
@Jadeye,您在这里:男子:ListLoaderSupportListLoader
克里斯蒂安

23

编写使用数据库类而不是内容提供者的自己的加载器。最简单的方法是CursorLoader从兼容性库中获取类的源,并用对您自己的db helper类的查询替换提供程序查询。


1
我认为这是最简单的方法。在我的应用程序中,我创建了一个 CursorLoader后继项来管理SQLite游标,而构造函数中的loadInBackground
appart

14

SimpleCursorLoader是一个简单的解决方案,但是它不支持在数据更改时更新加载程序。CommonsWare有一个loaderex库,该库添加了SQLiteCursorLoader并支持对数据更改进行重新查询。

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


2
但是,要使用自动重新查询,您需要为UI和更新使用相同的加载器,从而限制了其在后台服务中的可用性。
ge0rg 2012年

12

第三种选择是简单地覆盖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;
    }
};

当数据库更改时,这还将照顾到重新查询游标。

唯一需要注意的是:您必须定义另一个观察者,因为Google的无限智慧决定将他们的程序包设为私有。如果将类与原始类(或兼容类)放在同一包中,则实际上可以使用原始观察者。观察者是一个非常轻便的对象,在其他任何地方都没有使用,因此这没有太大区别。


我在快速测试中的观察结果是,仅当游标以内容提供者为目标时,才会针对游标调用registerContentObserver。您可以确认/否认吗?
尼克·坎皮恩

1
它不一定必须是ContentProvider。但是,游标需要注册到通知uri(setNotificationUri),然后需要通过调用ContentResolver.notifyChange由某人(通常为ContentProvider,但可以是任何东西)进行通知。
Timo Ohr

4
是的 在您的CustomLoader上loadInBackground() ,在返回游标之前,说cursor.setNotificationUri(getContext().getContentResolver(), uri);uri可能只是来自于String之类的随机字符串Uri.parse("content://query_slot1")。似乎并不在乎uri是否真的存在。一旦完成对DB的操作。说getContentResolver().notifyChange(uri, null);可以解决问题。然后,我可能会在少量查询的应用程序的内容文件中创建一些“查询uri插槽”。我测试了在运行时中插入数据库记录,它似乎可以正常工作,但我仍然怀疑它是一种好的做法。有什么建议吗?

我将这种方法与@Yeung的建议一起使用,并且一切正常,包括在数据库更新时自动重新加载光标。
DavidH

不需要任何unregisterContentObserver吗?
GPack '16

2

Timo Ohr提出的第三个选项与Yeung的评论一起提供了最简单的答案(Occam的剃刀)。以下是对我有用的完整类的示例。使用该类有两个规则。

  1. 扩展此抽象类并实现方法getCursor()和getContentUri()。
  2. 每当基础数据库更改时(例如,在插入或删除之后),请确保调用

    getContentResolver().notifyChange(myUri, null);

    其中,myUri与方法getContentUri()的实现返回的结果相同。

这是我使用的类的代码:

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();
  }
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.