房间持久化库和内容提供者

Posted

技术标签:

【中文标题】房间持久化库和内容提供者【英文标题】:Room persistence library and Content provider 【发布时间】:2018-03-30 00:12:40 【问题描述】:

最近几天我一直在花时间学习新的 Android 架构组件。在跟进了一些博客文章、文档和教程之后,我对每个组件都变得清楚了。但突然间,我意识到我们的老朋友 Content Provider 怎么样了。我可能听起来很傻,因为在写这个问题之前我花了很多时间搜索,我是唯一一个提出这个问题的人吗?我没有任何有用的解决方案。不管怎样,如果我想用本地数据库构建一个应用程序,我现在显然会选择新的架构组件(实时数据、视图模型、房间),而不会进一步认为这将有助于使应用程序 10 倍健壮。但是如果我希望其他应用程序可以访问我的数据库数据,例如 To Widget 如何将 Content Provider 与 Room 集成?

【问题讨论】:

您好,我们可以将 Room DB 与 Content Provider 连接起来,使用 Kotlin 搜索共享房间数据库,您会看到我的帖子解释相同 medium.com/@aniket93shetty/… medium.com/@aniket93shetty/… 【参考方案1】:

顺便说一句,我也有同样的问题。我找到了一个样本here,它回答了我的问题。希望它对你也一样。

简而言之,这是在 DAO 对象中,它将被 Content Provider 的 query() 方法调用。

/**
 * Select all cheeses.
 *
 * @return A @link Cursor of all the cheeses in the table.
 */
@Query("SELECT * FROM " + Cheese.TABLE_NAME)
Cursor selectAll();

注意它是如何返回Cursor 对象的。其他操作,您可以在示例中自己查看更多详细信息。

我认为这是@CommonsWare 答案中的第 3 项选择。

【讨论】:

感谢一群朋友。我对最后一个答案感到困惑。我不知道什么是矩阵光标。找到它后我告诉自己,好吧,离开它。现在你的答案和你给出的 github 链接;将是一个有用的建议。我总是喜欢看样品。 您还可以直接从 DAO 获取实体列表,避免使用内容提供者。您只需将所有内容包装到 AsyncTask 中即可完成。 这仅在您向其他应用程序提供数据时才有效,但当您想使用来自其他应用程序的数据时它无济于事。这是 AAC 中的一个主要漏洞。 @Mark 提到的示例是解决方案的真正瑰宝。这是一个很好的例子,展示了如何使用内容提供者和 Room 作为后端。我更进一步,通过存储库模式访问 Room;这样只有存储库正在访问 Room 数据库。如果有人需要,很高兴发布示例,但这个答案就足够了。 感谢您对示例的参考。 dagger 用 dagger 注入内容提供者有没有意义?【参考方案2】:

如果我想用本地数据库构建应用程序,我现在显然会选择新的架构组件(实时数据、视图模型、房间)

我不会在那里使用“显然”这个词。架构组件是一种选择,但不是必需的。

但如果我希望其他应用程序可以访问我的数据库数据,例如 To Widget 如何将 Content Provider 与 Room 集成?

应用小部件与ContentProvider 无关。恕我直言,很少应用程序应该通过ContentProvider向第三方公开数据库,没有应用程序应该纯粹出于内部目的使用ContentProvider

话虽如此,您有几个选择:

    不要使用 Room,至少要通过 ContentProvider 公开表格

    将 Room 用于内部用途,然后通过在 RoomDatabase 上调用 getOpenHelper()ContentProvider 使用经典的 SQLite 编程技术

    ContentProvider 中使用 Room,编写自己的代码从检索到的 Room 实体中构建 MatrixCursor(对于 query())或创建用于其他操作的实体(对于 @ 987654332@、update()delete()等)

【讨论】:

可以将 ContentProvider 用于内部目的;特别是如果您需要使用 CursorAdapters。即使这样,您也可以并且应该使用 ContentProviders,“因为它们提供了一个很好的抽象”Source 由您自行决定。 @Josh:Google 经常无法更新其文档。我想不出任何当前的 android 应用程序开发专家会主张将 ContentProvider 用于纯粹的内部使用。 @Josh:我不知道任何情况下需要光标。例如,您引用了 CursorAdapter。不仅有其他 ListAdapter 实现,而且 RecyclerView 在很多情况下是更好的视图选择,而且 RecyclerView 不使用 CursorAdapter。 我在所有数据库驱动的应用程序中都使用 ContentProviders,即使仅供内部使用。这样做的主要原因是它的线程安全实现。当然,从开发的角度来看,它增加了一些开销,但好处远远超过了 IMO。使用 CursorAdapter 功能扩展 RecyclerView.Adapter 类也同样容易。一旦你这样做了,我认为使用 ContentProvider 比直接访问本地数据库更容易,尤其是没有线程安全方面。 它不流行,因为大多数人不明白如何使用它。对我来说,这是衡量初级和中级在经验方面的差异。这真的不是太难,一旦你掌握了窍门,它对你来说非常有用,值得花时间。这就是为什么您手机上的大多数主要应用程序都使用 SyncManager。【参考方案3】:

