Tout d'abord, j'ai regardé ces derniers;
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,
startForeground()
dans onCreate()
Service.startForeground()
avant Context.stopService()
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?
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()
onCreate()
. Doc officielService.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.
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 :))
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
J'ai presque éliminé le problème avec startForeground () dans les méthodes MediaSessionCompat.Callback comme onPlay (), onPause ().
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é
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.