Додаток продовжує працювати, коли служба переднього плану остаточно зупинена


9

Я зіткнувся з поведінкою в управлінні процесами Android у поєднанні з сервісами переднього плану, що мене дійсно бентежить.

Що для мене розумно

  1. Коли ви проведіть пальцем із програми "Останні додатки", ОС має закінчити процес додатків у найближчому майбутньому.
  2. Коли ви проведіть пальцем із програми "Останні додатки", виконуючи службу переднього плану, додаток залишається живим.
  3. Якщо ви зупините послугу переднього плану перед тим, як перетягнути додаток із "Останні програми", ви отримаєте те саме, що і для 1).

Що мене бентежить

Якщо ви зупините службу переднього плану, не маючи жодної активності на передньому плані (додаток НЕ відображається в "Останні програми"), я б очікував, що додаток буде вбито зараз.

Однак цього не відбувається, процес додавання ще живий.

Приклад

Я створив мінімальний приклад, який показує таку поведінку.

Служба переднього плану:

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)
    }
}

Транслятор прийому припиняє послугу:

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)
    }
}

Діяльність:

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))
    }
}

Маніфест:

<?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>

Спробуйте наступними способами:

  1. Запустіть додаток, зупиніть службу переднього плану, видаліть додаток із "Останні програми"
  2. Запустіть додаток, видаліть додаток із "Останні програми", зупиніть службу переднього плану

У програмі LogCat Android Studio ви бачите, що для програми додаток позначено [DEAD] для випадку 1, а не для випадку 2.

Оскільки відтворити це досить просто, це може бути цілеспрямована поведінка, але я не знайшов реальних згадок про це в документах.

Хтось знає, що тут відбувається?

Відповіді:


0

Це залежить від того, що насправді робить послуга переднього плану. Якщо він використовує потоки, мережеві з'єднання, введення / виведення файлів і т. Д., Які активно споживають пам'ять, навіть якщо ви намагаєтеся зупинити службу, вона не буде знищена, отже, процес залишиться живим. Це також включає в себе будь-які зворотні дзвінки інтерфейсу, які залишаються живими, поки ви намагаєтесь зупинити послугу. Особливо потоки, які все ще запущені (навіть перервані) та пов'язані служби, блокують життєвий цикл, який зупиняє послугу належним чином.

Переконайтесь, що у вашій службі немає витоків пам'яті та закрийте кожне з'єднання (база даних, мережа тощо), видаліть усі зворотні дзвінки з усіх інтерфейсів, перш ніж зупиняти службу. Ви можете переконатися, що служба буде знищена, якщо onDestroy()її викликають.

Для різних процесів: я вважаю, що причина залишається живою в тому, що система бачить такий спосіб, коли служба може запускатися знову на деякий час, тому вона залишає процес живим ненадовго. Принаймні, це те, що я спостерігав, тому що навіть після того, onDestroy()як викликали, процес залишився живим, я зміг його побачити з налагоджувача.

Якщо ви хочете переконатись (хоча цей спосіб не рекомендується), щоб процес був убитий напевно після того, як все знищено, ви завжди можете викликати це, щоб припинити сам процес:

android.os.Process.killProcess(android.os.Process.myPid());

Ви можете відтворити поведінку на прикладі із запитання & mdash; ніякі з'єднання, потоки чи щось інше не працює. onDestroy(Служба) телефонує, але процес залишається живим довгий час, коли все пройде. Навіть якщо ОС підтримує процес живим, якщо служба перезапускається, я не бачу, чому вона не робить те саме, коли ви зупиняєте службу спочатку, а потім видаляйте додаток із залишків. Показана поведінка здається досить неінтуїтивною, особливо з огляду на останні зміни лімітів фонового виконання, тому було б непогано знати, як забезпечити припинення процесу
David Medenjak

@DavidMedenjak Спробуйте використовувати getApplicationContext().startService(MyService.newIntent(this));замість того, що ви використовуєте, і скажіть будь-ласка результати. Я вважаю, що діяльність залишається живою, оскільки контекст діяльності використовується замість контексту програми. Також якщо ви тестуєте на Oreo або вище, спробуйте скористатисяgetApplicationContext().startforegroundService(MyService.newIntent(this));
Furkan

0

Система Android відома своєю самосвідомістю з точки зору пам’яті, потужності процесора та тривалості процесів роботи - сама вирішує, чи вбивати процес не (те саме з діяльністю та сервісами)

Ось офіційна документація щодо цього питання.

Подивіться, що йдеться про передній план

У системі завжди буде лише кілька таких процесів, і вони будуть вбиті лише в крайньому випадку, якщо пам'ять буде настільки низькою, що навіть ці процеси не можуть продовжуватися. Як правило, в цей момент пристрій досяг статусу підкачки пам'яті, тому ця дія потрібна для того, щоб підтримувати інтерфейс користувача чуйним.

і видимі процеси (Служба переднього плану - це видимий процес)

Кількість цих процесів, що виконуються в системі, є менш обмеженою, ніж процеси переднього плану, але все ще відносно контрольована. Ці процеси вважаються надзвичайно важливими і не вбиватимуться, якщо цього не потрібно, щоб підтримувати всі процеси переднього плану.

Це означає, що в ОС Android буде працювати ваш додаток до тих пір, поки йому буде достатньо пам'яті для підтримки всіх процесів переднього плану . Навіть якщо ви зупините це - система може просто перемістити його в кешовані процеси та обробляти його в порядку, подібному до черги. Врешті-решт його вб'ють у будь-якому випадку - але зазвичай це не вирішувати. Відверто кажучи, вам взагалі не варто байдуже, що відбувається з вашим процесом додаток після (і поки) всі методи життєвого циклу Android. Android знає краще.

Звичайно, ви можете вбити процес, android.os.Process.killProcess(android.os.Process.myPid());але це не рекомендується, оскільки це порушує життєвий цикл належних елементів Android, і належні зворотні виклики можуть не викликатися, тому ваша програма може в деяких випадках поводитися неправильно.

Сподіваюся, це допомагає.


0

На Android саме операційна система вирішує, які програми вбиваються.

Є пріоритетний порядок:
програми, які мають службу переднього плану, рідко вбиваються, натомість інші програми та служби вбиваються після періоду бездіяльності, і саме це відбувається з вашим додатком.

Коли ви закінчите службу переднього плану, це збільшує можливості системи вбивства вашої програми, але ні в якому разі не означає, що вона буде вбита негайно.


0

Недавній екран [...] являє собою систему на рівень UI , що списки в останній час доступу до діяльності і завдань .

Ви можете мати кілька видів діяльності або завдання з однієї програми у списку. Це не так Останні програми список.

Тому немає прямої кореляції між елементами екрану отримання та процесом застосування.

У будь-якому випадку , якщо ви закриєте останню активність у вашому додатку і у вас більше нічого не працює в процесі (наприклад, послуга переднього плану), процес просто очищається.

Тож, коли ви зупините запущений передній план (шляхом stopService()чи stopSelf()відключення), система також очистить процес, в якому він працював.

Отже, це дійсно цілеспрямована поведінка .

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.