在 Android 的所有最新限制之后,如何设置要在确切时间安排的闹钟?

and*_*per 35 android alarmmanager

注意:我尝试了在 StackOverflow 上写的各种解决方案(这里的例子)。请不要在没有使用我在下面编写的测试中检查您发现的解决方案是否有效的情况下关闭它。

背景

对应用程序有一个要求,即用户将提醒设置为在特定时间安排,因此当应用程序在这个时间被触发时,它会在后台做一些微小的事情(只是一些数据库查询操作),并显示一个简单的通知,讲述提醒。

过去,我用一个简单的代码来设置一些在相对特定的时间被调度的东西:

            val alarmManager = context.getSystemService(Context.ALARM_SERVICE) as AlarmManager
            val pendingIntent = PendingIntent.getBroadcast(context, requestId, Intent(context, AlarmReceiver::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
            when {
                VERSION.SDK_INT >= VERSION_CODES.KITKAT -> alarmManager.setExact(AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
                else -> alarmManager.set(AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
            }
Run Code Online (Sandbox Code Playgroud)
class AlarmReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        Log.d("AppLog", "AlarmReceiver onReceive")
        //do something in the real app
    }
}
Run Code Online (Sandbox Code Playgroud)

用法:

            val timeToTrigger = System.currentTimeMillis() + java.util.concurrent.TimeUnit.MINUTES.toMillis(1)
            setAlarm(this, timeToTrigger, 1)
Run Code Online (Sandbox Code Playgroud)

问题

我现在已经在新 Android 版本和带有 Android 10 的 Pixel 4 上的模拟器上测试了此代码,它似乎没有触发,或者它可能在我提供它很长时间后触发。我深知的可怕行为是一些OEM厂商加入到去除近期任务的应用程序,但是这一次是两个仿真器和像素4装置(股票)。

我已经阅读了关于设置闹钟的文档,它限制了应用程序,因此它不会经常发生,但这并没有解释如何在特定时间设置闹钟,也没有解释谷歌的时钟应用程序是如何成功做到这一点的。

不仅如此,根据我的理解,它说限制应该特别适用于设备的低功耗状态,但就我而言,我在设备和模拟器上都没有这种状态。我已将警报设置为在大约一分钟后触发。

看到许多闹钟应用程序不再像以前那样工作,我认为文档中缺少一些东西。此类应用程序的示例是流行的Timely应用程序,该应用程序被谷歌购买但从未获得新的更新来处理新的限制,现在用户想要它回来。. 但是,一些流行的应用程序确实可以正常工作,例如这个。

我试过的

为了测试警报是否确实有效,我在第一次安装应用程序后,从现在起一分钟内尝试触发警报时执行这些测试,同时设备连接到 PC(查看日志):

  1. 测试应用程序何时处于前台,对用户可见。- 花了 1-2 分钟。
  2. 测试应用何时被发送到后台(例如使用主页按钮) - 大约需要 1 分钟
  3. 测试应用程序的任务何时从最近的任务中删除。- 我等了 20 多分钟,没有看到警报被触发,写入日志。
  4. 像#3,但也关掉屏幕。可能会更糟...

我尝试使用接下来的东西,都不起作用:

  1. alarmManager.setAlarmClock(AlarmManager.AlarmClockInfo(timeToTrigger, pendingIntent), pendingIntent)

  2. alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)

  3. AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)

  4. 以上任何一项的组合,与:

    if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) alarmManager.setWindow(AlarmManager.RTC_WAKEUP, 0, 60 * 1000L, pendingIntent)

  5. 尝试使用服务而不是 BroadcastReceiver。还尝试了不同的过程。

  6. 尝试从电池优化中忽略该应用程序(没有帮助),但由于其他应用程序不需要它,我也不应该使用它。

  7. 尝试使用这个:

            if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP)
                alarmManager.setAlarmClock(AlarmManager.AlarmClockInfo(timeToTrigger, pendingIntent), pendingIntent)
            AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
Run Code Online (Sandbox Code Playgroud)
  1. 尝试使用将触发onTaskRemoved的服务,在那里重新安排警报,但这也无济于事(尽管该服务运行良好)。

至于谷歌的时钟应用,除了在触发前显示通知之外,我没有看到任何特别之处,而且我在电池优化设置屏幕的“未优化”部分也没有看到它。

看到这似乎是一个错误,我在这里报告了这个问题,包括一个示例项目和视频来展示这个问题。

我检查了模拟器的多个版本,似乎这种行为是从 API 27 (Android 8.1 - Oreo) 开始的。查看文档,我没有看到提到 AlarmManager,而是写了有关各种背景工作的内容。

问题

  1. 我们现在如何设置在相对准确的时间触发的东西?

  2. 为什么上述解决方案不再起作用?我错过了什么吗?允许?也许我应该使用 Worker 来代替?但这不就意味着它可能根本不会按时触发吗?

  3. 谷歌“时钟”应用程序如何克服所有这些问题,并始终在准确的时间触发,即使它是在一分钟前触发的?仅仅是因为它是一个系统应用程序吗?如果它作为用户应用程序安装在没有内置它的设备上怎么办?

