AppWidget et le risque d'ANR
ANR sur AppWidget
Bon, vous devez commencer à connaître maintenant ce problème d'ANR que nous n'avons pas cessé de croiser avec les activités d'une application.
Et bien, là, c'est pareil. L'application ici n'est pas la notre, puisque les app widgets sont gérés sur l'écran d'accueil. Vous obtiendrez donc un message demandant s'il faut arrêter ou non l'application, voire celui qui gère l'écran d'accueil lui-même !
Heu...pas bon du tout là !
Il faudra donc à tout prix éviter les traitements longs (encore une fois) lorsque la méthode updateAppWidget est lancée !
Essayez avec un simple Thread.sleep (20000) pour simuler un traitement long avec l'exemple donné pour les bases d'un appWidget, vous verrez, mais ne fermer rien, attendez que cela se termine ! De plus, pendant ce long laps de temps, votre écran d'accueil ne répondra plus à vos solicitations !
Voici ce que vous pouvez obtienir:
Alors comment faire pour éviter cela ?
Lancer un service pour mettre à jour toutes les instances AppWidget d'un coup
Besoin d'un service ? Et oui, la création d'un service va nous permettre de nous sortir de ce problème !
Vous allez lancer un service depuis votre app widget afin qu'il prenne en charge ces traitements longs. Mais lui aussi ne pourra pas traiter directement ces traitements longs, car lui aussi risquerait un ANR. Par contre, il sera en mesure de lancer un thread qui pourra prendre son temps pour effectuer ces traitements.
Reste un problème, qui n'en sera évidemment plus en quelques secondes: remonter les majs de l'app widget suivant les résultats des traitements longs. En effet, si ces traitements sont lancés pour un appWidget, c'est certainement que celui-ci va devoir afficher des informations en rapport avec les résultats de ces traitements, sinon, cela ne servirait à rien !
Pour pouvoir modifier "à distance" l'appWidget, vous aurez besoin d'une instance du manager d'appWidget sur votre contexte actuel:
static AppWidgetManager getInstance(Context context), qui donne:
AppWidgetManager manager = AppWidgetManager.getInstance(this);
C'est en effet lui qui va gérer la maj à l'écran du contenu de l'appWidget grâce à l'une de ces méthodes updateAppWidget (avec l'une d'elles déjà vue).
void updateAppWidget(ComponentName provider, RemoteViews views)
Ici, nous indiquerons que nous voulons que le manager d'appWidget maj toutes les vues des instances de notre AppWidget.
Pour y parvenir, nous lui indiquerons le package et le nom de la classe de notre AppWidget sous forme de ComponentName, donc en lui passant :
// Récupération d'une instance ComponentName sur le package et
la classe MonAppWidget
componentNameSurAppWidget = new ComponentName(this, MonAppWidget.class);
// Récupération du layout utilisé dans l'appWidget
views = new RemoteViews(context.getPackageName(), R.layout.widget_layout);
// Mettre à jour les vues de toutes les instances de notre AppWidget
appWidgetManager.updateAppWidget(componentNameSurAppWidget, views);
Evidemment, avant de mettre à jour les vues, il faudra positionner les nouvelles informations dans les widgets ! Sinon, rien ne changera dans les vues !
Exemple de code
L'AppWidget MonWidget maj pour gérer le service:
Le service nommé ServiceUpdateAppWidget
Le service utilisant AsyncTask pour lancer le traitement long...
La méthode
updateAppWidget de la classe AppWidget MonWidget ne met pas à jour les
mêmes views que celui du service. Donc tout fonctionne. Mais si le service
et cette méthode devait mettre à jour les mêmes views, seules
les maj updateAppWidget de la classe AppWidget MonWidget seraient prises en
compte. En effet, le service demande au manager d'AppWidget de mettre à
jour les views. Celui-ci va donc redéclencher la méthode updateAppWidget
!
Le layout
et AndroidManifest maj pour inclure le nouveau service
Pour le reste, les codes sont inchangés...
pour mon_widget_meta.xml et WidgetConfigureActivity.
Lancement d'un service pour cibler les maj sur certains AppWidget
Avec l'exemple précédent, les traitements longs étaient lancés une fois, le service ordonnait ensuite la maj de toutes les instances avec le résultat obtenu des traitements longs.
Mais il peut aussi arriver que vous ayez besoin de personnaliser les résultats. Dans ce cas, lors du lancement de la méthode startCommande du service, il faudra passer des informations via les extra/data de l'intention. Au moins une information sera nécessaire, l'ID de l'AppWidget pour qu'il soit ensuite le seul à recevoir les informations !
Voici un exemple
MonAppWidget:
La différence par rapport au précédent exemple étant que le service sera déclenchée dans la boucle for du traitement des instances d'AppWidget. Vous en profiterez ainsi pour transmettre l'id de l'appWidget au service.
Puis le service:
Ici, disparaissent les références à ComponentName qui ne servent en effet plus, puisque l'on connait la cible !
De plus, utilisation de la méthodes updateAppWidget (déjà vue) avec l'id de l'appWidget cette fois-ci.
Bien sûr, vous profiterez dans votre cas de l'Id et d'autres informations pour personnaliser aussi le traitement à lancer.
Pour le reste, aucun changement...