当ContentProvider跨进程遇上Room数据库

        Android开发使用到的数据库,官方都已推荐直接使用Room,那我们为何不跟着时代的脚步走呢。可是当需要使用ContentProvider提供跨进程的数据操作时,可能会由于资料不多,会遇到不少的坑,那么这就是本文的目的——方便你我他,快捷使用Room+ContentProvider。

        本文主要内容包括如下三个部分:

  1. Room数据库使用
  2. ContentProvider服务端
  3. ContentProvider客户端

———————————————— 下面我们一一道来———————————————— 

1. 📂 Room数据库使用

1.1 引入Room依赖

        在应用中使用 Room,需要将以下依赖项添加到应用的 build.gradle 文件。

dependencies {
    def ROOM_VERSION= "2.5.0"

    api "androidx.room:room-runtime:$ROOM_VERSION"
    kapt "androidx.room:room-compiler:$ROOM_VERSION"
}

:kapt使用到Kotlin插件,记得在build.gradle 文件顶部添加 plugins { id 'kotlin-kapt' }。另外2.5.0版本的Room需要compileSdk为33。

1.2 创建数据实体

        以下代码定义了一个 Note 数据实体。Note 的每个实例都代表应用数据库中 Note 表中的一行。

@Entity
data class Note(
    @PrimaryKey var id: String = UUID.randomUUID().toString(), // 记录唯一标识,生成标准确认,uuid
    @ColumnInfo(name = "type") var type: Int = -1, // 记录类型:1:语音记录;2:照片记录
    @ColumnInfo(name = "time") var time: Long = -1, // 记录生成时间
    @ColumnInfo(name = "time_update") var timeUpdate: Long? = -1, // 记录更新时间(暂未使用,此字段为后面业务进行预留)
    @ColumnInfo(name = "content") var content: String = "", // 语音流式/手指取词,识别到的文本内容
    @ColumnInfo(name = "content_origin") var contentOrigin: String? = "", // 照片识别到的整体文本内容(不显示给用户看,仅做ChatGPT上下文使用)
    @ColumnInfo(name = "content_gpt") var contentGpt: String? = "", // ChatGPT洗稿后的文本内容
    @ColumnInfo(name = "gpt_context1") var gptContext1: String? = "", // ChatGPT的联想文本1
    @ColumnInfo(name = "gpt_context2") var gptContext2: String? = "", // ChatGPT的联想文本2
    @ColumnInfo(name = "gpt_context3") var gptContext3: String? = "", // ChatGPT的联想文本3
    @ColumnInfo(name = "gpt_explain") var gptExplain: String? = "", // ChatGPT的解释内容
    @ColumnInfo(name = "gpt_summary") var gptSummary: String? = "", // ChatGPT的总结文本
    @ColumnInfo(name = "img_url") var imgUrl: String? = "", // 照片记录保存的本机图片路径(sdcard路径)
    @ColumnInfo(name = "operate") var operate: Int? = -1, // 记录操作:增加(1)、删除(2)、修改(3)
    @ColumnInfo(name = "is_sync") var isSync: Boolean? = false, // 是否同步:已同步(true)、未同步(false)
    @ColumnInfo(name = "extra1") var extra1: String? = "", // 扩展字段1(暂未使用,此字段为后面业务进行预留)
    @ColumnInfo(name = "extra2") var extra2: String? = "", // 扩展字段2(暂未使用,此字段为后面业务进行预留)
) {
    companion object {
        const val TYPE_VOICE = 1
        const val TYPE_CAMERA = 2
    }
}

1.3 创建数据访问对象 (DAO)

        以下代码定义了一个名为 NoteDao 的 DAO。NoteDao 提供了应用的其余部分用于与 Note 表中的数据交互的方法。

@Dao
interface NoteDao {

    @Query("SELECT * FROM Note order by time")
    fun getAll(): List<Note>

    @Insert
    fun insertAll(notes: List<Note>)

    @Insert(onConflict = OnConflictStrategy.REPLACE)
    fun insert(note: Note)

    @Delete
    fun delete(note: Note)

    @Update
    fun update(note: Note)

}

