Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

App keeps running when foreground service is stopped last

I came across a behaviour in Android's process management in conjunction with foreground services, that really confuses me.

What is reasonable for me

  1. When you swipe your app from 'Recent apps', the OS should finish the app process in the relatively near future.
  2. When you swipe your app from 'Recent apps' while running a foreground service, the app stays alive.
  3. When you stop the foreground service before swiping your app from 'Recent apps' you get the same as for 1).

What confuses me

When you stop the foreground service while having no activities in foreground (app does NOT appear in 'Recent apps'), I would expect the app being killed now.

However, this is not happening, the app process is still alive.

Example

I have created a minimal example that shows this behaviour.

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

The BroadcastReceiver to stop the service:

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

The Activity:

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

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

Try it out the following ways:

  1. Start app, stop foreground service, remove app from 'Recent apps'
  2. Start app, remove app from 'Recent apps', stop foreground service

You can see in Android Studio's LogCat that the app process is marked [DEAD] for case 1 but not for case 2.

Since it is pretty easy to reproduce it might be an intended behaviour, but I did not find any real mention of this in the docs.

Does anybody know what is going on here?

like image 839
Christoph Lutz Avatar asked Oct 11 '19 17:10

Christoph Lutz


1 Answers

Android system is known for its self-awareness in terms of memory, processor power and applications processes lifetime - it decides by itself whether to kill a process of not(the same with activities and services)

Here is the official documentation regarding this matter.

Look at what it says regarding foreground

There will only ever be a few such processes in the system, and these will only be killed as a last resort if memory is so low that not even these processes can continue to run. Generally, at this point, the device has reached a memory paging state, so this action is required in order to keep the user interface responsive.

and visible processes(Foreground Service is a visible process)

The number of these processes running in the system is less bounded than foreground processes, but still relatively controlled. These processes are considered extremely important and will not be killed unless doing so is required to keep all foreground processes running.

It means that the Android OS will have your app process running as long as it will have memory enough to support all foreground processes. Even if you stop it - the system may just move it to cached processes and handle it in queue-like manner. Eventually it will be killed either way - but usually it is not for you to decide. Frankly you should not care at all what is happening with your app process after(and as long as) all the Android lifecycle methods are called. Android knows better.

Of course you ca kill the process with android.os.Process.killProcess(android.os.Process.myPid()); but it is not recommended since it disrupts proper Android elements lifecycle and proper callbacks may not be called, thus your app may misbehave in some cases.

Hope it helps.

like image 173
Pavlo Ostasha Avatar answered Oct 08 '22 13:10

Pavlo Ostasha