Context.startForegroundService () n'a pas appelé ensuite Service.startForeground ()


258

J'utilise ServiceClass sur Android O OS.

J'ai l'intention d'utiliser le Serviceen arrière-plan.

La documentation Android indique que

Si votre application cible l'API de niveau 26 ou supérieur, le système impose des restrictions sur l'utilisation ou la création de services d'arrière-plan, sauf si l'application elle-même est au premier plan. Si une application doit créer un service de premier plan, elle doit appeler startForegroundService().

Si vous utilisez startForegroundService(), le Servicejette l'erreur suivante.

Context.startForegroundService() did not then call
Service.startForeground() 

Quel est le problème avec ça?


IOW, veuillez fournir un exemple reproductible minimal . Cela inclurait l'intégralité de la trace de la pile Java et le code qui déclenche le crash.
CommonsWare

3
Le bogue est toujours là dans les API 26 et 27 (27.0.3). Les versions Android affectées sont 8.0 et 8.1. Vous pouvez réduire le nombre de plantages en ajoutant startForeground () à la fois à onCreate () et à onStartCommand (), mais les plantages se produiront toujours pour certains utilisateurs. La seule façon de le réparer atm est targetSdkVersion 25 dans votre build.gradle.
Alex


1
J'utilise cette solution de contournement maintenant. fonctionne comme un charme theandroiddeveloper.com/blog/…
Prasad Pawar

1
J'ai le même problème. Je corrige ce problème. J'ai partagé mon implémentation dans ce sujet stackoverflow.com/questions/55894636/…
Beyazid

Réponses:


99

À partir des documents de Google sur les changements de comportement d'Android 8.0 :

Le système permet aux applications d'appeler Context.startForegroundService () même lorsque l'application est en arrière-plan. Cependant, l'application doit appeler la méthode startForeground () de ce service dans les cinq secondes suivant la création du service.

Solution: Appel startForeground()à onCreate()laService que vous utilisezContext.startForegroundService()

Voir aussi: Limites d'exécution en arrière - plan pour Android 8.0 (Oreo)


19
Je l'ai fait dans la onStartCommandméthode, mais j'obtiens toujours cette erreur. J'ai appelé startForegroundService(intent)mon MainActivity. Peut-être que le service est démarré trop lentement. Je pense que la limite de cinq secondes ne devrait pas exister avant de pouvoir promettre que le service est démarré immédiatement.
Kimi Chiu du

29
Une période de 5 secondes n'est certainement pas suffisante, cette exception se produit très souvent dans les sessions de débogage. Je soupçonne que cela arriverait également de temps en temps en mode de libération. Et étant FATAL EXCEPTION, cela plante l'application! J'ai essayé de l'attraper avec Thread.setDefaultUncaughtExceptionHandler (), mais même en l'y obtenant et en ignorant Android, l'application se fige. C'est parce que cette exception est déclenchée à partir de handleMessage (), et la boucle principale se termine effectivement ... à la recherche d'une solution de contournement.
sud

J'ai appelé la méthode Context.startForegroundService () dans le récepteur de diffusion. alors comment gérer dans cette situation? parce que onCreate () n'est pas disponible dans le récepteur de diffusion
Anand Savjani

6
@southerton Je pense que vous devriez l'appeler au onStartCommand()lieu de onCreate, parce que si vous fermez le service et le redémarrez, il pourrait passer onStartCommand()sans appeler onCreate...
développeur Android

1
Nous pouvons vérifier la réponse de l'équipe google ici issuetracker.google.com/issues/76112072#comment56
Prags

82

J'ai appelé ContextCompat.startForegroundService(this, intent)pour démarrer le service

En service onCreate

 @Override
 public void onCreate() {
        super.onCreate();

        if (Build.VERSION.SDK_INT >= 26) {
            String CHANNEL_ID = "my_channel_01";
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                    "Channel human readable title",
                    NotificationManager.IMPORTANCE_DEFAULT);

            ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);

            Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                    .setContentTitle("")
                    .setContentText("").build();

            startForeground(1, notification);
        }
}

48
Moi aussi. Mais je reçois toujours cette erreur de temps en temps. Peut-être que l'Android ne peut pas garantir qu'il appellera le onCreatedans 5 secondes. Ils devraient donc le repenser avant de nous forcer à suivre les règles.
Kimi Chiu

5
J'appelais startForeground dans onStartCommand (), et je recevais parfois cette erreur. Je l'ai déplacé vers onCreate et je ne l'ai plus vu depuis (croisant les doigts).
Tyler

4
Cela crée une notification locale dans Oreo disant "APP_NAME est en cours d'exécution. Appuyez pour fermer ou voir les informations". Comment arrêter d'afficher cette notification?
Artist404

6
Non, l'équipe Android a déclaré qu'il s'agissait d'un comportement voulu. J'ai donc juste repensé mon application. C'est ridicule. Vous devez toujours repenser les applications en raison de ces «comportements prévus». C'est la troisième fois.
Kimi Chiu du

12
La cause principale de ce problème est que le service a été arrêté avant d'être promu au premier plan. Mais l'assertion ne s'est pas arrêtée après la destruction du service. Vous pouvez essayer de reproduire cela en ajoutant StopServiceaprès avoir appelé startForegroundService.
Kimi Chiu

55

Ce problème se produit parce que le framework Android ne peut pas garantir que votre service démarre dans les 5 secondes mais d'autre part, le framework a une limite stricte sur la notification au premier plan doit être déclenchée dans les 5 secondes, sans vérifier si le framework a essayé de démarrer le service .

Il s'agit certainement d'un problème de cadre, mais tous les développeurs confrontés à ce problème ne font pas de leur mieux:

  1. startForegroundune notification doit être dans les deux onCreateet onStartCommand, parce que si votre service est déjà créé et que votre activité tente de le redémarrer, onCreateil ne sera pas appelé.

  2. l'ID de notification ne doit pas être 0 sinon le même crash se produira même si ce n'est pas la même raison.

  3. stopSelfne doit pas être appelé avant startForeground.

Avec tous les 3 ci-dessus, ce problème peut être réduit un peu mais toujours pas un correctif, le vrai correctif ou disons que la solution consiste à rétrograder votre version de SDK cible à 25.

Et notez que très probablement Android P portera toujours ce problème parce que Google refuse même de comprendre ce qui se passe et ne pense pas que ce soit leur faute, lisez # 36 et # 56 pour plus d'informations


9
Pourquoi onCreate et onStartCommand? Pouvez-vous simplement le mettre dans onStartCommand?
rcell

stopSelf ne doit pas être appelé avant startForeground => ou directement après, car il semble annuler le "startForeground" lorsque vous le faites.
Frank

