Приложение продолжает работать, когда служба переднего плана остановлена ​​последней - PullRequest
7 голосов
/ 11 октября 2019

Я столкнулся с поведением в управлении процессами 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. Запустите приложение, удалите приложение из«Недавние приложения», остановка службы переднего плана

В Android Studio LogCat видно, что процесс приложения помечен как [DEAD] для случая 1, но не для случая 2.

Поскольку ондовольно легко воспроизвести, это может быть предполагаемое поведение, но я не нашел никакого реального упоминания об этом в документации.

Кто-нибудь знает, что здесь происходит?

Ответы [ 4 ]

0 голосов
/ 21 октября 2019

Экран Недавние [...] - это пользовательский интерфейс системного уровня, который отображает недавно посещенные действия и задачи .

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

Таким образом, нет прямой связи между элементами экрана недавних данных и процессом приложения.

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

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

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

0 голосов
/ 15 октября 2019

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

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

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

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

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

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

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

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

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

0 голосов
/ 20 октября 2019

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

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

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

0 голосов
/ 14 октября 2019

Это зависит от того, что на самом деле делает служба переднего плана. Если он использует потоки, сетевые подключения, файловый ввод-вывод и т. Д., Которые активно потребляют память, даже если вы попытаетесь остановить службу, она не будет уничтожена, следовательно, процесс останется живым. Это также включает в себя любые обратные вызовы интерфейса, которые остаются в живых, пока вы пытаетесь остановить службу. Особенно потоки, которые все еще работают (даже прерываются), и связанные службы блокируют жизненный цикл, который правильно останавливает службу.

Убедитесь, что у вас нет утечек памяти в вашей службе, и закройте все соединения (база данных, сеть и т. Д.). .), удалите все обратные вызовы со всех интерфейсов, прежде чем останавливать службу. Вы можете быть уверены, что служба будет уничтожена, если вызывается onDestroy().

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

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

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