【问题标题】:How to set an alarm to be scheduled at an exact time after all the newest restrictions on Android?在 Android 的所有最新限制之后,如何设置闹钟以在准确的时间安排?
【发布时间】:2020-05-21 14:01:55
【问题描述】:

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

背景

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

过去,我使用简单的代码来设置要在相对特定的时间安排的事情:

            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)
            }
class AlarmReceiver : BroadcastReceiver() {
    override fun onReceive(context: Context, intent: Intent) {
        Log.d("AppLog", "AlarmReceiver onReceive")
        //do something in the real app
    }
}

用法:

            val timeToTrigger = System.currentTimeMillis() + java.util.concurrent.TimeUnit.MINUTES.toMillis(1)
            setAlarm(this, timeToTrigger, 1)

问题

我现在已经在新 Android 版本的模拟器和带有 Android 10 的 Pixel 4 上测试了这段代码,但它似乎没有触发,或者它可能在我提供它很长时间后触发。我很清楚 some OEMs 添加的 terrible behavior 用于从最近的任务中删除应用程序,但这个在模拟器和 Pixel 4 设备(库存)上都有。

我在the docs 上阅读了有关设置警报的信息,它对应用程序进行了限制,因此不会经常发生,但这并没有解释如何在特定时间设置警报,而且没有解释Google's Clock app 是如何成功的。

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

看到许多闹钟应用不再像以前那样工作,我认为文档中缺少某些内容。此类应用程序的示例是流行的Timely 应用程序bought by Google,但从未获得新的更新来处理新的限制,现在用户want it back.。但是,一些流行的应用程序确实可以正常工作,例如this one

我尝试过的

为了测试警报确实有效,我会在一分钟后尝试触发警报时执行这些测试,在第一次安装应用程序后,在设备连接到 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)
  1. 尝试使用触发 onTaskRemoved 的服务来重新安排那里的警报,但这也无济于事(尽管服务运行良好)。

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

看到这似乎是一个错误,我报告了这个here,包括一个示例项目和显示问题的视频。

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

问题

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

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

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

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

编辑:创建了一个小型 Github 存储库来尝试想法,here


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

【问题讨论】:

  • 我在服务上工作了很长时间(我什至不是专业开发人员建议的),但我可以建议你避免使用 alarmManager 来设置低于 5 分钟的闹钟,因为由于 android在后端运行时间服务后的限制,它每 5 分钟或大于不少于 5 分钟后被调用一次。相反,我使用了处理程序。为了在后台继续运行我的服务,我提到了 [github.com/fabcira/neverEndingAndroidService]
  • 那么具体的限制是什么?保证触发器在相对精确的给定时间内工作的最短时间是多少?
  • 我不记得确切的限制,但是当我在处理它时,我用谷歌搜索了几天以克服后台服务被自动终止的问题。从个人观察我注意到三星、小米等的问题,你不能在 5 分钟的间隔内调用 alarmManger,我有一个使用 alarmManger 实现的数据上传服务,每 1 分钟触发一次,但它让我们抱怨该服务的客户感到失望根本没有运行。对于模拟器来说效果很好。
  • 我知道你不能在 android Q 中从后台启动活动,但它看起来不像你的情况。
  • @greeble31 我现在试过了。您认为哪种解决方案有效?由于某种原因,我仍然无法正常工作。我设置了闹钟,我从最近的任务中删除了应用程序,但我没有看到闹钟被触发,即使屏幕已打开并且设备已连接到充电器。它发生在真实设备(带有 Android 10 的 Pixel 4)和模拟器(例如 API 27)上。对你起作用吗?你能分享完整的代码吗?也许在 Github 上?

标签: android alarmmanager


【解决方案1】:

找到了一个奇怪的解决方法(示例 here),似乎适用于所有版本,甚至包括 Android R:

  1. 具有在清单中声明的​​权限 SAW 权限:
      <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

