Android Room插入数据时获取自动生成的主键ID问题
解决Room插入后获取自增ID的正确姿势
1. DAO层正确定义
先把TaskItemDao的insert方法改成返回Long,单条插入返回单个自增ID,还要加上suspend关键字(Room操作不能跑在主线程):
@Dao interface TaskItemDao { @Insert suspend fun insertTaskItem(taskItem: TaskItem): Long }
2. Repository层透传返回值
Repository里直接调用DAO的方法,把返回的Long透传出去就行:
class TaskItemRepository(private val taskItemDao: TaskItemDao) { suspend fun insertTaskItem(taskItem: TaskItem): Long { return taskItemDao.insertTaskItem(taskItem) } }
3. ViewModel层协程里处理
ViewModel里必须用viewModelScope启动协程调用插入方法,拿到ID后再创建PendingIntent——别直接在主线程调用suspend方法,不然必崩:
class TaskItemViewModel(private val repository: TaskItemRepository) : ViewModel() { fun insertTaskAndSetAlarm(taskItem: TaskItem, context: Context) { viewModelScope.launch { val taskId = repository.insertTaskItem(taskItem) // 把Long转成Int当requestCode,只要你的闹钟条目数不超过Int最大值(21亿)就没问题 val pendingIntent = PendingIntent.getBroadcast( context, taskId.toInt(), Intent(context, AlarmReceiver::class.java), PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE ) // 调用AlarmManager设置闹钟 val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager alarmManager.setExactAndAllowWhileIdle( AlarmManager.RTC_WAKEUP, taskItem.alarmTime, pendingIntent ) } } }
4. 踩坑排查
- 崩溃提示主线程访问数据库:肯定是没在协程里调用insert,必须用
viewModelScope或者其他协程Scope包裹。 - 拿到的ID是-1:说明插入失败,检查TaskItem实体的主键定义,必须给id设默认值0,Room才会自动生成自增ID:
@Entity(tableName = "task_items") data class TaskItem( @PrimaryKey(autoGenerate = true) val id: Long = 0, // 默认值0是关键 val alarmTime: Long, // 其他字段 )
其他管理闹钟requestCode的方案
如果不想依赖Room的自增ID,或者担心ID范围问题,试试这几种:
- UUID生成唯一标识:生成UUID字符串,转成hashCode当requestCode,同时把UUID存在TaskItem里,删除时用这个hashCode找PendingIntent:
val uniqueId = UUID.randomUUID().toString() val requestCode = uniqueId.hashCode() // 把uniqueId存进TaskItem val taskItem = TaskItem(uniqueId = uniqueId, alarmTime = ...) // 删除闹钟时 val pendingIntent = PendingIntent.getBroadcast( context, taskItem.uniqueId.hashCode(), Intent(context, AlarmReceiver::class.java), PendingIntent.FLAG_NO_CREATE or PendingIntent.FLAG_IMMUTABLE ) pendingIntent?.cancel()
优点是完全唯一,几乎不会冲突;缺点是hashCode理论上有碰撞概率,但实际用起来基本遇不到。
- 时间戳+随机数:用当前毫秒时间戳加个随机数转成Int,存进TaskItem当requestCode:
val requestCode = (System.currentTimeMillis() + Random().nextInt(1000)).toInt() val taskItem = TaskItem(requestCode = requestCode, alarmTime = ...)
简单粗暴,重复概率极低,适合快速实现。
- 全局自增计数器:用SharedPreferences存一个计数器,每次创建闹钟时自增,把值当requestCode:
val sharedPref = context.getSharedPreferences("alarm_prefs", Context.MODE_PRIVATE) val requestCode = sharedPref.getInt("next_alarm_code", 0) sharedPref.edit().putInt("next_alarm_code", requestCode + 1).apply() val taskItem = TaskItem(requestCode = requestCode, alarmTime = ...)
ID连续不重复,就算应用重装,之前的闹钟也会被清掉,影响不大。
内容的提问来源于stack exchange,提问作者lapdoggo
相关产品推荐
相关产品推荐