如果你说是因为它是一个系统应用程序,我找到了另一个可以在2分钟内触发两次警报的应用程序,在这里,虽然我认为它有时可能会使用前台服务。

编辑:在这里制作了一个小的 Github 存储库来尝试想法。


编辑:终于找到了一个既开源又没有这个问题的示例。可悲的是它非常复杂,我仍然试图弄清楚是什么让它如此不同(以及我应该添加到我的 POC 的最少代码是什么),以便在从最近的任务中删除应用程序后让它的警报保持预定

Ibr*_*Ali 6

我们无事可做。

一旦您的应用未列入白名单,一旦从最近的应用中删除,它将始终被杀死。

因为原始设备制造商 (OME) 不断违反 Android 合规性。

因此,如果您的应用程序未从设备制造商中列入白名单,则它不会触发任何后台工作,甚至不会触发警报- 以防您的应用程序从最近的应用程序中删除。

您可以在此处找到具有该行为的设备列表,您也可能会找到一个辅助解决方案,但是,它不会很好地工作。


and*_*per 6

发现了一个奇怪的解决方法(示例在这里),它似乎适用于所有版本,甚至包括 Android R:

  1. 在清单中声明 SAW 权限:
      <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
Run Code Online (Sandbox Code Playgroud)

在 Android R 上,您还必须授予它。在之前,似乎不需要授予,只是声明。不知道为什么这会在 R 上发生变化,但我可以说可能需要 SAW 作为在后台启动的可能解决方案,如此处针对 Android 10所写。

  1. 有一个服务来检测任务何时被删除,当它被删除时,打开一个假活动,它所做的就是关闭自己:
class OnTaskRemovedDetectorService : Service() {
    override fun onBind(intent: Intent?) = null

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int) = START_STICKY

    override fun onTaskRemoved(rootIntent: Intent?) {
        super.onTaskRemoved(rootIntent)
        Log.e("AppLog", "onTaskRemoved")
        applicationContext.startActivity(Intent(this, FakeActivity::class.java).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK))
        stopSelf()
    }

}
Run Code Online (Sandbox Code Playgroud)

假活动.kt

class FakeActivity : AppCompatActivity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        Log.d("AppLog", "FakeActivity")
        finish()
    }
}
Run Code Online (Sandbox Code Playgroud)

您还可以使用此主题使该 Activity 几乎对用户不可见:

      <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
Run Code Online (Sandbox Code Playgroud)

可悲的是,这是一个奇怪的解决方法。我希望找到一个更好的解决方法。

这个限制是关于启动 Activity 的,所以我目前的想法是,如果我启动一个前台服务一秒钟,它也会有所帮助,为此我什至不需要 SAW 许可。

编辑:好的,我尝试了前台服务(此处的示例),但没有奏效。不知道为什么 Activity 正在工作而不是服务。我什至试图在那里重新安排闹钟,并试图让服务停留一段时间,即使在重新安排之后也是如此。还尝试了一个正常的服务,但当然它立即关闭了,因为任务被删除了,它根本不起作用(即使我创建了一个在后台运行的线程)。

我没有尝试过的另一个可能的解决方案是永远拥有前台服务,或者至少在任务被删除之前,但这有点奇怪,我没有看到我提到的应用程序使用它。

编辑:尝试在删除应用程序任务之前运行前台服务,然后运行一段时间,警报仍然有效。还试图让这个服务成为负责任务删除事件的服务,并在它发生时立即关闭,它仍然有效(示例在这里)。这种变通方法的优点是您根本不需要拥有 SAW 权限。缺点是您有一个带有通知的服务,而该应用程序已经对用户可见。我想知道是否可以在应用程序已经通过 Activity 处于前台时隐藏通知。


编辑:似乎这是 Android Studio 上的一个错误(在此处报告,包括比较版本的视频)。当您从我尝试过的有问题的版本启动应用程序时,可能会导致警报被清除。

如果您从启动器启动应用程序,它工作正常。

这是设置闹钟的当前代码:

        val timeToTrigger = System.currentTimeMillis() + 10 * 1000
        val pendingShowList = PendingIntent.getActivity(this, 1, Intent(this, SomeActivity::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
        val pendingIntent = PendingIntent.getBroadcast(this, 1, Intent(this, AlarmReceiver::class.java), PendingIntent.FLAG_UPDATE_CURRENT)
        manager.setAlarmClock(AlarmManager.AlarmClockInfo(timeToTrigger, pendingShowList), pendingIntent)
Run Code Online (Sandbox Code Playgroud)

我什至不必使用“pendingShowList”。使用 null 也可以。

  • 为什么 Google 总是因为一些简单的事情而让 Android 开发者的生活陷入地狱?! (4认同)