Room持久化库和内容提供程序

70

最近几天,我一直在学习新的Android架构组件。阅读了一些博客、文档和教程之后,每个组件都变得清晰明了。但突然意识到我们的老朋友Content Provider怎么办呢?我可能听起来有点傻,因为在写这个问题之前,我已经花了很长时间搜索,难道只有我一个人想到这个问题吗?我没有得到任何有用的解决方案。无论如何,如果我想建立一个具有本地数据库的应用程序,我现在显然会选择新的架构组件(LiveData、ViewModel、Room),而不需要更深入的思考,这将非常有助于使应用程序更加健壮。但是,如果我希望我的数据库数据可供其他应用程序访问,例如小部件,该如何将Content Provider与Room集成?


1
嗨,我们可以使用内容提供程序连接房间数据库,使用Kotlin进行搜索共享房间数据库,你可以查看我的帖子了解详细信息。 - Anikethan Shetty
https://medium.com/@aniket93shetty/content-provider-for-sharing-room-database-using-kotlin-c196ca1d8471 - Anikethan Shetty
5个回答

46

顺便问一下,我也有同样的问题。 我在这里找到了一个示例,它回答了我的问题。希望它也能对你有所帮助。

简而言之,这个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个选择,我认为。


{btsdaf} - Wubbalubbadubdub
您还可以直接从DAO获取实体列表,避免使用内容提供程序。您只需将所有内容包装在AsyncTask中即可轻松完成。 - JustMeh
只有在向其他应用程序提供数据时,此方法才有效,但是当您想从其他应用程序中使用数据时,它对您没有帮助。这是AAC中的一个重大漏洞。 - Brill Pappin
2
@Mark提到的示例是解决方案中真正的亮点。它是如何使用内容提供程序和Room作为后端的绝佳示例。我将其稍微扩展,通过仓库模式访问Room;因此只有仓库可以访问Room数据库。如果有人需要,我很乐意发布示例,但这个答案已经足够了。 - Todd DeLand
感谢提供样例参考。在使用Dagger将DAO注入到内容提供程序中是否有意义? - a_chubenko

14
如果我想要建立一个具有本地数据库的应用程序,我现在显然会选择新的架构组件(LiveData、ViewModel和Room)。
我不会使用“显然”这个词。架构组件是一种选择,但不是必需品。
但如果我想让我的数据库数据可以被其他应用程序访问,比如小部件,那么我该如何将内容提供程序与Room集成呢?
应用小部件与ContentProvider无关。 在我看来,很少有应用程序应该通过ContentProvider向第三方公开数据库,而且没有应用程序应该仅出于内部目的使用ContentProvider。
话虽如此,您还有几个选择:
1. 至少不要为ContentProvider暴露的表使用Room 2. 用Room进行内部目的,但然后使用经典的SQLite编程技术对ContentProvider进行编码,方法是在RoomDatabase上调用getOpenHelper() 3. 在ContentProvider中使用Room,从检索的Room实体构建自己的代码以生成MatrixCursor(对于查询())或创建可用于其他操作的实体(对于insert(),update() ,delete()等)

6
如果需要与 CursorAdapters 一起工作,使用 ContentProvider 用于内部目的是可以接受的。即使如此,您也可以并且应该自行决定是否使用 ContentProvider,因为它们提供了很好的抽象层。来源 - Josh
8
@Josh: Google经常无法更新其文档。我想不出任何当前的Android应用程序开发专家会主张仅将ContentProvider用于内部使用。 - CommonsWare
4
@Josh:我不知道任何情况下需要使用Cursor。例如,您引用了CursorAdapter。不仅有其他ListAdapter实现,而且在许多情况下,RecyclerView是更好的视图选择,而RecyclerView不使用CursorAdapter。 - CommonsWare
1
@CommonsWare 谷歌已经更新了有关何时使用ContentProvider的文档。这是链接:https://developer.android.com/guide/topics/providers/content-provider-basics.html。虽然我已经使用Room进行数据库抽象,但我仍需要使用存根ContentProvider来实现SyncAdapter。此外,CursorLoaders依赖于ContentProvider。因此,我请求您更新您的答案,该答案表示“没有应用程序应该使用ContentProvider”用于内部目的,因为这是误导性的。Google IO scheduler app已经为内部目的实施了ContentProvider,请参见链接:https://github.com/google/iosched - Manish Mulimani
3
它不受欢迎,因为大多数人不知道如何使用它。对我而言,它是经验上初级和中级的区别度量标准。一旦你掌握了它,它其实并不太难,并且可以为你做很多事情,所以花时间去学习它是非常值得的。这就是为什么你手机上的大多数主要应用程序都使用SyncManager。 - Brill Pappin
显示剩余14条评论

8

Room库不特别支持内容提供者。您只能自己编写内容提供者,然后使用Room查询数据库。

如果您想使用Android架构组件并且要使用基于SQLite的内容提供者,请考虑使用Kripton Persistence Library:它允许从DB查询生成LiveData、为您生成内容提供者等等。最后但并非最不重要的是:当您只需要编写where条件时,为什么必须编写整个SQL呢?

只是为了明确,我是Kripton Persistence Library的作者。我写它是因为我没有找到一个适合我所有持久性管理需求的唯一库(是的,因为我喜欢编程)。

我用Kripton编写了Google内容提供者示例的转换版本。您可以在此处找到它。

简化阅读。使用Kripton,您只需要定义DAO接口。注释将生成内容提供者。同样的DAO转换为Kripton将是:

@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方法。为了澄清,我在此处仅放置由Kripton始终生成的JavaDoc。

enter image description here

关于 Kripton 的更多信息,请查看 它的维基我的网站我的文章


7

虽然晚了点,但我最近遇到了同样的问题。最终我使用了相同的 Room 数据库实例来同时用于本地和内容提供程序。

enter image description here

这个应用程序像往常一样使用 Room 数据库和内容提供者,但是将 Room 数据库通过“打开助手”进行“包装”,代码如下:

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? {
    }
}

2

建议您使用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);
    }
}

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