web-dev-qa-db-fra.com

Android 9 (Pie), Context.startForegroundService () n'a pas ensuite appelé Service.startForeground (): ServiceRecord

Tout d'abord, j'ai regardé ces derniers;

enter image description here

J'ai une application de streaming utilisée par près d'un million de personnes. J'utilise le service de premier plan pour le joueur. Je n'ai pas encore implémenté MediaSession. J'ai 99,95% de sessions sans crash. Cette application fonctionne donc sur toutes les versions, mais j'ai commencé à obtenir des rapports de plantage (ANR) avec Android 9. Ce plantage se produit uniquement sur les téléphones Samsung, en particulier les modèles s9, s9+, s10, s10+, note9.

J'ai essayé ça,

  • Appel de la méthode startForeground() dans onCreate()
  • Appel de Service.startForeground() avant Context.stopService()
  • Autres réponses stackoverflow à des questions similaires

J'ai lu certains commentaires des développeurs de Google, ils ont dit que c'était juste Intended Behavior. Je me demande si cela est dû au système de Samsung ou à Android OS. Quelqu'un a-t-il une opinion à ce sujet? Comment puis-je résoudre ce problème?

16
Beyazid

J'attendais mon rapport de crash pour partager la solution. Je n'ai eu aucun crash ou ANR près de 20 jours. Je souhaite partager ma solution. Cela peut aider ceux qui rencontrent ce problème.

Dans la méthode onCreate()

  • Tout d'abord, mon application est une application multimédia. Je n'ai pas encore implémenté la médiation. Je crée un canal de notification en haut de onCreate(). Doc officiel
  • J'appelle la méthode Service.startForeground() après la méthode Context.startForegroundService(). Dans ma méthode prepareAndStartForeground().

    Remarque: je ne sais pas pourquoi mais ContextCompat.startForegroundService () ne fonctionne pas correctement.

Pour cette raison, j'ai ajouté manuellement la même fonction à ma classe de service au lieu d'appeler ContextCompat.startForegroundService()

private fun startForegroundService(intent: Intent) {
    if (Build.VERSION.SDK_INT >= 26) {
        context.startForegroundService(intent)
    } else {
        // Pre-O behavior.
        context.startService(intent)
    }
}

prepareAndStartForeground() méthode

private fun prepareAndStartForeground() {
    try {
        val intent = Intent(ctx, MusicService::class.Java)
        startForegroundService(intent)

        val n = mNotificationBuilder.build()
        // do sth
        startForeground(Define.NOTIFICATION_ID, n)
    } catch (e: Exception) {
        Log.e(TAG, "startForegroundNotification: " + e.message)
    }
}

C'est ma onCreate()

override fun onCreate() {
    super.onCreate()
    createNotificationChannel()
    prepareAndStartForeground()
}

Ma onStartCommand()

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    if (intent == null) {
        return START_STICKY_COMPATIBILITY
    } else {
        //....
        //...
    }
    return START_STICKY
}

onRebind, onBind, onUnbind méthodes comme celles-ci

internal var binder: IBinder? = null

override fun onRebind(intent: Intent) {
    stopForeground(true) // <- remove notification
}

override fun onBind(intent: Intent): IBinder? {
    stopForeground(true) // <- remove notification
    return binder
}

override fun onUnbind(intent: Intent): Boolean {
    prepareAndStartForeground() // <- show notification again
    return true
}

Nous devons effacer quelque chose lors de l'appel de onDestroy ()

   override fun onDestroy() {
    super.onDestroy()
    releaseService()
   }

private fun releaseService() {
    stopMedia()
    stopTimer()
    // sth like these
    player = null
    mContext = null
    afChangeListener = null
    mAudioBecomingNoisy = null
    handler = null
    mNotificationBuilder = null
    mNotificationManager = null
    mInstance = null
}

J'espère que cette solution fonctionne correctement pour vous.

4
Beyazid

Après trop de difficultés avec ce crash, j'ai finalement corrigé cette exception complètement et trouvé la solution.

Assurez-vous que vous avez fait ces trucs dans votre service, je les énumère comme ci-dessous: (certains de ces trucs sont répétitifs comme mentionné dans une autre réponse, je les réécris juste).

1- Appeler

startForeground ()

dans les deux onCreate et onStartCommand . (il est correct d'appeler startForeground () plusieurs fois)

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

@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    if (intent == null) {
        return START_NOT_STICKY;
    }
    final int command = intent.getIntExtra(MAIN_SERVICE_COMMAND_KEY, -1);

    if (command == MAIN_SERVICE_START_COMMAND) {
        startCommand();
        return START_STICKY;
    }
    return START_NOT_STICKY;
}

 private void startCommand() {
    createNotificationAndStartForeground();
    runningStatus.set(STARTED);
}

2- Arrêtez votre service en utilisant

context.stopService ()

