Natknąłem się na zachowanie w zarządzaniu procesami Androida w połączeniu z usługami pierwszego planu, co naprawdę mnie dezorientuje.
Co jest dla mnie rozsądne
- Po przesunięciu aplikacji z „Najnowsze aplikacje” system operacyjny powinien zakończyć proces aplikacji w stosunkowo niedalekiej przyszłości.
- Po przesunięciu aplikacji z „Najnowsze aplikacje” podczas uruchamiania usługi pierwszoplanowej aplikacja pozostaje aktywna.
- Gdy zatrzymasz usługę pierwszoplanową przed przesunięciem aplikacji z „Najnowsze aplikacje”, otrzymasz to samo, co dla 1).
Co mnie dezorientuje
Gdy zatrzymasz usługę pierwszoplanową, ale nie masz żadnych działań na pierwszym planie (aplikacja NIE pojawia się w „Najnowsze aplikacje”), oczekiwałbym, że aplikacja zostanie teraz zabita.
Jednak tak się nie dzieje, proces aplikacji wciąż trwa.
Przykład
Stworzyłem minimalny przykład, który pokazuje to zachowanie.
Usługa pierwszoplanowa:
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)
}
}
Odbiorca transmisji, aby zatrzymać usługę:
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)
}
}
Aktywność:
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))
}
}
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>
Wypróbuj następujące sposoby:
- Uruchom aplikację, zatrzymaj usługę pierwszego planu, usuń aplikację z „Najnowsze aplikacje”
- Uruchom aplikację, usuń aplikację z „Najnowsze aplikacje”, zatrzymaj usługę pierwszego planu
W LogCat Android Studio widać, że proces aplikacji jest oznaczony [DEAD] dla przypadku 1, ale nie dla przypadku 2.
Ponieważ reprodukowanie jest dość łatwe, może to być zamierzone zachowanie, ale nie znalazłem żadnej prawdziwej wzmianki o tym w dokumentacji.
Czy ktoś wie, co się tutaj dzieje?
onDestroy
Wywoływana jest usługa (Service), ale proces pozostaje aktywny długo po tym, jak wszystko zniknie. Nawet jeśli system operacyjny utrzymywał proces przy ponownym uruchomieniu usługi, nie rozumiem, dlaczego nie robi tego samego, gdy najpierw zatrzymasz usługę, a następnie usuniesz aplikację z ostatnich. Wyświetlane zachowanie wydaje się raczej nieintuicyjne, zwłaszcza biorąc pod uwagę ostatnie zmiany w limitach wykonania w tle, więc dobrze byłoby wiedzieć, jak zapewnić zatrzymanie procesu