Reputation: 161
I came across a behaviour in Android's process management in conjunction with foreground services, that really 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.
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:
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
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
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
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
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