Comment définir une alarme à programmer à une heure exacte après toutes les dernières restrictions sur Android?

27

Remarque: J'ai essayé diverses solutions qui sont écrites ici sur StackOverflow (exemple ici ). Veuillez ne pas fermer cela sans vérifier si votre solution à partir de ce que vous avez trouvé fonctionne en utilisant le test que j'ai écrit ci-dessous.

Contexte

Il existe une exigence sur l'application, que l'utilisateur définit un rappel à planifier à une heure spécifique, donc lorsque l'application est déclenchée à ce moment, elle fait quelque chose de minuscule en arrière-plan (juste une opération de requête DB), et affiche un simple notification, pour parler du rappel.

Dans le passé, j'utilisais un code simple pour définir quelque chose à planifier à une heure relativement spécifique:

            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
    }
}

Usage:

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

Le problème

J'ai maintenant testé ce code sur des émulateurs sur de nouvelles versions d'Android et sur Pixel 4 avec Android 10, et il ne semble pas se déclencher, ou peut-être qu'il se déclenche très longtemps après ce que je lui ai fourni. Je suis bien conscient du comportement terrible que certains OEM ont ajouté à la suppression des applications des tâches récentes, mais celle-ci se trouve à la fois sur les émulateurs et sur le périphérique Pixel 4 (stock).

J'ai lu dans les documents sur la définition d'une alarme, qu'elle a été restreinte pour les applications afin qu'elle ne se produise pas trop souvent, mais cela n'explique pas comment définir une alarme à un moment précis, et cela n'explique pas comment se fait de Google app de l' horloge réussit à le faire.

Non seulement cela, mais d'après ce que je comprends, cela dit que les restrictions devraient être appliquées en particulier pour l'état de faible puissance de l'appareil, mais dans mon cas, je n'avais pas cet état, à la fois sur l'appareil et sur les émulateurs. J'ai réglé les alarmes pour qu'elles se déclenchent dans environ une minute à partir de maintenant.

Voyant que de nombreuses applications de réveil ne fonctionnent plus comme avant, je pense qu'il manque quelque chose dans les documents. Un exemple de telles applications est l' application Timely populaire qui a été achetée par Google mais n'a jamais reçu de nouvelles mises à jour pour gérer les nouvelles restrictions, et maintenant les utilisateurs veulent la récupérer. . Cependant, certaines applications populaires fonctionnent bien, comme celle-ci .

Ce que j'ai essayé

Pour tester le fait que l'alarme fonctionne, j'effectue ces tests lorsque j'essaie de déclencher l'alarme dans une minute à partir de maintenant, après avoir installé l'application pour la première fois, le tout pendant que l'appareil est connecté au PC (pour voir les journaux):

  1. Testez lorsque l'application est au premier plan, visible par l'utilisateur. - a pris 1-2 minutes.
  2. Test lorsque l'application a été envoyée en arrière-plan (en utilisant le bouton d'accueil, par exemple) - a pris environ 1 minute
  3. Testez quand la tâche de l'application a été supprimée des tâches récentes. - J'ai attendu plus de 20 minutes et je n'ai pas vu l'alarme se déclencher, écrivant dans les journaux.
  4. Comme # 3, mais aussi éteignez l'écran. Ce serait probablement pire ...

J'ai essayé d'utiliser les choses suivantes, tout ne fonctionne pas:

  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. combinaison de l'un des éléments ci-dessus, avec:

    if (VERSION.SDK_INT >= VERSION_CODES.KITKAT) alarmManager.setWindow(AlarmManager.RTC_WAKEUP, 0, 60 * 1000L, pendingIntent)

  5. J'ai essayé d'utiliser un service au lieu de BroadcastReceiver. Également essayé sur un processus différent.

  6. J'ai essayé de faire en sorte que l'application soit ignorée de l'optimisation de la batterie (n'a pas aidé), mais comme d'autres applications n'en ont pas besoin, je ne devrais pas l'utiliser non plus.

  7. Essayé en utilisant ceci:

            if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP)
                alarmManager.setAlarmClock(AlarmManager.AlarmClockInfo(timeToTrigger, pendingIntent), pendingIntent)
            AlarmManagerCompat.setExactAndAllowWhileIdle(alarmManager, AlarmManager.RTC_WAKEUP, timeToTrigger, pendingIntent)
  1. J'ai essayé d'avoir un service qui aura un déclencheur de onTaskRemoved , pour replanifier l'alarme là-bas, mais cela n'a pas aidé non plus (le service a bien fonctionné cependant).

Quant à l'application Horloge de Google, je n'ai rien vu de spécial à part qu'elle montre une notification avant d'être déclenchée, et je ne la vois pas non plus dans la section "non optimisée" de l'écran des paramètres d'optimisation de la batterie.

