Kotlin开发习惯跟踪应用如何在每日午夜更新数据库与RecyclerView
解决方案
一、前置优化:补充DAO层必要方法
你现有DAO缺少批量插入、按日期查询、去重查习惯模板的能力,先修改HabitDao.kt:
@Dao interface HabitDao { @Query("SELECT * FROM habititem") suspend fun getAll(): List<HabitItem> // 按日期查询当日所有习惯 @Query("SELECT * FROM habititem WHERE date = :targetDate") suspend fun getByDate(targetDate: String): List<HabitItem> // 查询所有去重的习惯模板(名称+描述) @Query("SELECT DISTINCT name, description FROM habititem") suspend fun getAllHabitTemplates(): List<HabitTemplate> @Insert suspend fun insert(habitItem: HabitItem): Long // 批量插入新日期的习惯条目 @Insert suspend fun insertBatch(vararg habitItems: HabitItem) @Update suspend fun update(habitItem: HabitItem) @Delete suspend fun deleteItem(habitItem: HabitItem) } // 用于接收去重的习惯模板 data class HabitTemplate( val name: String, val description: String )
如果需要更严谨的逻辑,建议单独建一张HabitTemplate表存储用户创建的习惯(含是否启用状态),避免把用户已删除的历史习惯重新生成出来。
二、使用WorkManager实现午夜定时触发
这是Android官方推荐的后台定时任务方案,适配全系统版本,省电且稳定性优于AlarmManager。
2.1 引入依赖
在module级build.gradle.kts中添加WorkManager依赖:
dependencies { implementation("androidx.work:work-runtime-ktx:2.8.1") }
2.2 实现后台任务Worker
新建HabitDailyInsertWorker.kt,核心逻辑为生成新日期的未勾选习惯条目并插入数据库:
import android.content.Context import androidx.work.CoroutineWorker import androidx.work.WorkerParameters import java.text.SimpleDateFormat import java.util.* class HabitDailyInsertWorker( context: Context, params: WorkerParameters ) : CoroutineWorker(context, params) { private val dateFormat = SimpleDateFormat("yyyy-MM-dd", Locale.getDefault()) private val db = HabitListDatabase.getDatabase(context) override suspend fun doWork(): Result { return try { val today = dateFormat.format(Date()) // 先判断今日是否已经生成过条目,避免重复插入 val existingItems = db.habitItemDao().getByDate(today) if (existingItems.isNotEmpty()) return Result.success() // 获取所有习惯模板,生成今日的未勾选条目 val templates = db.habitItemDao().getAllHabitTemplates() val newItems = templates.map { template -> HabitItem( name = template.name, description = template.description, date = today, isDone = false ) } db.habitItemDao().insertBatch(*newItems.toTypedArray()) Result.success() } catch (e: Exception) { Result.retry() } } }
2.3 注册周期性定时任务
新建自定义Application类,在APP启动时注册定时任务:
import android.app.Application import androidx.work.PeriodicWorkRequestBuilder import androidx.work.WorkManager import java.util.Calendar import java.util.concurrent.TimeUnit class HabitApp : Application() { override fun onCreate() { super.onCreate() setupDailyWorker() } private fun setupDailyWorker() { // 计算到下一个午夜的延迟时间 val currentTime = Calendar.getInstance() val nextMidnight = Calendar.getInstance().apply { set(Calendar.HOUR_OF_DAY, 0) set(Calendar.MINUTE, 0) set(Calendar.SECOND, 0) add(Calendar.DAY_OF_MONTH, 1) } val initialDelay = nextMidnight.timeInMillis - currentTime.timeInMillis // 构建周期为1天的任务 val dailyWorkRequest = PeriodicWorkRequestBuilder<HabitDailyInsertWorker>( repeatInterval = 1, repeatIntervalTimeUnit = TimeUnit.DAYS, flexTimeInterval = 15, flexTimeIntervalUnit = TimeUnit.MINUTES ).setInitialDelay(initialDelay, TimeUnit.MILLISECONDS) .build() // 注册任务,避免重复注册 WorkManager.getInstance(this) .enqueueUniquePeriodicWork( "HabitDailyInsert", androidx.work.ExistingPeriodicWorkPolicy.KEEP, dailyWorkRequest ) } }
在AndroidManifest.xml的application标签中添加android:name=".HabitApp"注册自定义Application。
2.4 添加必要权限
在AndroidManifest.xml中添加权限,保证设备重启后定时任务依然生效:
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" /> <uses-permission android:name="android.permission.POST_NOTIFICATIONS" /> <!-- Android13+需要 -->
三、边缘场景补充
如果用户多日未打开APP,定时任务不会触发,建议在每日视图页面的onCreate/onResume生命周期中,先检查当前日期是否有对应的习惯条目,如果没有则直接调用一次和Worker相同的插入逻辑,避免数据缺失。
内容的提问来源于stack exchange,提问作者csl15
相关产品推荐
相关产品推荐

