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

Android中使用AlarmManager时闹钟仅触发指定日期,忽略设定时间

问题分析与解决方案

核心问题原因

  1. 变量名拼写错误:代码中存在多处变量名不一致的情况,比如定义的daterPickState在后续使用时写成了datePickState,timePickerState写成了timePickState,导致无法正确获取用户选择的最新日期和时间值。
  2. 日期选择器初始值类型错误:rememberDatePickerState的initialSelectedDateMillis参数需要传入毫秒级时间戳,但你传入的是LocalDate.now(),这会导致初始状态异常,进而影响后续日期转换逻辑。
  3. 时间戳计算未实时更新:selectedDate和selectedDateTime的计算代码放在Composable顶层,只会在组件初始化时执行一次,不会随着用户修改日期/时间选择器的操作同步更新,导致AlarmManager始终使用初始的时间值。
  4. 全局状态变量的生命周期问题:直接在BroadcastReceiver中修改全局的text变量,不仅存在线程安全风险,还会在Activity重建时丢失状态。

修正后的代码实现

1. 新增ViewModel管理状态(替代全局变量)

import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.launch

class AlarmViewModel : ViewModel() {
    private val _alarmText = MutableStateFlow("")
    val alarmText: StateFlow<String> = _alarmText

    fun updateAlarmText(newText: String) {
        viewModelScope.launch {
            _alarmText.emit(newText)
        }
    }
}

2. 修正MainActivity的Composable代码

import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material3.Button
import androidx.compose.material3.DatePicker
import androidx.compose.material3.Text
import androidx.compose.material3.TimeInput
import androidx.compose.material3.rememberDatePickerState
import androidx.compose.material3.rememberTimePickerState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.lifecycle.viewmodel.compose.viewModel
import java.time.Instant
import java.time.LocalDate
import java.time.LocalDateTime
import java.time.LocalTime
import java.time.ZoneId
import android.app.AlarmManager
import android.app.PendingIntent
import android.content.Context
import android.content.Intent

@Composable
fun AlarmScreen() {
    val viewModel: AlarmViewModel = viewModel()
    val alarmText by viewModel.alarmText.collectAsState()

    // 修正:正确初始化日期选择器状态(传入毫秒时间戳)
    val datePickerState = rememberDatePickerState(
        initialSelectedDateMillis = LocalDate.now()
            .atStartOfDay(ZoneId.systemDefault())
            .toInstant()
            .toEpochMilli()
    )

    val timePickerState = rememberTimePickerState(
        initialHour = LocalTime.now().hour,
        initialMinute = LocalTime.now().minute,
        is24Hour = false
    )

    val alarmManager = remember {
        ContextCompat.getSystemService(
            LocalContext.current,
            AlarmManager::class.java
        ) as AlarmManager
    }

    // 实时计算用户选择的日期
    val selectedDate by remember {
        derivedStateOf {
            Instant.ofEpochMilli(datePickerState.selectedDateMillis!!)
                .atZone(ZoneId.systemDefault())
                .toLocalDate()
        }
    }

    // 实时计算合并后的日期时间戳,同时处理12小时制转24小时制
    val selectedDateTime by remember(selectedDate, timePickerState.hour, timePickerState.minute, timePickerState.period) {
        derivedStateOf {
            val hour24 = if (timePickerState.is24Hour) {
                timePickerState.hour
            } else {
                when (timePickerState.period) {
                    TimePickerPeriod.AM -> if (timePickerState.hour == 12) 0 else timePickerState.hour
                    TimePickerPeriod.PM -> if (timePickerState.hour == 12) 12 else timePickerState.hour + 12
                }
            }

            LocalDateTime.of(
                selectedDate.year,
                selectedDate.monthValue,
                selectedDate.dayOfMonth,
                hour24,
                timePickerState.minute
            ).atZone(ZoneId.systemDefault())
                .toInstant()
                .toEpochMilli()
        }
    }

    val alarmIntent = remember {
        Intent(LocalContext.current, AlarmReceiver::class.java)
    }

    val pendingIntent = remember {
        PendingIntent.getBroadcast(
            LocalContext.current,
            0,
            alarmIntent,
            PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE // 适配Android 12+
        )
    }

    Column(
        modifier = Modifier.fillMaxSize(),
        horizontalAlignment = Alignment.CenterHorizontally,
        verticalArrangement = Arrangement.Center
    ) {
        Text(text = alarmText)

        DatePicker(state = datePickerState)

        TimeInput(state = timePickerState)

        Button(
            onClick = {
                alarmManager.setExact(
                    AlarmManager.RTC_WAKEUP,
                    selectedDateTime,
                    pendingIntent
                )
            }
        ) {
            Text(text = "设置闹钟事件")
        }
    }
}

3. 修正BroadcastReceiver代码

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import androidx.localbroadcastmanager.content.LocalBroadcastManager

class AlarmReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        // 通过LocalBroadcast发送更新事件
        val updateIntent = Intent("ALARM_TRIGGERED")
        updateIntent.putExtra("TEXT", "生效了!")
        LocalBroadcastManager.getInstance(context).sendBroadcast(updateIntent)
    }
}

4. 在MainActivity中注册LocalBroadcast接收器

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.compose.ui.platform.ComposeView
import androidx.localbroadcastmanager.content.LocalBroadcastManager
import android.content.IntentFilter
import androidx.lifecycle.ViewModelProvider

class MainActivity : AppCompatActivity() {
    private lateinit var alarmUpdateReceiver: BroadcastReceiver

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val viewModel = ViewModelProvider(this)[AlarmViewModel::class.java]

        // 注册本地广播接收器,接收闹钟触发事件
        alarmUpdateReceiver = object : BroadcastReceiver() {
            override fun onReceive(context: Context?, intent: Intent?) {
                intent?.getStringExtra("TEXT")?.let {
                    viewModel.updateAlarmText(it)
                }
            }
        }

        LocalBroadcastManager.getInstance(this).registerReceiver(
            alarmUpdateReceiver,
            IntentFilter("ALARM_TRIGGERED")
        )

        setContentView(ComposeView(this).apply {
            setContent {
                AlarmScreen()
            }
        })
    }

    override fun onDestroy() {
        super.onDestroy()
        // 注销广播接收器,避免内存泄漏
        LocalBroadcastManager.getInstance(this).unregisterReceiver(alarmUpdateReceiver)
    }
}

额外说明

  • 12小时制转24小时制:rememberTimePickerState在12小时制下返回的hour是1-12,必须根据AM/PM转换为0-23的24小时制数值,否则会出现时间计算错误(比如下午3点被识别为3点而非15点)。
  • PendingIntent标志位:加入FLAG_IMMUTABLE适配Android 12及以上系统的权限要求,避免应用崩溃。
  • 状态管理:使用ViewModel+Flow管理UI状态,解决全局变量的生命周期问题,确保Activity重建时状态不会丢失。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.20 05:30:17