Як встановити сигнал тривоги, який повинен бути запланований в точний час після всіх останніх обмежень на 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 , щоб перепланувати будильник там, але це теж не допомогло (сервіс працював нормально).

Що стосується програми Clock від Google, я не бачив нічого особливого в цьому, окрім того, що воно показує сповіщення перед запуском, і я також не бачу його в розділі "не оптимізовано" на екрані налаштувань оптимізації акумулятора.

Побачивши, що це здається помилкою, я повідомив про це тут , включаючи зразок проекту та відео, щоб показати проблему.

Я перевірив кілька версій емулятора, і схоже, що така поведінка почалася з API 27 (Android 8.1 - Oreo). Дивлячись на документи , я не бачу згадування AlarmManager, але натомість було написано про різні фонові роботи.

Питання

  1. Як ми встановимо щось, що може спрацювати у відносно точний час у наш час?

  2. Чому вищезазначені рішення більше не працюють? Я щось пропускаю? Дозвіл? Можливо, замість цього я повинен використовувати Worker? Але чи не означало б це, що воно може не спрацьовувати вчасно?

  3. Як додаток Google "Годинник" долає все це і все-таки спрацьовує в точний час завжди, навіть якщо це було запущено лише хвилину тому? Це лише тому, що це системний додаток? Що робити, якщо він встановлюється як користувальницький додаток на пристрої, в якому його немає?

Якщо ви говорите , що це тому , що це системне додаток, я знайшов інший додаток , яке може викликати тривогу в два рази в протягом 2 хвилин, тут , хоча я думаю , що він може використовувати службу переднього плану іноді.

EDIT: створив крихітний сховище Github, щоб спробувати ідеї тут .


EDIT: нарешті знайдено зразок, який є і з відкритими джерелами, і не має цієї проблеми. На жаль, це дуже складно, і я все ще намагаюся розібратися, що робить його таким різним (і який мінімальний код, який я повинен додати до своєї POC), що дозволяє його тривоги залишатись запланованими після видалення програми з останніх завдань


Давно я працював над сервісом (я навіть не пропоную розробника), але я можу запропонувати вам уникати alarmManager у випадках, коли встановити тривогу нижче 5 хв, бо через обмеження для android після того, як служба працює на час у вихідний день йому дзвонять через кожні 5 хв і більше не менше 5 хв. Натомість я використав Handler. А для запуску моя служба продовжується у фоновому режимі, я згадав [ github.com/fabcira/neverEndingAndroidService]
Blu

То які точні обмеження? Який мінімальний час, який гарантується, що тригер буде працювати у відносно точний час?
андроїд розробник

Я не можу згадати точних обмежень, але коли я працював над цим, я гуглив, як дні, щоб подолати фонову службу, загинувши автоматично. І з особистого спостереження я помітив проблему на Samsung, Xiaomi тощо, ви не можете зателефонувати на alarmManger між інтервалом в 5 хвилин, у мене була служба завантаження даних, реалізована за допомогою alarmManger, яка спрацьовує кожні 1 хв, але це розчарувало нашого клієнта, який скаржився на послугу зовсім не працює. Для емуляторів це добре працює.
Blu

Я знаю, що ви не можете розпочати діяльність з фонового режиму в android Q, але це не схоже на ваш випадок.
marcinj

@ greeble31 Я спробував зараз. Яке рішення там, на вашу думку, працює? Чомусь я все ще не змушую його працювати. Я встановлюю будильник, вилучаю програму з останніх завдань, і не бачу, як спрацьовує сигнал, навіть якщо екран увімкнено, а пристрій підключено до зарядного пристрою. Це відбувається як на реальному пристрої (Pixel 4 з Android 10), так і на емуляторі (наприклад, API 27). Це працює на вас? Чи можете ви поділитися повним кодом? Може, в Ґітуб?
андроїд розробник

Відповіді:


4

Ми нічого не маємо робити.

Якщо ваш додаток не включено в білий список, воно завжди буде вбито після видалення з останніх програм.

Тому що виробник оригінального обладнання (OME) постійно порушує вимоги Android .

Отже, якщо ваш додаток не включено в список дозволених пристроїв, він не спрацює з фоновими роботами, навіть з тривожними сигналами - якщо ваш додаток буде видалено з останніх програм.

Список пристроїв з такою поведінкою ви можете знайти тут ТАКОЖ ви можете знайти побічне рішення, однак воно не працюватиме добре.


Я добре знаю цю проблему китайських оригіналів. Але, як я писав, це відбувається навіть на емуляторі та пристрої Pixel 4. Це не якийсь китайський OEM, який зробив це таким. Перевірте емулятор та / або пристрій Pixel. Проблема існує і там. Встановіть сигнал тривоги, видаліть додаток із останніх завдань і переконайтеся, що тривога не спрацьовує. Я бачу це як помилку і повідомляю тут (він включає відео та зразок проекту, якщо ви хочете спробувати): issueetracker.google.com/isissue/149556385. Я оновив своє запитання, щоб було зрозуміло. Питання в тому, як прийти якомусь додатку вдалося.
андроїд розробник

@androiddeveloper Я вважаю, що це має працювати на Емуляторі. Який емулятор у вас є?
Ібрагім Алі

Я також вірив, поки не спробував. Просто спробуйте, наприклад, на API 29, що може запропонувати Android Studio. Я впевнений, що те саме відбуватиметься і на трохи старших версіях.
андроїд розробник

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>