Voyant que cela semble être un bug, j'ai signalé à ce sujet ici , y compris un exemple de projet et une vidéo pour montrer le problème.

J'ai vérifié plusieurs versions de l'émulateur, et il semble que ce comportement soit parti de l'API 27 (Android 8.1 - Oreo). En regardant les documents , je ne vois pas que AlarmManager soit mentionné, mais à la place, il a été écrit sur divers travaux d'arrière-plan.

Questions

  1. Comment définissons-nous quelque chose à déclencher à une heure relativement exacte de nos jours?

  2. Comment se fait-il que les solutions ci-dessus ne fonctionnent plus? Suis-je en train de manquer quelque chose? Autorisation? Peut-être que je suis censé utiliser un Worker à la place? Mais cela ne signifierait-il pas que cela pourrait ne pas se déclencher du tout?

  3. Comment l'application "Horloge" de Google surmonte-t-elle tout cela et se déclenche-t-elle de toute façon à l'heure exacte, toujours, même si elle a été déclenchée il y a une minute? Est-ce uniquement parce que c'est une application système? Que faire si elle est installée en tant qu'application utilisateur, sur un appareil qui ne l'a pas intégré?

Si vous dites que c'est parce que c'est une application système, j'ai trouvé une autre application qui peut déclencher une alarme deux fois en 2 minutes, ici , bien que je pense qu'elle utilise parfois un service de premier plan.

EDIT: créé un petit dépôt Github pour essayer des idées, ici .


EDIT: a finalement trouvé un échantillon qui est à la fois open-source et n'a pas ce problème. Malheureusement, c'est très complexe et j'essaie toujours de comprendre ce qui le rend si différent (et quel est le code minimal que je devrais ajouter à mon POC) qui permet à ses alarmes de rester programmées après la suppression de l'application des tâches récentes

développeur android
la source
ça fait longtemps que j'ai travaillé sur le service (je ne suis même pas développeur professionnel à suggérer), mais je peux vous suggérer d'éviter alarmManager pour les cas comme régler l'alarme en dessous de 5 minutes, car en raison des restrictions Android après un service de temps fonctionnant dans le backend, il est appelé toutes les 5 minutes ou plus, pas moins de 5 minutes. Au lieu de cela, j'ai utilisé Handler. Et pour faire fonctionner mon service continue en arrière-plan, j'ai fait référence [ github.com/fabcira/neverEndingAndroidService]
Blu
Quelles sont donc les restrictions exactes? Quel est le temps minimal garanti qu'un déclencheur fonctionnera dans un délai relativement précis?
développeur Android
Je ne me souviens pas des restrictions exactes mais quand je travaillais dessus, j'ai googlé pendant des jours similaires pour éviter que le service d'arrière-plan ne soit tué automatiquement. Et à partir d'une observation personnelle, j'ai remarqué un problème sur Samsung, Xiaomi, etc., vous ne pouvez pas appeler alarmManger dans un intervalle de 5 minutes, j'ai eu un service de téléchargement de données implémenté en utilisant alarmManger qui se déclenche toutes les 1 min, mais cela a déçu notre client qui s'est plaint du service ne fonctionne pas du tout. Pour les émulateurs, cela fonctionne bien.
Blu
Je sais que vous ne pouvez pas démarrer l'activité à partir de l'arrière-plan dans Android Q, mais cela ne ressemble pas à votre cas.
marcinj
@ greeble31 J'ai essayé maintenant. Quelle solution voyez-vous fonctionner? Pour une raison quelconque, je n'arrive toujours pas à le faire fonctionner. J'ai défini l'alarme, je supprime l'application des tâches récentes et je ne vois pas l'alarme se déclencher, même si l'écran est allumé et que l'appareil est connecté à un chargeur. Cela se produit à la fois sur un appareil réel (Pixel 4 avec Android 10) et sur un émulateur (API 27 par exemple). Est-ce que ça marche pour toi? Pouvez-vous s'il vous plaît partager le code complet? Peut-être à Github?
développeur Android

Réponses:

4

Nous n'avons rien à faire.

Une fois que votre application n'est pas sur liste blanche, elle sera toujours supprimée une fois supprimée des applications récentes.

Parce que le fabricant d'équipement d'origine (OME) viole constamment la conformité Android .

Donc, si votre application n'est pas ajoutée à la liste blanche de la fabrication de l'appareil, elle ne déclenchera aucun travail en arrière-plan, même des alarmes - au cas où votre application serait supprimée des applications récentes.

Vous pouvez trouver une liste d'appareils avec ce comportement ici AUSSI vous pourriez trouver une solution secondaire, Cependant, cela ne fonctionnera pas bien.

