如何通过CursorLoader使用ContentProvider的insert()方法正确地将值插入SQLite数据库?

12
我在阅读文档,但还不太确定。它说要使用getContentResolver(),但这并没有真正使用CursorLoader。那么有没有办法通过CursorLoader来做呢?我知道如何使用query()。步骤非常相似吗?即使只是一个解释这个的链接也会很有帮助。
请注意,不要将Google文档链接给我,因为他们从未使用过CursorLoader来使用ContentProvider中的insert()方法。
谢谢!Edit: 我应该说明一下我为什么对此感到困惑,因为调用一个新的CursorLoader会自动调用ContentProviderquery()方法。但是我如何在插入时做到同样的事情呢?

https://developer.android.com/guide/topics/providers/content-provider-basics.html#Inserting - Mia
2个回答

40

请查看我关于此主题的博客文章

内容解析器和内容提供者


CursorLoader与此无关。

插入是一个完全不同的概念...它与CursorLoader没有任何关系。当与LoaderManager结合使用时,CursorLoader会自动查询您的数据库,并在ContentObserver接收到数据存储更改通知时更新自己。它与实际插入数据到数据库的过程无关。

如何解决对ContentResolver的请求

当你通过内容提供者向数据库插入(或查询、更新或删除)数据时,你并不直接与提供者通信。相反,你使用ContentResolver对象与提供者通信(注意,ContentResolver是应用程序全局Context中的私有实例变量)。更具体地说,执行的步骤序列为:

  1. 调用getContentResolver().insert(Uri, ContentValues);

  2. ContentResolver对象确定Uri的权限。

  3. ContentResolver将请求转发给在权限注册的内容提供者(这就是为什么你需要在AndroidManifest.xml中指定权限的原因)。

  4. 内容提供者收到请求并执行指定的操作(在本例中为插入)。数据的插入方式和位置取决于您实现了insert方法的方式(ContentProvider是一个抽象类,要求用户实现insertquerydeleteupdategetType方法)。

希望您至少能够对此有所了解。为什么会涉及这么多步骤呢?因为Android允许应用程序拥有多个内容提供者,并且需要确保应用程序可以安全地与其他第三方应用程序共享数据。(我保证这不是为了让您感到困惑。)

通过ContentProvider插入数据

现在,您(希望)更好地了解了ContentResolver如何将这些请求中继到内容提供者,插入数据也变得相当简单:

  1. 首先,决定要让内容提供者匹配哪个URI。这取决于您如何使用UriMatcher匹配URI。每个URI都代表一种不同的将数据插入内部数据库的方式(例如,如果您的应用程序有两个表,则可能会有两个URI,每个表一个URI)。

  2. 创建一个新的ContentValues对象,将要发送到内容提供者的数据打包到其中。 ContentValues对象将列名映射到数据值。在下面的示例中,列名为“column_1”,在该列下插入的值为“value_1”:

    ContentValues values = new ContentValues();
    values.put("column_1", "value_1");
    
    一旦接收到数据,内容提供者(在您的情况下)将把values对象通过SQLiteDatabase.insert(String table, String nullColumnHack, ContentValues values)方法传递给您的SQLiteDatabase。与ContentProvider不同,此方法已经为您实现了... SQLiteDatabase知道如何处理values对象,并将该行插入数据库中,返回插入的行的行ID,如果插入失败则返回-1

    这就是如何向数据库中插入数据。


    太长不看

    使用getContentResolver().insert(Uri, ContentValues);


2
实际上,现在读了大约20遍后,你基本上是在说CursorLoaderLoaderManager与此没有什么关系。但我的问题现在是,这是在后台完成的,基本上是在UI线程之外吗? - Andy
4
“插入”和“查询”操作是在UI线程上执行的。我计划在本周末撰写一篇文章,介绍如何在单独的线程上执行耗时的插入/查询操作...我会通知您。 - Alex Lockwood
2
@Andy,第一篇关于LoaderLoaderManager的帖子已经完成了...下一篇将非常详细地介绍LoaderManager的工作原理。然后第三篇将非常详细地介绍Loader的工作原理...之后我可能会提供一些示例代码,演示如何将插入/删除/更新事务转移到单独的线程中...并且还会提供一些有关如何使用Loader从SQLite数据库查询数据而不需要ContentProvider的详细信息。我需要停止懒惰了。http://www.androiddesignpatterns.com/2012/07/loaders-and-loadermanager-background.html - Alex Lockwood
2
@AlexLockwood,你说过“在线上唯一提供CursorLoader教程的例子都是涉及到查询...而不是插入。这就是为什么我想写点关于它的东西”,但是你的博客文章实际上并没有讲解如何使用CursorLoader来异步执行insert()。建议的方式是使用AsyncTaskAsyncQueryHandler,扩展AsyncTaskLoader,还是其他的方式? - Tim Rae
1
@AlexLockwood 很抱歉打扰您,但是您写了如何在单独的线程上执行插入/更新/删除操作的文章吗?目前没有一个好的例子可以参考。他们唯一使用的是AsyncTasks,这显然不是一个好主意。我想使用AsyncQueryHandler,但我不确定配置更改后会发生什么(操作会继续进行还是被取消了?)。如果您能抽出时间写这篇文章,我们将非常感激。谢谢。 - Axel
显示剩余15条评论

2

将光标转换为ContentValues使数据库插入更容易。

它建议使用getContentResolver(),但这实际上并没有使用CursorLoader

除了Alex所说的之外,你完全可以遍历返回的 Cursor ,将它们放入 ContentValues 中,然后插入到另一个数据库中。

这只是我脑海中的一个简单示例(一个包含两个 String 列的简单 Cursor):

@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
    if (cursor.moveToFirst()) {
        ArrayList<ContentValues> values = new ArrayList<ContentValues>();
        do {
            ContentValues row = new ContentValues();
            DatabaseUtils.cursorStringToContentValues(cursor, fieldFirstName, row);
            DatabaseUtils.cursorStringToContentValues(cursor, fieldLastName, row);
            values.add(row);
        } while (cursor.moveToNext());
        ContentValues[] cv = new ContentValues[values.size()];
        values.toArray(cv);
        getContentResolver().bulkInsert(CONTENT_NAMES, cv);
    }
}

“可能有更有效的方法来完成这个任务(肯定还需要一些完整性检查),但这是我的第一个想法...”

显然,这主要适用于Cursor源不是你自己的数据库的情况。否则,你会经常更新行,而不是从你自己的数据中插入新行(插入相同的数据)。尽管如此,你仍然可以根据你想要做的事情进行适应... - davidcesarino
此刻我的脑袋已经因为浏览了太多StackOverflow而快要烧毁,但这段代码确实是正确和高效的(即使用bulkInsert代替逐条插入、在onLoadFinished返回之前重用旧数据等)。尽管如此,我想不出一个需要这样做的理由(一时之间)。 - Alex Lockwood

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接