На жаль, це дивне рішення. Я сподіваюся знайти приємніший спосіб вирішення цього питання.

Обмеження говорить про початок діяльності, тому моя теперішня ідея полягає в тому, що, можливо, якщо я запускаю службу переднього плану на частку секунди, це також допоможе, і для цього мені навіть не потрібен дозвіл SAW.

EDIT: Гаразд, я спробував послугу переднього плану (зразок тут ), і це не вийшло. Не маю поняття, чому діяльність працює, але не є послугою. Я навіть намагався перепланувати будильник там і намагався дозволити службі трохи зупинитися, навіть після перепланування. Також спробував звичайний сервіс, але, звичайно, він закрився відразу, оскільки завдання було видалено, і воно не працювало зовсім (навіть якщо я створив потік для запуску у фоновому режимі).

Ще одне можливе рішення, яке я не намагався - мати службу переднього плану назавжди або, принаймні, поки завдання не буде знято, але це трохи дивно, і я не бачу програм, які я згадав.

EDIT: намагався запустити службу переднього плану перед тим, як видалити завдання додатка, і трохи пізніше, і тривога спрацювала. Також намагався, щоб ця служба була відповідальною за подію, яку було видалено, і закривати себе відразу, коли вона виникає, і вона все ще працювала (зразок тут ). Перевага цього рішення полягає в тому, що вам взагалі не потрібно мати дозвіл SAW. Недоліком є ​​те, що у вас є послуга з повідомленням, поки додаток уже видно користувачеві. Цікаво, чи можна приховати сповіщення, поки додаток уже на першому плані через "Діяльність".


EDIT: Здається, це помилка в 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 також нормально.


Я просто хочу розпочати діяльність на Receive () на AndroidQ. Чи існують шляхи вирішення цього питання без SYSTEM_ALERT_WINDOWдозволу?
doctorram

2
Чому Google завжди робить життя розробників Android життєвим пеклом над простими речами ?!
doctorram

@doctorram Так, у документах написано про різні винятки: developer.android.com/guide/components/activities/… . Я просто вибрав SYSTEM_ALERT_WINDOW, тому що це найпростіше перевірити.
андроїд розробник

Чи ви маєте на увазі під час останнього редагування, що зараз нам не потрібно використовувати жодних обхідних шляхів, які ви згадали, для збереження тривог після видалення програми зі списку останнього ??
Pradeepkumar Reddy

Я хочу запускати фрагмент коду щодня між 6 та 7 ранку у фоновому режимі, навіть якщо додаток видалено зі списку останніх. Я повинен використовувати WorkManager або AlarmManager ?? Спробував наступний код для моєї скриньки, і він не працював. У чому проблема з кодом нижче? Calendar.setTimeInMillis (System.currentTimeMillis ()); Calendar.set (Календар.HOUR_OF_DAY, 6); alarmManager.setInexactRepeating (AlarmManager.RTC_WAKEUP, Calendar.getTimeInMillis (), AlarmManager.INTERVAL_DAY, очікуючий намір);
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 взагалі не отримує наміру (або близько часу)? Це перший крок ... Також, чи AlarmManagerCompat вже не пропонує цей самий код? Ви пробували це за допомогою тестів, які я написав, включаючи видалення програми з останніх завдань? Чи можете ви, будь ласка, показати весь код? Може поділитися на Github?
андроїд розробник

@androiddeveloper оновив відповідь.
Максим Іванов

Досі, здається, не працює. Ось зразок проекту: ufile.io/6qrsor7o . Спробуйте на Android 10 (емулятор також у порядку), встановіть будильник і видаліть програму з останніх завдань. Якщо ви не видаляєте останні завдання, він працює добре і спрацьовує через 10 секунд.
андроїд розробник

Я також оновив питання, щоб мати посилання на звіт про помилку, який включає зразок проекту та відео, тому що я думаю, що це помилка, оскільки я не бачу інших причин для цього.
андроїд розробник

0

Я знаю, що це не ефективно, але може відповідати точності 60 секунд.

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

якщо цей приймач широкомовної передачі використовується в сервісі переднього плану, ви можете перевіряти час щохвилини і приймати рішення про прийняття дії.


Якщо я маю службу переднього плану, навіщо мені це потрібно? Я можу просто використати Handler.postDelayed або будь-яке інше подібне рішення, якщо хочу ...
андроїд розробник

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

Я вже спробував це, оскільки я помітив, що жоден інший додаток не робить цього і мені було цікаво, чи може це допомогти. Не працювало. Оновлене запитання.
андроїд розробник

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/isissue/…
андроїд розробник

Я перевірив ваш код на GitHub. Транслятор трансляції працює після того, як додаток буде видалено з прийомів на Moto Z2 Play. Я можу спробувати його на Pixel, але код мені здається нормальним. Примусове зупинення програми видаляє заплановану тривогу, але це станеться з будь-яким додатком, який примусово припиняється.
Юрій Куликов

Я вже неодноразово показував: все, що я роблю після планування, - це видалити з останніх завдань. І я це робив і на емуляторі, і на Pixel 4.
андроїд розробник

Перевірте, чи використовує PendingShowList обхід проблеми, і якщо так, я оновлю відповідь. Можливо, комусь це стане в нагоді.
Юрій Куликов
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.