Mi sono imbattuto in un comportamento nella gestione dei processi di Android in combinazione con i servizi di primo piano, che mi confonde davvero.
Ciò che è ragionevole per me
- Quando passi l'app da "App recenti", il sistema operativo dovrebbe terminare il processo dell'app in un futuro relativamente prossimo.
- Quando scorri l'app da "App recenti" mentre esegui un servizio in primo piano, l'app rimane attiva.
- Quando interrompi il servizio in primo piano prima di scorrere l'app da "App recenti" ottieni lo stesso di 1).
Ciò che mi confonde
Quando interrompi il servizio in primo piano senza avere attività in primo piano (l'app NON viene visualizzata in "App recenti"), mi aspetto che l'app venga uccisa ora.
Tuttavia, ciò non accade, il processo dell'app è ancora attivo.
Esempio
Ho creato un esempio minimo che mostra questo comportamento.
The 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)
}
}
Il BroadcastReceiver per interrompere il servizio:
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'attività:
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))
}
}
Il manifest:
<?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>
Provalo nei modi seguenti:
- Avvia app, interrompi il servizio in primo piano, rimuovi l'app da "App recenti"
- Avvia l'app, rimuovi l'app da "App recenti", interrompi il servizio in primo piano
Nel LogCat di Android Studio puoi vedere che il processo dell'app è contrassegnato come [DEAD] per il caso 1 ma non per il caso 2.
Poiché è abbastanza facile riprodurlo, potrebbe essere un comportamento previsto, ma non ho trovato alcuna menzione reale di questo nei documenti.
Qualcuno sa cosa sta succedendo qui?
onDestroy
(Servizio) viene chiamato, ma il processo rimane in vita per molto tempo dopo che tutto è finito. Anche se il sistema operativo manteneva in vita il processo nel caso in cui il servizio venisse riavviato, non vedo perché non fa la stessa cosa quando si interrompe prima il servizio, quindi rimuove l'app dai recenti. Il comportamento visualizzato sembra piuttosto non intuitivo, soprattutto date le recenti modifiche ai limiti di esecuzione in background, quindi sarebbe bello sapere come assicurarsi che il processo venga interrotto