没有 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接口目录遍历漏洞

ContentProvider openFile接口目录遍历漏洞

ContentProvider openFile接口目录遍历漏洞