1
J'ai exécuté quelques tests, et même si onCreate n'est pas appelé si le service est déjà créé, vous n'avez pas besoin d'appeler à nouveau startForeground. Par conséquent, vous ne pouvez l'appeler que sur la méthode onCreate et non dans onStartCommand. Ils considèrent probablement la seule instance de service comme étant au premier plan après le premier appel jusqu'à sa fin.
Lxu

J'utilise 0 comme ID de notification de l'appel initial startForeground. Le changer en 1 résout le problème pour moi (j'espère)
mr5

Alors, quelle est la solution?
IgorGanapolsky

35

Je sais que trop de réponses ont déjà été publiées, mais la vérité est que startForegroundService ne peut pas être corrigé au niveau d'une application et vous devez cesser de l'utiliser. Cette recommandation de Google d'utiliser l'API Service # startForeground () dans les 5 secondes après l'appel de Context # startForegroundService () n'est pas quelque chose qu'une application peut toujours faire.

Android exécute de nombreux processus simultanément et il n'y a aucune garantie que Looper appellera votre service cible qui est censé appeler startForeground () dans les 5 secondes. Si votre service cible n'a pas reçu l'appel dans les 5 secondes, vous n'avez pas de chance et vos utilisateurs connaîtront une situation ANR. Dans votre trace de pile, vous verrez quelque chose comme ceci:

Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{1946947 u0 ...MessageService}

main" prio=5 tid=1 Native
  | group="main" sCount=1 dsCount=0 flags=1 obj=0x763e01d8 self=0x7d77814c00
  | sysTid=11171 nice=-10 cgrp=default sched=0/0 handle=0x7dfe411560
  | state=S schedstat=( 1337466614 103021380 2047 ) utm=106 stm=27 core=0 HZ=100
  | stack=0x7fd522f000-0x7fd5231000 stackSize=8MB
  | held mutexes=
  #00  pc 00000000000712e0  /system/lib64/libc.so (__epoll_pwait+8)
  #01  pc 00000000000141c0  /system/lib64/libutils.so (android::Looper::pollInner(int)+144)
  #02  pc 000000000001408c  /system/lib64/libutils.so (android::Looper::pollOnce(int, int*, int*, void**)+60)
  #03  pc 000000000012c0d4  /system/lib64/libandroid_runtime.so (android::android_os_MessageQueue_nativePollOnce(_JNIEnv*, _jobject*, long, int)+44)
  at android.os.MessageQueue.nativePollOnce (MessageQueue.java)
  at android.os.MessageQueue.next (MessageQueue.java:326)
  at android.os.Looper.loop (Looper.java:181)
  at android.app.ActivityThread.main (ActivityThread.java:6981)
  at java.lang.reflect.Method.invoke (Method.java)
  at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:493)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1445)

Si je comprends bien, Looper a analysé la file d'attente ici, a trouvé un "agresseur" et l'a simplement tué. Le système est heureux et sain maintenant, alors que les développeurs et les utilisateurs ne le sont pas, mais puisque Google limite leurs responsabilités au système, pourquoi devraient-ils se soucier des deux derniers? Apparemment non. Pourraient-ils faire mieux? Bien sûr, par exemple, ils auraient pu ouvrir la boîte de dialogue "L'application est occupée", demandant à un utilisateur de décider d'attendre ou de tuer l'application, mais pourquoi, ce n'est pas de sa responsabilité. L'essentiel est que le système soit sain maintenant.

D'après mes observations, cela se produit relativement rarement, dans mon cas, environ 1 crash en un mois pour les utilisateurs 1K. La reproduire est impossible, et même si elle est reproduite, vous ne pouvez rien faire pour la réparer définitivement.

Il y avait une bonne suggestion dans ce fil pour utiliser "bind" au lieu de "start" et puis quand le service est prêt, traiter onServiceConnected, mais encore une fois, cela signifie ne pas utiliser du tout les appels startForegroundService.

Je pense que la bonne et honnête action de la part de Google serait de dire à tout le monde que startForegourndServcie a une déficience et ne doit pas être utilisé.

La question demeure: quoi utiliser à la place? Heureusement pour nous, il existe maintenant JobScheduler et JobService, qui sont une meilleure alternative pour les services de premier plan. C'est une meilleure option, à cause de cela:

Pendant l'exécution d'un travail, le système détient un verrou de réveil au nom de votre application. Pour cette raison, aucune action n'est nécessaire pour garantir que l'appareil reste éveillé pendant la durée du travail.

Cela signifie que vous n'avez plus besoin de vous soucier de la manipulation des verrous de réveil et c'est pourquoi ce n'est pas différent des services de premier plan. Du point de vue de l'implémentation, JobScheduler n'est pas votre service, c'est celui d'un système, il gèrera probablement la file d'attente correctement, et Google ne terminera jamais son propre enfant :)

Samsung est passé de startForegroundService à JobScheduler et JobService dans leur Samsung Accessory Protocol (SAP). C'est très utile lorsque des appareils comme les montres connectées doivent parler à des hôtes comme les téléphones, où le travail doit interagir avec un utilisateur via le thread principal d'une application. Étant donné que les tâches sont publiées par le planificateur dans le thread principal, cela devient possible. Vous devez cependant vous rappeler que le travail s'exécute sur le thread principal et décharger toutes les tâches lourdes sur d'autres threads et tâches asynchrones.

Ce service exécute chaque tâche entrante sur un gestionnaire exécuté sur le thread principal de votre application. Cela signifie que vous devez décharger votre logique d'exécution vers un autre thread / gestionnaire / AsyncTask de votre choix

Le seul écueil du passage à JobScheduler / JobService est que vous devrez refactoriser l'ancien code, et ce n'est pas amusant. J'ai passé les deux derniers jours à faire exactement cela pour utiliser la nouvelle implémentation SAP de Samsung. Je regarderai mes rapports de plantage et je vous ferai savoir si je reverrai les plantage. Théoriquement, cela ne devrait pas se produire, mais il y a toujours des détails dont nous ne sommes peut-être pas conscients.

MISE À JOUR Plus de plantages signalés par le Play Store. Cela signifie que JobScheduler / JobService n'ont pas un tel problème et le passage à ce modèle est la bonne approche pour se débarrasser du problème startForegroundService une fois pour toujours. J'espère que Google / Android le lit et finira par commenter / conseiller / fournir un guide officiel pour tout le monde.

MISE À JOUR 2

Pour ceux qui utilisent SAP et qui demandent comment SAP V2 utilise JobService, l'explication est ci-dessous.