Ibrahim Ali
la source
Je connais bien ce problème des OEM chinois. Mais comme je l'ai écrit, cela se produit même sur émulateur et appareil Pixel 4. Ce n'est pas un OEM chinois qui l'a fait en tant que tel. Veuillez vérifier l'émulateur et / ou l'appareil Pixel. Le problème existe là aussi. Définissez une alarme, supprimez l'application des tâches récentes et vérifiez que l'alarme n'est pas déclenchée. Je vois cela comme un bug et signalé ici (il comprend une vidéo et un exemple de projet si vous voulez essayer): issuetracker.google.com/issues/149556385. J'ai mis à jour ma question pour qu'elle soit claire. La question est de savoir comment une application a réussi.
développeur Android
@androiddeveloper Je pense que cela devrait fonctionner sur Emulator .. Quel émulateur avez-vous?
Ibrahim Ali
J'ai aussi cru, jusqu'à ce que j'essaye. Essayez-le sur l'API 29, par exemple, de ce qu'Android Studio a à offrir. Je suis sûr que la même chose se produira également sur les versions un peu plus anciennes.
développeur android
4

Trouvé une solution de contournement étrange (exemple ici ) qui semble fonctionner pour toutes les versions, y compris même Android R:

  1. Demandez l'autorisation SAW déclarée dans le manifeste:
      <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />

Sur Android R, vous devrez également l'avoir. Sur avant, il ne semble pas que cela devait être accordé, vient d'être déclaré. Je ne sais pas pourquoi cela a changé sur R, mais je peux dire que SAW pourrait être nécessaire comme solution possible pour démarrer les choses en arrière-plan, comme écrit ici pour Android 10.

  1. Avoir un service qui détectera quand les tâches ont été supprimées, et quand il le fera, ouvrir une fausse activité qui ne fera que se fermer:
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()
    }
}

Vous pouvez également rendre cette activité presque invisible pour l'utilisateur en utilisant ce thème:

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

Malheureusement, c'est une solution de contournement étrange. J'espère trouver une meilleure solution à cela.

La restriction parle de démarrer l'activité, donc mon idée actuelle est que si je démarre un service de premier plan pendant une fraction de seconde, cela aidera également, et pour cela, je n'aurai même pas besoin de l'autorisation SAW.

EDIT: OK, j'ai essayé avec un service de premier plan (exemple ici ), et cela n'a pas fonctionné. Aucune idée pourquoi une activité fonctionne mais pas un service. J'ai même essayé de reprogrammer l'alarme là-bas et j'ai essayé de laisser le service rester un peu, même après avoir reprogrammé. A également essayé un service normal, mais bien sûr, il a fermé immédiatement, car la tâche a été supprimée, et cela n'a pas fonctionné du tout (même si j'ai créé un thread à exécuter en arrière-plan).

Une autre solution possible que je n'ai pas essayée est d'avoir un service de premier plan pour toujours, ou du moins jusqu'à ce que la tâche soit supprimée, mais c'est un peu bizarre et je ne vois pas les applications que j'ai mentionnées l'utiliser.

EDIT: essayé de faire fonctionner un service de premier plan avant la suppression de la tâche de l'application, et pendant un peu après, et l'alarme fonctionnait toujours. A également essayé d'avoir ce service pour être celui en charge de l'événement supprimé par la tâche, et de se fermer immédiatement quand il se produit, et cela a toujours fonctionné (exemple ici ). L'avantage de cette solution de contournement est que vous n'avez pas du tout besoin de l'autorisation SAW. L'inconvénient est que vous disposez d'un service avec une notification alors que l'application est déjà visible pour l'utilisateur. Je me demande s'il est possible de masquer la notification alors que l'application est déjà au premier plan via l'activité.


EDIT: Il semble que ce soit un bug sur Android Studio (signalé ici , y compris les vidéos comparant les versions). Lorsque vous lancez l'application à partir de la version problématique que j'ai essayée, cela pourrait entraîner la suppression des alarmes.

Si vous lancez l'application à partir du lanceur, cela fonctionne très bien.

Voici le code actuel pour régler l'alarme:

        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)

Je n'ai même pas besoin d'utiliser "pendingShowList". L'utilisation de null est également correcte.

