Как установить будильник на точное время после всех новейших ограничений на Android?

27

Примечание: я пробовал различные решения, о которых написано здесь, в 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. Проверьте, когда приложение находится на переднем плане, видимом для пользователя. - заняло 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. Почему вышеперечисленные решения больше не работают? Я что-то пропустил? Разрешение? Может быть, я должен использовать Worker вместо этого? Но тогда не значит ли это, что это может вообще не сработать вовремя?

  3. Как приложение Google «Часы» преодолевает все это и в любом случае срабатывает на точное время, всегда, даже если оно было запущено минуту назад? Только потому, что это системное приложение? Что, если оно будет установлено как пользовательское приложение на устройстве, которое не имеет встроенного?

Если вы говорите , что это потому , что это системное приложение, я нашел другое приложение , которое может вызвать тревогу в два раза в течение 2 минут, здесь , хотя я думаю , что он может использовать службу переднего плана иногда.

РЕДАКТИРОВАТЬ: сделал крошечный репозиторий Github, чтобы примерить идеи, здесь .


РЕДАКТИРОВАТЬ: наконец-то нашел образец, который с открытым исходным кодом и не имеет этой проблемы. К сожалению, это очень сложно, и я все еще пытаюсь выяснить, чем он отличается (и каков минимальный код, который я должен добавить в свой POC), который позволяет планировать его тревоги после удаления приложения из недавних задач

разработчик Android
источник
Я долгое время работал над сервисом (я даже не профессиональный разработчик, чтобы предложить), но я могу предложить вам избегать alarmManager для случаев, таких как установка будильника ниже 5 минут, потому что из-за ограничений Android после запуска службы времени в бэкэнд вызывается через каждые 5 минут или больше, не менее чем через 5 минут. Вместо этого я использовал Handler. И для работы мой сервис продолжается в фоновом режиме, я сослался [ github.com/fabcira/neverEndingAndroidService]
Blu
Так каковы точные ограничения? Какое минимальное время гарантировано, что триггер будет работать в относительно точное время?
Android-разработчик
Я не могу вспомнить точные ограничения, но когда я работал над этим, я в течение нескольких дней гуглял, чтобы автоматически убить фоновый сервис. А из личного наблюдения я заметил проблему с Samsung, Xiaomi и т. Д., Вы не можете вызывать alarmManger в интервале 5 минут, у меня была реализована служба загрузки данных с использованием alarmManger, которая срабатывает каждые 1 минуту, но это разочаровало нашего клиента, который жаловался на сервис не работает вообще. Для эмуляторов это работает хорошо.
Blu
Я знаю, что вы не можете начать деятельность с фона в Android Q, но это не похоже на ваш случай.
марта
@ greeble31 Я попробовал сейчас. Какое решение вы видите работающим? По какой-то причине я до сих пор не могу заставить его работать. Я установил будильник, удаляю приложение из последних задач и не вижу срабатывания будильника, даже если экран включен и устройство подключено к зарядному устройству. Это происходит как на реальном устройстве (Pixel 4 с Android 10), так и на эмуляторе (API 27, например). Работает ли это для вас? Можете ли вы поделиться полным кодом? Может быть, в Github?
Android-разработчик

Ответы:

4

Нам нечего делать.

Если ваше приложение не занесено в белый список, оно всегда будет уничтожено после удаления из недавних приложений.

Потому что производитель оригинального оборудования (OME) постоянно нарушает соответствие Android .

Поэтому, если ваше приложение не занесено в белый список с устройства «Производство», оно не будет запускать фоновую работу, даже будильник - в случае, если ваше приложение будет удалено из недавних приложений.

Вы можете найти список устройств с таким поведением здесь, также вы можете найти побочное решение, однако, оно не будет работать хорошо.