Dans votre code personnalisé, vous devrez initialiser SAP (c'est Kotlin):

SAAgentV2.requestAgent(App.app?.applicationContext, 
   MessageJobs::class.java!!.getName(), mAgentCallback)

Vous devez maintenant décompiler le code de Samsung pour voir ce qui se passe à l'intérieur. Dans SAAgentV2, jetez un œil à l'implémentation de requestAgent et à la ligne suivante:

SAAgentV2.d var3 = new SAAgentV2.d(var0, var1, var2);

where d defined as below

private SAAdapter d;

Accédez maintenant à la classe SAAdapter et recherchez la fonction onServiceConnectionRequested qui planifie un travail à l'aide de l'appel suivant:

SAJobService.scheduleSCJob(SAAdapter.this.d, var11, var14, var3, var12); 

SAJobService n'est qu'une implémentation d'Android'd JobService et c'est celle qui fait la planification des travaux:

private static void a(Context var0, String var1, String var2, long var3, String var5, SAPeerAgent var6) {
    ComponentName var7 = new ComponentName(var0, SAJobService.class);
    Builder var10;
    (var10 = new Builder(a++, var7)).setOverrideDeadline(3000L);
    PersistableBundle var8;
    (var8 = new PersistableBundle()).putString("action", var1);
    var8.putString("agentImplclass", var2);
    var8.putLong("transactionId", var3);
    var8.putString("agentId", var5);
    if (var6 == null) {
        var8.putStringArray("peerAgent", (String[])null);
    } else {
        List var9;
        String[] var11 = new String[(var9 = var6.d()).size()];
        var11 = (String[])var9.toArray(var11);
        var8.putStringArray("peerAgent", var11);
    }

    var10.setExtras(var8);
    ((JobScheduler)var0.getSystemService("jobscheduler")).schedule(var10.build());
}

Comme vous le voyez, la dernière ligne ici utilise Android'd JobScheduler pour obtenir ce service système et planifier un travail.

Dans l'appel requestAgent, nous avons passé mAgentCallback, qui est une fonction de rappel qui recevra le contrôle lorsqu'un événement important se produit. Voici comment le rappel est défini dans mon application:

private val mAgentCallback = object : SAAgentV2.RequestAgentCallback {
    override fun onAgentAvailable(agent: SAAgentV2) {
        mMessageService = agent as? MessageJobs
        App.d(Accounts.TAG, "Agent " + agent)
    }

    override fun onError(errorCode: Int, message: String) {
        App.d(Accounts.TAG, "Agent initialization error: $errorCode. ErrorMsg: $message")
    }
}

MessageJobs ici est une classe que j'ai implémentée pour traiter toutes les demandes provenant d'une smartwatch Samsung. Ce n'est pas le code complet, seulement un squelette:

class MessageJobs (context:Context) : SAAgentV2(SERVICETAG, context, MessageSocket::class.java) {


    public fun release () {

    }


    override fun onServiceConnectionResponse(p0: SAPeerAgent?, p1: SASocket?, p2: Int) {
        super.onServiceConnectionResponse(p0, p1, p2)
        App.d(TAG, "conn resp " + p1?.javaClass?.name + p2)


    }

    override fun onAuthenticationResponse(p0: SAPeerAgent?, p1: SAAuthenticationToken?, p2: Int) {
        super.onAuthenticationResponse(p0, p1, p2)
        App.d(TAG, "Auth " + p1.toString())

    }


    override protected fun onServiceConnectionRequested(agent: SAPeerAgent) {


        }
    }

    override fun onFindPeerAgentsResponse(peerAgents: Array<SAPeerAgent>?, result: Int) {
    }

    override fun onError(peerAgent: SAPeerAgent?, errorMessage: String?, errorCode: Int) {
        super.onError(peerAgent, errorMessage, errorCode)
    }

    override fun onPeerAgentsUpdated(peerAgents: Array<SAPeerAgent>?, result: Int) {

    }

}

Comme vous le voyez, MessageJobs nécessite également la classe MessageSocket que vous devez implémenter et qui traite tous les messages provenant de votre appareil.

En bout de ligne, ce n'est pas si simple et cela nécessite quelques recherches sur les internes et le codage, mais cela fonctionne, et surtout - il ne plante pas.


Bonne réponse, mais il y a un problème majeur, JobIntentServices'exécute immédiatement en IntentServicedessous d'Oreo, mais planifie un travail sur et au-dessus d'Oreo, donc JobIntentServicene démarre pas immédiatement. Plus d'infos
CopsOnRoad

1
@CopsOnRoad C'est très utile et cela commence immédiatement dans mon cas, comme je l'ai écrit, il est utilisé pour les interactions en temps réel entre le téléphone et une smartwatch. Aucun moyen, un utilisateur attendrait 15 minutes pour envoyer des données entre ces deux. Fonctionne très bien et ne plante jamais.
Oleg Gryb

1
Cela a l'air cool, votre réponse en vaudrait la peine si vous pouvez partager un exemple de code, je suis nouveau sur Android, et j'ai trouvé que cela Job...prend du temps pour démarrer et c'est une version avancée de AlarmManager.
CopsOnRoad

2
Je le ferai probablement quand le temps le permettra: je devrai le
découpler du

1
@batmaci - JobService est utilisé par SAP en interne. Voir la mise à jour 2 dans OP pour plus de détails.
Oleg Gryb

32

Votre application se bloque si vous appelez Context.startForegroundService(...), puis appelez Context.stopService(...)avant Service.startForeground(...)est appelé.

J'ai une repro claire ici ForegroundServiceAPI26

J'ai ouvert un bug à ce sujet sur: Google issue tracker

Plusieurs bugs sur ce sujet ont été ouverts et fermés ne seront pas corrigés.

J'espère que le mien avec des étapes de reproduction claires fera la coupe.

Informations fournies par l'équipe Google

Outil de suivi des problèmes de Google Commentaire 36

Ce n'est pas un bug de framework; c'est intentionnel. Si l'application démarre une instance de service avec startForegroundService(), elle doit passer cette instance de service à l'état de premier plan et afficher la notification. Si l'instance de service est arrêtée avantstartForeground() être appelée dessus, cette promesse n'est pas tenue: il s'agit d'un bogue dans l'application.

Concernant # 31 , publier un service que d'autres applications peuvent démarrer directement est fondamentalement dangereux. Vous pouvez atténuer cela un peu en traitant toutes les actions de démarrage de ce service comme nécessitantstartForeground() , bien que ce ne soit évidemment pas ce que vous aviez en tête.

Outil de suivi des problèmes de Google Commentaire 56

Il y a quelques scénarios différents qui mènent au même résultat ici.

Le problème purement sémantique, que c'est simplement une erreur de lancer quelque chose, startForegroundService()mais néglige de le faire passer au premier plan viastartForeground() , n'est que cela: un problème sémantique. C'est traité comme un bug d'application, intentionnellement. L'arrêt du service avant de le faire passer au premier plan est une erreur d'application. C'était le nœud du PO, et c'est pourquoi ce problème a été marqué «fonctionnant comme prévu».

