Christoph Lutz
Christoph Lutz

Reputation: 161

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?

Upvotes: 16

Views: 6576

Answers (4)

tynn
tynn

Reputation: 39843

The Recents Screen [...] is a system-level UI that lists recently accessed activities and tasks.

You could have several activities or task from a single app in the list. It's not a Recent Apps list.

Therefore there's no direct correlation between the elements of the Recents Screen and the app process.

Anyhow, if you close the last activity of your app and don't have anything else running within the process (like a foreground service), the process just gets cleaned up.

So when you stop a running foreground (by stopService() or stopSelf() and unbinding), the system will also cleanup the process it was running in.

So it's indeed an intended behavior.

Upvotes: 0

from56
from56

Reputation: 4127

On Android it is the operating system who decides which applications are killed.

There is an order of priority:
Applications that have a foreground service are rarely killed, instead other apps and services are killed after a period of inactivity and that is what happens with your app.

When you finish the foreground service, this increases the possibilities of the system killing your app but in no case does it mean that it will be killed immediately.

Upvotes: 0

Pavlo Ostasha
Pavlo Ostasha

Reputation: 16699

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.

Upvotes: 4

Furkan Yurdakul
Furkan Yurdakul

Reputation: 3147

This depends on what the foreground service actually does. If it uses threads, network connections, file I/O etc.. that consumes memory actively, even if you try to stop the service, it will not be destroyed, hence the process will stay alive. This also includes within any interface callbacks that stay alive while you're trying to stop the service. Especially threads that still run (even interrupted) and bound services block the lifecycle that is stopping the service properly.

Make sure that you have no memory leaks in your service and close every connection (database, network etc...), remove all callbacks from all interfaces before stopping your service. You can make sure that the service is going to be destroyed if onDestroy() is called.

For different processes: I believe the reason that the process stays alive is that, the system sees in a way where the service may start again for some time, so it keeps the process alive for a short time. At least, that is what I have observed, because even after onDestroy() was called, the process stayed alive, I was able to see it from the debugger.

If you want to ensure (even though this way is not recommended) that the process gets killed for sure after everything is destroyed, you can always call this to terminate the process itself:

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

Upvotes: 0

Related Questions