Aplikacja działa, gdy usługa pierwszego planu jest zatrzymana jako ostatnia

9

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

  1. Po przesunięciu aplikacji z „Najnowsze aplikacje” system operacyjny powinien zakończyć proces aplikacji w stosunkowo niedalekiej przyszłości.
  2. Po przesunięciu aplikacji z „Najnowsze aplikacje” podczas uruchamiania usługi pierwszoplanowej aplikacja pozostaje aktywna.
  3. 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:

  1. Uruchom aplikację, zatrzymaj usługę pierwszego planu, usuń aplikację z „Najnowsze aplikacje”
  2. 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?

Christoph Lutz
źródło

Odpowiedzi:

0

Zależy to od tego, co faktycznie robi usługa pierwszego planu. Jeśli używa wątków, połączeń sieciowych, plików I / O itp., Które aktywnie zużywa pamięć, nawet jeśli spróbujesz zatrzymać usługę, nie zostanie ona zniszczona, a zatem proces pozostanie aktywny. Obejmuje to również wszelkie wywołania zwrotne interfejsu, które pozostają aktywne podczas próby zatrzymania usługi. Zwłaszcza wątki, które nadal działają (a nawet przerywają) i powiązane usługi blokują cykl życia, który właściwie zatrzymuje usługę.

Upewnij się, że nie ma wycieków pamięci w usłudze i zamknij każde połączenie (baza danych, sieć itp.), Usuń wszystkie połączenia zwrotne ze wszystkich interfejsów przed zatrzymaniem usługi. Możesz się upewnić, że usługa zostanie zniszczona, jeśli onDestroy()zostanie wywołana.

W przypadku różnych procesów: Uważam, że powodem, dla którego proces pozostaje aktywny, jest to, że system widzi sposób, w którym usługa może zostać ponownie uruchomiona na pewien czas, więc utrzymuje ten proces na krótki czas. Przynajmniej tak zaobserwowałem, ponieważ nawet po onDestroy()wywołaniu proces pozostał przy życiu, mogłem zobaczyć go z debuggera.

Jeśli chcesz upewnić się (choć nie jest to zalecane), że proces zostanie zabity na pewno po zniszczeniu wszystkiego, zawsze możesz to wywołać, aby zakończyć sam proces:

android.os.Process.killProcess(android.os.Process.myPid());
Furkan Yurdakul
źródło
Możesz odtworzyć zachowanie na przykładzie z pytania & ndash; żadne połączenia, wątki ani nic innego nie działa. onDestroyWywoł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
David Medenjak,
@DavidMedenjak Spróbuj użyć getApplicationContext().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śli getApplicationContext().startforegroundService(MyService.newIntent(this));
testujesz
0

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

W systemie będzie tylko kilka takich procesów, a zostaną one zabite w ostateczności, jeśli pamięć jest tak niska, że ​​nawet te procesy nie mogą być kontynuowane. Ogólnie rzecz biorąc, w tym momencie urządzenie osiągnęło stan stronicowania pamięci, więc to działanie jest wymagane, aby interfejs użytkownika był responsywny.

i widoczne procesy (usługa pierwszego planu jest widocznym procesem)

Liczba tych procesów działających w systemie jest mniej ograniczona niż procesów pierwszego planu, ale nadal względnie kontrolowana. Procesy te są uważane za niezwykle ważne i nie zostaną zabite, chyba że jest to wymagane do utrzymania wszystkich procesów na pierwszym planie.

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.

Pavlo Ostasha
źródło
0

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.

Lluis Felisart
źródło
0

Niedawnych ekranu [...] jest UI poziomie systemu, że listy ostatnio obejrzano działań i zadań .

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() lub stopSelf()i niezwiązanie), system wyczyści również proces, w którym był uruchomiony.

To jest rzeczywiście zamierzone zachowanie .

tynn
źródło