Android 8.0: java.lang.IllegalStateException: non autorisé à démarrer le service


360

Au lancement de l'application, l'application démarre le service qui doit effectuer une tâche réseau. Après avoir ciblé le niveau 26 de l'API, mon application ne parvient pas à démarrer le service sur Android 8.0 en arrière-plan.

Causé par: java.lang.IllegalStateException: non autorisé à démarrer le service Intention {cmp = my.app.tt / com.my.service}: l'application est en arrière-plan uid UidRecord {90372b1 u0a136 Procès inactif CEM: 1 seq (0,0 , 0)}

si je comprends bien: limites d'exécution en arrière - plan

La méthode startService () lève désormais une exception IllegalStateException si une application ciblant Android 8.0 essaie d'utiliser cette méthode dans une situation où elle n'est pas autorisée à créer des services d'arrière-plan.

" dans une situation où ce n'est pas permis " - qu'est-ce que cela signifie réellement ?? Et comment y remédier. Je ne veux pas définir mon service comme "premier plan"


4
Cela signifie que vous ne pouvez pas démarrer un service lorsque votre application est en arrière
Tim

22
cela n'a rien à voir avec les autorisations d'exécution
Tim

11
Utilisez startForegroundService()au lieu de startService().
frogatto

2
Vous pouvez essayer d'utiliser targetSdkVersion 25 mais compiler avec compileSdkVersion 26. De cette façon, vous pouvez utiliser de nouvelles classes d'Android 8 et la plus récente bibliothèque de support, mais votre application ne sera pas limitée par les limites d'exécution en arrière-plan.
Kacper Dziubek

2
@KacperDziubek Cela devrait fonctionner mais c'est une solution temporaire car il sera nécessaire de cibler le SDK26 à l'automne 2018.
RightHandedMonkey

Réponses:


194

Les situations autorisées sont une liste blanche temporaire dans laquelle le service d'arrière-plan se comporte comme avant Android O.

Dans certaines circonstances, une application d'arrière-plan est placée sur une liste blanche temporaire pendant plusieurs minutes. Lorsqu'une application est sur la liste blanche, elle peut lancer des services sans limitation et ses services d'arrière-plan sont autorisés à s'exécuter. Une application est placée sur la liste blanche lorsqu'elle gère une tâche visible par l'utilisateur, telle que:

  • Gestion d'un message Firebase Cloud Messaging (FCM) hautement prioritaire.
  • Réception d'une diffusion, telle qu'un message SMS / MMS.
  • Exécution d'un PendingIntent à partir d'une notification.
  • Démarrer un VpnService avant que l'application VPN ne se propage au premier plan.

Source: https://developer.android.com/about/versions/oreo/background.html

En d'autres termes, si votre service d'arrière-plan ne répond pas aux exigences de la liste blanche, vous devez utiliser le nouveau JobScheduler . C'est fondamentalement la même chose qu'un service en arrière-plan, mais il est appelé périodiquement au lieu de s'exécuter en arrière-plan en continu.

Si vous utilisez un IntentService, vous pouvez passer à un JobIntentService. Voir la réponse de @ kosev ci-dessous .


Je reçois un plantage après avoir voulu démarrer le service juste après avoir reçu un message GCM de priorité "élevée". J'utilise toujours GCM: "com.google.android.gms: play-services-gcm: 11.4.2", pas 'com.google.firebase: firebase-messaging: 11.4.2'. Je ne suis pas sûr que cela compte ...
Alex Radzishevsky

"C'est fondamentalement la même chose qu'un service en arrière-plan, mais il est appelé périodiquement au lieu de s'exécuter en arrière-plan en continu." - Je ne sais pas ce que vous entendez par là, car les services Android n'ont jamais été exécutés en continu. Ils démarrent, courent, puis s'arrêtent.
Melllvar

2
Le FirebaseInstanceIdService et sa onTokenRefreshméthode sont-ils un message FCM de haute priorité?
Cord Rehn

@phnmnn non, GCMTaskService ne suit pas vraiment FCM donc ils ne fonctionnent pas.
Abhinav Upadhyay