Ибрагим Али
источник
Я хорошо осведомлен об этой проблеме китайских производителей оборудования. Но, как я уже писал, это происходит даже на эмуляторе и устройстве Pixel 4. Это не какой-то китайский OEM, который сделал это как таковой. Пожалуйста, проверьте на эмуляторе и / или устройстве Pixel. Проблема существует и там. Установите будильник, удалите приложение из последних задач и убедитесь, что будильник не сработал. Я вижу это как ошибку, и здесь сообщается (она включает в себя видео и пример проекта, если вы хотите попробовать): issetracker.google.com/issues/149556385. Я обновил свой вопрос, чтобы прояснить. Вопрос в том, как получилось какое-то приложение.
Android-разработчик
@androiddeveloper Я считаю, что он должен работать на эмуляторе .. Какой эмулятор у вас есть?
Ибрагим Али
Я тоже верил, пока не попробовал. Попробуйте, например, на 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. Иметь службу, которая будет определять, когда задачи были удалены, а когда она это делает, открыть поддельное действие, которое все, что он делает, это закрывает себя:
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. Недостатком является то, что у вас есть служба с уведомлением, а приложение уже видно пользователю. Интересно, можно ли скрыть уведомление, когда приложение уже на переднем плане через активность.


РЕДАКТИРОВАТЬ: Кажется, это ошибка в 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". Использование нуля тоже нормально.

разработчик Android
источник
Я просто хочу, чтобы Sart активность наReceive () на AndroidQ. Есть ли обходные пути для этого без SYSTEM_ALERT_WINDOWразрешения?
докторрам
2
Почему Google всегда делает жизнь разработчиков Android адом из простых вещей ?!
докторрам
@doctorram Да, в документации написано о различных исключениях: developer.android.com/guide/components/activities/… . Я просто выбрал SYSTEM_ALERT_WINDOW, потому что это проще всего проверить.
Android-разработчик
Из вашего последнего редактирования вы имеете в виду, что теперь нам не нужно использовать какие-либо обходные пути, о которых вы упоминали, для сохранения сигналов тревоги после удаления приложения из недавнего списка?
user3410835
Я хочу запускать фрагмент кода каждый день с 6 до 7 в фоновом режиме, даже если приложение удалено из недавнего списка. Я должен использовать WorkManager или AlarmManager ?? Попробовал следующий код для моего usecase, и он не работал. В чем проблема с кодом ниже? calendar.setTimeInMillis (System.currentTimeMillis ()); calendar.set (Calendar.HOUR_OF_DAY, 6); alarmManager.setInexactRepeating (AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis (), AlarmManager.INTERVAL_DAY, pendingIntent);
user3410835
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 вообще не получает намерения (или почти сразу)? Это первый шаг ... Кроме того, AlarmManagerCompat уже не предлагает этот же код? Вы пробовали это, используя тесты, которые я написал, включая удаление приложения из недавних задач? Можете ли вы показать весь код? Может быть, поделиться на Github?
Android-разработчик
@androiddeveloper обновил ответ.
Максим Иванов
По-прежнему не работает. Вот пример проекта: ufile.io/6qrsor7o . Пожалуйста, попробуйте на Android 10 (эмулятор тоже хорошо), установите будильник и удалите приложение из последних задач. Если вы не удалите из недавних задач, он работает нормально и срабатывает через 10 секунд.
Android-разработчик
Я также обновил вопрос, добавив ссылку на отчет об ошибке, который включает в себя пример проекта и видео, потому что я думаю, что это ошибка, поскольку я не вижу другой причины для этого.
Android-разработчик
0

Я знаю, что это не эффективно, но может быть более согласованным с точностью до 60 секунд.

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

если этот вещательный приемник используется внутри службы переднего плана, вы можете проверять время каждую минуту и ​​принимать решение о совершении действия.

жесткий
источник
Если у меня есть служба переднего плана, зачем мне это нужно? Я мог бы просто использовать Handler.postDelayed или любое другое подобное решение, если я хочу ...
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);
user2638180
источник
Я уже попробовал это, так как заметил, что никакое другое приложение не делает это, и было любопытно, может ли это помочь. Не сработало Обновленный вопрос.
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 4.
Android-разработчик
Пожалуйста, проверьте, если использование pendingShowList обходит проблему, и если да, я обновлю ответ. Может быть, это будет кому-то полезно.
Юрий Куликов