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?
źródło
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 procesugetApplicationContext().startService(MyService.newIntent(this));
zamiast tego, czego używasz i powiedz mi o wynikach. Uważam, że działanie pozostaje aktywne, ponieważ kontekst działania jest używany zamiast kontekstu aplikacji. Również jeśligetApplicationContext().startforegroundService(MyService.newIntent(this));
System Android jest znany ze swojej samoświadomości pod względem pamięci, mocy procesora i czasu życia aplikacji - sam decyduje, czy zabić proces nie (tak samo z działaniami i usługami)
Oto oficjalna dokumentacja dotycząca tej kwestii.
Zobacz, co mówi na temat pierwszego planu
i widoczne procesy (usługa pierwszego planu jest widocznym procesem)
Oznacza to, że system operacyjny Android będzie działał tak długo, jak będzie miał wystarczającą ilość pamięci do obsługi wszystkich procesów pierwszego planu . Nawet jeśli go zatrzymasz - system może po prostu przenieść go do buforowanych procesów i obsłużyć w sposób podobny do kolejki. W końcu zostanie zabity w obu kierunkach - ale zazwyczaj decyzja nie zależy od ciebie. Szczerze mówiąc, nie powinieneś przejmować się tym, co dzieje się z procesem aplikacji po (i tak długo, jak) są wywoływane wszystkie metody cyklu życia Androida. Android wie lepiej.
Oczywiście możesz zabić cały proces,
android.os.Process.killProcess(android.os.Process.myPid());
ale nie jest to zalecane, ponieważ zakłóca to prawidłowy cykl życia elementów Androida i nie można wywoływać odpowiednich wywołań zwrotnych, dlatego w niektórych przypadkach aplikacja może działać nieprawidłowo.Mam nadzieję, że to pomoże.
źródło
W systemie Android to system operacyjny decyduje, które aplikacje zostaną zabite.
Istnieje kolejność pierwszeństwa:
aplikacje, które mają usługę pierwszoplanową, są rzadko zabijane, zamiast tego inne aplikacje i usługi są zabijane po okresie bezczynności i tak dzieje się z twoją aplikacją.
Po zakończeniu usługi pierwszego planu zwiększa to możliwości systemu zabijania aplikacji, ale w żadnym wypadku nie oznacza to, że zostanie ona natychmiast zabita.
źródło
Możesz mieć kilka działań lub zadań z jednej aplikacji na liście. To nie jest lista najnowszych aplikacji .
Dlatego nie ma bezpośredniej korelacji między elementami ekranu Ostatnie a procesem aplikacji.
W każdym razie , jeśli zamkniesz ostatnią aktywność aplikacji i nie masz nic innego uruchomionego w procesie (jak usługa pierwszego planu), proces zostanie po prostu wyczyszczony.
Więc kiedy zatrzymasz bieżący pierwszy plan (wg
stopService()
lubstopSelf()
i niezwiązanie), system wyczyści również proces, w którym był uruchomiony.To jest rzeczywiście zamierzone zachowanie .
źródło