Приложение продолжает работать, когда служба переднего плана остановлена ​​последней

9

Я столкнулся с поведением в управлении процессами Android в сочетании с сервисами переднего плана, которое меня действительно смущает.

Что для меня разумно

  1. Когда вы проводите свое приложение из «Недавних приложений», ОС должна завершить процесс приложения в относительно ближайшем будущем.
  2. При удалении приложения из списка «Недавние приложения» во время работы службы переднего плана оно остается активным.
  3. Когда вы останавливаете службу переднего плана перед удалением приложения из списка «Недавние приложения», вы получаете то же, что и для 1).

Что меня смущает

Когда вы останавливаете службу переднего плана, не имея никаких действий на переднем плане (приложение не отображается в «Недавних приложениях»), я ожидаю, что приложение будет убито сейчас.

Однако этого не происходит, процесс приложения еще жив.

пример

Я создал минимальный пример, который показывает это поведение.

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

BroadcastReceiver для остановки службы:

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());
Фуркан Юрдакул
источник
Вы можете воспроизвести это поведение на примере из вопроса - ни одно из соединений, потоков или чего-либо еще не работает. onDestroy(Служба) вызывается, но процесс остается живым даже после того, как все прошло. Даже если ОС поддерживала процесс в случае перезапуска службы, я не понимаю, почему она не делает то же самое, когда вы сначала останавливаете службу, а затем удаляете приложение из последних. Отображаемое поведение кажется довольно неинтуитивным, особенно с учетом недавних изменений пределов фонового выполнения, поэтому было бы неплохо узнать, как обеспечить остановку процесса
Дэвид Меденжак,
@DavidMedenjak Попробуйте использовать getApplicationContext().startService(MyService.newIntent(this));вместо того, что вы используете, и сообщите мне результаты, пожалуйста. Я считаю, что активность остается в живых, потому что контекст деятельности используется вместо контекста приложения. Кроме того, если вы тестируете на Oreo или выше, попробуйте использоватьgetApplicationContext().startforegroundService(MyService.newIntent(this));
Furkan Yurdakul
0

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

Вот официальная документация по этому вопросу.

Посмотрите, что он говорит о переднем плане

В системе будет только несколько таких процессов, и они будут уничтожены только в крайнем случае, если память настолько мала, что даже эти процессы не смогут продолжать работать. Как правило, в этот момент устройство перешло в состояние подкачки памяти, поэтому это действие необходимо для обеспечения отзывчивости интерфейса пользователя.

и видимые процессы (Foreground Service - видимый процесс)

Число этих процессов, запущенных в системе, менее ограничено, чем процессы переднего плана, но все еще относительно контролируемо. Эти процессы считаются чрезвычайно важными и не будут уничтожены, если это не требуется для поддержания работы всех процессов переднего плана.

Это означает, что в ОС Android будет запущен процесс вашего приложения, если у него будет достаточно памяти для поддержки всех процессов переднего плана . Даже если вы остановите это - система может просто переместить его в кэшированные процессы и обработать его в порядке очереди. В конце концов это будет убито в любом случае - но обычно это не вам решать. Честно говоря, вам совершенно не важно, что происходит с процессом вашего приложения после (и до тех пор, пока) будут вызваны все методы жизненного цикла Android. Андроид знает лучше.

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

Надеюсь, поможет.

Павло Осташа
источник
0

На Android это операционная система, которая решает, какие приложения будут убиты.

Существует порядок приоритета:
приложения, имеющие службу переднего плана, редко удаляются, вместо этого другие приложения и службы уничтожаются после периода бездействия, и именно это происходит с вашим приложением.

Когда вы заканчиваете службу переднего плана, это увеличивает возможности системы, убивающей ваше приложение, но ни в коем случае не означает, что оно будет немедленно убито.

Луис Фелисарт
источник
0

Экран « Последние» [...] представляет собой пользовательский интерфейс системного уровня, в котором перечислены недавно выполненные действия и задачи .

Вы можете иметь несколько действий или задач из одного приложения в списке. Это не список недавних приложений .

Следовательно, нет прямой связи между элементами экрана «Недавние» и процессом приложения.

В любом случае , если вы закроете последнюю активность вашего приложения и в этом процессе больше ничего не будет запущено (например, служба переднего плана), процесс будет просто очищен.

Поэтому, когда вы останавливаете работающий передний план (путем stopService()или stopSelf()отменой привязки), система также очищает процесс, в котором она выполнялась.

Так что это действительно намеренное поведение .

tynn
источник