3. 数据模型设计:队列任务实体类定义
数据模型是持久化队列的基石。Room 实体类直接映射为 SQLite 表结构,字段设计是否合理决定了队列的吞吐能力、重试可靠性与查询效率。本章从主键策略、状态枚举、重试计数、时间戳四大核心维度出发,结合 Room 注解体系,逐步构造出一个可直接投入生产的队列任务实体类。
3.1 实体类设计思路
队列任务实体类需要同时满足三方面约束:
- 存储完备性——每一条任务记录必须具备全局唯一标识、当前状态、重试上下文和生命周期时间线。
- 查询效率——状态字段需支持索引,时间戳字段需支持范围排序,以便 WorkManager 快速拉取待执行任务。
- 类型安全——状态值使用枚举而非整数常量,编译期即可杜绝非法状态赋值。
基于以上约束,实体类字段划分为四个逻辑组:主键组、状态组、重试组、时间戳组。Room 通过注解将 Kotlin 类与 SQLite 列进行声明式绑定,无需手写建表语句。
3.2 核心字段设计
3.2.1 主键字段
使用 Long 类型的自增主键 id,由 Room 自动生成唯一值。自增主键在 SQLite 内部以 INTEGER PRIMARY KEY AUTOINCREMENT 实现,写入性能稳定且不会因删除记录而重用主键。
@PrimaryKey(autoGenerate = true)
val id: Long = 0
0,Room 在插入时会忽略该值并自动分配。若声明为 null 则必须使用可空类型 Long?,推荐非零默认值方案。3.2.2 状态字段
状态字段使用枚举 TaskStatus 定义,包含任务生命周期中的所有合法阶段。Room 默认不支持枚举存储,必须配合 @TypeConverters 将枚举序列化为字符串。
enum class TaskStatus {
PENDING, // 等待执行
RUNNING, // 正在执行
SUCCESS, // 执行成功
FAILED, // 执行失败(可重试)
CANCELLED // 已被取消
}
@ColumnInfo(name = "status")
var status: TaskStatus = TaskStatus.PENDING
默认值为 PENDING,新插入的任务自动进入待执行队列。字段上建立索引以加速按状态筛选的查询:
@Entity(
tableName = "queue_task",
indices = [Index(value = ["status"], name = "idx_status")]
)
3.2.3 重试次数
重试机制需要两个字段配合:retryCount 记录当前已重试次数,maxRetries 记录最大允许重试次数。二者均为 Int 类型,不可为 null。
@ColumnInfo(name = "retry_count", defaultValue = "0")
var retryCount: Int = 0
@ColumnInfo(name = "max_retries", defaultValue = "3")
var maxRetries: Int = 3
maxRetries 的默认值设为 3,符合大多数业务场景的"最多重试三次"惯例。特殊场景可在插入任务时按需覆盖。3.2.4 时间戳
时间戳使用 Long 类型存储 Unix 毫秒值,避免 Date 或 LocalDateTime 带来的序列化开销与时区歧义。创建时由应用层写入 System.currentTimeMillis(),更新时在 DAO 中通过 SQL 表达式赋值。
@ColumnInfo(name = "created_at")
val createdAt: Long = System.currentTimeMillis()
@ColumnInfo(name = "updated_at")
var updatedAt: Long = System.currentTimeMillis()
3.3 Room 注解速查
下表列出队列任务实体类中使用的所有 Room 注解及其作用:
| 注解 | 作用 | 常用参数 | 在本章中的使用位置 |
|---|---|---|---|
@Entity | 声明该类为 Room 实体 | tableName, indices, foreignKeys | 类级别,绑定表名与索引 |
@PrimaryKey | 标记主键字段 | autoGenerate | id 字段,自动增长 |
@ColumnInfo | 自定义列名与属性 | name, defaultValue, typeAffinity | 所有字段,映射列名与默认值 |
@NonNull | 标记字段不可为 null | 无 | 用于 retryCount、maxRetries 等 |
@TypeConverters | 注册自定义类型转换器 | value(转换器类) | 数据库类级别,处理枚举序列化 |
3.4 完整实体类实现
将上述字段与注解组合为完整的 Room 实体类:
import androidx.room.ColumnInfo
import androidx.room.Entity
import androidx.room.Index
import androidx.room.PrimaryKey
@Entity(
tableName = "queue_task",
indices = [
Index(value = ["status"], name = "idx_queue_task_status"),
Index(value = ["created_at"], name = "idx_queue_task_created_at")
]
)
data class QueueTaskEntity(
@PrimaryKey(autoGenerate = true)
@ColumnInfo(name = "id")
val id: Long = 0,
@ColumnInfo(name = "payload")
val payload: String = "",
@ColumnInfo(name = "priority", defaultValue = "0")
val priority: Int = 0,
@ColumnInfo(name = "status")
var status: TaskStatus = TaskStatus.PENDING,
@ColumnInfo(name = "retry_count", defaultValue = "0")
var retryCount: Int = 0,
@ColumnInfo(name = "max_retries", defaultValue = "3")
var maxRetries: Int = 3,
@ColumnInfo(name = "error_message")
var errorMessage: String? = null,
@ColumnInfo(name = "created_at")
val createdAt: Long = System.currentTimeMillis(),
@ColumnInfo(name = "updated_at")
var updatedAt: Long = System.currentTimeMillis(),
@ColumnInfo(name = "scheduled_at")
var scheduledAt: Long? = null
)
scheduledAt 字段用于延迟执行场景,声明为可空类型 Long?。若为 null 表示立即执行,Room 在数据库中存储为 NULL,使用时需做空安全处理。3.5 类型转换器配置
Room 无法直接存储 Kotlin 枚举,必须编写 TypeConverter 将 TaskStatus 与 String 互转:
import androidx.room.TypeConverter
class TaskStatusConverter {
@TypeConverter
fun fromTaskStatus(status: TaskStatus): String {
return status.name
}
@TypeConverter
fun toTaskStatus(value: String): TaskStatus {
return TaskStatus.valueOf(value)
}
}
然后在 Room 数据库类上通过 @TypeConverters 注册:
@Database(
entities = [QueueTaskEntity::class],
version = 1,
exportSchema = true
)
@TypeConverters(TaskStatusConverter::class)
abstract class QueueDatabase : RoomDatabase() {
abstract fun queueTaskDao(): QueueTaskDao
}
name,否则会导致数据库中已存在的记录反序列化失败。若需展示不同文案,应使用枚举的额外属性,而非修改 name。