34 votes

Comment mettre à jour correctement un widget dans Android 8.0 - Oreo - API 26

Imaginons que j'ai un widget pour une application avec targetSDKVersion réglé sur 26. Ce widget met entre 100ms et 10s pour se mettre à jour. La plupart du temps en moins de 1s. Avant Android O, si onUpdate() était appelé sur mon AppWidgetProvider, je pouvais lancer un service en arrière-plan pour mettre à jour ce widget. Cependant, Android O renvoie une IllegalStateException si vous tentez ce comportement. La solution évidente de démarrer un service en premier plan semble être une mesure extrême pour quelque chose qui sera fait en moins de 10s 99% du temps.

Solutions possibles

  • Démarrer un service en premier plan pour mettre à jour le widget. Importuner l'utilisateur avec une notification qui disparaîtra en 10 secondes.
  • Utiliser JobScheduler pour planifier un travail le plus rapidement possible. Votre widget peut être mis à jour ou non pendant un certain temps.
  • Essayer de faire le travail dans un récepteur de diffusion. Bloquez le thread UI pour d'autres applications. Beurk.
  • Essayer de travailler dans le récepteur de widget. Bloquez le thread UI pour d'autres applications. Beurk.
  • Abuser de GCM pour exécuter un service en arrière-plan. Beaucoup de travail et cela semble bricolé.

Je n'aime personnellement aucune des solutions ci-dessus. J'espère que je passe à côté de quelque chose.

(Encore plus frustrant est que mon application est déjà chargée en mémoire par le système appelant onUpdate(). Je ne vois pas en quoi charger mon application en mémoire pour appeler onUpdate(), mais ensuite ne pas donner à mon application 1s pour mettre à jour le widget en dehors du thread UI ne sauvegarde la batterie de personne.)

1 votes

Avez-vous trouvé un moyen plus propre de le faire? Je suis également confronté à l'IllegalStateException

11voto

CommonsWare Points 402670

Vous ne précisez pas quel est le mécanisme de déclenchement de la mise à jour. Vous semblez préoccupé par la latence ("Votre widget peut ou non être mis à jour pendant un certain temps"), donc je vais supposer que votre préoccupation est liée à l'interaction de l'utilisateur avec le widget de l'application, comme appuyer sur un bouton.

Utilisez JobScheduler pour planifier un travail le plus rapidement possible. Votre widget peut ou non être mis à jour pendant un certain temps.

C'est une variante de "utiliser JobIntentService", qui est à ma connaissance la solution recommandée pour ce type de scénario.

D'autres options incluent :

  • Utilisez getForegroundService() avec PendingIntent. Avec cela, vous vous engagez effectivement à ce que votre service appelle startForeground() dans le délai ANR. Si le travail prend plus de quelques secondes, appelez startForeground() pour veiller à ce qu'Android ne s'irrite pas. Cela devrait minimiser le nombre de fois où la notification en premier plan apparaît. Et, si l'utilisateur a appuyé sur un bouton et que vous êtes toujours occupé à travailler quelques secondes plus tard, vous voulez probablement afficher une notification ou faire quelque chose pour informer l'utilisateur que ce qu'il a demandé est toujours en cours.

  • Utilisez goAsync() sur BroadcastReceiver, pour effectuer un travail dans le contexte du récepteur sans bloquer le thread d'application principal. Je n'ai pas essayé cela avec Android 8.0+, donc vos résultats peuvent varier.

0 votes

Merci pour la réponse! Je précise que le mécanisme de déclenchement est onUpdate() appelé sur l'AppWidgetProvider. Le système appelle onUpdate lorsque l'écran d'accueil a besoin d'être rafraîchi, par exemple après un redémarrage ou un basculement entre 20 applications, ce qui force l'écran d'accueil à sortir de la mémoire. Je ne pense pas que ces informations modifient votre réponse cependant. Je vais tester un JobIntentService et voir comment cela se comporte. Merci!

1 votes