Cependant, il existe également des questions sur la détection parasite de ce problème. C'est est en cours de traitement comme un véritable problème, bien que son être suivi séparément de cette question de bug tracker. Nous ne sommes pas sourds à la plainte.


2
La meilleure mise à jour que j'ai vue est issuetracker.google.com/issues/76112072#comment56 . J'ai réécrit mon code pour utiliser Context.bindService, ce qui évite complètement l'appel problématique à Context.startForegroundService. Vous pouvez voir mon exemple de code sur github.com/paulpv/ForegroundServiceAPI26/tree/bound/app/src/…
swooby

oui, comme je l'ai écrit, bind devrait fonctionner, mais je suis déjà passé à JobServive et je ne changerai rien à moins que celui-ci ne soit trop cassé ':)
Oleg Gryb

20

J'ai fait des recherches à ce sujet pendant quelques jours et j'ai trouvé la solution. Maintenant, dans Android O, vous pouvez définir la limitation d'arrière-plan comme ci-dessous

Le service qui appelle une classe de service

Intent serviceIntent = new Intent(SettingActivity.this,DetectedService.class);
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.O) {
    SettingActivity.this.startForegroundService(serviceIntent);
} else {
    startService(serviceIntent);
}

et la classe de service devrait être comme

public class DetectedService extends Service { 
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        return START_STICKY;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        int NOTIFICATION_ID = (int) (System.currentTimeMillis()%10000);
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            startForeground(NOTIFICATION_ID, new Notification.Builder(this).build());
        }


        // Do whatever you want to do here
    }
}

3
L'avez-vous essayé dans une application qui compte au moins plusieurs milliers d'utilisateurs? J'ai essayé, certains utilisateurs ont toujours des problèmes de plantage.
Alex

Non non, j'utilise le code exact et je ne vois pas les clients se bloquer.
Ahmad Arslan

Combien d'utilisateurs avez-vous? J'ai vu ~ 10-30 + plantages par jour (avec l'erreur) dans l'application qui comptait 10 000 utilisateurs par jour. Bien sûr, cela ne se produit que pour les utilisateurs d'Android 8.0 et d'Android 8.1, dans le cas où targetSdkVersion est 27. Tous les problèmes disparaissent à 0 plantages juste après que j'ai défini targetSdkVersion à 25.
Alex

seulement 2200 utilisateurs: - / mais n'a pas eu de plantage
Ahmad Arslan

1
@Jeeva, pas vraiment. Atm, j'utilise targetSdkVersion 25 avec compileSdkVersion 27. Il semble que la meilleure façon après beaucoup d'expériences .... J'espère qu'ils termineront developer.android.com/topic/libraries/architecture/… avant août 2018 parce que android-developers.googleblog .com / 2017/12 /…
Alex

16

J'ai un widget qui fait des mises à jour relativement fréquentes lorsque l'appareil est réveillé et je voyais des milliers de plantages en quelques jours seulement.

Le déclencheur du problème

J'ai même remarqué le problème même sur mon Pixel 3 XL alors que je n'aurais pas pensé que l'appareil aurait beaucoup de charge. Et tous les chemins de code étaient couverts startForeground(). Mais j'ai réalisé que dans de nombreux cas, mon service fait le travail très rapidement.Je crois que le déclencheur de mon application était que le service se terminait avant que le système ne parvienne à afficher une notification.

La solution de contournement / solution

J'ai pu me débarrasser de tous les accidents. J'ai supprimé l'appel stopSelf(). (Je pensais à retarder l'arrêt jusqu'à ce que je sois presque sûr que la notification a été affichée, mais je ne veux pas que l'utilisateur voit la notification si elle n'est pas nécessaire.) Lorsque le service est inactif depuis une minute ou que le système le détruit normalement sans lancer d'exceptions.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    stopForeground(true);
} else {
    stopSelf();
}

11

Juste un avertissement car j'ai perdu trop d'heures à ce sujet. J'ai continué à recevoir cette exception même si j'appelais startForeground(..)en premier onCreate(..). En fin de compte, j'ai constaté que le problème était dû à l'utilisation NOTIFICATION_ID = 0. L'utilisation de toute autre valeur semble résoudre ce problème.


Dans mon cas, j'utilisais ID 1, merci, le problème a été résolu
Amin Pinjari

4
J'utilise l'ID 101, je reçois toujours cette erreur parfois.
CopsOnRoad

9

J'ai un travail autour de ce problème. J'ai vérifié ce correctif dans ma propre application (300K + DAU), ce qui peut réduire au moins 95% de ce type de plantage, mais ne peut toujours pas éviter à 100% ce problème.

Ce problème se produit même lorsque vous vous assurez d'appeler startForeground () juste après le démarrage du service, comme indiqué par Google. Cela peut être dû au fait que le processus de création et d'initialisation de service coûte déjà plus de 5 secondes dans de nombreux scénarios, alors peu importe quand et où vous appelez la méthode startForeground (), ce plantage est inévitable.

Ma solution consiste à garantir que startForeground () sera exécuté dans les 5 secondes après la méthode startForegroundService (), quelle que soit la durée de création et d'initialisation de votre service. Voici la solution détaillée.

  1. N'utilisez pas startForegroundService en premier lieu, utilisez bindService () avec l'indicateur auto_create. Il attendra l'initialisation du service. Voici le code, mon exemple de service est MusicService:

    final Context applicationContext = context.getApplicationContext();
    Intent intent = new Intent(context, MusicService.class);
    applicationContext.bindService(intent, new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder binder) {
            if (binder instanceof MusicBinder) {
                MusicBinder musicBinder = (MusicBinder) binder;
                MusicService service = musicBinder.getService();
                if (service != null) {
                    // start a command such as music play or pause.
                    service.startCommand(command);
                    // force the service to run in foreground here.
                    // the service is already initialized when bind and auto_create.
                    service.forceForeground();
                }
            }
            applicationContext.unbindService(this);
        }
    
        @Override
        public void onServiceDisconnected(ComponentName name) {
        }
    }, Context.BIND_AUTO_CREATE);
  2. Alors voici l'implémentation de MusicBinder:

    /**
     * Use weak reference to avoid binder service leak.
     */
     public class MusicBinder extends Binder {
    
         private WeakReference<MusicService> weakService;
    
         /**
          * Inject service instance to weak reference.
          */
         public void onBind(MusicService service) {
             this.weakService = new WeakReference<>(service);
         }
    
         public MusicService getService() {
             return weakService == null ? null : weakService.get();
         }
     }
  3. La partie la plus importante, l'implémentation de MusicService, la méthode forceForeground () garantira que la méthode startForeground () est appelée juste après startForegroundService ():

    public class MusicService extends MediaBrowserServiceCompat {
    ...
        private final MusicBinder musicBind = new MusicBinder();
    ...
        @Override
        public IBinder onBind(Intent intent) {
            musicBind.onBind(this);
            return musicBind;
        }
    ...
        public void forceForeground() {
            // API lower than 26 do not need this work around.
            if (Build.VERSION.SDK_INT >= 26) {
                Intent intent = new Intent(this, MusicService.class);
                // service has already been initialized.
                // startForeground method should be called within 5 seconds.
                ContextCompat.startForegroundService(this, intent);
                Notification notification = mNotificationHandler.createNotification(this);
                // call startForeground just after startForegroundService.
                startForeground(Constants.NOTIFICATION_ID, notification);
            }
        }
    }
  4. Si vous souhaitez exécuter l'extrait de code de l'étape 1 dans une intention en attente, par exemple si vous souhaitez démarrer un service de premier plan dans un widget (un clic sur le bouton du widget) sans ouvrir votre application, vous pouvez envelopper l'extrait de code dans un récepteur de diffusion. et déclenchez un événement de diffusion au lieu de la commande start service.