4
Ne devriez-vous pas utiliser WorkManager (ici: developer.android.com/topic/libraries/architecture/workmanager ) au lieu de JobScheduler ou autres? Je veux dire ceci: youtu.be/IrKoBFLwTN0
développeur Android

257

J'ai une solution. Pour les appareils antérieurs à 8.0, vous devez simplement utiliser startService(), mais pour les appareils postérieurs à 7.0, vous devez utiliser startForgroundService(). Voici un exemple de code pour démarrer le service.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        context.startForegroundService(new Intent(context, ServedService.class));
    } else {
        context.startService(new Intent(context, ServedService.class));
    }

Et dans la classe de service, veuillez ajouter le code ci-dessous pour la notification:

@Override
public void onCreate() {
    super.onCreate();
    startForeground(1,new Notification());
}

Où O est la version Android 26.


9
Un service de premier plan est quelque chose que l'utilisateur connaîtra et qui a besoin d'une notification. Il sera également ANR s'il fonctionne trop longtemps. Ce n'est donc pas vraiment une réponse appropriée si l'application fonctionne déjà en arrière-plan.
SimonH

80
Il existe une ContextCompat.startForegroundService(...)bibliothèque de support qui peut être utilisée à la place.
jayeffkay

37
Ce n'est pas une solution.
JacksOnF1re

17
Je conviens également que ce n'est pas une solution. C'est une solution de contournement et cela aide, mais les limites d'arrière-plan dans Oreo ont été introduites pour une raison. Contourner ces limites de cette façon n'est certainement pas la bonne approche (même si cela fonctionne). La meilleure façon est d'utiliser JobScheduler (reportez-vous à la réponse acceptée).
Vratislav Jindra

6
Je ne pense pas que ce sera une bonne expérience utilisateur si vous devez afficher une notification de premier plan vide. Compte tenu du fait que vous devez. - Android 8.0 introduit la nouvelle méthode startForegroundService () pour démarrer un nouveau service au premier plan. Une fois que le système a créé le service, l'application dispose de cinq secondes pour appeler la méthode startForeground () du service pour afficher la notification visible par l'utilisateur du nouveau service. Si l'application n'appelle pas startForeground () dans le délai imparti, le système arrête le service et déclare que l'application est ANR.
heeleeaz

85

La meilleure façon est d'utiliser JobIntentService qui utilise le nouveau JobScheduler pour Oreo ou les anciens services s'ils ne sont pas disponibles.

Déclarez dans votre manifeste:

<service android:name=".YourService"
         android:permission="android.permission.BIND_JOB_SERVICE"/>

Et dans votre service, vous devez remplacer onHandleIntent par onHandleWork:

public class YourService extends JobIntentService {

    public static final int JOB_ID = 1;

    public static void enqueueWork(Context context, Intent work) {
        enqueueWork(context, YourService.class, JOB_ID, work);
    }

    @Override
    protected void onHandleWork(@NonNull Intent intent) {
        // your code
    }

}

Ensuite, vous démarrez votre service avec:

YourService.enqueueWork(context, new Intent());


Comment pouvez-vous appeler une méthode non statique à l'intérieur d'une méthode statique? Pouvez-vous s'il vous plaît expliquer?
Maddy

@Maddy enqueueWork(...)est également une méthode statique.
hgoebl

2
Où appelleriez-vous YourService.enqueueWork (context, new Intent ()); ? Du récepteur de diffusion?
TheLearner

Je ne pense pas que ce soit la solution la plus simple. Voir mon commentaire ci-dessous sur WorkManager. Il utilise JobIntentService le cas échéant, mais il a beaucoup moins de plaque de chaudière.
TALE

36

Si le service s'exécute dans un thread d'arrière-plan en s'étendant IntentService, vous pouvez le remplacer IntentServiceparJobIntentService celui fourni dans le cadre de la bibliothèque de support Android

L'avantage d'utiliser JobIntentServiceest qu'il se comporte comme unIntentService sur des périphériques pré-O et sur O et supérieur, il le distribue comme un travail

