L'application continue de fonctionner lorsque le service de premier plan est arrêté en dernier

9

Je suis tombé sur un comportement dans la gestion des processus d'Android en conjonction avec les services de premier plan, qui me déroute vraiment.

Ce qui est raisonnable pour moi

  1. Lorsque vous glissez votre application dans «Applications récentes», le système d'exploitation devrait terminer le processus d'application dans un avenir relativement proche.
  2. Lorsque vous faites glisser votre application depuis «Applications récentes» tout en exécutant un service de premier plan, l'application reste en vie.
  3. Lorsque vous arrêtez le service de premier plan avant de faire glisser votre application dans «Applications récentes», vous obtenez le même que pour 1).

Ce qui me déroute

Lorsque vous arrêtez le service de premier plan sans avoir d'activités au premier plan (l'application n'apparaît PAS dans les «applications récentes»), je m'attends à ce que l'application soit supprimée maintenant.

Cependant, cela ne se produit pas, le processus d'application est toujours en cours.

Exemple

J'ai créé un exemple minimal qui montre ce comportement.

Le ForegroundService:

import android.app.Notification
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
import android.content.Context
import android.content.Intent
import android.os.Build
import android.os.IBinder
import androidx.core.app.NotificationCompat
import timber.log.Timber

class MyService : Service() {

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

    override fun onCreate() {
        super.onCreate()
        Timber.d("onCreate")
    }

    override fun onDestroy() {
        super.onDestroy()
        Timber.d("onDestroy")

        // just to make sure the service really stops
        stopForeground(true)
        stopSelf()
    }

    override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
        Timber.d("onStartCommand")
        startForeground(ID, serviceNotification())
        return START_NOT_STICKY
    }

    private fun serviceNotification(): Notification {
        createChannel()

        val stopServiceIntent = PendingIntent.getBroadcast(
            this,
            0,
            Intent(this, StopServiceReceiver::class.java),
            PendingIntent.FLAG_UPDATE_CURRENT
        )
        return NotificationCompat.Builder(this, CHANNEL_ID)
            .setSmallIcon(R.drawable.ic_launcher_foreground)
            .setContentTitle("This is my service")
            .setContentText("It runs as a foreground service.")
            .addAction(0, "Stop", stopServiceIntent)
            .build()
    }

    private fun createChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            val notificationManager = getSystemService(NotificationManager::class.java)
            notificationManager.createNotificationChannel(
                NotificationChannel(
                    CHANNEL_ID,
                    "Test channel",
                    NotificationManager.IMPORTANCE_DEFAULT
                )
            )
        }
    }

    companion object {
        private const val ID = 532207
        private const val CHANNEL_ID = "test_channel"

        fun newIntent(context: Context) = Intent(context, MyService::class.java)
    }
}

Le BroadcastReceiver pour arrêter le service:

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

class StopServiceReceiver : BroadcastReceiver() {

    override fun onReceive(context: Context, intent: Intent) {

        val serviceIntent = MyService.newIntent(context)

        context.stopService(serviceIntent)
    }
}

L'activité:

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle

class MainActivity : AppCompatActivity() {

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        startService(MyService.newIntent(this))
    }
}

Le manifeste:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.christophlutz.processlifecycletest">

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

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

        <service android:name=".MyService"/>
        <receiver android:name=".StopServiceReceiver" />
    </application>

</manifest>

Essayez-le des manières suivantes:

  1. Démarrer l'application, arrêter le service de premier plan, supprimer l'application des «applications récentes»
  2. Démarrer l'application, supprimer l'application des «applications récentes», arrêter le service de premier plan

Vous pouvez voir dans LogCat d'Android Studio que le processus d'application est marqué [DEAD] pour le cas 1 mais pas pour le cas 2.

Comme il est assez facile à reproduire, cela pourrait être un comportement prévu, mais je n'ai trouvé aucune mention réelle de cela dans les documents.

Quelqu'un sait-il ce qui se passe ici?

Christoph Lutz
la source

Réponses:

0

Cela dépend de ce que fait réellement le service de premier plan. S'il utilise des threads, des connexions réseau, des E / S de fichiers, etc. qui consomme activement de la mémoire, même si vous essayez d'arrêter le service, il ne sera pas détruit, le processus restera donc en vie. Cela inclut également dans tous les rappels d'interface qui restent en vie pendant que vous essayez d'arrêter le service. En particulier, les threads qui s'exécutent encore (même interrompus) et les services liés bloquent le cycle de vie qui arrête le service correctement.

Assurez-vous que vous n'avez aucune fuite de mémoire dans votre service et fermez chaque connexion (base de données, réseau etc ...), supprimez tous les rappels de toutes les interfaces avant d'arrêter votre service. Vous pouvez vous assurer que le service sera détruit s'il onDestroy()est appelé.