C'est tout. J'espère que ça aide. Bonne chance.



7

Étant donné que tous ceux qui visitent ici souffrent de la même chose, je veux partager ma solution que personne d'autre n'a essayée auparavant (dans cette question de toute façon). Je peux vous assurer que cela fonctionne, même sur un point d'arrêt arrêté qui confirme cette méthode.

Le problème est d'appeler à Service.startForeground(id, notification)partir du service lui-même, non? Android Framework ne garantit malheureusement pas d'appeler Service.startForeground(id, notification)dans les Service.onCreate()5 secondes mais lève l'exception quand même, donc j'ai trouvé cette façon.

  1. Liez le service à un contexte avec un classeur du service avant d'appeler Context.startForegroundService()
  2. Si la liaison réussit, appelez à Context.startForegroundService() partir de la connexion de service et appelez immédiatement Service.startForeground() à l'intérieur de la connexion de service.
  3. REMARQUE IMPORTANTE: appelez la Context.bindService()méthode dans un try-catch, car dans certains cas, l'appel peut lever une exception, auquel cas vous devez vous fier à l'appel Context.startForegroundService()direct et espérer qu'il n'échouera pas. Un exemple peut être un contexte de récepteur de diffusion, mais l'obtention du contexte d'application ne lève pas d'exception dans ce cas, mais l'utilisation directe du contexte le fait.

Cela fonctionne même lorsque j'attends un point d'arrêt après avoir lié le service et avant de déclencher l'appel "startForeground". Attendre entre 3 et 4 secondes ne déclenche pas l'exception tandis qu'au bout de 5 secondes, il lève l'exception. (Si l'appareil ne peut pas exécuter deux lignes de code en 5 secondes, il est temps de le jeter à la poubelle.)

Commencez donc par créer une connexion de service.

// Create the service connection.
ServiceConnection connection = new ServiceConnection()
{
    @Override
    public void onServiceConnected(ComponentName name, IBinder service)
    {
        // The binder of the service that returns the instance that is created.
        MyService.LocalBinder binder = (MyService.LocalBinder) service;

        // The getter method to acquire the service.
        MyService myService = binder.getService();

        // getServiceIntent(context) returns the relative service intent 
        context.startForegroundService(getServiceIntent(context));

        // This is the key: Without waiting Android Framework to call this method
        // inside Service.onCreate(), immediately call here to post the notification.
        myService.startForeground(myNotificationId, MyService.getNotification());

        // Release the connection to prevent leaks.
        context.unbindService(this);
    }

    @Override
    public void onBindingDied(ComponentName name)
    {
        Log.w(TAG, "Binding has dead.");
    }

    @Override
    public void onNullBinding(ComponentName name)
    {
        Log.w(TAG, "Bind was null.");
    }

    @Override
    public void onServiceDisconnected(ComponentName name)
    {
        Log.w(TAG, "Service is disconnected..");
    }
};

Dans votre service, créez un classeur qui renvoie l'instance de votre service.

public class MyService extends Service
{
    public class LocalBinder extends Binder
    {
        public MyService getService()
        {
            return MyService.this;
        }
    }

    // Create the instance on the service.
    private final LocalBinder binder = new LocalBinder();

    // Return this instance from onBind method.
    // You may also return new LocalBinder() which is
    // basically the same thing.
    @Nullable
    @Override
    public IBinder onBind(Intent intent)
    {
        return binder;
    }
}

Ensuite, essayez de lier le service à partir de ce contexte. S'il réussit, il appellera la ServiceConnection.onServiceConnected()méthode à partir de la connexion de service que vous utilisez. Ensuite, gérez la logique du code illustré ci-dessus. Un exemple de code ressemblerait à ceci:

// Try to bind the service
try
{
     context.bindService(getServiceIntent(context), connection,
                    Context.BIND_AUTO_CREATE);
}
catch (RuntimeException ignored)
{
     // This is probably a broadcast receiver context even though we are calling getApplicationContext().
     // Just call startForegroundService instead since we cannot bind a service to a
     // broadcast receiver context. The service also have to call startForeground in
     // this case.
     context.startForegroundService(getServiceIntent(context));
}

Il semble fonctionner sur les applications que je développe, donc ça devrait marcher quand vous essayez aussi.


5

Problème avec Android O API 26

Si vous arrêtez le service immédiatement (de sorte que votre service ne fonctionne pas vraiment (formulation / compréhension) et que vous êtes bien en dessous de l'intervalle ANR, vous devez toujours appeler startForeground avant stopSelf

https://plus.google.com/116630648530850689477/posts/L2rn4T6SAJ5

J'ai essayé cette approche, mais cela crée toujours une erreur: -

if (Util.SDK_INT > 26) {
    mContext.startForegroundService(playIntent);
} else {
    mContext.startService(playIntent);
}

J'utilise ceci jusqu'à ce que l'erreur soit résolue

mContext.startService(playIntent);

3
Doit être Util.SDK_INT> = 26, pas seulement plus grand
Andris

4

Je suis confronté au même problème et après avoir passé du temps à trouver des solutions, vous pouvez essayer le code ci-dessous. Si vous utilisez Servicealors mettez ce code dans onCreate sinon votre utilisation Intent Servicemettez alors ce code dans onHandleIntent.

if (Build.VERSION.SDK_INT >= 26) {
        String CHANNEL_ID = "my_app";
        NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                "MyApp", NotificationManager.IMPORTANCE_DEFAULT);
        ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);
        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle("")
                .setContentText("").build();
        startForeground(1, notification);
    }

4

