La aplicación sigue ejecutándose cuando el servicio en primer plano se detiene por última vez

9

Encontré un comportamiento en la gestión de procesos de Android junto con los servicios en primer plano, que realmente me confunde.

Lo que es razonable para mi

  1. Cuando desliza su aplicación desde 'Aplicaciones recientes', el sistema operativo debe finalizar el proceso de la aplicación en un futuro relativamente cercano.
  2. Cuando desliza su aplicación desde 'Aplicaciones recientes' mientras ejecuta un servicio en primer plano, la aplicación se mantiene activa.
  3. Cuando detiene el servicio en primer plano antes de deslizar su aplicación desde 'Aplicaciones recientes', obtiene lo mismo que para 1).

Lo que me confunde

Cuando detiene el servicio en primer plano sin tener actividades en primer plano (la aplicación NO aparece en 'Aplicaciones recientes'), esperaría que la aplicación se elimine ahora.

Sin embargo, esto no está sucediendo, el proceso de la aplicación sigue vivo.

Ejemplo

He creado un ejemplo mínimo que muestra este comportamiento.

El servicio de primer plano:

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)
    }
}

BroadcastReceiver para detener el servicio:

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)
    }
}

La actividad:

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))
    }
}

El manifiesto:

<?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>

Pruébelo de las siguientes maneras:

  1. Inicie la aplicación, detenga el servicio en primer plano, elimine la aplicación de 'Aplicaciones recientes'
  2. Inicie la aplicación, elimine la aplicación de 'Aplicaciones recientes', detenga el servicio en primer plano

Puede ver en LogCat de Android Studio que el proceso de la aplicación está marcado [MUERTO] para el caso 1 pero no para el caso 2.

Dado que es bastante fácil de reproducir, podría ser un comportamiento previsto, pero no encontré ninguna mención real de esto en los documentos.

¿Alguien sabe lo que está pasando aquí?

Christoph Lutz
fuente

Respuestas:

0

Esto depende de lo que realmente haga el servicio en primer plano. Si utiliza subprocesos, conexiones de red, E / S de archivos, etc. que consume memoria activamente, incluso si intenta detener el servicio, no se destruirá, por lo tanto, el proceso se mantendrá vivo. Esto también incluye dentro de cualquier interfaz devoluciones de llamada que permanecen activas mientras intenta detener el servicio. Especialmente los subprocesos que aún se ejecutan (incluso interrumpidos) y los servicios vinculados bloquean el ciclo de vida que detiene el servicio correctamente.

Asegúrese de no tener pérdidas de memoria en su servicio y cierre cada conexión (base de datos, red, etc.), elimine todas las devoluciones de llamada de todas las interfaces antes de detener su servicio. Puede asegurarse de que el servicio se destruirá si onDestroy()se llama.

Para diferentes procesos: creo que la razón por la que el proceso se mantiene vivo es que, el sistema ve de alguna manera que el servicio puede comenzar nuevamente por algún tiempo, por lo que mantiene el proceso vivo por un corto tiempo. Al menos, eso es lo que he observado, porque incluso después de que onDestroy()se llamó, el proceso se mantuvo vivo, pude verlo desde el depurador.

Si desea asegurarse (aunque no se recomienda esta forma) que el proceso se cancele con seguridad después de que todo se destruya, siempre puede llamar a esto para finalizar el proceso en sí mismo:

