web-dev-qa-db-fra.com

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

J'utilise Service Class sur le système d'exploitation Android O. 

Je prévois d'utiliser la Service en arrière-plan. 

La recommandation Android indique que startService doit utiliser startForegroundService.

Si vous utilisez startForegroundService, la Service lève une Context.startForegroundService() n'a pas appelé l'erreur Service.startForeground()

Quel est le problème avec cela?

121
NiceGuy

À partir de la documentation de Google sur Changements de comportement sur Android 8.0 :

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

Solution: Appelez startForeground() dans onCreate() pour la Service que vous utilisez Context.startForegroundService()

Voir aussi: Background Execution Limits pour Android 8.0 (Oreo)

63
zhuzhumouse

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

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);
        }
}
45
humazed

Pourquoi ce problème se pose-t-il parce que l'infrastructure Android ne peut pas garantir que votre service sera démarré dans les 5 secondes mais que, par ailleurs, l'infrastructure est strictement limitée aux notifications de premier plan qui doivent être déclenchées dans les 5 secondes, sans vérifier si l'infrastructure a tenté de démarrer le service .

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

  1. startForeground une notification doit figurer à la fois dans onCreate et onStartCommand, car si votre service est déjà créé et que votre activité tente de la redémarrer, onCreate ne sera pas appelée.

  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. stopSelf ne doit pas être appelé avant startForeground.

Avec tout ce qui précède 3, ce problème peut être réduit un peu, mais ce n’est toujours pas un correctif. La solution ou la solution de contournement consiste à rétrograder la version de votre 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 croit pas que ce soit de sa faute, lisez # 36 ici: https://issuetracker.google.com/issues/76112072 .

19
ZhouX

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

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
    }
}
8
Ahmad Arslan

Juste un tête-à-tête car j'ai perdu beaucoup trop d'heures à ce sujet. J'ai continué à avoir cette exception même si j'appelais startForeground(..) comme première chose dans onCreate(..). Finalement, j'ai découvert que le problème était dû à l'utilisation de NOTIFICATION_ID = 0. L'utilisation de toute autre valeur semble résoudre ce problème.

6
tobalr

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

Le déclencheur de 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 était très chargé. Et tous les chemins de code étaient recouverts de startForeground(). Mais ensuite, j'ai réalisé que dans de nombreux cas, mon service effectue le travail très rapidement. Je pense que le déclencheur de mon application était que le service se terminait avant que le système ne parvienne réellement à afficher une notification.

La solution de contournement

J'ai pu me débarrasser de tous les accidents. Ce que j'ai fait était de supprimer l'appel à stopSelf(). (Je pensais retarder l’arrêt jusqu’à ce que la notification soit affichée, mais je ne veux pas que l’utilisateur la voie si elle n’est pas nécessaire.) le système le détruit normalement sans lancer d'exception.

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

Cette erreur se produit également sur Android 8+ lorsque Service.startForeground (int id, Notification Notification) est appelé lorsque id est défini sur 0.

id int: identifiant de cette notification selon NotificationManager.notify (int, Notification); ne doit pas être 0.

4
almisoft

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 blocage, mais je ne peux toujours pas à 100% éviter 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 en tant que documenté par Google. Cela peut être dû au fait que le processus de création et d’initialisation du service coûte déjà plus de 5 secondes dans de nombreux scénarios. Par conséquent, quel que soit le moment et le lieu où vous appelez la méthode startForeground (), cette panne est inévitable.

Ma solution consiste à m'assurer que startForeground () sera exécuté dans les 5 secondes suivant la méthode startForegroundService (), quelle que soit la durée pendant laquelle votre service doit être créé et initialisé. Voici la solution détaillée.

  1. N'utilisez pas startForegroundService en premier lieu, utilisez bindService () avec 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. Ensuite, voici l'implémentation 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 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 insérer l'extrait de code dans un récepteur de diffusion. et déclenchez un événement de diffusion au lieu de démarrer la commande de service.

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

4
Hexise