Pour différents processus: je crois que la raison pour laquelle le processus reste en vie est que le système voit d'une manière où le service peut redémarrer pendant un certain temps, donc il maintient le processus en vie pendant une courte période. Du moins, c'est ce que j'ai observé, car même après l' onDestroy()appel, le processus est resté en vie, j'ai pu le voir depuis le débogueur.

Si vous voulez vous assurer (même si cette méthode n'est pas recommandée) que le processus est tué à coup sûr après que tout soit détruit, vous pouvez toujours appeler ceci pour terminer le processus lui-même:

android.os.Process.killProcess(android.os.Process.myPid());
Furkan Yurdakul
la source
Vous pouvez reproduire le comportement avec l'exemple de la question & mdash; aucune connexion, thread ou autre ne continue de fonctionner. onDestroy(Service) est appelé, mais le processus reste en vie longtemps après que tout soit parti. Même si le système d'exploitation gardait le processus en vie au cas où le service serait redémarré, je ne vois pas pourquoi il ne fait pas la même chose lorsque vous arrêtez le service en premier, puis supprimez l'application des recents. Le comportement affiché semble plutôt peu intuitif, en particulier compte tenu des modifications récentes apportées aux limites d'exécution en arrière-plan, il serait donc intéressant de savoir comment s'assurer que le processus est arrêté
David Medenjak
@DavidMedenjak Essayez d'utiliser à la getApplicationContext().startService(MyService.newIntent(this));place de ce que vous utilisez et dites-moi les résultats s'il vous plaît. Je pense que l'activité reste active car le contexte d'activité est utilisé à la place du contexte d'application. Aussi, si vous testez sur Oreo ou au-dessus, essayez d'utilisergetApplicationContext().startforegroundService(MyService.newIntent(this));
Furkan Yurdakul
0

Le système Android est connu pour sa conscience de soi en termes de durée de vie de la mémoire, de la puissance du processeur et des applications - il décide lui-même s'il doit ou non tuer un processus (de même pour les activités et les services)

Voici la documentation officielle à ce sujet.

Regardez ce qu'il dit au premier plan

Il n'y aura jamais que quelques-uns de ces processus dans le système, et ceux-ci ne seront tués qu'en dernier recours si la mémoire est si faible que même ces processus ne peuvent pas continuer à s'exécuter. Généralement, à ce stade, le périphérique a atteint un état de pagination de la mémoire, donc cette action est requise afin de maintenir l'interface utilisateur réactive.

et processus visibles (Foreground Service est un processus visible )

Le nombre de ces processus en cours d'exécution dans le système est moins limité que les processus de premier plan, mais reste relativement contrôlé. Ces processus sont considérés comme extrêmement importants et ne seront pas détruits sauf si cela est nécessaire pour maintenir tous les processus de premier plan en cours d'exécution.

Cela signifie que le système d'exploitation Android aura votre processus d'application en cours d'exécution tant qu'il aura suffisamment de mémoire pour prendre en charge tous les processus de premier plan . Même si vous l'arrêtez, le système peut simplement le déplacer vers des processus mis en cache et le gérer de la manière d'une file d'attente. Finalement, il sera tué de toute façon - mais généralement ce n'est pas à vous de décider. Franchement, vous ne devriez pas vous soucier du tout de ce qui se passe avec votre processus d'application après (et tant que) toutes les méthodes de cycle de vie Android sont appelées. Android sait mieux.

Bien sûr, vous pouvez tuer le processus avec android.os.Process.killProcess(android.os.Process.myPid());mais il n'est pas recommandé car il perturbe le cycle de vie des éléments Android appropriés et les rappels peuvent ne pas être appelés, votre application peut donc mal se comporter dans certains cas.

J'espère que cela aide.

Pavlo Ostasha
la source
0

Sur Android, c'est le système d'exploitation qui décide quelles applications sont supprimées.

Il y a un ordre de priorité: les
applications qui ont un service de premier plan sont rarement tuées, mais d'autres applications et services sont tués après une période d'inactivité et c'est ce qui se passe avec votre application.

Lorsque vous terminez le service de premier plan, cela augmente les possibilités du système de tuer votre application, mais cela ne signifie en aucun cas qu'elle sera supprimée immédiatement.

Lluis Felisart
la source
0

L' écran Récent est une [...] interface utilisateur au niveau du système qui répertorie les activités et tâches récemment consultées .

Vous pouvez avoir plusieurs activités ou tâches à partir d'une seule application dans la liste. Ce n'est pas une liste d' applications récentes .

Par conséquent, il n'y a pas de corrélation directe entre les éléments de l' écran Récents et le processus d'application.

Quoi qu'il en soit , si vous fermez la dernière activité de votre application et que rien d'autre ne s'exécute dans le processus (comme un service de premier plan), le processus est simplement nettoyé.

Ainsi, lorsque vous arrêtez un premier plan en cours d'exécution (par stopService()ou stopSelf()et sans liaison), le système nettoie également le processus dans lequel il était exécuté.

Il s'agit donc bien d'un comportement voulu .

tynn
la source