1.4 创建数据库

         以下代码定义了用于保存数据库的 AppDatabase 类。 AppDatabase 定义数据库配置,并作为应用对持久性数据的主要访问点。

        数据库类必须满足以下条件:

  • 该类必须带有 @Database 注解,该注解包含列出所有与数据库关联的数据实体的 entities 数组;
  • 该类必须是一个抽象类,用于扩展 RoomDatabase;
  • 对于与数据库关联的每个 DAO 类,数据库类必须定义一个具有零参数的抽象方法,并返回 DAO 类的实例。
@Database(entities = [Note::class], version = 1)
abstract class AppDatabase : RoomDatabase() {
    abstract fun noteDao(): NoteDao
}

1.5 创建数据库帮助类

        定义数据实体、DAO 和数据库后,再定义一个数据库帮助类即可。

object RoomDBHelper {

    private const val DATABASE_NAME = "mo_notes_db"
    private var db: AppDatabase? = null

    fun init(appContext: Context) {
        if (db == null) {
            db = Room.databaseBuilder(appContext, AppDatabase::class.java, DATABASE_NAME).build()
        }
    }

    fun getInstance(): AppDatabase {
        if (db == null) init(Utils.getApp())
        return db!!
    }

}

:Utils.getApp()方法位于blankj大神奉献的com.blankj.utilcode.util.Utils工具类下 。

1.6 使用Room数据库

        使用 AppDatabase 中的抽象方法获取 DAO 的实例,转而可以使用 DAO 实例中的方法与数据库进行交互。

// 建议在Application的onCreate()方法中初始化数据库
    RoomDBHelper.init(this)


// 在Model层子线程访问数据库
    suspend fun insertAllNotes(notes: List<Note>) = withContext(Dispatchers.IO) {
        RoomDBHelper.getInstance().noteDao().insertAll(notes)
    }

    suspend fun getAllNotes(): List<Note> = withContext(Dispatchers.IO) {
        RoomDBHelper.getInstance().noteDao().getAll()
    }

2. 🔱 ContentProvider服务端

2.1 实现ContentProvider

        接下来咱们康康如何在服务端创建ContentProvider,代码如下:

/**
 * Description:    提供给其他进程使用,可对记录进行增删改查。
 * CreateDate:     2023/11/7 14:37
 * Author:         agg
 */
class NotesProvider : ContentProvider() {

    private val NOTE_TABLE_NAME = "Note"

    override fun onCreate(): Boolean {
        context?.let { RoomDBHelper.init(it) }
        return true
    }

    override fun insert(uri: Uri, values: ContentValues?): Uri? {
        val db = getWritableDatabase()
        var insertUri: Uri? = null
        try {
            db.beginTransaction()
            values?.let {
                it.put("id", UUID.randomUUID().toString())
                insertUri = ContentUris.withAppendedId(
                    uri, db.insert(NOTE_TABLE_NAME, SQLiteDatabase.CONFLICT_REPLACE, it)
                )
            }
            db.setTransactionSuccessful()
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            db.endTransaction()
        }
        return insertUri
    }

    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int {
        var numRemoved: Int = -1
        val db = getWritableDatabase()
        try {
            db.beginTransaction()
            numRemoved = db.delete(NOTE_TABLE_NAME, selection, selectionArgs)
            db.setTransactionSuccessful()
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            db.endTransaction()
        }
        return numRemoved
    }

    override fun update(
        uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?
    ): Int {
        var numUpdated: Int = -1
        val db = getWritableDatabase()
        try {
            db.beginTransaction()
            values?.let {
                numUpdated = db.update(
                    NOTE_TABLE_NAME, SQLiteDatabase.CONFLICT_NONE, it, selection, selectionArgs
                )
            }
            db.setTransactionSuccessful()
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            db.endTransaction()
        }
        return numUpdated
    }

    override fun query(
        uri: Uri,
        projection: Array<out String>?,
        selection: String?,
        selectionArgs: Array<out String>?,
        sortOrder: String?
    ): Cursor? {
        val db = getReadableDatabase()
        var cursor: Cursor? = null
        try {
            db.beginTransaction()
            cursor = db.query("select * from $NOTE_TABLE_NAME")
        } catch (e: Exception) {
            e.printStackTrace()
        } finally {
            db.endTransaction()
        }
        return cursor
    }

    override fun getType(uri: Uri): String? {
        return null
    }

