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


27

注意:我尝试了各种关于StackOverflow的解决方案(此处为 example )。请不要在未检查发现的解决方案是否使用我在下面编写的测试中起作用的情况下关闭此功能。

背景

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

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

            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的模拟器上测试了此代码,它似乎没有触发,或者自从我提供它以来经过了很长时间才触发。我深知的可怕行为一些OEM厂商加入到去除近期任务的应用程序,但是这一次是两个仿真器和像素4装置(股票)。

我已经阅读有关设置警报的文档,该警报受到应用程序的限制,因此不会经常发生,但这并不能解释如何在特定时间设置警报,也不能解释Google的Clock应用程序如何成功做到这一点。

不仅如此,而且据我了解,它说应该对设备特别是低功耗状态应用限制,但就我而言,我在设备和仿真器上都没有这种状态。我将警报设置为从现在开始大约一分钟后触发。

看到许多闹钟应用无法像以前那样工作了,我认为文档中缺少某些内容。这样的应用程序的例子是流行的Timely应用程序,该应用程序已被Google收购,但从未获得新的更新来处理新的限制,现在用户希望将其取回。。但是,某些流行的应用程序确实可以正常工作,例如this

我尝试过的

为了测试警报的确有效,在首次安装该应用程序之后的一分钟之后,我尝试在从现在开始的一分钟内触发警报,并在设备连接到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的Clock应用程序,除了在触发之前会显示通知,我没有看到其他特别的东西,而且在电池优化设置屏幕的“未优化”部分也没有看到它。

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

我检查了模拟器的多个版本,看来此行为始于API 27(Android 8.1-Oreo)。看一下文档,我没有看到AlarmManager被提及,而是写了有关各种背景工作的文章。

问题

  1. 我们如何设置如今要在相对准确的时间触发的内容?

  2. 上述解决方案为何不再起作用?我有什么想念的吗?允许?也许我应该改为使用工人?但是那不是意味着它可能根本不会按时触发吗?

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

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

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


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


我从事服务工作已经有很长时间了(我什至都​​不建议专业开发人员来建议),但是我建议您避免将alarmManager设置为5分钟以下的情况,因为由于在时间服务中运行了android的限制,cuz后端每5分钟或更长时间不少于5分钟就会被调用一次。相反,我使用了Handler。为了继续在后台运行我的服务,我提到了[ github.com/fabcira/neverEndingAndroidService]
蓝光

那么确切的限制是什么?保证触发器在相对精确的给定时间内工作的最短时间是多少?
Android开发人员

我不记得确切的限制,但是在进行这项工作时,我搜寻了几天来克服后台服务被自动杀死的麻烦。从我的个人观察中,我注意到三星,小米等的问题,您无法在5分钟的间隔内致电alarmManger,我有一个使用alarmManger实现的数据上传服务,每1分钟触发一次,但令投诉该服务的客户感到失望根本没有运行。对于仿真器,它运行良好。
蓝光

我知道您无法从android Q中的后台启动活动,但看起来不像您的情况那样。
marcinj

@ greeble31我现在尝试了。您认为哪种解决方案有效?由于某种原因,我仍然无法正常工作。我设置了闹钟,我从最近的任务中删除了该应用,即使打开了屏幕并且设备已连接至充电器,我也没有看到闹钟被触发。它在真实设备(带有Android 10的Pixel 4)和模拟器(例如API 27)上都发生。对你起作用吗?您能分享完整的代码吗?也许在Github中?
android开发者

Answers:


4

我们没事做

一旦您的应用未列入白名单,一旦将其从最新应用中删除,该应用将永远被杀死。

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

因此,如果您的应用未从制造商设备中列入白名单,则即使您的应用从最新应用中删除,它也不会触发任何后台工作,甚至不会发出警报

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


