ROOM 数据库 | 实现自定义 ContentProvider 插入数据
代码目标
- insert()/bulkInsert() 分别完成单条和多条插入。
- 数据转换/校验、并发控制、数据库事务、协程切换等操作严谨
- 插入后通知外界内容变化,便于观测或数据同步。
- 自动维护数据库容量,避免记录爆炸增长。
疑问
当Dao 定义 insert 添加了 suspend,那么直接在 ContentProvider insert 中直接调用这个方法的时候会报错。
//DataDao.kt@Insertsuspend fun insert(data: Data): Long//DataContentProvider.ktdatabase.runInTransaction<Long> {DataDao.insert(data)}
报错提示如下:
Suspension functions can be called only within coroutine body
The 'insertData' suspension point is inside a critical section
解决方案:
删除 suspend。
- 数据库事务/同步逻辑优先用数据库自身机制,不要人工加同步锁和suspend混用
DEMO
-
线程与协程:
- 使用
Dispatchers.IO
在后台线程执行数据库操作。 runBlocking
确保阻塞当前线程直到协程完成。
- 使用
-
事务处理:
runInTransaction
保证插入操作的原子性(要么全部成功,要么全部失败)。
-
数据一致性:
- 通过
synchronized(mLock)
防止多线程竞争。 - 插入后调用
notifyChange
通知其他组件数据已更新。
- 通过
-
性能优化:
- 批量插入减少数据库操作次数。
- 自动清理旧数据避免表过大。
//功能:处理单条记录的插入请求。override fun insert(uri: Uri, values: ContentValues?): Uri? {Log.d(TAG, "insert uri=$uri")values ?: throw IllegalArgumentException("ContentValues cannot be null")return when (sURLMatcher.match(uri)) {SINGLE_RECORD_URI_CODE, MULTIPLE_RECORD_URI_CODE -> {processInsert(uri, values)} //返回值:插入成功后返回新记录的URI。(因为processInsert的方法是返回URI)else -> throw IllegalArgumentException("Invalid URI, cannot insert with URI: $uri")}}//功能:执行具体的插入逻辑。private fun processInsert(uri: Uri, values: ContentValues?): Uri? {Log.d(TAG, "processInsert() -- uri=$uri")values ?: throw IllegalArgumentException("ContentValues cannot be null")//【数据转换】将 ContentValues 转换成自定义的 Data 对象val data = convertContentValuesToData(values)//【协程与线程】使用 runBlocking 启动协程,在 OI 线程中执行数据库操作val id = runBlocking {withContext(Dispatchers.IO) {synchronized(mLock) {//【事务处理】通过database.runInTransaction确保插入操作的原子性。database.runInTransaction<Long> {dataDao.insertData(data)}}}}// 插入失败(id < 0)应该返回null。val insertUri = ContentUris.withAppendedId(uri, id)//成功时构造新记录的URI,并通过ContentResolver通知数据变化。context?.contentResolver?.notifyChange(uri, null)return insertUri}//功能:将ContentValues转换为自定义的Data对象。//其中Constants.GET_FEATURE_FAIL_INT是预定义的常量private fun convertContentValuesToData(values: ContentValues): Data {return Data(netId= values.getAsLong(DataContract.DataColumns.netId)?: Constants.GET_FEATURE_FAIL_INT.toLong(),rat = values.getAsFloat(DataContract.DataColumns.rat)?: Constants.GET_FEATURE_FAIL_INT.toFloat(),ratType = values.getAsString(DataContract.DataColumns.ratType)?: Constants.GET_FEATURE_FAIL// ...其他字段类似处理)}// 批量插入 start//功能:批量插入多条记录。override fun bulkInsert(uri: Uri, values: Array<ContentValues>): Int {Log.d(TAG, "bulkInsert() -- uri=$uri")return when (sURLMatcher.match(uri)) {MULTIPLE_RECORD_URI_CODE -> performBulkInsert(uri, values)else -> throw IllegalArgumentException("Invalid URI, cannot bulk insert with URI: $uri")}}private fun performBulkInsert(uri: Uri, values: Array<ContentValues>): Int {Log.d(TAG, "performBulkInsert() -- uri=$uri")val dataList = values.map { convertContentValuesToData(it) }val sumInserted = runBlocking {withContext(Dispatchers.IO) {synchronized(mLock) {var insertedCount = 0database.runInTransaction {insertedCount =dataDao.insertRadioList(dataList).size}return@synchronized insertedCount}}}if (numInserted > 0) {context?.contentResolver?.notifyChange(DataContract.CONTENT_URI, null)} else {Log.d(TAG, "bulkInsert: Fail to insert values.")}return sumInserted}
相关介绍
ROOM 数据库接口和代码实现-CSDN博客