没有 ContentProvider 的 CursorLoader 使用
Posted
技术标签:
【中文标题】没有 ContentProvider 的 CursorLoader 使用【英文标题】:CursorLoader usage without ContentProvider 【发布时间】:2011-11-03 04:38:01 【问题描述】:android SDK 文档说 startManagingCursor()
方法已弃用:
此方法已弃用。将新的 CursorLoader 类与 LoaderManager 一起使用;这也可以通过 Android 兼容包在旧平台上使用。此方法允许活动根据活动的生命周期为您管理给定游标的生命周期。也就是说,当活动停止时,它会自动在给定的光标上调用 deactivate(),当它稍后重新启动时,它会为你调用 requery()。当活动被销毁时,所有托管游标将自动关闭。如果您的目标是 HONEYCOMB 或更高版本,请考虑改用 LoaderManager,可通过 getLoaderManager() 获得
所以我想使用CursorLoader
。但是,当我需要 CursorLoader
的构造函数中的 URI 时,如何将它与自定义 CursorAdapter
和没有 ContentProvider
一起使用?
【问题讨论】:
【参考方案1】:我写了一个不需要内容提供者的simple 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 或更高版本的,或者兼容包自带的。
我也是wrote a ListLoader
,它与LoadManager
兼容,用于检索通用java.util.List
集合。
【讨论】:
找到一个很好的代码示例,使用它 - bitbucket.org/ssutee/418496_mobileapp/src/fc5ee705a2fd/demo/… - 发现它非常有用! @Cristian 感谢您的示例。与您的课程相关的许可证是什么。如何重复使用? 许可证是 Apache 2.0;您可以随时随地重复使用它。如果您有任何改进,请告诉我。 好东西!用户应该知道一个限制,那就是它没有刷新数据更改的机制(就像加载器应该做的那样) @Jadeye 这里有男人:ListLoader 和 SupportListLoader【参考方案2】:编写您自己的加载器,该加载器使用您的数据库类而不是内容提供程序。最简单的方法是从兼容性库中获取CursorLoader
类的源代码,并将提供程序查询替换为对您自己的数据库助手类的查询。
【讨论】:
这是我认为最简单的方法。在我的应用程序中,我创建了一个CursorLoader
后代来管理 SQLite 游标,除了构造函数之外,我只需要覆盖 loadInBackground
方法即可用我的游标查询替换提供程序查询【参考方案3】:
SimpleCursorLoader 是一个简单的解决方案,但是它不支持在数据更改时更新加载器。 CommonsWare 有一个 loaderex 库,它添加了一个 SQLiteCursorLoader 并支持对数据更改的重新查询。
https://github.com/commonsguy/cwac-loaderex
【讨论】:
但是,要使用自动重新查询,您需要为 UI 和更新使用相同的加载器,从而限制了其对后台服务的可用性。【参考方案4】:第三种选择是简单地覆盖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;
;
这也将负责在数据库更改时重新查询您的光标。
唯一需要注意的是:您必须定义另一个观察者,因为谷歌以其无限的智慧决定将他们的包设为私有。如果您将类放入与原始类(或兼容类)相同的包中,您实际上可以使用原始观察者。观察者是一个非常轻量级的对象,不会在其他任何地方使用,所以这并没有太大的区别。
【讨论】:
我在快速测试中的观察是,只有当光标指向内容提供者时,才会针对光标调用 registerContentObserver。你能确认/否认吗? 不一定是 ContentProvider。但是游标需要注册到通知uri(setNotificationUri),然后需要有人(通常是ContentProvider,但可以是任何东西)通过调用ContentResolver.notifyChange来通知它。 是的。在您的 CustomLoader 的loadInBackground()
上,在返回光标之前,说 cursor.setNotificationUri(getContext().getContentResolver(), uri);
uri 可能只是来自随机字符串,如 Uri.parse("content://query_slot1")
。似乎它不在乎uri是否真的存在。一旦我对数据库进行了操作。说getContentResolver().notifyChange(uri, null);
可以解决问题。然后我可能会在一个 contant 文件中为具有少量查询的应用程序创建几个“查询 uri 插槽”。我测试在运行时插入数据库记录,它似乎有效,但我仍然怀疑它是否是好的做法。有什么建议吗?
我在@Yeung 的建议下使用这种方法,一切正常,包括在数据库更新时自动重新加载光标。
不需要任何 unregisterContentObserver 吗?【参考方案5】:
Timo Ohr 提出的第三个选项与 Yeung 的 cmets 一起提供了最简单的答案(奥卡姆剃刀)。下面是一个适合我的完整类的示例。使用这个类有两个规则。
-
扩展此抽象类并实现方法 getCursor() 和 getContentUri()。
只要底层数据库发生变化(例如,在插入或删除之后),请确保调用
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();
【讨论】:
以上是关于没有 ContentProvider 的 CursorLoader 使用的主要内容,如果未能解决你的问题,请参考以下文章
ContentProvider(联系人) - 没有这样的列:metadata_dirty
java 在没有Web请求的情况下使用DXA 1.7 ContentProvider和Localization。
Android 四大组件 ContentProvider介绍
ContentProvider openFile接口目录遍历漏洞