@Justin: "Je précise que le mécanisme de déclenchement est onUpdate() appelé sur l'AppWidgetProvider" - Pour ce que ça vaut, ce n'est pas ce que je voulais dire par le mécanisme de déclenchement. Les événements que vous avez cités (par exemple, le redémarrage) sont un déclencheur, tout comme le passage du temps si vous utilisez updatePeriodMillis. Et, rien ne vous empêche de créer un PendingIntent lié à un bouton dans le widget de l'application qui finit par être routé vers onUpdate(). Si votre préoccupation était updatePeriodMillis, vous pourriez abandonner cela et utiliser JobScheduler pour des mises à jour périodiques. Pour les autres déclencheurs, je recommanderais d'essayer les techniques décrites dans ma réponse.

2 votes

Mon widget ne montrait parfois pas les réponses des mises à jour après le passage à JobIntentService. En mettant des informations de journalisation, j'ai pu voir que, dans un cas, le travail était retardé de 7 minutes, toutes les mises à jour du widget des sept dernières minutes étaient affichées en une seule fois. J'ai accepté la réponse, mais je dois continuer à essayer d'autres approches, car JobIntentService n'est clairement pas destiné à quoi que ce soit lié à l'interface utilisateur.

4voto

Kasım Özdemir Points 4028

Vous pouvez utiliser WorkManager pour mettre à jour un widget. Utilisez WorkManager sur les appareils avec API 14+. Vous devez remplacer fun onReceive(context: Context?, intent: Intent?) de cette manière:

val ACTION_AUTO_UPDATE: String = "auto_update";

override fun onReceive(context: Context?, intent: Intent?) {
    super.onReceive(context, intent)
    if(intent?.action.equals(ACTION_AUTO_UPDATE))
    {
        val appWidgetManager = AppWidgetManager.getInstance(context)
        val thisAppWidgetComponentName = ComponentName(context!!.getPackageName(), javaClass.name)
        val appWidgetIds = appWidgetManager.getAppWidgetIds(thisAppWidgetComponentName)
        for (appWidgetId in appWidgetIds) {
            // mettre à jour le widget
        }
    }
}

Et vous devriez créer PeriodicWorkRequest. Vous devez l'utiliser pour un travail récurrent. Le travail périodique a un intervalle minimum de 15 minutes. Nous mettons en file d'attente le periodicWork lorsque le widget est activé:

override fun onEnabled(context: Context) {
    val periodicWorkRequest = PeriodicWorkRequest.Builder(YourWorker::class.java, 15, TimeUnit.MINUTES).build()
    WorkManager.getInstance(context).enqueueUniquePeriodicWork("YourWorker", ExistingPeriodicWorkPolicy.REPLACE,periodicWorkRequest)
}

Et annulez-le lorsque le widget est désactivé:

override fun onDisabled(context: Context) {
    WorkManager.getInstance(context).cancelAllWork()
}

Enfin, nous créons la classe de travailleur:

class YourWorker(ctx: Context, params: WorkerParameters) : Worker(ctx, params) {
var context : Context? = null

init {
    context = ctx
}

override fun doWork(): Result {
    val alarmIntent = Intent(context, YourWidget::class.java)
    alarmIntent.action = YourWidget().ACTION_AUTO_UPDATE
    context?.sendBroadcast(alarmIntent)
    return Result.success()
}

Si vous voulez utiliser WorkerManager, ajoutez à build.gradle implementation 'androidx.work:work-runtime:2.3.1'

Vous pouvez trouver l'exemple ici.

0 votes

1) L'échantillon ne contient rien lié au WorkManager dans les AppWidgetProviders. 2) L'utilisation de WorkManager.getInstance(context) dans AppWidgetProvider entraînera une IllegalStateException sur certains appareils. Parce que le contexte pourrait ne pas être le contexte de l'application et WorkManager n'a probablement pas été initialisé dans ce contexte. 3) onEnabled/onDisabled() ne contribuera pas à la mise à jour des widgets pour onReceive()

0 votes

Utilisez getApplicationContext() dans votre classe YourWorker au lieu de stocker le deuxième champ contexte

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X