JobSchedulerpeut également être utilisé pour des travaux périodiques / à la demande. Mais, assurez-vous de gérer la compatibilité descendante car l' JobSchedulerAPI n'est disponible qu'à partir de l'API 21


1
Le problème avec JobIntentService est qu'Android peut planifier votre travail de façon plutôt arbitraire, et il ne peut pas être démarré implicitement sans bricoler, contrairement à IntentService. Voir stackoverflow.com/questions/52479262/…
kilokahn

15

Dans Oreo, Android a défini des limites aux services d'arrière-plan .

Pour améliorer l'expérience utilisateur, Android 8.0 (API niveau 26) impose des limitations sur ce que les applications peuvent faire lorsqu'elles s'exécutent en arrière-plan.

Si vous avez toujours besoin d'un service en cours d'exécution, vous pouvez utiliser le service de premier plan.

Limitations du service d'arrière-plan: bien qu'une application soit inactive, son utilisation des services d'arrière-plan est limitée. Cela ne s'applique pas aux services de premier plan, qui sont plus visibles pour l'utilisateur.

Vous pouvez donc faire un service de premier plan . Vous devrez montrer une notification à l'utilisateur lorsque votre service est en cours d'exécution. Voir cette réponse (il y en a beaucoup d'autres)

Une solution si -

vous ne voulez pas de notification pour votre service?

Vous pouvez effectuer des tâches périodiques, 1. il démarre votre service, 2. le service fera son travail et 3. s'arrête. Par cela, votre application ne sera pas considérée comme une décharge de la batterie.

Vous pouvez utiliser des tâches périodiques avec Alarm Manager , Job Scheduler , Evernote-Jobs ou Work Manager .

J'ai testé le service pour toujours avec Work-Manager.


WorkManager semble être la meilleure façon de procéder, en supposant que le travail ne doit pas être exécuté immédiatement. Il est rétrocompatible avec l'API 14, en utilisant JobScheduler sur les appareils avec l'API 23+ et une combinaison de BroadcastReceiver + AlarmManager sur les appareils avec l'API 14-22
James Allen

l'élément clé de WorkManager est que WorkManager est destiné à des tâches qui sont reportables, c'est-à-dire qu'il n'est pas nécessaire de les exécuter immédiatement
touhid udoy

13

Oui, c'est parce que vous ne pouvez plus démarrer les services en arrière-plan sur l'API 26. Vous pouvez donc démarrer ForegroundService au-dessus de l'API 26.

Vous devrez utiliser

ContextCompat.startForegroundService(...)

et publier une notification lors du traitement de la fuite.


1
L'OP a spécifiquement déclaré qu'il ne voulait pas comme premier plan. Cela devrait être mis sous forme de commentaire ou dans le cadre d'une réponse plus complète.
Ricardo A.

7

Comme @kosev l'a dit dans sa réponse, vous pouvez utiliser JobIntentService. Mais j'utilise une solution alternative - j'attrape IllegalStateException et démarre le service en premier plan. Par exemple, cette fonction démarre mon service:

@JvmStatic
protected fun startService(intentAction: String, serviceType: Class<*>, intentExtraSetup: (Intent) -> Unit) {
    val context = App.context
    val intent = Intent(context, serviceType)
    intent.action = intentAction
    intentExtraSetup(intent)
    intent.putExtra(NEED_FOREGROUND_KEY, false)

    try {
        context.startService(intent)
    }
    catch (ex: IllegalStateException) {
        intent.putExtra(NEED_FOREGROUND_KEY, true)
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            context.startForegroundService(intent)
        }
        else {
            context.startService(intent)
        }
    }
}

et quand je traite l'intention, je fais une telle chose:

override fun onHandleIntent(intent: Intent?) {
    val needToMoveToForeground = intent?.getBooleanExtra(NEED_FOREGROUND_KEY, false) ?: false
    if(needToMoveToForeground) {
        val notification = notificationService.createSyncServiceNotification()
        startForeground(notification.second, notification.first)

        isInForeground = true
    }

    intent?.let {
        getTask(it)?.process()
    }
}

