数据库与WorkManager:让后台任务像瑞士钟表一样精准
说实话,做Android开发这么多年,我最怕听到的一句话就是:「用户反馈App越来越卡,数据还丢了」。排查下来,十有八九是数据库维护没做好。今天我们就来聊聊如何用WorkManager这把瑞士军刀,把数据库的脏活累活安排得明明白白。
为什么需要WorkManager来管理数据库?
你想想看,数据库就像你的衣柜。平时往里塞数据很容易,但时间久了,旧衣服(过期数据)不清理,衣柜就炸了。手动清理?用户可没这个耐心。用Service?Android系统会杀后台。用JobScheduler?兼容性又是个坑。
WorkManager就是官方给出的标准答案。它能在各种Android版本上稳定运行,还能处理设备重启、省电模式等极端情况。我个人习惯把所有数据库维护任务都交给它,省心。
定期清理数据库:给数据做「断舍离」
我在项目中遇到过这样一个场景:聊天App的本地消息表,三个月就涨到了500MB。用户手机存储告急,差评如潮。解决方案就是定期清理。
核心思路:使用PeriodicWorkRequest设置定期任务,比如每天凌晨清理7天前的日志数据。
// 定义清理Worker
class CleanupWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
val db = AppDatabase.getInstance(applicationContext)
val cutoff = System.currentTimeMillis() - 7 * 24 * 60 * 60 * 1000L
db.messageDao().deleteOldMessages(cutoff)
Log.d("Cleanup", "已清理 $cutoff 之前的消息")
return Result.success()
}
}
// 创建定期任务
val cleanupRequest = PeriodicWorkRequestBuilder<CleanupWorker>(
24, TimeUnit.HOURS
).setConstraints(
Constraints.Builder()
.setRequiresBatteryNotLow(true) // 电量不低于低电量阈值
.build()
).build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"db_cleanup",
ExistingPeriodicWorkPolicy.KEEP,
cleanupRequest
)
避坑指南:我曾经把清理间隔设成1小时,结果用户手机电量哗哗掉。后来改成24小时+电量约束,再也没收到投诉。记住:数据库操作是IO密集型,频繁执行会加速电池消耗。
数据同步后台任务:让云端和本地「握手」
数据同步是个老生常谈的话题。用户离线时写本地,联网后同步到服务器。这里有个关键点:同步失败怎么办?重试策略怎么定?
WorkManager提供了指数退避重试,说白了就是第一次失败等10秒,第二次等20秒,第三次等40秒……直到成功或放弃。我建议最多重试3次,超过就标记为失败,等下次周期任务再处理。
class SyncWorker(context: Context, params: WorkerParameters) : Worker(context, params) {
override fun doWork(): Result {
return try {
val pendingSyncs = SyncDao.getPendingSyncs()
for (sync in pendingSyncs) {
val response = apiService.syncData(sync.data)
if (response.isSuccessful) {
SyncDao.markSynced(sync.id)
} else {
return Result.retry() // 触发重试
}
}
Result.success()
} catch (e: IOException) {
Result.retry() // 网络异常也重试
}
}
}
// 设置重试策略
val syncRequest = OneTimeWorkRequestBuilder<SyncWorker>()
.setBackoffCriteria(
BackoffPolicy.EXPONENTIAL,
10, TimeUnit.SECONDS
)
.setConstraints(
Constraints.Builder()
.setRequiredNetworkType(NetworkType.CONNECTED) // 必须有网络
.build()
).build()
注意:重试次数不是无限的。默认WorkManager会重试3次,之后标记为失败。你可以通过setBackoffCriteria调整,但别超过5次,否则用户会看到「同步失败」的弹窗,体验很差。
WorkManager链式任务:像流水线一样工作
有时候一个任务依赖另一个任务的结果。比如:先清理数据库,再同步数据,最后生成统计报告。这就是链式任务。
我个人特别喜欢这种模式,它把复杂的业务流程拆解成一个个小任务,每个任务只做一件事。出了问题也容易定位。
// 定义三个Worker
class CleanWorker : Worker() { /* 清理逻辑 */ }
class SyncWorker : Worker() { /* 同步逻辑 */ }
class ReportWorker : Worker() { /* 生成报告 */ }
// 构建链式任务
val chain = WorkManager.getInstance(context)
.beginWith(OneTimeWorkRequest.from(CleanWorker::class.java))
.then(OneTimeWorkRequest.from(SyncWorker::class.java))
.then(OneTimeWorkRequest.from(ReportWorker::class.java))
chain.enqueue()
你可能会问:如果中间某个任务失败了怎么办?默认情况下,链式任务会中断,后续任务不会执行。但你可以通过beginUniqueWork设置策略,比如KEEP保留已有链,或者REPLACE替换。
约束条件:别让App成为「电老虎」
数据库操作很耗电,尤其是大量写入时。WorkManager提供了丰富的约束条件,我总结了一张表:
| 约束条件 | 说明 | 适用场景 |
|---|---|---|
| NetworkType | 网络类型(WIFI/移动数据/不限) | 数据同步、大文件下载 |
| BatteryNotLow | 电量不低于低电量阈值 | 数据库清理、索引重建 |
| StorageNotLow | 存储空间充足 | 数据库备份、日志导出 |
| IdleRequired | 设备处于空闲状态 | 数据库VACUUM、碎片整理 |
我的经验:数据库VACUUM(整理碎片)一定要加IdleRequired约束。因为VACUUM会锁表,如果用户正在操作App,会导致界面卡顿。我曾经没加这个约束,结果用户反馈「App突然不动了」,排查半天才发现是VACUUM在搞鬼。
数据库维护自动化:打造「无人值守」的维护系统
把上面所有知识点整合起来,就是一个完整的自动化维护系统。我画了一张流程图,帮你理清思路:
这个流程看起来简单,但实际落地时要注意几个细节:
- 任务唯一性:使用
enqueueUniquePeriodicWork确保同一时间只有一个清理任务在运行,避免数据竞争。 - 日志记录:每个Worker执行前后都打日志,方便排查问题。我习惯用
Log.d记录开始和结束时间。 - 异常处理:数据库操作可能抛出
SQLiteException,一定要捕获并返回Result.failure(),否则WorkManager会认为任务还在运行,导致后续任务阻塞。
重要提醒:不要在Worker里持有Activity或Fragment的引用。Worker运行在后台线程,生命周期不受UI控制。如果你需要更新UI,用LiveData或Flow监听WorkInfo的状态变化。
实战中的那些坑
最后分享几个我踩过的坑,希望能帮你少走弯路:
- 数据库版本升级时,WorkManager任务会中断。 因为升级过程中数据库被锁定,Worker执行会失败。解决方案:在升级回调中暂停所有WorkManager任务,升级完成后再恢复。
- 不要用OneTimeWorkRequest做定期任务。 我见过有人用AlarmManager+OneTimeWorkRequest模拟定期任务,结果设备重启后任务丢失。直接用PeriodicWorkRequest,系统会帮你处理重启恢复。
- 约束条件不是万能的。 比如
BatteryNotLow在Android 8.0以下不支持。记得做版本判断,或者使用Constraints.Builder().apply { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) setRequiresBatteryNotLow(true) }。
好了,关于数据库与WorkManager的结合就聊到这里。这套方案我在多个项目中使用过,稳定运行超过两年,没出过问题。你完全可以放心使用。