J'ai fait des recherches sur ce problème et c'est ce que j'ai découvert jusqu'à présent. Ce plantage pourrait se produire si nous avons un code similaire à ceci:

MyForegroundService.java

public class MyForegroundService extends Service {
    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(...);
    }
}

MainActivity.java

Intent serviceIntent = new Intent(this, MyForegroundService.class);
startForegroundService(serviceIntent);
...
stopService(serviceIntent);

L'exception est levée dans le bloc suivant du code:

ActiveServices.java

private final void bringDownServiceLocked(ServiceRecord r) {
    ...
    if (r.fgRequired) {
        Slog.w(TAG_SERVICE, "Bringing down service while still waiting for start foreground: "
                  + r);
        r.fgRequired = false;
        r.fgWaiting = false;
        mAm.mAppOpsService.finishOperation(AppOpsManager.getToken(mAm.mAppOpsService),
                    AppOpsManager.OP_START_FOREGROUND, r.appInfo.uid, r.packageName);
        mAm.mHandler.removeMessages(
                    ActivityManagerService.SERVICE_FOREGROUND_TIMEOUT_MSG, r);
        if (r.app != null) {
            Message msg = mAm.mHandler.obtainMessage(
                ActivityManagerService.SERVICE_FOREGROUND_CRASH_MSG);
            msg.obj = r.app;
            msg.getData().putCharSequence(
                ActivityManagerService.SERVICE_RECORD_KEY, r.toString());
            mAm.mHandler.sendMessage(msg);
         }
    }
    ...
}

Cette méthode est exécutée avant onCreate()de MyForegroundServiceparce que les horaires Android la création du service sur le principal gestionnaire de threads , mais bringDownServiceLockedest appelé un BinderThread, Wich est une condition de course. Cela signifie que vous MyForegroundServicen'avez pas eu la possibilité d'appeler, startForegroundce qui entraînera le crash.

Pour résoudre ce problème, nous devons nous assurer que ce bringDownServiceLockedn'est pas appelé avant onCreate()de MyForegroundService.

public class MyForegroundService extends Service {

    private static final String ACTION_STOP = "com.example.MyForegroundService.ACTION_STOP";

    private final BroadcastReceiver stopReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            context.removeStickyBroadcast(intent);
            stopForeground(true);
            stopSelf();
        }
    };

    @Override
    public void onCreate() {
        super.onCreate();
        startForeground(...);
        registerReceiver(
            stopReceiver, new IntentFilter(ACTION_STOP));
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        unregisterReceiver(stopReceiver);
    }

    public static void stop(Context context) {
        context.sendStickyBroadcast(new Intent(ACTION_STOP));
    }
}

En utilisant des émissions collantes, nous nous assurons que l'émission ne se perd pas et stopReceiverreçoit l'intention d'arrêt dès qu'elle a été enregistrée dans onCreate()of MyForegroundService. À ce moment-là, nous avons déjà appelé startForeground(...). Nous devons également supprimer cette diffusion collante pour éviter que stopReceiver ne soit averti la prochaine fois.

Veuillez noter que la méthode sendStickyBroadcastest obsolète et je l'utilise uniquement comme solution de contournement temporaire pour résoudre ce problème.


Vous devez l'appeler au lieu de context.stopService (serviceIntent) lorsque vous souhaitez arrêter le service.
makovkastar

4

Tant de réponses mais aucune n'a fonctionné dans mon cas.

J'ai commencé un service comme celui-ci.

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    startForegroundService(intent);
} else {
    startService(intent);
}

Et à mon service dans onStartCommand

    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        Notification.Builder builder = new Notification.Builder(this, ANDROID_CHANNEL_ID)
                .setContentTitle(getString(R.string.app_name))
                .setContentText("SmartTracker Running")
                .setAutoCancel(true);
        Notification notification = builder.build();
        startForeground(NOTIFICATION_ID, notification);
    } else {
        NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                .setContentTitle(getString(R.string.app_name))
                .setContentText("SmartTracker is Running...")
                .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                .setAutoCancel(true);
        Notification notification = builder.build();
        startForeground(NOTIFICATION_ID, notification);
    }

Et n'oubliez pas de mettre NOTIFICATION_ID non nul

private static final String ANDROID_CHANNEL_ID = "com.xxxx.Location.Channel";
private static final int NOTIFICATION_ID = 555;

Donc, tout était parfait mais plantait toujours sur 8.1, donc la cause était comme ci-dessous.

     if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            stopForeground(true);
        } else {
            stopForeground(true);
        }

J'ai appelé arrêter le premier plan avec supprimer la notification, mais une fois que le service de notification a été supprimé, le service d'arrière-plan ne peut pas fonctionner en arrière-plan. commencé après la réception du push.

Donc, le mot magique est

   stopSelf();

Jusqu'à présent, quelle que soit la raison de la panne de votre service, suivez toutes les étapes ci-dessus et profitez-en.


if (Build.VERSION.SDK_INT> = Build.VERSION_CODES.O) {stopForeground (true); } else {stopForeground (true); } à quoi ça sert sinon? dans les deux cas, vous faisiez de même, stopForeground (vrai);
user3135923

Je crois que vous vouliez mettre stopSelf (); à l'intérieur du bloc else au lieu de stopForeground (true);
Justin Stanley

1
Oui, @JustinStanley utilise stopSelf ();
sable

Ne devrait pas startForegroundêtre appelé onCreate?
Aba

J'utilise NotificationManager et non la classe Notification. Comment puis-je implémenter cela?
Prajwal W

3

https://developer.android.com/reference/android/content/Context.html#startForegroundService(android.content.Intent)

Similaire à startService (Intent), mais avec une promesse implicite que le Service appellera startForeground (int, android.app.Notification) une fois qu'il commencera à fonctionner. Le service dispose d'un temps comparable à l'intervalle ANR pour ce faire, sinon le système arrêtera automatiquement le service et déclarera l'application ANR.

Contrairement au startService ordinaire (Intent), cette méthode peut être utilisée à tout moment, que l'application hébergeant le service soit dans un état de premier plan.

assurez-vous d'appeler le Service.startForeground(int, android.app.Notification)onCreate () afin de vous assurer qu'il sera appelé .. si vous avez une condition qui peut vous empêcher de le faire, alors vous feriez mieux d'utiliser la normale Context.startService(Intent)et d'appeler Service.startForeground(int, android.app.Notification)vous - même.

Il semble que l' Context.startForegroundService()ajoute un chien de garde pour s'assurer que vous avez appelé Service.startForeground(int, android.app.Notification)avant qu'il ne soit détruit ...


3

Veuillez ne pas appeler de StartForgroundServices dans la méthode onCreate () , vous devez appeler les services StartForground dans onStartCommand () après avoir créé le thread de travail, sinon vous obtiendrez toujours ANR, donc s'il vous plaît n'écrivez pas de connexion complexe dans le thread principal de onStartCommand () ;

