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
- 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.
- Lorsque vous faites glisser votre application depuis «Applications récentes» tout en exécutant un service de premier plan, l'application reste en vie.
- 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:
- Démarrer l'application, arrêter le service de premier plan, supprimer l'application des «applications récentes»
- 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?
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é