    private fun getReadableDatabase(): SupportSQLiteDatabase {
        return RoomDBHelper.getInstance().openHelper.readableDatabase
    }

    private fun getWritableDatabase(): SupportSQLiteDatabase {
        return RoomDBHelper.getInstance().openHelper.writableDatabase
    }

}

注:本文核心在此。 

2.2 manifest声明ContentProvider

<provider
    android:name=".middleware.provider.NotesProvider"
    android:authorities="com.agg.monotes"
    android:exported="true"
    tools:ignore="ExportedContentProvider" />

3. 💠 ContentProvider客户端

3.1 创建数据实体

        客户端可不引入Room数据库,于是客户端可对Note表进行改造如下:

data class Note(
    val id: String = "", // 记录唯一标识,生成标准确认,uuid
    val type: Int = -1, // 记录类型:1:语音记录;2:照片记录
    val time: Long = -1, // 记录生成时间
    val timeUpdate: Long = -1, // 记录更新时间(暂未使用,此字段为后面业务进行预留)
    val content: String = "", // 语音流式/手指取词,识别到的文本内容
    val contentOrigin: String = "", // 照片识别到的整体文本内容(不显示给用户看,仅做ChatGPT上下文使用)
    val contentGpt: String = "", // ChatGPT洗稿后的文本内容
    val gptContext1: String = "", // ChatGPT的联想文本1
    val gptContext2: String = "", // ChatGPT的联想文本2
    val gptContext3: String = "", // ChatGPT的联想文本3
    val gptExplain: String = "", // ChatGPT的解释内容
    val gptSummary: String = "", // ChatGPT的总结文本
    val imgUrl: String = "", // 照片记录保存的本机图片路径(sdcard路径)
    val operate: Int = -1, // 记录操作:增加(1)、删除(2)、修改(3)
    val isSync: Boolean = false, // 是否同步:已同步(true)、未同步(false)
    val extra1: String = "", // 扩展字段1(暂未使用,此字段为后面业务进行预留)
    val extra2: String = "", // 扩展字段2(暂未使用,此字段为后面业务进行预留)
)

3.2 跨进程操作数据

        客户端创建ContentResolver,跨进程操作ContentProvider的数据。

import androidx.core.content.contentValuesOf

class MyResolver(context: Context) {

    private var mContentResolver: ContentResolver? = null
    private val AUTHORITES = "com.agg.monotes"

    init {
        mContentResolver = context.contentResolver
    }

    fun query(): ArrayList<Note> {
        val list = ArrayList<Note>()
        val cursor =
            mContentResolver?.query(Uri.parse("content://$AUTHORITES/Note"), null, null, null, null)
        while (cursor?.moveToNext() == true) {
            list.add(cursor2Note(cursor))
        }
        cursor?.close()
        return list
    }

    fun insert(note: Note) {
        mContentResolver?.insert(Uri.parse("content://$AUTHORITES/Note"), note2ContentValues(note))
    }

    private fun cursor2Note(cursor: Cursor): Note = Note(
        cursor.getString(0),
        cursor.getInt(1),
        cursor.getLong(2),
        cursor.getLong(3),
        cursor.getString(4),
        cursor.getString(5),
        cursor.getString(6),
        cursor.getString(7),
        cursor.getString(8),
        cursor.getString(9),
        cursor.getString(10),
        cursor.getString(11),
        cursor.getString(12),

        cursor.getInt(13),
        cursor.getInt(14) > 0,
        cursor.getString(15),
        cursor.getString(16),
    )

    private fun note2ContentValues(note: Note): ContentValues = contentValuesOf(
        "id" to note.id,
        "type" to note.type,
        "time" to note.time,
        "time_update" to note.timeUpdate,
        "content" to note.content,
        "content_origin" to note.contentOrigin,
        "content_gpt" to note.contentGpt,
        "gpt_context1" to note.gptContext1,
        "gpt_context2" to note.gptContext2,
        "gpt_context3" to note.gptContext3,
        "gpt_explain" to note.gptExplain,
        "gpt_summary" to note.gptSummary,
        "img_url" to note.imgUrl,
        "operate" to note.operate,
        "is_sync" to note.isSync,
        "extra1" to note.extra1,
        "extra2" to note.extra2,
    )

}

———————————————— 说完了分割线———————————————— 

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值