在 Observer 内部使用 AlarmManager 会导致 observe() 只被调用一次
Using AlarmManager inside Observer causes observe() to be called only once
我已经为一个带有 ToggleButton 的闹钟应用程序实现了 MVVM 架构,它在打开时激活闹钟,在关闭时停用闹钟。单击 ToggleButton 会调用 ViewModel 中的一个函数,该函数根据 ToggleButton 的切换状态,通过将值设置为带有警报信息的 LiveData 来激活或停用警报。
在 Fragment 中,我从 ViewModel 观察 LiveData 并通过使用 AlarmManager 调度 PendingIntent 来设置警报,或者通过取消 PendingIntent 和 AlarmManager 来取消警报。问题是,当我第一次启动应用程序时,第一次单击 ToggleButton 会触发 Fragment 中的 Observer,但之后即使 LiveData 值发生变化,Observer 也拒绝触发。
我发现删除与 AlarmManager 相关的函数 (setExactAndAllowWhileIdle() / cancel()) 会使 Observer 每次都被触发,但是添加这些函数会使 Observer 不再对任何 LiveData 更改做出反应.
我认为用代码和日志更好地解释。
AlarmViewModel.kt
private val newToast: MutableLiveData<SingleEvent<String>> = MutableLiveData()
private val activateEvent: MutableLiveData<SingleEvent<AlarmData>> = MutableLiveData()
private val deactivateEvent: MutableLiveData<SingleEvent<AlarmData>> = MutableLiveData()
...
private fun activateAlarm(alarmData: AlarmData) {
newToast.value = SingleEvent("Alarm has been set!")
activateEvent.value = SingleEvent(alarmData)
}
private fun deactivateAlarm(alarmData: AlarmData) {
newToast.value = SingleEvent("Alarm has been cleared")
deactivateEvent.value = SingleEvent(alarmData)
}
...
fun observeNewToast(): LiveData<SingleEvent<String>> = newToast
fun observeActivateEvent(): LiveData<SingleEvent<AlarmData>> = activateEvent
fun observeDeactivateEvent(): LiveData<SingleEvent<AlarmData>> = deactivateEvent
AlarmFragment.kt
@Inject
lateinit var alarmManager: AlarmManager
private val viewModel: SingleAlarmViewModel by lazy {
ViewModelProviders.of(activity!!).get(AlarmViewModel::class.java)
}
private lateinit var newToastObserver: Observer<SingleEvent<String>>
private lateinit var activateEventObserver: Observer<SingleEvent<AlarmData>>
private lateinit var deactivateEventObserver: Observer<SingleEvent<AlarmData>>
...
override fun onResume() {
super.onResume()
newToastObserver = Observer {
it?.getContentIfNotHandled()?.let { toastText ->
info("observeNewToast() -> message: $toastText")
Toast.makeText(activity, toastText, Toast.LENGTH_SHORT).show()
}
}
activateEventObserver = Observer {
it?.getContentIfNotHandled()?.let { alarmData ->
info("activateEventObserver -> $alarmData")
val idInteger = alarmData.timeInMillis.toInt()
val alarmTime = alarmData.timeInMillis
val activateAlarmIntent = Intent(activity, AlarmReceiver::class.java)
activateAlarmIntent.putExtra("message", alarmData.toString())
val pendingIntent = PendingIntent.getBroadcast(
activity,
idInteger,
activateAlarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
AlarmManagerCompat.setExactAndAllowWhileIdle(
alarmManager,
AlarmManager.RTC_WAKEUP,
alarmTime,
pendingIntent
)
}
}
deactivateEventObserver = Observer {
it?.getContentIfNotHandled()?.let { alarmData ->
info("deactivateEventObserver -> $alarmData")
val idInteger = alarmData.timeInMillis.toInt()
val deactivateAlarmIntent = Intent(activity, AlarmReceiver::class.java)
deactivateAlarmIntent.putExtra("message", alarmData.toString())
val pendingIntent = PendingIntent.getBroadcast(
activity,
idInteger,
deactivateAlarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
pendingIntent.cancel()
alarmManager.cancel(pendingIntent)
}
}
viewModel.observeNewToast().observe(activity!!, newToastObserver)
viewModel.observeActivateEvent().observe(activity!!, activateEventObserver)
viewModel.observeDeactivateEvent().observe(activity!!, deactivateEventObserver)
}
override fun onPause() {
super.onPause()
viewModel.apply {
observeNewToast().removeObserver(newToastObserver)
observeActivateEvent().removeObserver(activateEventObserver)
observeDeactivateEvent().removeObserver(deactivateEventObserver)
}
}
像这样启动应用程序,activateEventObserver 和 deactivateEventObserver 仅被观察一次,然后停止被观察,如下面的日志所示。
02-20 18:38:23.707 23671-23671/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been cleared
02-20 18:38:23.722 23671-23671/com.aly.alarm I/AlarmFragment: deactivateEventObserver -> AlarmData(id=1, timeInMillis=1550730780000, isToggledOn=true)
02-20 18:38:28.547 23671-23671/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been set!
02-20 18:38:28.557 23671-23671/com.aly.alarm I/AlarmFragment: activateEventObserver -> AlarmData(id=1, timeInMillis=1550730780000, isToggledOn=true)
02-20 18:38:34.647 23671-23671/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been cleared
02-20 18:38:35.357 23671-23671/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been set!
02-20 18:38:37.472 23671-23671/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been cleared
02-20 18:38:38.112 23671-23671/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been set!
02-20 18:38:38.962 23671-23671/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been cleared
02-20 18:38:39.377 23671-23671/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been set!
但是如果我删除 AlarmManager 相关代码,就会像预期的那样一直调用 Observers。
已修改 AlarmFragment.kt
@Inject
lateinit var alarmManager: AlarmManager
private val viewModel: SingleAlarmViewModel by lazy {
ViewModelProviders.of(activity!!).get(AlarmViewModel::class.java)
}
private lateinit var newToastObserver: Observer<SingleEvent<String>>
private lateinit var activateEventObserver: Observer<SingleEvent<AlarmData>>
private lateinit var deactivateEventObserver: Observer<SingleEvent<AlarmData>>
...
override fun onResume() {
super.onResume()
newToastObserver = Observer {
it?.getContentIfNotHandled()?.let { toastText ->
info("observeNewToast() -> message: $toastText")
Toast.makeText(activity, toastText, Toast.LENGTH_SHORT).show()
}
}
activateEventObserver = Observer {
it?.getContentIfNotHandled()?.let { alarmData ->
info("activateEventObserver -> $alarmData")
val idInteger = alarmData.timeInMillis.toInt()
val alarmTime = alarmData.timeInMillis
val activateAlarmIntent = Intent(activity, AlarmReceiver::class.java)
activateAlarmIntent.putExtra("message", alarmData.toString())
val pendingIntent = PendingIntent.getBroadcast(
activity,
idInteger,
activateAlarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
// AlarmManager code commented out
// AlarmManagerCompat.setExactAndAllowWhileIdle(
// alarmManager,
// AlarmManager.RTC_WAKEUP,
// alarmTime,
// pendingIntent
// )
}
}
deactivateEventObserver = Observer {
it?.getContentIfNotHandled()?.let { alarmData ->
info("deactivateEventObserver -> $alarmData")
val idInteger = alarmData.timeInMillis.toInt()
val deactivateAlarmIntent = Intent(activity, AlarmReceiver::class.java)
deactivateAlarmIntent.putExtra("message", alarmData.toString())
val pendingIntent = PendingIntent.getBroadcast(
activity,
idInteger,
deactivateAlarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
pendingIntent.cancel()
// AlarmManager code commented out
// alarmManager.cancel(pendingIntent)
}
}
viewModel.observeNewToast().observe(activity!!, newToastObserver)
viewModel.observeActivateEvent().observe(activity!!, activateEventObserver)
viewModel.observeDeactivateEvent().observe(activity!!, deactivateEventObserver)
}
override fun onPause() {
super.onPause()
viewModel.apply {
observeNewToast().removeObserver(newToastObserver)
observeActivateEvent().removeObserver(activateEventObserver)
observeDeactivateEvent().removeObserver(deactivateEventObserver)
}
}
像这样启动应用程序时的日志(观察者按预期观察):
02-20 18:44:18.207 24565-24565/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been cleared
02-20 18:44:18.222 24565-24565/com.aly.alarm I/AlarmFragment: deactivateEventObserver -> AlarmData(id=1, timeInMillis=1550730780000, isToggledOn=true)
02-20 18:44:18.367 24565-24565/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been set!
02-20 18:44:18.382 24565-24565/com.aly.alarm I/AlarmFragment: activateEventObserver -> AlarmData(id=1, timeInMillis=1550730780000, isToggledOn=true)
02-20 18:44:21.277 24565-24565/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been cleared
02-20 18:44:21.297 24565-24565/com.aly.alarm I/AlarmFragment: deactivateEventObserver -> AlarmData(id=1, timeInMillis=1550730780000, isToggledOn=true)
02-20 18:44:22.177 24565-24565/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been set!
02-20 18:44:22.197 24565-24565/com.aly.alarm I/AlarmFragment: activateEventObserver -> AlarmData(id=1, timeInMillis=1550730780000, isToggledOn=true)
02-20 18:44:22.842 24565-24565/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been cleared
02-20 18:44:22.857 24565-24565/com.aly.alarm I/AlarmFragment: deactivateEventObserver -> AlarmData(id=1, timeInMillis=1550730780000, isToggledOn=true)
02-20 18:44:23.447 24565-24565/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been set!
我试过将 ViewModel 生命周期所有者设置为父级 Activity,然后将其设置回 Fragment,但这并没有什么不同。删除与 AlarmManger 相关的代码使观察者工作,但由于我试图通过观察者设置警报,这让我很沮丧。为什么 AlarmManager 使 Observer 在第一次后停止被观察?它是否在某处未注册?
提前致谢。
事实证明这是我犯的一个完全不同的错误。我错误地忘记将 AlarmManager 注入到我本应通过 Dagger 注入的特定 Fragment 中。
感谢您的贡献!
我已经为一个带有 ToggleButton 的闹钟应用程序实现了 MVVM 架构,它在打开时激活闹钟,在关闭时停用闹钟。单击 ToggleButton 会调用 ViewModel 中的一个函数,该函数根据 ToggleButton 的切换状态,通过将值设置为带有警报信息的 LiveData 来激活或停用警报。
在 Fragment 中,我从 ViewModel 观察 LiveData 并通过使用 AlarmManager 调度 PendingIntent 来设置警报,或者通过取消 PendingIntent 和 AlarmManager 来取消警报。问题是,当我第一次启动应用程序时,第一次单击 ToggleButton 会触发 Fragment 中的 Observer,但之后即使 LiveData 值发生变化,Observer 也拒绝触发。
我发现删除与 AlarmManager 相关的函数 (setExactAndAllowWhileIdle() / cancel()) 会使 Observer 每次都被触发,但是添加这些函数会使 Observer 不再对任何 LiveData 更改做出反应.
我认为用代码和日志更好地解释。
AlarmViewModel.kt
private val newToast: MutableLiveData<SingleEvent<String>> = MutableLiveData()
private val activateEvent: MutableLiveData<SingleEvent<AlarmData>> = MutableLiveData()
private val deactivateEvent: MutableLiveData<SingleEvent<AlarmData>> = MutableLiveData()
...
private fun activateAlarm(alarmData: AlarmData) {
newToast.value = SingleEvent("Alarm has been set!")
activateEvent.value = SingleEvent(alarmData)
}
private fun deactivateAlarm(alarmData: AlarmData) {
newToast.value = SingleEvent("Alarm has been cleared")
deactivateEvent.value = SingleEvent(alarmData)
}
...
fun observeNewToast(): LiveData<SingleEvent<String>> = newToast
fun observeActivateEvent(): LiveData<SingleEvent<AlarmData>> = activateEvent
fun observeDeactivateEvent(): LiveData<SingleEvent<AlarmData>> = deactivateEvent
AlarmFragment.kt
@Inject
lateinit var alarmManager: AlarmManager
private val viewModel: SingleAlarmViewModel by lazy {
ViewModelProviders.of(activity!!).get(AlarmViewModel::class.java)
}
private lateinit var newToastObserver: Observer<SingleEvent<String>>
private lateinit var activateEventObserver: Observer<SingleEvent<AlarmData>>
private lateinit var deactivateEventObserver: Observer<SingleEvent<AlarmData>>
...
override fun onResume() {
super.onResume()
newToastObserver = Observer {
it?.getContentIfNotHandled()?.let { toastText ->
info("observeNewToast() -> message: $toastText")
Toast.makeText(activity, toastText, Toast.LENGTH_SHORT).show()
}
}
activateEventObserver = Observer {
it?.getContentIfNotHandled()?.let { alarmData ->
info("activateEventObserver -> $alarmData")
val idInteger = alarmData.timeInMillis.toInt()
val alarmTime = alarmData.timeInMillis
val activateAlarmIntent = Intent(activity, AlarmReceiver::class.java)
activateAlarmIntent.putExtra("message", alarmData.toString())
val pendingIntent = PendingIntent.getBroadcast(
activity,
idInteger,
activateAlarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
AlarmManagerCompat.setExactAndAllowWhileIdle(
alarmManager,
AlarmManager.RTC_WAKEUP,
alarmTime,
pendingIntent
)
}
}
deactivateEventObserver = Observer {
it?.getContentIfNotHandled()?.let { alarmData ->
info("deactivateEventObserver -> $alarmData")
val idInteger = alarmData.timeInMillis.toInt()
val deactivateAlarmIntent = Intent(activity, AlarmReceiver::class.java)
deactivateAlarmIntent.putExtra("message", alarmData.toString())
val pendingIntent = PendingIntent.getBroadcast(
activity,
idInteger,
deactivateAlarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
pendingIntent.cancel()
alarmManager.cancel(pendingIntent)
}
}
viewModel.observeNewToast().observe(activity!!, newToastObserver)
viewModel.observeActivateEvent().observe(activity!!, activateEventObserver)
viewModel.observeDeactivateEvent().observe(activity!!, deactivateEventObserver)
}
override fun onPause() {
super.onPause()
viewModel.apply {
observeNewToast().removeObserver(newToastObserver)
observeActivateEvent().removeObserver(activateEventObserver)
observeDeactivateEvent().removeObserver(deactivateEventObserver)
}
}
像这样启动应用程序,activateEventObserver 和 deactivateEventObserver 仅被观察一次,然后停止被观察,如下面的日志所示。
02-20 18:38:23.707 23671-23671/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been cleared
02-20 18:38:23.722 23671-23671/com.aly.alarm I/AlarmFragment: deactivateEventObserver -> AlarmData(id=1, timeInMillis=1550730780000, isToggledOn=true)
02-20 18:38:28.547 23671-23671/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been set!
02-20 18:38:28.557 23671-23671/com.aly.alarm I/AlarmFragment: activateEventObserver -> AlarmData(id=1, timeInMillis=1550730780000, isToggledOn=true)
02-20 18:38:34.647 23671-23671/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been cleared
02-20 18:38:35.357 23671-23671/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been set!
02-20 18:38:37.472 23671-23671/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been cleared
02-20 18:38:38.112 23671-23671/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been set!
02-20 18:38:38.962 23671-23671/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been cleared
02-20 18:38:39.377 23671-23671/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been set!
但是如果我删除 AlarmManager 相关代码,就会像预期的那样一直调用 Observers。
已修改 AlarmFragment.kt
@Inject
lateinit var alarmManager: AlarmManager
private val viewModel: SingleAlarmViewModel by lazy {
ViewModelProviders.of(activity!!).get(AlarmViewModel::class.java)
}
private lateinit var newToastObserver: Observer<SingleEvent<String>>
private lateinit var activateEventObserver: Observer<SingleEvent<AlarmData>>
private lateinit var deactivateEventObserver: Observer<SingleEvent<AlarmData>>
...
override fun onResume() {
super.onResume()
newToastObserver = Observer {
it?.getContentIfNotHandled()?.let { toastText ->
info("observeNewToast() -> message: $toastText")
Toast.makeText(activity, toastText, Toast.LENGTH_SHORT).show()
}
}
activateEventObserver = Observer {
it?.getContentIfNotHandled()?.let { alarmData ->
info("activateEventObserver -> $alarmData")
val idInteger = alarmData.timeInMillis.toInt()
val alarmTime = alarmData.timeInMillis
val activateAlarmIntent = Intent(activity, AlarmReceiver::class.java)
activateAlarmIntent.putExtra("message", alarmData.toString())
val pendingIntent = PendingIntent.getBroadcast(
activity,
idInteger,
activateAlarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
// AlarmManager code commented out
// AlarmManagerCompat.setExactAndAllowWhileIdle(
// alarmManager,
// AlarmManager.RTC_WAKEUP,
// alarmTime,
// pendingIntent
// )
}
}
deactivateEventObserver = Observer {
it?.getContentIfNotHandled()?.let { alarmData ->
info("deactivateEventObserver -> $alarmData")
val idInteger = alarmData.timeInMillis.toInt()
val deactivateAlarmIntent = Intent(activity, AlarmReceiver::class.java)
deactivateAlarmIntent.putExtra("message", alarmData.toString())
val pendingIntent = PendingIntent.getBroadcast(
activity,
idInteger,
deactivateAlarmIntent,
PendingIntent.FLAG_UPDATE_CURRENT
)
pendingIntent.cancel()
// AlarmManager code commented out
// alarmManager.cancel(pendingIntent)
}
}
viewModel.observeNewToast().observe(activity!!, newToastObserver)
viewModel.observeActivateEvent().observe(activity!!, activateEventObserver)
viewModel.observeDeactivateEvent().observe(activity!!, deactivateEventObserver)
}
override fun onPause() {
super.onPause()
viewModel.apply {
observeNewToast().removeObserver(newToastObserver)
observeActivateEvent().removeObserver(activateEventObserver)
observeDeactivateEvent().removeObserver(deactivateEventObserver)
}
}
像这样启动应用程序时的日志(观察者按预期观察):
02-20 18:44:18.207 24565-24565/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been cleared
02-20 18:44:18.222 24565-24565/com.aly.alarm I/AlarmFragment: deactivateEventObserver -> AlarmData(id=1, timeInMillis=1550730780000, isToggledOn=true)
02-20 18:44:18.367 24565-24565/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been set!
02-20 18:44:18.382 24565-24565/com.aly.alarm I/AlarmFragment: activateEventObserver -> AlarmData(id=1, timeInMillis=1550730780000, isToggledOn=true)
02-20 18:44:21.277 24565-24565/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been cleared
02-20 18:44:21.297 24565-24565/com.aly.alarm I/AlarmFragment: deactivateEventObserver -> AlarmData(id=1, timeInMillis=1550730780000, isToggledOn=true)
02-20 18:44:22.177 24565-24565/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been set!
02-20 18:44:22.197 24565-24565/com.aly.alarm I/AlarmFragment: activateEventObserver -> AlarmData(id=1, timeInMillis=1550730780000, isToggledOn=true)
02-20 18:44:22.842 24565-24565/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been cleared
02-20 18:44:22.857 24565-24565/com.aly.alarm I/AlarmFragment: deactivateEventObserver -> AlarmData(id=1, timeInMillis=1550730780000, isToggledOn=true)
02-20 18:44:23.447 24565-24565/com.aly.alarm I/AlarmFragment: observeNewToast() -> message: Alarm has been set!
我试过将 ViewModel 生命周期所有者设置为父级 Activity,然后将其设置回 Fragment,但这并没有什么不同。删除与 AlarmManger 相关的代码使观察者工作,但由于我试图通过观察者设置警报,这让我很沮丧。为什么 AlarmManager 使 Observer 在第一次后停止被观察?它是否在某处未注册?
提前致谢。
事实证明这是我犯的一个完全不同的错误。我错误地忘记将 AlarmManager 注入到我本应通过 Dagger 注入的特定 Fragment 中。 感谢您的贡献!