J'essaie de démarrer un IntentService dans mon récepteur BOOT_COMPLETED, mais en Android O (API 26), j'obtiens:
Java.lang.RuntimeException:
Java.lang.IllegalStateException:
Not allowed to start service Intent { act=intent.action.update cmp=packageName.services.OwnService }:
app is in background
(Le message est sur une seule ligne, mais de cette façon, il est plus facile à lire)
Comment puis-je procéder correctement?
Voici quelques options que j'ai décrites dans n article de blog :
Votre BroadcastReceiver
qui reçoit la diffusion ACTION_BOOT_COMPLETED
Pourrait appeler startForegroundService()
au lieu de startService()
lorsque allumé Android 8.0+:
import Android.content.BroadcastReceiver;
import Android.content.Context;
import Android.content.Intent;
import Android.os.Build;
public class OnBootReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
Intent i=new Intent(context, TestIntentService.class);
if (Build.VERSION.SDK_INT>=Build.VERSION_CODES.O) {
context.startForegroundService(i);
}
else {
context.startService(i);
}
}
}
Notez que cela fonctionne, dans une certaine mesure, même si votre service n'appelle pas réellement startForeground()
. Vous disposez d'un laps de temps pour vous déplacer pour appeler startForeground()
, "comparable à l'intervalle ANR pour ce faire". Si votre travail est supérieur à une milliseconde mais inférieur à quelques secondes, vous pouvez ignorer l'appel Notification
et startForeground()
. Cependant, vous obtiendrez une erreur dans LogCat:
E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.commonsware.myapplication, PID: 5991
Android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground()
at Android.app.ActivityThread$H.handleMessage(ActivityThread.Java:1775)
at Android.os.Handler.dispatchMessage(Handler.Java:105)
at Android.os.Looper.loop(Looper.Java:164)
at Android.app.ActivityThread.main(ActivityThread.Java:6541)
at Java.lang.reflect.Method.invoke(Native Method)
at com.Android.internal.os.Zygote$MethodAndArgsCaller.run(Zygote.Java:240)
at com.Android.internal.os.ZygoteInit.main(ZygoteInit.Java:767)
Bien sûr, si cela ne vous dérange pas d'avoir un Notification
brièvement, vous êtes invités à utiliser startForeground()
as Android attend de vous, auquel cas vous pouvez faire un travail d'arrière-plan normalement, mais avec une entrée apparaissant dans l'ombre de notification de l'utilisateur.
BroadcastReceiver
propose goAsync()
depuis le niveau d'API 11. Cela permet à votre récepteur de travailler à partir du thread principal de l'application, de sorte que vous pouvez vous débarrasser complètement de IntentService
et déplacer votre code dans le BroadcastReceiver
. Vous n'avez toujours que le délai d'expiration ANR pour travailler, mais vous ne lierez pas votre thread d'application principal. C'est mieux que la première solution de contournement, dans la mesure où il a la même limitation de temps mais évite l'erreur désagréable. Cependant, cela nécessite une certaine quantité de retouches.
Si votre travail prend plus de quelques secondes et vous voulez éviter le Notification
, vous pouvez modifier votre code pour implémenter un JobService
et travailler avec JobScheduler
. Cela a l'avantage supplémentaire de ne vous donner le contrôle que lorsque d'autres critères sont remplis (par exemple, il existe une connexion Internet utilisable). Cependant, non seulement cela nécessite une réécriture, mais JobScheduler
n'est disponible que sur Android 5.0+, donc si votre minSdkVersion
est inférieur à 21, vous besoin d'une autre solution sur les anciens appareils.
[~ # ~] mise à jour [~ # ~] : Eugen Pechanec a souligné JobIntentService
, qui est un mashup JobService
/IntentService
intéressant.
Vous voudrez peut-être vérifier la section suivante de la documentation sur les changements de comportement Android O --- https://developer.Android.com/preview/features/background.html#services
Il limite désormais le moment où l'application est en mesure de démarrer les services d'arrière-plan.