public class Services extends Service {

    private static final String ANDROID_CHANNEL_ID = "com.xxxx.Location.Channel";
    @Nullable
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }


    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            Notification.Builder builder = new Notification.Builder(this, ANDROID_CHANNEL_ID)
                    .setContentTitle(getString(R.string.app_name))
                    .setContentText("SmartTracker Running")
                    .setAutoCancel(true);
            Notification notification = builder.build();
            startForeground(0, notification);
            Log.e("home_button","home button");
        } else {
            NotificationCompat.Builder builder = new NotificationCompat.Builder(this)
                    .setContentTitle(getString(R.string.app_name))
                    .setContentText("SmartTracker is Running...")
                    .setPriority(NotificationCompat.PRIORITY_DEFAULT)
                    .setAutoCancel(true);
            Notification notification = builder.build();
            startForeground(0, notification);
            Log.e("home_button_value","home_button_value");

        }
        return super.onStartCommand(intent, flags, startId);

    }
}

EDIT: Attention! La fonction startForeground ne peut pas prendre 0 comme premier argument, elle lèvera une exception! cet exemple contient un mauvais appel de fonction, remplacez 0 par votre propre const qui ne peut pas être 0 ou être supérieur à Max (Int32)


2

Même après avoir appelé l' startForegrounden Service, il se bloque sur certains appareils si l' on appelle stopServicejuste avant que l' onCreateon appelle. J'ai donc résolu ce problème en démarrant le service avec un indicateur supplémentaire:

Intent intent = new Intent(context, YourService.class);
intent.putExtra("request_stop", true);
context.startService(intent);

et ajouté une vérification dans onStartCommand pour voir si elle a commencé à s'arrêter:

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    //call startForeground first
    if (intent != null) {
        boolean stopService = intent.getBooleanExtra("request_stop", false);
        if (stopService) {
            stopSelf();
        }
    }

    //Continue with the background task
    return START_STICKY;
}

PS Si le service n'était pas en cours d'exécution, il démarrerait le service en premier, ce qui est une surcharge.


2

Vous devez ajouter une autorisation en tant que ci-dessous pour l'appareil Android 9 lorsque vous utilisez le SDK cible 28 ou ultérieur, sinon l'exception se produira toujours:

<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />

1

Je vérifie simplement le PendingIntentnull ou non avant d'appeler la context.startForegroundService(service_intent)fonction.

ça marche pour moi

PendingIntent pendingIntent=PendingIntent.getBroadcast(context,0,intent,PendingIntent.FLAG_NO_CREATE);

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && pendingIntent==null){
            context.startForegroundService(service_intent);
        }
        else
        {
            context.startService(service_intent);
        }
}

En fait, cela lance simplement le service à startService plutôt que startForegroundService s'il est nul. L'instruction if devrait être imbriquée, sinon l'application se bloquerait en essayant d'utiliser les services sur une version ultérieure à un moment donné.
a54studio

1

il suffit d'appeler la méthode startForeground immédiatement après la création de Service ou IntentService. comme ça:

import android.app.Notification;
public class AuthenticationService extends Service {

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

1
EXCEPTION FATALE: android.app.RemoteServiceException principale: notification incorrecte pour startForeground: java.lang.RuntimeException: canal non valide pour la notification de service: notification (canal = null pri = 0 contentView = null vibrate = null sound = null defaults = 0x0 flags = 0x40 color = 0x00000000 vis = PRIVATE) sur android.app.ActivityThread $ H.handleMessage (ActivityThread.java:1821) sur android.os.Handler.dispatchMessage (Handler.java:106) sur android.os.Looper.loop (Looper. java: 164) sur android.app.ActivityThread.main (ActivityThread.java:6626)
Gerry

1

Ok, quelque chose que j'ai remarqué à ce sujet qui pourrait aussi en aider quelques autres. Il s'agit strictement de tests pour voir si je pouvais comprendre comment corriger les occurrences que je vois. Par souci de simplicité, disons que j'ai une méthode qui appelle cela du présentateur.

context.startForegroundService(new Intent(context, TaskQueueExecutorService.class));

try {
    Thread.sleep(10000);
} catch (InterruptedException e) {
    e.printStackTrace();
}       

Cela plantera avec la même erreur. Le service ne démarrera PAS tant que la méthode n'est pas terminée, donc pas onCreate()dans le service.

Donc, même si vous mettez à jour l'interface utilisateur hors du thread principal, SI vous avez quelque chose qui pourrait retarder cette méthode après, elle ne démarrera pas à temps et vous donnera l'erreur redoutée de premier plan. Dans mon cas, nous étions en train de charger certaines choses dans une file d'attente et chacun a appelé startForegroundService, mais une logique était impliquée avec chacun en arrière-plan. Donc, si la logique a pris trop de temps pour terminer cette méthode puisqu'elles ont été rappelées, le temps de plantage. L'ancien l'a startServicesimplement ignoré et a continué son chemin et puisque nous l'avons appelé à chaque fois, le prochain tour allait se terminer.

Cela m'a laissé me demander, si j'appelais le service à partir d'un thread en arrière-plan, s'il ne pouvait pas être entièrement lié au démarrage et s'exécuter immédiatement, j'ai donc commencé à expérimenter. Même si cela ne démarre pas immédiatement, il ne se bloque pas.

new Handler(Looper.getMainLooper()).post(new Runnable() {
        public void run() {
               context.startForegroundService(new Intent(context, 
           TaskQueueExecutorService.class));
               try {
                   Thread.sleep(10000);
               } catch (InterruptedException e) {
                  e.printStackTrace();
              }       
        }
});

Je ne prétendrai pas savoir pourquoi il ne plante pas bien que je soupçonne que cela l'oblige à attendre que le thread principal puisse le gérer en temps opportun. Je sais que ce n'est pas idéal pour le lier au thread principal, mais puisque mon utilisation l'appelle en arrière-plan, je ne suis pas vraiment inquiet s'il attend jusqu'à ce qu'il puisse terminer plutôt que de planter.


vous démarrez votre service depuis l'arrière-plan ou en activité?
Mateen Chaudhry

Contexte. Le problème est qu'Android ne peut garantir que le service démarrera dans le temps imparti. On pourrait penser qu'ils lèveraient simplement une erreur pour l'ignorer si vous voulez, mais hélas pas Android. Cela doit être fatal. Cela a aidé dans notre application, mais ne l'a pas totalement résolu.
a54studio

1

J'ajoute du code dans la réponse @humazed. Il n'y a donc pas de notification initiale. Ce pourrait être une solution de contournement, mais cela fonctionne pour moi.

@Override
 public void onCreate() {
        super.onCreate();

        if (Build.VERSION.SDK_INT >= 26) {
            String CHANNEL_ID = "my_channel_01";
            NotificationChannel channel = new NotificationChannel(CHANNEL_ID,
                    "Channel human readable title",
                    NotificationManager.IMPORTANCE_DEFAULT);

            ((NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE)).createNotificationChannel(channel);

            Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                    .setContentTitle("")
                    .setContentText("")
                    .setColor(ContextCompat.getColor(this, R.color.transparentColor))
                    .setSmallIcon(ContextCompat.getColor(this, R.color.transparentColor)).build();

            startForeground(1, notification);
        }
}

J'ajoute transparentColor en petite icône et couleur sur notification. Ça va marcher.


0

J'ai résolu le problème avec le démarrage du service avec startService(intent)au lieu de Context.startForeground()et en appelant startForegound()immédiatement après super.OnCreate(). De plus, si vous démarrez le service au démarrage, vous pouvez démarrer l'activité qui démarre le service lors de la diffusion de démarrage. Bien que ce ne soit pas une solution permanente, cela fonctionne.


dans certains appareils Xiaomi, les activités ne peuvent pas démarrer à partir de l'arrière
Mateen Chaudhry

0

Mise à jour des données dans onStartCommand(...)

onBind (...)

onBind(...)est un meilleur événement de cycle de vie à initier par startForegroundrapport à onCreate(...)parce que onBind(...)passe dans un Intentqui peut contenir des données importantes dans le Bundlenécessaire pour initialiser le Service. Cependant, il n'est pas nécessaire car il onStartCommand(...)est appelé lorsque le Serviceest créé pour la première fois ou appelé par la suite.

onStartCommand (...)

startForegrounden onStartCommand(...)est important afin de mettre à jour le Serviceune fois qu'il a déjà été créé.

Quand ContextCompat.startForegroundService(...)est appelé après la Servicecréation d'un onBind(...)et onCreate(...)n'est pas appelé. Par conséquent, les données mises à jour peuvent être transmises onStartCommand(...)via le Intent Bundlepour mettre à jour les données dans le Service.

Échantillon

J'utilise ce modèle pour mettre en œuvre la PlayerNotificationManagerdans la Coinverse application de nouvelles crypto-monnaie.

Activity / Fragment.kt

context?.bindService(
        Intent(context, AudioService::class.java),
        serviceConnection, Context.BIND_AUTO_CREATE)
ContextCompat.startForegroundService(
        context!!,
        Intent(context, AudioService::class.java).apply {
            action = CONTENT_SELECTED_ACTION
            putExtra(CONTENT_SELECTED_KEY, contentToPlay.content.apply {
                audioUrl = uri.toString()
            })
        })

AudioService.kt

private var uri: Uri = Uri.parse("")

override fun onBind(intent: Intent?) =
        AudioServiceBinder().apply {
            player = ExoPlayerFactory.newSimpleInstance(
                    applicationContext,
                    AudioOnlyRenderersFactory(applicationContext),
                    DefaultTrackSelector())
        }

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    intent?.let {
        when (intent.action) {
            CONTENT_SELECTED_ACTION -> it.getParcelableExtra<Content>(CONTENT_SELECTED_KEY).also { content ->
                val intentUri = Uri.parse(content.audioUrl)
                // Checks whether to update Uri passed in Intent Bundle.
                if (!intentUri.equals(uri)) {
                    uri = intentUri
                    player?.prepare(ProgressiveMediaSource.Factory(
                            DefaultDataSourceFactory(
                                    this,
                                    Util.getUserAgent(this, getString(app_name))))
                            .createMediaSource(uri))
                    player?.playWhenReady = true
                    // Calling 'startForeground' in 'buildNotification(...)'.          
                    buildNotification(intent.getParcelableExtra(CONTENT_SELECTED_KEY))
                }
            }
        }
    }
    return super.onStartCommand(intent, flags, startId)
}

// Calling 'startForeground' in 'onNotificationStarted(...)'.
private fun buildNotification(content: Content): Unit? {
    playerNotificationManager = PlayerNotificationManager.createWithNotificationChannel(
            this,
            content.title,
            app_name,
            if (!content.audioUrl.isNullOrEmpty()) 1 else -1,
            object : PlayerNotificationManager.MediaDescriptionAdapter {
                override fun createCurrentContentIntent(player: Player?) = ...
                override fun getCurrentContentText(player: Player?) = ...
                override fun getCurrentContentTitle(player: Player?) = ...
                override fun getCurrentLargeIcon(player: Player?,
                                                 callback: PlayerNotificationManager.BitmapCallback?) = ...
            },
            object : PlayerNotificationManager.NotificationListener {
                override fun onNotificationStarted(notificationId: Int, notification: Notification) {
                    startForeground(notificationId, notification)
                }
                override fun onNotificationCancelled(notificationId: Int) {
                    stopForeground(true)
                    stopSelf()
                }
            })
    return playerNotificationManager.setPlayer(player)
}

-1

Un service

class TestService : Service() {

    override fun onCreate() {
        super.onCreate()
        Log.d(TAG, "onCreate")

        val nBuilder = NotificationCompat.Builder(this, "all")
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .setContentTitle("TestService")
            .setPriority(NotificationCompat.PRIORITY_DEFAULT)
        startForeground(1337, nBuilder.build())
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        val rtn = super.onStartCommand(intent, flags, startId)

        if (intent?.action == STOP_ACTION) {
            Log.d(TAG, "onStartCommand -> STOP")
            stopForeground(true)
            stopSelf()
        } else {
            Log.d(TAG, "onStartCommand -> START")
        }

        return rtn
    }

    override fun onDestroy() {
        Log.d(TAG, "onDestroy")
        super.onDestroy()
    }

    override fun onBind(intent: Intent?): IBinder? = null

    companion object {

        private val TAG = "TestService"
        private val STOP_ACTION = "ly.zen.test.TestService.ACTION_STOP"

        fun start(context: Context) {
            ContextCompat.startForegroundService(context, Intent(context, TestService::class.java))
        }

        fun stop(context: Context) {
            val intent = Intent(context, TestService::class.java)
            intent.action = STOP_ACTION
            ContextCompat.startForegroundService(context, intent)
        }

    }

}

Testeur

val nChannel = NotificationChannel("all", "All", NotificationManager.IMPORTANCE_NONE)
val nManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
nManager.createNotificationChannel(nChannel)

start_test_service.setOnClickListener {
    TestService.start(this@MainActivity)
    TestService.stop(this@MainActivity)
}

Résultat

D/TestService: onCreate
D/TestService: onStartCommand -> START
D/TestService: onStartCommand -> STOP
D/TestService: onDestroy
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.