在 Android R 上,您还必须授予它。在之前,似乎不需要授予它,只需声明即可。不知道为什么这会在 R 上发生变化,但我可以说 SAW 可能是在后台启动事物的一种可能解决方案,正如 Android 10 中的 here 所写的那样。

编辑:这是一个guide,关于如何请求它。

  1. 有一个服务会检测任务何时被删除,当它被删除时,打开一个虚假的 Activity,它所做的只是关闭自己:
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()
    }

}

FakeActivity.kt

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

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

    <style name="AppTheme.Translucent" parent="@style/Theme.AppCompat.NoActionBar">
        <item name="android:windowBackground">@android:color/transparent</item>
        <item name="android:colorBackgroundCacheHint">@null</item>
        <item name="android:windowIsTranslucent">true</item>
    </style>

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

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

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

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

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


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

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

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

        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)

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

【讨论】:

  • 我只想在 AndroidQ 上启动一个活动 onReceive()。在没有SYSTEM_ALERT_WINDOW 权限的情况下有什么解决方法吗?
  • 为什么 Google 总是让 Android 开发者的生活因为简单的东西而活得像地狱一样?!
  • @doctorram 是的,它写在有关各种异常的文档上:developer.android.com/guide/components/activities/…。我只是选择了 SYSTEM_ALERT_WINDOW,因为它最容易测试。
  • 从您上次的编辑中,您的意思是说现在我们不必使用您提到的任何解决方法来在从最近列表中删除应用程序后保留警报??
  • 我想每天早上 6 点到 7 点在后台运行一段代码,即使应用程序已从最近列表中删除。我应该使用 WorkManager 或 AlarmManager ??为我的用例尝试了以下代码,但没有成功。下面的代码有什么问题? calendar.setTimeInMillis(System.currentTimeMillis()); calendar.set(Calendar.HOUR_OF_DAY, 6); alarmManager.setInexactRepeating(AlarmManager.RTC_WAKEUP,calendar.getTimeInMillis(), AlarmManager.INTERVAL_DAY, pendingIntent);
【解决方案2】:

我是您在问题中提到的开源项目的作者(simple alarm clock)

我很惊讶使用 AlarmManager.setAlarmClock 对您不起作用,因为我的应用程序就是这样做的。代码在文件 AlarmSetter.kt 中。这是一个sn-p:

  val pendingAlarm = Intent(ACTION_FIRED)
                .apply {
                    setClass(mContext, AlarmsReceiver::class.java)
                    putExtra(EXTRA_ID, id)
                    putExtra(EXTRA_TYPE, typeName)
                }
                .let { PendingIntent.getBroadcast(mContext, pendingAlarmRequestCode, it, PendingIntent.FLAG_UPDATE_CURRENT) }

            val pendingShowList = PendingIntent.getActivity(
                    mContext,
                    100500,
                    Intent(mContext, AlarmsListActivity::class.java),
                    PendingIntent.FLAG_UPDATE_CURRENT
            )

            am.setAlarmClock(AlarmManager.AlarmClockInfo(calendar.timeInMillis, pendingShowList), pendingAlarm)

基本上没什么特别的,只要确保意图有一个动作和一个目标类,在我的例子中是一个广播接收器。

【讨论】:

  • 很遗憾没用。这就是我尝试过的。在此处查看文件:github.com/yuriykulikov/AlarmClock/issues/…
  • 我已经在 GitHub 上查看了您的代码。从 Moto Z2 Play 上的最新消息中删除应用程序后,广播接收器工作。我可以在 Pixel 上试用它,但代码对我来说似乎没问题。强制停止应用程序会删除预定的警报,但这会发生在任何被强制停止的应用程序上。
  • 我已经多次展示了:我在安排之后所做的就是从最近的任务中删除。我在模拟器和 Pixel 4 上都做到了。
  • 请检查使用 pendingShowList 是否可以绕过问题,如果是,我会更新答案。也许它对某人有用。
【解决方案3】:

我认为你可以要求用户设置权限,使其禁用节能模式,并警告用户如果他不使用它,将无法达到准确的时间。

