Des rappels Android fiables en 2026 : WorkManager, alarmes exactes et les nouvelles règles de batterie
Comment publier en 2026 des rappels Android qui se déclenchent vraiment — WorkManager vs AlarmManager, SCHEDULE_EXACT_ALARM, POST_NOTIFICATIONS et les bizarreries des constructeurs qui piègent encore.
Une application de rappels sur Android ne fonctionne que si le rappel s’affiche. Ça paraît évident — jusqu’à ce que vous en publiiez une. Vous découvrez alors le mode doze, les seaux de veille des applications (app standby buckets), les optimiseurs de batterie des constructeurs, la permission SCHEDULE_EXACT_ALARM, la permission d’exécution POST_NOTIFICATIONS, les restrictions sur les services au premier plan, et le charmant fait que Samsung et Xiaomi tueront silencieusement votre travail planifié pour « économiser la batterie », peu importe la justesse de votre code.
J’ai construit Hydrame, une application de suivi d’hydratation dont toute la proposition de valeur est « rappelle-moi au bon moment, en douceur ». J’ai passé plus de temps que je ne voudrais l’admettre à forcer la pile de rappels d’Android à devenir fiable. Cet article est la version 2026 du manuel qui finit par fonctionner.
Le problème des rappels en un paragraphe
Android est agressif quand il s’agit de tuer le travail en arrière-plan, et il le doit — sans contrôle, chaque application réveillerait la radio toutes les quelques minutes et votre batterie serait morte avant le déjeuner. Le résultat pour les applications de rappels légitimes est un empilement d’API qui se chevauchent et se comportent chacune un peu différemment selon les versions et les surcouches des constructeurs. Le travail consiste à choisir la bonne primitive pour votre niveau de « doit être exact » et à accepter que le reste passe par des tests sur des appareils réels.
Les deux primitives qui comptent en 2026 sont :
WorkManager— pour le travail inexact et différable. Vérifications périodiques, cas d’usage « rappelle-moi à un moment dans l’heure qui vient ». Consciente de la batterie, regroupée, s’exécute même après un redémarrage.AlarmManager.setExactAndAllowWhileIdle— pour des notifications exactes, visibles par l’utilisateur, à un moment précis. La coûteuse. À utiliser avec parcimonie et uniquement lorsqu’un décalage de 15 minutes ferait paraître la fonctionnalité cassée.
Si votre rappel doit se déclencher exactement à 19h30, il vous faut une alarme exacte. Si « aux alentours de 19h30 » convient, utilisez WorkManager. La plupart des applications choisissent par défaut la mauvaise.
Trois permissions à ne pas rater
En 2026, trois permissions d’exécution/d’installation décident si l’utilisateur voit un jour un rappel :
POST_NOTIFICATIONS(exécution, Android 13+). Sans elle, votre notification n’apparaît tout simplement pas. Demandez-la au bon moment, pas au premier lancement.SCHEDULE_EXACT_ALARM(Android 12+, restreinte à partir de 13+). Requise poursetExactAndAllowWhileIdle. Sur Android 14+, elle n’est accordée qu’après que l’utilisateur l’a explicitement activée dans les paramètres système ; l’application doit y renvoyer l’utilisateur.USE_EXACT_ALARM(Android 13+, usage spécial). Pour les applications où les alarmes exactes sont au cœur du produit (réveils, rappels d’agenda, hydratation). Play demande une justification.
Le manifeste de Hydrame ressemble à ceci :
<uses-permission android:name="android.permission.POST_NOTIFICATIONS" />
<uses-permission android:name="android.permission.USE_EXACT_ALARM" />
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM" />
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
RECEIVE_BOOT_COMPLETED est celle que la plupart des gens oublient — sans elle, vos alarmes planifiées disparaissent au prochain redémarrage. Reprogrammez-les depuis un BroadcastReceiver sur ACTION_BOOT_COMPLETED.
Demander POST_NOTIFICATIONS au bon moment
Ne demandez pas au premier lancement. Le taux d’acceptation est nettement plus élevé si vous demandez à un moment qui correspond à la valeur : juste au moment où l’utilisateur active son premier rappel. Un flux réel :
@Composable
fun EnableReminderButton(onEnabled: () -> Unit) {
val context = LocalContext.current
val launcher = rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestPermission(),
) { granted ->
if (granted) onEnabled()
}
FilledButton(onClick = {
if (Build.VERSION.SDK_INT >= 33) {
val state = ContextCompat.checkSelfPermission(
context,
Manifest.permission.POST_NOTIFICATIONS,
)
if (state == PackageManager.PERMISSION_GRANTED) onEnabled()
else launcher.launch(Manifest.permission.POST_NOTIFICATIONS)
} else {
onEnabled()
}
}) { Text("Enable reminders") }
}
Deux autres choses que je fais toujours :
- Une justification de seconde chance. Si l’utilisateur refuse, ne redemandez pas immédiatement. La prochaine fois qu’il tente d’activer un rappel, affichez un petit message en ligne avec un bouton qui ouvre l’écran des paramètres de l’application, afin qu’il puisse l’activer lui-même.
- Détecter l’état « refusé avec ne-plus-demander » en mémorisant le dernier refus dans DataStore —
shouldShowRequestPermissionRationalen’est pas fiable à lui seul pour cela.
Inexact : le travail périodique WorkManager
Pour un rappel où « à un moment dans la matinée » est acceptable, WorkManager est la bonne réponse ennuyeuse :
class DailyHydrationCheckWorker(
appContext: Context,
params: WorkerParameters,
) : CoroutineWorker(appContext, params) {
override suspend fun doWork(): Result {
scheduleTodayReminders()
return Result.success()
}
}
fun scheduleDailyHydrationCheck(context: Context) {
val request = PeriodicWorkRequestBuilder<DailyHydrationCheckWorker>(
repeatInterval = 1, repeatIntervalTimeUnit = TimeUnit.DAYS,
)
.setConstraints(
Constraints.Builder()
.setRequiresBatteryNotLow(true)
.build(),
)
.setInitialDelay(secondsUntil(6, 0).toLong(), TimeUnit.SECONDS)
.build()
WorkManager.getInstance(context).enqueueUniquePeriodicWork(
"daily_hydration_check",
ExistingPeriodicWorkPolicy.UPDATE,
request,
)
}
Quelques pièges classiques :
- Période minimale de 15 minutes. Un
PeriodicWorkRequestne s’exécutera pas plus souvent que toutes les 15 minutes, quoi que vous demandiez. Pour une planification sous les 15 minutes, utilisez un travail ponctuel qui s’enchaîne lui-même. - Pas de timing exact. WorkManager planifie à l’intérieur d’une fenêtre. Si vous voulez « à 8h00m00 pile », ce n’est pas la bonne primitive.
- Battery-not-low est bienveillant envers vos utilisateurs et envers le système ; activez-la sauf raison contraire.
Exact : AlarmManager.setExactAndAllowWhileIdle
Quand le rappel doit se déclencher à un moment précis — par exemple, un utilisateur a dit « rappelle-moi à 19h30 » et il le pense vraiment — il vous faut une alarme exacte. Le motif :
fun scheduleExactReminder(
context: Context,
triggerAtMillis: Long,
requestCode: Int,
) {
val am = context.getSystemService(AlarmManager::class.java)
if (Build.VERSION.SDK_INT >= 31 && !am.canScheduleExactAlarms()) {
// Send the user to the system page to grant the permission.
context.startActivity(
Intent(Settings.ACTION_REQUEST_SCHEDULE_EXACT_ALARM)
.setData(Uri.parse("package:${context.packageName}"))
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK),
)
return
}
val intent = Intent(context, ReminderReceiver::class.java)
.putExtra("requestCode", requestCode)
val pending = PendingIntent.getBroadcast(
context, requestCode, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE,
)
am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, triggerAtMillis, pending)
}
setExactAndAllowWhileIdle est celle qui respecte le mode doze. N’utilisez setAlarmClock que si votre application est vraiment un réveil — elle affiche la puce du prochain réveil dans l’interface système et obtient la priorité la plus élevée.
Receiver + service au premier plan pour le moment lourd
Quand l’alarme se déclenche, vous disposez de quelques secondes de temps d’éveil pour publier une notification. Pour un simple rappel « ding ! », c’est suffisant :
class ReminderReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent) {
// Post the notification synchronously.
val nm = context.getSystemService(NotificationManager::class.java)
nm.notify(
intent.getIntExtra("requestCode", 0),
buildHydrationNotification(context),
)
// Re-schedule the next one.
scheduleNext(context)
}
}
Si vous devez faire du vrai travail lors de l’alarme (synchronisation, téléchargement), promouvez immédiatement un service au premier plan. N’essayez pas d’être malin — le système vous révoquera en un instant si vous traînez dans le receiver.
Redémarrages et changements de fuseau horaire
Deux événements cassent silencieusement une implémentation naïve :
- Redémarrage. Les alarmes sont effacées. Écoutez
ACTION_BOOT_COMPLETEDet reprogrammez à partir de votre état persistant. - Changement de fuseau horaire. Un rappel de 7h ne devrait pas se déclencher à 7h heure locale avant que l’utilisateur déménage, puis à 7h heure d’Istanbul après. Écoutez
ACTION_TIMEZONE_CHANGEDet recalculez.
Un seul receiver gère les deux :
<receiver android:name=".BootAndTimezoneReceiver" android:exported="false">
<intent-filter>
<action android:name="android.intent.action.BOOT_COMPLETED"/>
<action android:name="android.intent.action.TIMEZONE_CHANGED"/>
<action android:name="android.intent.action.MY_PACKAGE_REPLACED"/>
</intent-filter>
</receiver>
MY_PACKAGE_REPLACED est celui que déclenchent les mises à jour de l’application — votre code est frais, mais vos alarmes ont disparu. Reprogrammez-y aussi.
Les optimiseurs constructeurs — les tueurs silencieux
En 2026, le pire coupable restant est toujours la liste « Applications en veille » de Samsung, avec l’autostart et l’économiseur de batterie de Xiaomi juste derrière. Deux défenses pratiques :
- Une bannière de diagnostic dans l’application. Si aucune notification ne s’est déclenchée depuis 24 heures alors que l’utilisateur a des rappels activés, affichez une petite bannière avec un lien profond « Corriger ça » vers la bonne page de paramètres du constructeur. Stack Overflow et le projet Don’t Kill My App maintiennent ces liens profonds à jour ; mettez-les en cache localement.
- Ne demandez pas la liste blanche de batterie avec empressement. Cela effraie les utilisateurs et Play voit ça d’un mauvais œil. Réservez-la à l’utilisateur qui a déjà constaté un rappel manqué.
Le modèle qui a fini par fonctionner
Après trois itérations sur Hydrame, l’architecture qui a fini par paraître fiable :
- Un seul travail périodique
WorkManagers’exécute chaque nuit. Il lit le planning de l’utilisateur et insère les alarmes exactes des 24 heures suivantes. - Chaque alarme exacte publie une notification via un
BroadcastReceiver. Le receiver est bête — pas d’appel à la base de données, pas de réseau. - Les receivers de redémarrage + changement de fuseau + mise à jour de l’application déclenchent manuellement le même travail nocturne pour recréer le planning.
- Une métrique de battement de cœur (horodatage de la dernière notification réussie, stocké dans DataStore) alimente la bannière dans l’application « vos rappels peuvent être tués par votre téléphone ».
C’est plus de câblage que ce que j’aurais écrit si j’avais fait confiance à mon premier réflexe (« utilise juste WorkManager »). Mais chaque pièce existe parce que quelque chose s’est cassé silencieusement sans elle.
Tester sur de vrais téléphones, encore
Je répéterai la même phrase de chaque article Android que j’écris : vous ne pouvez pas tester des rappels fiables sur le seul émulateur. Procurez-vous un Samsung bon marché, un Xiaomi bon marché et un Pixel. Réglez un rappel pour dans 30 minutes. Verrouillez le téléphone et laissez-le sur le bureau. Revenez et voyez ce qui s’est déclenché et ce qui ne s’est pas déclenché. Cette seule expérience vaut plus que n’importe quel article de blog.
Celui-ci compris. Allez le tester.
// À lire aussi
D’autres notes du journal
Bien faire les actions de notification Android : marquer une dose comme prise sans ouvrir l'application
Un guide pratique des boutons d'action de notification Android — PendingIntent, la restriction du trampoline d'activité, goAsync() et la mise à jour sûre de l'état Room.
Bâtir une routine de prise de médicaments que vous suivrez vraiment
Un guide pratique sur l'observance médicamenteuse — pourquoi on manque ses doses, comment fonctionne réellement le minutage des rappels, et comment construire une routine qui survit à une mauvaise semaine.
Une routine d'hydratation qui ne dépend pas de la volonté
La plupart des conseils sur l'hydratation se résument à « buvez plus d'eau » et espèrent que la volonté comblera le reste. Voici une routine construite autour de déclencheurs et de rappels doux à la place.