J'aime votre solution try catch. Pour moi , il est une solution parce que parfois context.startServicefonctionne en arrière - plan - parfois - cela ressemble à la seule meilleure façon sinon vous devez mettre en œuvre plus de code dans votre classe principale extending Applicationet implementing ActivityLifecycleCallbackset de garder trace si l'application est au premier plan ou arrière - plan et commencer à votre intention en conséquence.
Pierre

Cette exception peut-elle être interceptée?
thecr0w

5

D'après les notes de version de Firebase , ils indiquent que la prise en charge d'Android O a été publiée pour la première fois dans 10.2.1 (bien que je recommande d'utiliser la version la plus récente).

veuillez ajouter de nouvelles dépendances de messagerie firebase pour android O

compile 'com.google.firebase:firebase-messaging:11.6.2'

mettez à niveau les services google play et les référentiels google si nécessaire.


Cela ne répond pas à la question, ni la question n'a rien à voir avec Firebase. Il faut le mettre en commentaire.
Ricardo A.

5

Si une intention fonctionnait bien auparavant lorsque l'application est en arrière-plan, ce ne sera plus le cas d'Android 8 et supérieur. Se référant uniquement à l'intention qui doit effectuer un traitement lorsque l'application est en arrière-plan.

Les étapes ci-dessous doivent être suivies:

  1. L'intention mentionnée ci-dessus doit être utilisée à la JobIntentServiceplace de IntentService.
  2. La classe qui s'étend JobIntentServicedevrait implémenter la onHandleWork(@NonNull Intent intent)méthode - et devrait avoir en dessous de la méthode, qui invoquera la onHandleWorkméthode:

    public static void enqueueWork(Context context, Intent work) {
        enqueueWork(context, xyz.class, 123, work);
    }
  3. Appelez enqueueWork(Context, intent)de la classe où votre intention est définie.

    Exemple de code:

    Public class A {
    ...
    ...
        Intent intent = new Intent(Context, B.class);
        //startService(intent); 
        B.enqueueWork(Context, intent);
    }

La classe ci-dessous étendait auparavant la classe Service

Public Class B extends JobIntentService{
...

    public static void enqueueWork(Context context, Intent work) {
        enqueueWork(context, B.class, JobId, work);
    }

    protected void onHandleWork(@NonNull Intent intent) {
        ...
        ...
    }
}
  1. com.android.support:support-compatest nécessaire pour JobIntentService- j'utilise 26.1.0 V.

  2. Le plus important est de s'assurer que la version des bibliothèques Firebase est au moins activée 10.2.1, j'ai eu des problèmes avec 10.2.0- si vous en avez!

  3. Votre manifeste doit avoir l'autorisation ci-dessous pour la classe Service:

    service android:name=".B"
    android:exported="false"
    android:permission="android.permission.BIND_JOB_SERVICE"

J'espère que cela t'aides.


4

Je vois beaucoup de réponses qui recommandent d'utiliser simplement un ForegroundService. Pour utiliser un ForegroundService, une notification doit lui être associée. Les utilisateurs verront cette notification. Selon la situation, ils peuvent être ennuyés par votre application et la désinstaller.

La solution la plus simple consiste à utiliser le nouveau composant d'architecture appelé WorkManager. Vous pouvez consulter la documentation ici: https://developer.android.com/topic/libraries/architecture/workmanager/

Vous venez de définir votre classe de travail qui étend Worker.

public class CompressWorker extends Worker {

    public CompressWorker(
        @NonNull Context context,
        @NonNull WorkerParameters params) {
        super(context, params);
    }

    @Override
    public Worker.Result doWork() {

        // Do the work here--in this case, compress the stored images.
        // In this example no parameters are passed; the task is
        // assumed to be "compress the whole library."
        myCompress();

        // Indicate success or failure with your return value:
        return Result.SUCCESS;

        // (Returning RETRY tells WorkManager to try this task again
        // later; FAILURE says not to try again.)
    }
}

Ensuite, vous planifiez quand vous voulez l'exécuter.

    OneTimeWorkRequest compressionWork = 
        new OneTimeWorkRequest.Builder(CompressWorker.class)
            .build();
    WorkManager.getInstance().enqueue(compressionWork);

Facile! Il existe de nombreuses façons de configurer les travailleurs. Il prend en charge les travaux récurrents et vous pouvez même faire des choses complexes comme le chaînage si vous en avez besoin. J'espère que cela t'aides.


3
Actuellement, WorkManager est toujours alpha.
pzulw

3
05 mars 2019 - Version stable de WorkManager 1.0.0.
phnmnn

devrait utiliser WorkManager au lieu d'utiliser interservice ou JobIntentService
sivaBE35

1
WorkManager is intended for tasks that are deferrable—that is, not required to run immediately... cela pourrait être plus simple, cependant, mon application a besoin d'un service d'arrière-plan qui exécute immédiatement les demandes des utilisateurs!
Quelqu'un quelque part

Si vous souhaitez que la tâche soit terminée immédiatement, vous devez utiliser un service de premier plan. L'utilisateur verra une notification et saura que vous travaillez. Consultez la documentation si vous avez besoin d'aide pour décider quoi utiliser. Ils ont un assez bon guide pour le traitement en arrière-plan. developer.android.com/guide/background
TALE

4

Solution alternative en utilisant JobScheduler, il peut démarrer le service en arrière-plan dans un intervalle de temps régulier.

Faites d'abord une classe nommée Util.java

import android.app.job.JobInfo;
import android.app.job.JobScheduler;
import android.content.ComponentName;
import android.content.Context;

public class Util {
// schedule the start of the service every 10 - 30 seconds
public static void schedulerJob(Context context) {
    ComponentName serviceComponent = new ComponentName(context,TestJobService.class);
    JobInfo.Builder builder = new JobInfo.Builder(0,serviceComponent);
    builder.setMinimumLatency(1*1000);    // wait at least
    builder.setOverrideDeadline(3*1000);  //delay time
    builder.setRequiredNetworkType(JobInfo.NETWORK_TYPE_UNMETERED);  // require unmetered network
    builder.setRequiresCharging(false);  // we don't care if the device is charging or not
    builder.setRequiresDeviceIdle(true); // device should be idle
    System.out.println("(scheduler Job");

    JobScheduler jobScheduler = null;
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.M) {
        jobScheduler = context.getSystemService(JobScheduler.class);
    }
    jobScheduler.schedule(builder.build());
   }
  }

