Примечание: я пробовал различные решения, о которых написано здесь, в 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)
}
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 и на Pixel 4 с Android 10, и он, похоже, не срабатывает, или, может быть, срабатывает по прошествии очень долгого времени с момента его предоставления. Мне хорошо известно об ужасном поведении, которое некоторые OEM-производители добавили при удалении приложений из недавних задач, но это относится как к эмуляторам, так и к устройству Pixel 4 (в наличии).
Я прочитал в документах о настройке будильника, что он ограничен для приложений, так что это не будет происходить слишком часто, но это не объясняет, как установить будильник в определенное время, и не объясняет почему приложение Google Clock успешно это делает.
Не только это, но, согласно тому, что я понимаю, говорится, что ограничения должны быть применены, особенно для состояния низкого энергопотребления устройства, но в моем случае у меня не было этого состояния, как на устройстве, так и на эмуляторах. Я установил будильник, который должен сработать примерно через минуту.
Видя, что многие приложения-будильники уже не работают, как раньше, я думаю, что в документах чего-то не хватает. Примером таких приложений является популярное приложение Timely , которое было куплено Google, но никогда не получало новых обновлений, чтобы справиться с новыми ограничениями, и теперь пользователи хотят его вернуть. , Тем не менее, некоторые популярные приложения работают нормально, например, это .
Что я пробовал
Чтобы проверить, действительно ли работает аварийный сигнал, я выполняю следующие тесты, пытаясь активировать аварийный сигнал через минуту, после первой установки приложения, пока устройство подключено к ПК (для просмотра журналов):
- Проверьте, когда приложение находится на переднем плане, видимом для пользователя. - заняло 1-2 минуты.
- Проверка того, когда приложение было отправлено в фоновый режим (например, с помощью кнопки «Домой») - заняло около 1 минуты
- Проверьте, когда задача приложения была удалена из последних задач. - Я ждал более 20 минут и не видел срабатывания будильника, записи в журналы.
- Как # 3, но и выключить экран. Наверное, было бы хуже ...
Я пытался использовать следующие вещи, все не работают:
alarmManager.setAlarmClock(AlarmManager.AlarmClockInfo(timeToTrigger, pendingIntent), pendingIntent)
alarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
комбинация любого из вышеперечисленного, с:
if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) alarmManager.setWindow(AlarmManager.RTC_WAKEUP, 0, 60 * 1000L, pendingIntent)
Пытался использовать сервис вместо BroadcastReceiver. Также попробовал другой процесс.
Попытка сделать приложение игнорируемым из-за оптимизации заряда батареи (не помогло), но, поскольку другие приложения не нуждаются в этом, я не должен его использовать.
Пробовал с помощью этого:
if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP)
alarmManager.setAlarmClock(AlarmManager.AlarmClockInfo(timeToTrigger, pendingIntent), pendingIntent)
AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
- Попытка иметь службу, которая будет иметь триггер onTaskRemoved , чтобы перепланировать тревогу там, но это также не помогло (хотя служба работала нормально).
Что касается приложения Google Clock, я не увидел в нем ничего особенного, за исключением того, что оно показывает уведомление до его запуска, а также я не вижу его в разделе «неоптимизировано» на экране настроек оптимизации батареи.
Видя, что это похоже на ошибку, я сообщил об этом здесь , включая пример проекта и видео, чтобы показать проблему.
Я проверил несколько версий эмулятора, и похоже, что это поведение началось с API 27 (Android 8.1 - Oreo). Глядя на документы , я не вижу упоминания AlarmManager, но вместо этого было написано о различных фоновых работах.
Вопросы
Как нам настроить что-то, что должно быть запущено в относительно точное время в настоящее время?
Почему вышеперечисленные решения больше не работают? Я что-то пропустил? Разрешение? Может быть, я должен использовать Worker вместо этого? Но тогда не значит ли это, что это может вообще не сработать вовремя?
Как приложение Google «Часы» преодолевает все это и в любом случае срабатывает на точное время, всегда, даже если оно было запущено минуту назад? Только потому, что это системное приложение? Что, если оно будет установлено как пользовательское приложение на устройстве, которое не имеет встроенного?
Если вы говорите , что это потому , что это системное приложение, я нашел другое приложение , которое может вызвать тревогу в два раза в течение 2 минут, здесь , хотя я думаю , что он может использовать службу переднего плана иногда.
РЕДАКТИРОВАТЬ: сделал крошечный репозиторий Github, чтобы примерить идеи, здесь .
РЕДАКТИРОВАТЬ: наконец-то нашел образец, который с открытым исходным кодом и не имеет этой проблемы. К сожалению, это очень сложно, и я все еще пытаюсь выяснить, чем он отличается (и каков минимальный код, который я должен добавить в свой POC), который позволяет планировать его тревоги после удаления приложения из недавних задач
источник
Ответы:
Нам нечего делать.
Если ваше приложение не занесено в белый список, оно всегда будет уничтожено после удаления из недавних приложений.
Потому что производитель оригинального оборудования (OME) постоянно нарушает соответствие Android .
Поэтому, если ваше приложение не занесено в белый список с устройства «Производство», оно не будет запускать фоновую работу, даже будильник - в случае, если ваше приложение будет удалено из недавних приложений.
Вы можете найти список устройств с таким поведением здесь, также вы можете найти побочное решение, однако, оно не будет работать хорошо.
источник
Найден странный обходной путь (пример здесь ), который, кажется, работает для всех версий, включая даже Android R:
На Android R вы также должны получить его. Прежде, не кажется, что это должно быть предоставлено, просто объявлено. Не уверен, почему это изменилось на R, но я могу сказать, что SAW может потребоваться как возможное решение для запуска вещей в фоновом режиме, как написано здесь для Android 10.
FakeActivity.kt
Вы также можете сделать это действие практически невидимым для пользователя, используя эту тему:
К сожалению, это странный обходной путь. Я надеюсь найти лучший способ обойти это.
Ограничение говорит о запуске Activity, поэтому моя текущая идея заключается в том, что, возможно, если я запусту службу переднего плана на долю секунды, это также поможет, и для этого мне даже не понадобится разрешение SAW.
РЕДАКТИРОВАТЬ: ОК, я попытался с услугой переднего плана (образец здесь ), и это не сработало. Понятия не имею, почему активность работает, а не сервис. Я даже попытался перепланировать тревогу там и попытался оставить службу на некоторое время, даже после перепланировки. Также попробовал обычный сервис, но, конечно, он сразу же закрылся, так как задача была удалена, и она не работала вообще (даже если я создал поток для запуска в фоновом режиме).
Другое возможное решение, которое я не пробовал, - это всегда иметь приоритетный сервис или, по крайней мере, до тех пор, пока задача не будет удалена, но это немного странно, и я не вижу приложений, которые я упомянул, использующих его.
РЕДАКТИРОВАТЬ: попытался запустить службу переднего плана перед удалением задачи приложения, и немного позже, и сигнал тревоги все еще работал. Также пытался сделать так, чтобы этот сервис отвечал за событие с удаленной задачей, и сразу же закрывался, когда это происходит, и он все еще работал (пример здесь ). Преимущество этого обходного пути заключается в том, что вам вообще не нужно иметь разрешение SAW. Недостатком является то, что у вас есть служба с уведомлением, а приложение уже видно пользователю. Интересно, можно ли скрыть уведомление, когда приложение уже на переднем плане через активность.
РЕДАКТИРОВАТЬ: Кажется, это ошибка в Android Studio (сообщается здесь , в том числе видео сравнения версий). Когда вы запускаете приложение из проблемной версии, которую я пробовал, это может привести к сбросу аварийных сигналов.
Если вы запускаете приложение из панели запуска, оно работает нормально.
Это текущий код для установки будильника:
Мне даже не нужно использовать "pendingShowList". Использование нуля тоже нормально.
источник
SYSTEM_ALERT_WINDOW
разрешения?Intent.FLAG_RECEIVER_FOREGROUND
флаг.https://developer.android.com/about/versions/oreo/background#broadcasts
setExactAndAllowWhileIdle()
при таргетинге API 23+.https://developer.android.com/about/versions/oreo/background#migration
источник
Я знаю, что это не эффективно, но может быть более согласованным с точностью до 60 секунд.
https://developer.android.com/reference/android/content/Intent#ACTION_TIME_TICK
если этот вещательный приемник используется внутри службы переднего плана, вы можете проверять время каждую минуту и принимать решение о совершении действия.
источник
Я думаю, вы можете попросить пользователя установить разрешение, чтобы он отключил энергосберегающий режим, и предупредить пользователя, что, если он его не использует, точное время не будет достигнуто.
Вот код для запроса:
источник
Я являюсь автором проекта с открытым исходным кодом, который вы упомянули в своем вопросе ( простой будильник) .
Я удивлен, что использование AlarmManager.setAlarmClock не работает для вас, потому что мое приложение делает именно это. Код находится в файле AlarmSetter.kt. Вот фрагмент кода:
По сути, в этом нет ничего особенного, просто убедитесь, что у намерения есть действие и целевой класс, который в моем случае является широковещательным приемником.
источник