我非常了解中国OEM的这一问题。但是正如我所写,它甚至在模拟器和Pixel 4设备上也会发生。并不是像这样的中国OEM。请检查模拟器和/或Pixel设备。这个问题也存在。设置警报,从最近的任务中删除该应用,然后查看未触发警报。我将其视为错误,并在此处进行了报告(如果您想尝试的话,它包括一个视频和一个示例项目):issuetracker.google.com/issues/149556385。我已经更新了我的问题以使其清楚。问题是某个应用程序如何成功。
Android开发人员

@androiddeveloper我相信它应该可以在Emulator上工作。
易卜拉欣·阿里

我也相信,直到我尝试了。例如,只需在API 29上尝试一下,即可获得Android Studio提供的功能。我敢肯定,在较旧的版本中也会发生相同的情况。
Android开发人员

4

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

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

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

  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许可。

编辑:好的,我尝试使用前台服务(这里的示例),但是没有用。不知道为什么活动有效但服务无效。我什至试图重新安排在那里的警报,并试图使服务停留一会儿,即使重新安排之后也是如此。还尝试了普通服务,但是由于任务被删除,它当然立即关闭,并且根本不起作用(即使我创建了在后台运行的线程)。

我没有尝试过的另一种可能的解决方案是永远拥有前台服务,或者至少要等到任务删除后再使用,但这有点奇怪,而且我看不到我提到的使用它的应用程序。

编辑:尝试在删除应用程序的任务之前以及之后进行一些前台服务运行,并且警报仍然有效。还尝试使该服务成为负责任务删除事件的服务,并在发生该事件时立即将其自身关闭,并且该服务仍然有效(此处的示例)。这种解决方法的优点是您完全不需要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)

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


我只想在AndroidQ上发送onReceive()活动。SYSTEM_ALERT_WINDOW未经许可,是否有任何解决方法?
doctorram

2
为什么Google总是因为简单的东西而使Android开发人员的生活陷入困境?!
Doctorram

@doctorram是的,它写在文档中有关各种异常的内容:developer.android.com/guide/components/activities/… 。我只是选择SYSTEM_ALERT_WINDOW,因为它最容易测试。
android开发者

从上次编辑开始,您的意思是说现在从最新列表中删除该应用程序后,我们不必使用您提到的任何解决方法来保留警报?
Pradeepkumar Reddy

我希望每天早上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,endingIntent);
Pradeepkumar Reddy

1
  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. setExactAndAllowWhileIdle()定位API 23+时使用。
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上?
android开发者

@androiddeveloper更新了答案。
Maksim Ivanov

仍然似乎不起作用。这是一个示例项目:ufile.io/6qrsor7o。请尝试使用Android 10(模拟器也可以),设置闹钟,然后从近期任务中删除该应用。如果您不从最近的任务中删除,它将正常工作并在10秒后触发。
Android开发人员

我还更新了问题,以链接到包含示例项目和视频的错误报告,因为我认为这是一个错误,因为我看不到发生此错误的任何其他原因。
android开发者


0

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

这是请求它的代码:

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);

我已经尝试过这样做,因为我注意到没有其他应用程序可以这样做,并且很好奇它是否可以提供帮助。没用 更新的问题。
Android开发人员

0

我是您在问题(简单闹钟)中提到的开源项目的作者。

令我惊讶的是,使用AlarmManager.setAlarmClock不适用于您,因为我的应用程序正是这样做的。代码位于文件AlarmSetter.kt中。这是一个片段:

  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/...
Android开发者

我已经在GitHub上签出了您的代码。在Moto Z2 Play上从最近的应用程序中删除该应用程序后,广播接收器才能工作。我可以在Pixel上试用,但是代码对我来说似乎还可以。强制停止应用程序会删除计划的警报,但是任何强制停止的应用程序都会发生这种情况。
Yuriy Kulikov

我已经显示了很多次:计划之后,我要做的就是从最近的任务中删除。我做了两个仿真器和像素4
Android开发者

请检查是否使用pendingShowList绕过了这个问题,如果可以,我将更新答案。也许对某人有用。
Yuriy Kulikov
By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.