Ensuite, créez la classe JobService nommée TestJobService.java

import android.app.job.JobParameters;
import android.app.job.JobService;
import android.widget.Toast;

  /**
   * JobService to be scheduled by the JobScheduler.
   * start another service
   */ 
public class TestJobService extends JobService {
@Override
public boolean onStartJob(JobParameters params) {
    Util.schedulerJob(getApplicationContext()); // reschedule the job
    Toast.makeText(this, "Bg Service", Toast.LENGTH_SHORT).show();
    return true;
}

@Override
public boolean onStopJob(JobParameters params) {
    return true;
  }
 }

Après cette classe BroadCast Receiver nommée ServiceReceiver.java

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;

 public class ServiceReceiver extends BroadcastReceiver {
 @Override
public void onReceive(Context context, Intent intent) {
    Util.schedulerJob(context);
 }
}

Mettre à jour le fichier manifeste avec le code de classe de service et de récepteur

<receiver android:name=".ServiceReceiver" >
        <intent-filter>
            <action android:name="android.intent.action.BOOT_COMPLETED" />
        </intent-filter>
    </receiver>
    <service
        android:name=".TestJobService"
        android:label="Word service"
        android:permission="android.permission.BIND_JOB_SERVICE" >

    </service>

Lanceur main_intent gauche vers le fichier mainActivity.java qui est créé par défaut, et les modifications dans le fichier MainActivity.java sont

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    Util.schedulerJob(getApplicationContext());
  }
 }

WOOAAH !! Le service en arrière-plan démarre sans le service de premier plan


2