android.os.Process.killProcess(android.os.Process.myPid());
Furkan Yurdakul
fuente
Puede reproducir el comportamiento con el ejemplo de la pregunta & mdash; no se siguen ejecutando conexiones, hilos o cualquier otra cosa. onDestroySe llama a (Servicio), pero el proceso permanece vivo mucho después de que todo se haya ido. Incluso si el sistema operativo mantenía vivo el proceso en caso de que el servicio se reiniciara, no veo por qué no hace lo mismo cuando se detiene el servicio primero, luego se elimina la aplicación de los recientes. El comportamiento mostrado parece bastante poco intuitivo, especialmente dados los cambios recientes en los límites de ejecución en segundo plano, por lo que sería bueno saber cómo garantizar que el proceso se detenga
David Medenjak
@DavidMedenjak Intente usar en getApplicationContext().startService(MyService.newIntent(this));lugar de lo que está usando y dígame los resultados, por favor. Creo que la actividad se mantiene viva porque se utiliza el contexto de la actividad en lugar del contexto de la aplicación. Además, si está probando en Oreo o superior, intente usargetApplicationContext().startforegroundService(MyService.newIntent(this));
Furkan Yurdakul el
0

El sistema Android es conocido por su autoconciencia en términos de memoria, potencia del procesador y procesos de aplicaciones de por vida: decide por sí mismo si matar o no un proceso (lo mismo con actividades y servicios)

Aquí está la documentación oficial sobre este asunto.

Mira lo que dice sobre el primer plano

Solo habrá unos pocos procesos de este tipo en el sistema, y ​​estos solo se eliminarán como último recurso si la memoria es tan baja que ni siquiera estos procesos pueden continuar ejecutándose. En general, en este punto, el dispositivo ha alcanzado un estado de paginación de memoria, por lo que esta acción es necesaria para que la interfaz de usuario responda.

y procesos visibles (el Servicio en primer plano es un proceso visible )

El número de estos procesos que se ejecutan en el sistema es menos limitado que los procesos en primer plano, pero aún está relativamente controlado. Estos procesos se consideran extremadamente importantes y no se eliminarán a menos que sea necesario para mantener en ejecución todos los procesos en primer plano.

Significa que el sistema operativo Android hará que el proceso de su aplicación se ejecute siempre que tenga memoria suficiente para admitir todos los procesos en primer plano . Incluso si lo detiene, el sistema puede simplemente moverlo a procesos en caché y manejarlo en forma de cola. Eventualmente será asesinado de cualquier manera, pero por lo general no le corresponde a usted decidir. Francamente, no debería preocuparte en absoluto lo que sucede con el proceso de tu aplicación después (y siempre que) se invoquen todos los métodos del ciclo de vida de Android. Android lo sabe mejor.

Por supuesto, puede matar el proceso, android.os.Process.killProcess(android.os.Process.myPid());pero no se recomienda ya que interrumpe el ciclo de vida adecuado de los elementos de Android y es posible que no se invoquen las devoluciones de llamada adecuadas, por lo que su aplicación puede comportarse mal en algunos casos.

Espero eso ayude.

Pavlo Ostasha
fuente
0

En Android, es el sistema operativo quien decide qué aplicaciones se eliminan.

Hay un orden de prioridad: las
aplicaciones que tienen un servicio en primer plano rara vez se eliminan, en cambio, otras aplicaciones y servicios se eliminan después de un período de inactividad y eso es lo que sucede con su aplicación.

Cuando finaliza el servicio en primer plano, esto aumenta las posibilidades de que el sistema elimine su aplicación, pero en ningún caso significa que se eliminará de inmediato.

Lluis Felisart
fuente
0

La pantalla Recientes [...] es una IU de nivel de sistema que enumera las actividades y tareas a las que accedió recientemente .

Podría tener varias actividades o tareas desde una sola aplicación en la lista. Es no un reciente Aplicaciones lista.

Por lo tanto, no existe una correlación directa entre los elementos de la pantalla Recientes y el proceso de la aplicación.

De todos modos , si cierra la última actividad de su aplicación y no tiene nada más ejecutándose dentro del proceso (como un servicio en primer plano), el proceso simplemente se limpia.

Entonces, cuando detiene un primer plano en ejecución (por stopService()ostopSelf() vinculación), el sistema también limpiará el proceso en el que se estaba ejecutando.

Entonces, de hecho, es un comportamiento previsto .

tynn
fuente