Problème avec Android O API 26

Si vous arrêtez le service immédiatement (pour que votre service ne fonctionne pas réellement (formulation/compréhension) et que vous vous trouviez sous l'intervalle ANR, vous devez toujours appeler startForeground avant stopSelf.

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

Essayé cette approche, mais il 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
Andy Cass

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

J'ai commencé le service comme ça.

              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 définir NOTIFICATION_ID non nul

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

SO tout était parfait mais continuait de planter sur 8.1 alors 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 notificaton, mais une fois le service de notification supprimé devenir arrière-plan et le service d'arrière-plan ne peut pas s'exécuter dans Android O à partir de l'arrière-plan. commencé après réception de Push.

La parole est magique

  stopSelf();

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

3
sandy

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

Semblable à startService (Intent), mais avec une promesse implicite que le Le service appellera startForeground (int, Android.app.Notification) une fois il commence à courir. Le service est donné une quantité de temps comparable à l’intervalle ANR, sinon le système Arrêtez automatiquement le service et déclarez l'application ANR.

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

assurez-vous d'appeler la Service.startForeground(int, Android.app.Notification) sur onCreate () afin de vous assurer qu'elle sera appelée. Si vous rencontrez un problème qui pourrait vous empêcher de le faire, vous feriez mieux d'utiliser la Context.startService(Intent) normale et d'appeler la Service.startForeground(int, Android.app.Notification) vous-même.

Il semble que la Context.startForegroundService() ajoute un chien de garde pour s'assurer que vous avez bien appelé la Service.startForeground(int, Android.app.Notification) avant qu'elle ne soit détruite ...

3
Alécio Carvalho

J'ajoute du code dans la réponse @humazed. Donc, il n'y a pas de notification initiale. Cela 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 lors de la notification. Ça va marcher.

2
KHEM RAJ MEENA

Je suis confronté au même problème et après avoir passé du temps à trouver des solutions, vous pouvez essayer ci-dessous le code. Si vous utilisez Service, mettez ce code dans onCreate, sinon utilisez Intent Service, puis placez-le 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);
    }
2
Sambhaji Karad

Même après avoir appelé startForeground dans Service, il se bloque sur certains périphériques si nous appelons stopService juste avant que onCreate soit appelé . 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 réellement commencé à s'arrêter:

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

    //call startForeground first
    boolean stopService = false;
    if (intent != null) {
        stopService = intent.getBooleanExtra("request_stop", false);
    }
    if (stopService) {
        stopSelf();
        return START_STICKY;
    }
    //Continue with the background task
    return START_STICKY;
}

P.S. Si le service ne fonctionnait pas réellement, il démarrera d'abord le service, ce qui constitue un temps système.

1
Gaurav Singla

Je viens de vérifier la PendingIntent null ou non avant d'appeler le 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);
        }
}
1
SaimumIslam27

J'ai fait des recherches sur ce problème et c'est ce que j'ai découvert jusqu'à présent. Ce crash 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() of MyForegroundService car Android planifie la création du service sur le gestionnaire de thread principal, mais bringDownServiceLocked est appelé sur une BinderThread qui est une condition de concurrence critique. Cela signifie que MyForegroundService n'a pas eu l'occasion d'appeler startForeground, ce qui provoquera le crash.

Pour résoudre ce problème, nous devons nous assurer que bringDownServiceLocked n'est pas appelé avant onCreate() of 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 soit pas perdue et que stopReceiver reçoive l’intention d’arrêter dès qu’elle a été enregistrée dans onCreate() of MyForegroundService. À ce stade, nous avons déjà appelé startForeground(...). Nous devons également supprimer cette émission collante pour éviter que stopReceiver ne soit averti la prochaine fois.

Veuillez noter que la méthode sendStickyBroadcast est obsolète et que je ne l'utilise que comme solution temporaire pour résoudre ce problème.

1
makovkastar