这是请求它的代码:

PowerManager powerManager = (PowerManager) getApplicationContext().getSystemService(POWER_SERVICE);
            String packageName = "your Package name";
            if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
                Intent i = new Intent();
                if (!powerManager.isIgnoringBatteryOptimizations(packageName)) {
                    i.setAction(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS);
                    i.setData(Uri.parse("package:" + packageName));
                    startActivity(i);

【讨论】:

  • 已经尝试过了,因为我注意到没有其他应用程序可以这样做,并且很好奇它是否可以提供帮助。没用。更新的问题。
【解决方案4】:

我知道这效率不高,但可能更符合 60 秒的准确度。

https://developer.android.com/reference/android/content/Intent#ACTION_TIME_TICK

如果在前台服务中使用此广播接收器,您可以每分钟检查一次时间并决定采取行动。

【讨论】:

  • 如果我有前台服务,我为什么需要这个?如果我愿意,我可以使用 Handler.postDelayed 或任何其他类似的解决方案......
【解决方案5】:

我们无事可做。

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

因为Original Equipment Manufacturer (OMEs) constantly violating Android compliance

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

您可以找到具有该行为的设备列表here 还可以找到一个侧面解决方案,但是它不会很好地工作。

【讨论】:

  • 中国主机厂的这个问题我很清楚。但正如我所写,它甚至发生在模拟器和 Pixel 4 设备上。并非某些中国 OEM 做到了这一点。请检查模拟器和/或像素设备。那里也存在问题。设置警报,从最近的任务中删除应用程序,并查看警报未触发。我认为这是一个错误,并在此处报告(如果您想尝试,它包括一个视频和一个示例项目):issuetracker.google.com/issues/149556385。我已经更新了我的问题以使其清楚。问题是某些应用程序是如何成功的。
  • @androiddeveloper 我相信它应该可以在模拟器上运行。你有哪个模拟器?
  • 我也相信,直到我尝试过。例如,只需在 API 29 上尝试一下,Android Studio 必须提供。我敢肯定,在更旧的版本上也会发生同样的情况。
【解决方案6】:
  1. 确保您广播的意图是明确的并且具有Intent.FLAG_RECEIVER_FOREGROUND 标志。

https://developer.android.com/about/versions/oreo/background#broadcasts

Intent intent = new Intent(context, Receiver.class);
intent.setAction(action);
...
intent.addFlags(Intent.FLAG_RECEIVER_FOREGROUND);

PendingIntent operation = PendingIntent.getBroadcast(context, 0, intent, flags);
  1. 针对 API 23+ 时使用 setExactAndAllowWhileIdle()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, time, operation);
} else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
    alarmManager.setExact(AlarmManager.RTC_WAKEUP, time, operation);
} else {
    alarmManager.set(AlarmManager.RTC_WAKEUP, time, operation);
}
  1. 将警报作为前台服务启动:

https://developer.android.com/about/versions/oreo/background#migration

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    context.startForegroundService(intent);
} else {
    context.startService(intent);
}
  1. 别忘了权限:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

【讨论】:

  • 如果 BroadcastReceiver 本身根本没有(或接近时间)没有得到 Intent,为什么服务很重要?这是第一步......另外,AlarmManagerCompat 不是已经提供了相同的代码吗?您是否使用我编写的测试尝试过这个,包括从最近的任务中删除应用程序?你能显示整个代码吗?也许在 Github 上分享?
  • 似乎仍然不起作用。这是一个示例项目:ufile.io/6qrsor7o。请在 Android 10 上尝试(模拟器也可以),设置闹钟,然后从最近的任务中删除该应用。如果您不从最近的任务中删除,它可以正常工作并在 10 秒后触发。
  • 我还更新了问题以提供指向错误报告的链接,其中包括示例项目和视频,因为我认为这是一个错误,因为我没有看到任何其他原因发生。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2011-12-29
  • 1970-01-01
  • 1970-01-01
  • 2014-04-08
相关资源
最近更新 更多