You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何实现每日凌晨3点自动更新所有习惯为未完成状态?

实现每日凌晨3点自动重置习惯状态的方案

问题背景

我已经有一个ViewModel层的函数用于将所有习惯更新为未完成状态:

fun updateAllHabitsAsNotCompleted() {
    viewModelScope.launch {
        updateAllHabitsAsNotCompletedUseCase()
    }
}

原本我在Fragment里用内部类BroadcastReceiver结合AlarmManager实现定时任务,但现在想改成独立的Receiver类+Manifest注册的方式,实现每日凌晨3点自动执行这个重置操作。当前的内部类实现如下:

@AndroidEntryPoint
class HabitsFragment : BaseFragment<FragmentHabitBinding>() {
    // ...其他代码

    inner class HabitAlertReceiver : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
            viewModel.updateAllHabitsAsNotCompleted()
        }
    }

    private fun updateHabitsAsNotCompletedEveryDay() {
        val alarmManager = requireContext().getSystemService(Context.ALARM_SERVICE) as AlarmManager
        val intent = Intent(requireContext(), HabitAlertReceiver::class.java)
        val pendingIntent = PendingIntent.getBroadcast(
            requireContext(),
            0,
            intent,
            PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
        )
        val calendar = Calendar.getInstance().apply {
            timeInMillis = System.currentTimeMillis()
            set(Calendar.HOUR_OF_DAY, 3)
            set(Calendar.MINUTE, 0)
            set(Calendar.SECOND, 0)
        }
        alarmManager.setRepeating(
            AlarmManager.RTC_WAKEUP,
            calendar.timeInMillis,
            AlarmManager.INTERVAL_DAY,
            pendingIntent
        )
    }
}

需要改成独立Receiver的实现方式,完成每日凌晨3点的自动重置。

解决方案

1. 创建独立的BroadcastReceiver类

由于要在Manifest中注册,Receiver不能是Fragment的内部类。推荐直接注入UseCase执行操作(避免ViewModel生命周期问题):

@AndroidEntryPoint
class HabitResetReceiver : BroadcastReceiver() {

    @Inject
    lateinit var updateAllHabitsAsNotCompletedUseCase: UpdateAllHabitsAsNotCompletedUseCase

    override fun onReceive(context: Context?, intent: Intent?) {
        context ?: return
        // 用IO协程作用域执行数据更新操作
        CoroutineScope(Dispatchers.IO).launch {
            updateAllHabitsAsNotCompletedUseCase()
            // 重置完成后设置下一次闹钟
            setupNextDayAlarm(context)
        }
    }

    private fun setupNextDayAlarm(context: Context) {
        val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
        val intent = Intent(context, HabitResetReceiver::class.java)
        val pendingIntent = PendingIntent.getBroadcast(
            context,
            1001, // 用唯一requestCode区分其他PendingIntent
            intent,
            PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
        )

        val calendar = Calendar.getInstance().apply {
            timeInMillis = System.currentTimeMillis()
            set(Calendar.HOUR_OF_DAY, 3)
            set(Calendar.MINUTE, 0)
            set(Calendar.SECOND, 0)
            set(Calendar.MILLISECOND, 0)
            add(Calendar.DAY_OF_MONTH, 1) // 直接设置为次日凌晨3点
        }

        alarmManager.setExactAndAllowWhileIdle(
            AlarmManager.RTC_WAKEUP,
            calendar.timeInMillis,
            pendingIntent
        )
    }
}

2. 在AndroidManifest.xml中注册Receiver

在<application>标签内添加Receiver注册,确保仅本应用可调用:

<receiver
    android:name=".receivers.HabitResetReceiver"
    android:exported="false">
</receiver>

3. 修改Fragment中的闹钟初始化逻辑

替换Receiver类,并修复"当前时间已过凌晨3点则当天不触发"的问题:

@AndroidEntryPoint
class HabitsFragment : BaseFragment<FragmentHabitBinding>() {
    // ...其他代码

    private fun setupDailyHabitResetAlarm() {
        val alarmManager = requireContext().getSystemService(Context.ALARM_SERVICE) as AlarmManager
        val intent = Intent(requireContext(), HabitResetReceiver::class.java)
        val pendingIntent = PendingIntent.getBroadcast(
            requireContext(),
            1001,
            intent,
            PendingIntent.FLAG_IMMUTABLE or PendingIntent.FLAG_UPDATE_CURRENT
        )

        val calendar = Calendar.getInstance().apply {
            timeInMillis = System.currentTimeMillis()
            set(Calendar.HOUR_OF_DAY, 3)
            set(Calendar.MINUTE, 0)
            set(Calendar.SECOND, 0)
            set(Calendar.MILLISECOND, 0)
        }

        // 如果当前时间已过凌晨3点,自动设置为次日的3点
        if (calendar.timeInMillis < System.currentTimeMillis()) {
            calendar.add(Calendar.DAY_OF_MONTH, 1)
        }

        // 使用setExactAndAllowWhileIdle替代setRepeating,提升Doze模式下的触发可靠性
        alarmManager.setExactAndAllowWhileIdle(
            AlarmManager.RTC_WAKEUP,
            calendar.timeInMillis,
            pendingIntent
        )
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        // ...其他初始化操作
        setupDailyHabitResetAlarm()
    }
}

关键说明

  • 放弃setRepeating改用setExactAndAllowWhileIdle:Android系统在Doze模式下会限制重复闹钟的触发,精确闹钟+手动重置下一次任务的方式更可靠
  • 独立Receiver直接调用UseCase:避免依赖Fragment/Activity的ViewModel生命周期,确保后台也能正常执行数据更新
  • 唯一requestCode:避免与其他PendingIntent冲突,确保闹钟能被正确更新或取消

内容的提问来源于stack exchange,提问作者Coder

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.14 16:45:23