Si vous exécutez votre code sur 8.0, l'application se bloquera. Commencez donc le service au premier plan. Si en dessous de 8.0 utilisez ceci:

Intent serviceIntent = new Intent(context, RingtonePlayingService.class);
context.startService(serviceIntent);

Si au-dessus ou 8.0 alors utilisez ceci:

Intent serviceIntent = new Intent(context, RingtonePlayingService.class);
ContextCompat.startForegroundService(context, serviceIntent );

Il est recommandé d'utiliser uniquement les services de premier plan dans les cas où l'utilisateur doit savoir qu'un service est en cours d'exécution. L'exemple typique est pour jouer de la musique en arrière-plan. Il existe d'autres cas qui ont du sens, mais vous ne devriez pas simplement convertir tous vos services en services de premier plan. Envisagez de convertir vos services pour utiliser WorkManager à partir des composants architecturaux de Google lorsque vous avez juste besoin de travailler en arrière-plan et d'être assuré qu'il fonctionnera.
TALE

sinon, startForegroundService nécessite une autorisation java.lang.SecurityException: Permission Denial: startForeground from pid=13708, uid=10087 requires android.permission.FOREGROUND_SERVICE. Correction sur stackoverflow.com/a/52382711/550471
Quelqu'un quelque part

1

si vous avez intégré la notification push de messagerie Firebase,

Ajoutez des dépendances de messagerie Firebase nouvelles / mises à jour pour Android O (Android 8.0), en raison des limites d'exécution en arrière-plan .

compile 'com.google.firebase:firebase-messaging:11.4.0'

mettez à niveau les services google play et les référentiels google si nécessaire.

Mise à jour:

 compile 'com.google.firebase:firebase-messaging:11.4.2'

0

Utilisez startForegroundService()au lieu de startService() et n'oubliez pas de créer startForeground(1,new Notification());votre service dans les 5 secondes suivant le démarrage du service.


2
Il semble que la nouvelle Notificaction () ne fonctionne pas à partir d'Android 8.1; Vous devez créer le canal de notification: stackoverflow.com/a/47533338/1048087
Prizoff

0

En raison de votes controversés sur cette réponse (+ 4 / -4 à compter de cette modification), VEUILLEZ REGARDER D'ABORD LES AUTRES RÉPONSES ET UTILISER CELLE-CI UNIQUEMENT COMME DERNIÈRE STATION . Je ne l'ai utilisé qu'une seule fois pour une application de mise en réseau qui s'exécute en tant que root et je suis d'accord avec l'opinion générale que cette solution ne doit pas être utilisée dans des circonstances normales.

Réponse originale ci-dessous:

Les autres réponses sont toutes correctes, mais je voudrais souligner qu'une autre façon de contourner ce problème consiste à demander à l'utilisateur de désactiver les optimisations de batterie pour votre application (ce n'est généralement pas une bonne idée, sauf si votre application est liée au système). Voir cette réponse pour savoir comment demander à désactiver l'optimisation de la batterie sans interdire votre application dans Google Play.

Vous devez également vérifier si les optimisations de batterie sont désactivées dans votre récepteur pour éviter les plantages via:

if (Build.VERSION.SDK_INT < 26 || getSystemService<PowerManager>()
        ?.isIgnoringBatteryOptimizations(packageName) != false) {
    startService(Intent(context, MyService::class.java))
} // else calling startService will result in crash

1
Demander à vos utilisateurs de vous laisser un laissez-passer gratuit en utilisant autant de batterie que possible n'est pas une bonne solution. Pensez à convertir votre code en une solution plus conviviale pour les batteries. Vos utilisateurs vous remercieront.
TALE

5
@TALE Tous les services d'arrière-plan ne peuvent pas être adaptés à la batterie en utilisant JobScheduleret autres. Certaines applications doivent fonctionner à un niveau inférieur à celui des applications de synchronisation classiques. Il s'agit d'une solution alternative lorsque cela ne fonctionne pas.
Mygod

-17

ne pas utiliser dans onStartCommand:

return START_NOT_STICKY

il suffit de le changer en:

return START_STICKY

et ça va marcher

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.