développeur android
la source
Je veux simplement lancer une activité surReceive () sur AndroidQ. Y a-t-il des solutions de contournement pour cela sans la SYSTEM_ALERT_WINDOWpermission?
doctorram
2
Pourquoi Google fait-il toujours de la vie des développeurs Android un enfer vivant sur des choses simples?
doctorram
@doctorram Oui, il est écrit sur les documents concernant les différentes exceptions: developer.android.com/guide/components/activities/… . Je viens de choisir SYSTEM_ALERT_WINDOW car c'est le plus simple à tester.
développeur Android
De votre dernière modification, voulez-vous dire que nous n'avons plus à utiliser les solutions de contournement que vous avez mentionnées pour persister les alarmes après avoir supprimé l'application de la liste récente ??
user3410835
Je veux exécuter un morceau de code tous les jours entre 6 h et 7 h en arrière-plan même si l'application est supprimée de la liste récente. Je devrais utiliser WorkManager ou AlarmManager ?? J'ai essayé le code suivant pour mon cas d'utilisation et cela n'a pas fonctionné. Quel est le problème avec le code ci-dessous? 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. Assurez-vous que l'intention que vous diffusez est explicite et a le Intent.FLAG_RECEIVER_FOREGROUNDdrapeau.

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. À utiliser setExactAndAllowWhileIdle()lors du ciblage de l'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. Démarrez votre alarme en tant que service de premier plan:

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. Et n'oubliez pas les autorisations:
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
Maksim Ivanov
la source
Pourquoi est-ce important pour le service, si le BroadcastReceiver lui-même n'obtient pas du tout l'intention (ou presque)? C'est la première étape ... De plus, AlarmManagerCompat n'offre-t-il pas déjà ce même code? Avez-vous essayé cela en utilisant les tests que j'ai écrits, y compris la suppression de l'application des tâches récentes? Pouvez-vous s'il vous plaît montrer le code entier? Peut-être partager sur Github?
développeur Android
@androiddeveloper a mis à jour la réponse.
Maksim Ivanov
Ne semble toujours pas fonctionner. Voici un exemple de projet: ufile.io/6qrsor7o . Veuillez essayer Android 10 (l'émulateur est également ok), régler l'alarme et supprimer l'application des tâches récentes. Si vous ne supprimez pas des tâches récentes, cela fonctionne bien et se déclenche après 10 secondes.
développeur Android
J'ai également mis à jour la question pour avoir un lien vers un rapport de bogue qui comprend un exemple de projet et une vidéo, car je pense que c'est un bogue car je ne vois aucune autre raison à cela.
développeur android
0

Je sais que ce n'est pas efficace mais pourrait être plus cohérent avec une précision de 60 secondes.

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

si ce récepteur de diffusion est utilisé à l'intérieur d'un service de premier plan, vous pouvez vérifier l'heure toutes les minutes et prendre la décision de prendre une mesure.

Dur
la source
Si j'ai un service de premier plan, pourquoi en aurais-je besoin? Je pourrais simplement utiliser un Handler.postDelayed ou toute autre solution similaire si je le souhaite ...
développeur Android
0

Je pense que vous pouvez demander à l'utilisateur de définir l'autorisation afin qu'il désactive le mode d'économie d'énergie et avertir l'utilisateur que s'il ne l'utilise pas, les heures exactes ne seront pas atteintes.

Voici le code pour le demander:

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
la source
J'ai déjà essayé cela car j'ai remarqué qu'aucune autre application ne le fait et j'étais curieux de savoir si cela pouvait aider. Ça n'a pas marché. Question mise à jour.
développeur Android
0

Je suis l'auteur du projet open source que vous avez évoqué dans votre question ( simple réveil) .

Je suis surpris que l'utilisation de AlarmManager.setAlarmClock n'ait pas fonctionné pour vous, car mon application fait exactement cela. Le code se trouve dans le fichier AlarmSetter.kt. Voici un extrait:

  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)

Fondamentalement, cela n'a rien de spécial, assurez-vous simplement que l'intention a une action et une classe cible, qui est un récepteur de diffusion dans mon cas.

Yuriy Kulikov
la source
Malheureusement, cela n'a pas fonctionné. Voilà ce que j'ai essayé. Voir les fichiers ici: github.com/yuriykulikov/AlarmClock/issues/…
développeur Android
J'ai vérifié votre code sur GitHub. Le récepteur de diffusion fonctionne après que l'application a été supprimée des recents sur Moto Z2 Play. Je peux l'essayer sur un Pixel, mais le code me semble correct. Forcer l'arrêt de l'application supprime l'alarme programmée, mais cela se produira pour toute application forcée.
Yuriy Kulikov
Je l'ai déjà montré plusieurs fois: tout ce que je fais après la planification est de supprimer des tâches récentes. Et je l'ai fait à la fois sur l'émulateur et le Pixel 4.
développeur Android
Veuillez vérifier si l'utilisation de pendingShowList contourne le problème et si oui, je mettrai à jour la réponse. Ce sera peut-être utile pour quelqu'un.
Yuriy Kulikov