, il n'est pas nécessaire d'appeler stopForeground () ou stopSelf () .

  try {
        context.stopService(
                new Intent(
                        context,
                        NavigationService.class
                )
        );
    } catch (Exception ex) {
        Crashlytics.logException(ex);
        LogManager.e("Service manager can't stop service ", ex);
    }

3- Démarrez votre service en utilisant

ContextCompat.startForegroundService ()

il gérera différentes versions d'API.

   ContextCompat.startForegroundService(
            context,
            NavigationService.getStartIntent(context)
    );

4- Si votre service a des actions (besoin d'intentions en attente), gérez vos intentions en attente avec un récepteur de diffusion plutôt que votre service actuel (il appellera votre service sur Create () et peut être dangereux, ou utiliser PendingIntent.FLAG_NO_CREATE), c'est une bonne pratique d'avoir un récepteur de diffusion spécifique pour gérer vos actions de notification de service , Je veux dire créer toutes vos intentions en attente en utilisant PendingIntent.getBroadcast () .

    private PendingIntent getStopActionPendingIntent() {
    final Intent stopNotificationIntent = getBroadCastIntent();

    stopNotificationIntent.setAction(BROADCAST_STOP_SERVICE);

    return getPendingIntent(stopNotificationIntent);
}

private PendingIntent getPendingIntent(final Intent intent) {
    return PendingIntent.getBroadcast(
            this,
            0,
            intent,
            0
    );
}

new NotificationCompat.Builder(this, CHANNEL_ID)
            .addAction(
                    new NotificationCompat.Action(
                            R.drawable.notification,
                            getString(R.string.switch_off),
                            getStopActionPendingIntent()
                    )
            )

5- Toujours avant d'arrêter votre service assurez-vous que votre service est créé et démarré (je crée une classe globale qui a mon état de service)

  if (navigationServiceStatus == STARTED) {
            serviceManager.stopNavigationService();
        }

6- Définissez votre notificationId sur un nombre long tel que 121412.

7- L'utilisation de NotificationCompat.Builder gérera différentes versions d'API dont vous avez juste besoin pour créer un canal de notification pour les versions Build> = Build.VERSION_CODES.O. (Celui-ci n'est pas une solution, il suffit de rendre votre code plus lisible)

8- Ajouter

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

autorisation à votre manifeste. (celui-ci est mentionné dans Android docs) Android Foreground Service

j'espère que ça aide :))

5
Sepehr

Le composant Android Service est un peu difficile à faire fonctionner correctement, en particulier sur les versions ultérieures Android où le système d'exploitation ajoute des restrictions supplémentaires. Comme mentionné dans les autres réponses, lors du démarrage de votre Service, utilisez ContextCompat.startForegroundService(). Ensuite, dans Service.onStartCommand(), appelez immédiatement startForeground(). Enregistrez le Notification vous souhaitez afficher en tant que champ membre et l'utiliser à moins qu'il ne soit nul. Exemple:

private var notification:Notification? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    if (notification == null) {
        notification = createDefaultNotification()
    }
    startForeground(NOTIFICATION_ID, notification)

    // Do any additional setup and work herre

    return START_STICKY
}

Toujours retourner START_STICKY dans votre Service. Tout le reste est probablement la mauvaise chose, surtout si vous utilisez un lecteur audio de quelque sorte que ce soit. En fait, si vous utilisez un lecteur audio, vous ne devez pas implémenter votre propre service mais utiliser plutôt MediaBrowserServiceCompat (depuis AndroidX).

Je recommande également les articles de blog que j'ai écrits à ce sujet: https://hellsoft.se/how-to-service-on-Android-part-3-1e24113152cd

1
Erik Hellman

J'ai presque éliminé le problème avec startForeground () dans les méthodes MediaSessionCompat.Callback comme onPlay (), onPause ().

0
Mateusz Kaflowski

Après avoir eu le même problème avec les mêmes téléphones, j'ai apporté quelques modifications et les plantages ont disparu. Je ne sais pas ce qui a fait l'affaire, mais je suppose que d'appeler startForeground dans onCreate et onStartCommand. Je ne sais pas pourquoi cela est nécessaire si le service est déjà démarré et que tout a été correctement appelé dans onCreate.

Autres modifications: - Modification de serviceId en un nombre faible (1-10) - Appel de startFororegroundService moins souvent via une classe synchrone singleton (cela a été implémenté auparavant avec les plantages pour empêcher l'arrêt du service avant qu'il ne démarre avec un rappel de onStartCommand, mais maintenant il le fait aussi filtre les appels si le service a déjà démarré). - en utilisant START_REDELIVER_INTENT (ne devrait rien affecter)

Le problème s'est produit sur les téléphones mentionnés uniquement pour certains utilisateurs, donc je soupçonne qu'il est lié à une nouvelle mise à jour de Samsung et sera finalement corrigé

0
Michal Polivka

Selon ce message d'erreur, cela signifie que lorsque vous appelez Context.startForegroundService (), vous devez émettre une notification à l'aide de la méthode Service.startForeground (). Voilà ce que je comprends.

0
user1090751