Room Library 对 Content Provider 没有任何特别的支持。只能自己写Content Provider,然后用Room查询数据库。

如果您想使用 Android 架构组件并且想使用基于 SQLite 的内容提供程序,请考虑使用Kripton Persistence Library:它允许从数据库查询生成实时数据生成内容为您提供服务,等等。最重要但并非最后一点:当你只需要编写 where 条件时,为什么还要编写整个 SQL?

澄清一下,我是 Kripton Persistence Library 的作者。我写它是因为我没有找到一个独特的库来满足我在持久性管理方面的所有需求(是的,因为我喜欢编程)。

我使用 Kripton 编写了 Google Content Provider Sample 的转换版本。你可以找到它here。

只是为了简化阅读。使用 Kripton,您只需要定义一个 DAO 接口。内容提供者将由注解生成。在 Kripton 中转换的相同 DAO 将是:

@BindContentProviderPath(path = "cheese")
@BindDao(Cheese.class)
public interface CheeseDao 

    @BindSqlSelect(fields="count(*)")
    int count();

    @BindContentProviderEntry
    @BindSqlInsert
    long insert(String name);

    @BindContentProviderEntry()
    @BindSqlSelect
    List<Cheese> selectAll();

    @BindContentProviderEntry(path = "$id")
    @BindSqlSelect(where ="id=$id")
    Cheese selectById(long id);

    @BindContentProviderEntry(path = "$id")
    @BindSqlDelete(where ="id=$id")
    int deleteById(long id);

    @BindContentProviderEntry(path = "$cheese.id")
    @BindSqlUpdate(where="id=$cheese.id")
    int update(Cheese cheese);


生成的内容提供者通过 URI 公开 DAO 的方法。为了清楚起见,我在这里只放了生成的 JavaDoc(总是由 Kripton 提供)。

有关 Kripton 的更多信息,请访问 its wiki、my site 和 my articles 。

【讨论】:

【参考方案4】:

迟到的帖子,但我最近遇到了同样的问题。最后最终将同一个 Room Database 实例用于本地和内容提供者目的。

所以应用程序本身像往常一样使用房间数据库,内容提供者用“open helper”“包装”房间数据库,如下所示:

class DatabaseProvider : ContentProvider() 

    override fun onCreate(): Boolean 
        return true
    

    override fun query(uri: Uri?, projection: Array<out String?>?, selection: String?, selectionArgs: Array<out String?>?, sortOrder: String?): Cursor? 
        val db = roomDatabase.openHelper.readableDatabase
        db.query(...)
    

    override fun insert(uri: Uri?, values: ContentValues?): Uri? 
        val db = roomDatabase.openHelper.writableDatabase
        db.insert(...)
    

    override fun update(uri: Uri?, values: ContentValues?, selection: String?, selectionArgs: Array<out String?>?): Int 
        val db = roomDatabase.openHelper.writableDatabase
        db.update(...)
    

    override fun delete(uri: Uri?, selection: String?, selectionArgs: Array<out String?>?): Int 
        val db = roomDatabase.openHelper.writableDatabase
        db.delete(...)
    

    override fun getType(uri: Uri?): String? 
    

【讨论】:

【参考方案5】:

你最好使用 SupportOpenHelper

public class MyContentProvider extends ContentProvider 
    public MyContentProvider() 
    

    @Override
    public String getType(Uri uri) 
        // TODO: Implement this to handle requests for the MIME type of the data
        // at the given URI.
        throw new UnsupportedOperationException("Not yet implemented");
    

    UserDatabase database;

    @Override
    public boolean onCreate() 
        database = Room.databaseBuilder(getContext(), UserDatabase.class, "user.db").allowMainThreadQueries().build();
        return false;
    

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) 
         return database.query(SupportSQLiteQueryBuilder.builder("user").selection(selection, selectionArgs).columns(projection).orderBy(sortOrder).create());
    

    @Override
    public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) 
        return database.getOpenHelper().getWritableDatabase().update("user", 0, values, selection, selectionArgs);
    

    @Override
    public int delete(Uri uri, String selection, String[] selectionArgs) 
        return database.getOpenHelper().getWritableDatabase().delete("user", selection, selectionArgs);
    

    @Override
    public Uri insert(Uri uri, ContentValues values) 
        long retId = database.getOpenHelper().getWritableDatabase().insert("user", 0, values);
        return ContentUris.withAppendedId(uri, retId);
    

【讨论】:

以上是关于房间持久化库和内容提供者的主要内容,如果未能解决你的问题,请参考以下文章

android: 内容提供器简介

Github API 列出所有存储库和 repo 的内容

Android-Android内容提供器之运行时权限

Android 房间持久库 - TypeConverter 错误错误:无法弄清楚如何将字段保存到数据库”

选择两个日期之间的可用房间

CoreData 关系内容不持久