N'appelez aucune méthode StartForgroundServices dans la méthode onCreate () , vous devez appeler des services StartForground dans onStartCommand () après avoir créé le thread de travail, sinon vous obtiendrez toujours l'ANR. login dans le fil 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);

    }
}
1
Shalu Gupta

pour moi j'ai ajouté un service de premier plan dans manifeste

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

si cela fonctionne commentaire ci-dessous faites le moi savoir

0
Arun Abimanyu

Ok, quelque chose que j'ai remarqué à ce sujet pourrait aider quelques autres aussi. C’est strictement à partir de tests pour voir si je pouvais trouver un moyen de corriger les occurrences que je vois. Par souci de simplicité, supposons 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 va planter avec la même erreur. Le service NE démarrera PAS tant que la méthode n'est pas terminée, donc pas de onCreate() dans le service.

Ainsi, même si vous mettez à jour l'interface utilisateur à partir du thread principal, SI vous avez quelque chose qui puisse retenir 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 chargions des éléments dans une file d’attente et chacun d'eux s'appelait startForegroundService, mais une certaine logique était impliquée dans chacun d'eux à l'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 crash. Le vieux startService l'a simplement ignoré et a poursuivi son chemin et puisque nous l'appelions à chaque fois, le tour suivant se terminerait.

Cela me laissait perplexe: si j'appelais le service à partir d'un fil en arrière-plan, il ne pourrait pas être entièrement lié au début et être exécuté immédiatement; j'ai donc commencé à expérimenter. Même si cela ne le démarre PAS immédiatement, il ne tombe pas en panne.

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 ferai pas semblant de savoir pourquoi il ne se bloque pas, même si je suppose que cela le force à attendre que le thread principal puisse le gérer rapidement. Je sais que ce n'est pas idéal pour le lier au fil principal, mais comme mon utilisation l'appelle en arrière-plan, je ne suis pas vraiment inquiet s'il attend jusqu'à ce qu'il puisse se terminer plutôt que de planter.

0
a54studio

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

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

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

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

0
mystogan

Je sais que c'est une réponse tardive, mais, je pense que cela pourrait aider à l'avenir, je viens d'utiliser JobIntentService au lieu de IntentService qui inclut son JobScheduler et gère tout pour moi. regarde cet exemple

0
raed

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 de l'application et vous devez cesser de l'utiliser. Le fait que Google recommande d'utiliser l'API Service # startForeground () dans les 5 secondes qui suivent l'appel de Context # startForegroundService () n'est pas quelque chose qu'une application peut toujours faire.

Android exécute simultanément de nombreux processus et rien ne garantit que Looper appellera votre service cible censé appeler startForeground () dans les 5 secondes. Si votre service cible n'a pas reçu l'appel dans les 5 secondes, vous avez de la chance et vos utilisateurs vont être confrontés à la 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 j'ai bien compris, Looper a analysé la file d'attente ici, a trouvé un "abuseur" et l'a simplement tué. Le système est heureux et en bonne santé à l’heure actuelle, contrairement aux développeurs et aux utilisateurs, mais puisque les responsabilités de Google lui incombent, pourquoi s’intéresser aux deux derniers? Apparemment non. Pourraient-ils l'améliorer? Bien sûr, par exemple ils auraient pu servir le dialogue "L'application est occupée", demandant à un utilisateur de prendre une décision quant à l'attente ou la destruction de l'application, mais pourquoi s'embêter, ce n'est pas leur responsabilité. L'essentiel est que le système soit en bonne santé maintenant.

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

Il a été suggéré dans ce fil d'utiliser "bind" au lieu de "start", puis lorsque le service est prêt, traitez onServiceConnected, mais là encore, cela signifie que vous n'utilisez pas les appels startForegroundService.

Je pense que l'action juste et honnête du côté de Google consisterait à dire à tout le monde que startForegourndServcie a un problème et ne doit pas être utilisé.

Le QS reste toujours: quoi utiliser à la place? Heureusement pour nous, il existe maintenant JobScheduler et JobService, qui constituent une meilleure alternative aux services de premier plan. C'est une meilleure option, à cause de cela:

Lorsqu'un travail est en cours d'exécution, le système organise un wakelock au nom de votre application. Pour cette raison, aucune action n'est nécessaire pour garantir que l'appareil reste en veille pendant toute la durée du travail.

Cela signifie que vous n'avez plus besoin de vous soucier de la gestion des wakelocks et c'est pourquoi ce n'est pas différent des services de premier plan. À partir de la mise en œuvre, JobScheduler n'est pas votre service, mais un système, et il traitera probablement la file d'attente correctement, et Google ne terminera jamais son propre enfant :)

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

Ce service exécute chaque travail entrant sur un gestionnaire s'exécutant sur le thread principal de votre application. Cela signifie que vous devez décharger votre logique d'exécution sur un autre thread/gestionnaire/AsyncTask de votre choix.

Le seul inconvénient de passer à JobScheduler/JobService est que vous aurez besoin de refactoriser l'ancien code, et ce n'est pas amusant. C'est ce que j'ai fait ces deux derniers jours pour utiliser la nouvelle implémentation SAP de Samsung. Je vais regarder mes rapports d'accident et vous laisser savoir si vous voyez à nouveau les accidents. Théoriquement, cela ne devrait pas arriver, mais il y a toujours des détails que nous ne connaissons peut-être pas.

0
Oleg Gryb

Puisque tous les visiteurs souffrent de la même chose, je veux partager ma solution que personne d’autre n’a essayée auparavant (de toute façon dans cette question). 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, n'est-ce pas? Android Framework ne garantit malheureusement pas d'appeler Service.startForeground(id, notification) au sein de Service.onCreate() dans 5 secondes, mais déclenche l'exception de toute façon. C'est ce que j'ai trouvé.

  1. Lier le service à un contexte avec un classeur du service avant d'appeler Context.startForegroundService()
  2. Si la liaison aboutit, appelez Context.startForegroundService()à partir de la connexion de service et appelez immédiatement Service.startForeground()à l'intérieur de la connexion de service.
  3. NOTE IMPORTANTE: Appelez la méthode Context.bindService() à l'intérieur de n try-catch car l'appel peut parfois renvoyer une exception, auquel cas vous devez vous fier à l'appel. Context.startForegroundService() directement et j'espère que cela ne faillira pas. Un exemple peut être un contexte de récepteur de diffusion. Cependant, l'obtention du contexte d'application ne déclenche pas d'exception dans ce cas, mais l'utilisation directe du contexte le fait.

Cela fonctionne même lorsque je suis en attente d'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 et après 5 secondes, il lève l'exception. (Si le périphérique ne peut pas exécuter deux lignes de code en 5 secondes, il est temps de le jeter à la corbeille.)

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 méthode ServiceConnection.onServiceConnected() à partir de la connexion de service que vous utilisez. Ensuite, gérez la logique dans le code présenté 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 cela devrait marcher quand vous essayez aussi.

0
Furkan Yurdakul

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

onBind (...)

onBind(...) est un meilleur événement de cycle de vie pour initier startForeground contre onCreate(...) car onBind(...) passe dans une Intent qui peut contenir des données importantes dans la Bundle nécessaires à l’initialisation de la Service. Cependant, il n'est pas nécessaire que onStartCommand(...) soit appelé lorsque la Service est créée pour la première fois ou appelée ultérieurement.

onStartCommand (...)

startForeground in onStartCommand(...) est important pour mettre à jour la Service une fois qu'elle a déjà été créée.

Lorsque ContextCompat.startForegroundService(...) est appelé après qu'une Service ait été créée, onBind(...) et onCreate(...) ne soient pas appelés. Par conséquent, les données mises à jour peuvent être transmises à onStartCommand(...) via IntentBundle pour mettre à jour les données dans Service.

Échantillon

J'utilise ce modèle pour implémenter la PlayerNotificationManager dans l'application de nouvelles Coinverse cryptocurrency.

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)
}
0
Adam Hurwitz