应用程序在前台服务停止后仍在运行。

14

我在使用Android的进程管理与前台服务时发现了一个行为,这让我非常困惑。

对我来说是合理的

  1. 当你从“最近使用的应用”中滑动移除应用时,操作系统应该会在不久的将来结束应用程序的进程。
  2. 当你在运行前台服务时从“最近使用的应用”中滑动移除应用程序时,应用程序仍然保持活动状态。
  3. 当你在从“最近使用的应用”中滑动移除应用程序之前停止前台服务时,你将得到与1)相同的结果。

让我感到困惑的是

当你没有正在运行的前台活动(在“最近使用的应用”中不显示应用程序)并停止了前台服务时,我希望应用程序现在已经被杀死了。

但是,这并没有发生,应用程序的进程仍然活着。

示例

我创建了一个最小的示例,展示了这种行为。

前台服务:

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中看到,在情况1下,应用程序进程被标记为[DEAD],而在情况2下没有。

既然很容易复制,这可能是一种有意的行为,但我没有在文档中找到任何真正的提及此事的地方。

有人知道这里发生了什么吗?

4个回答

3

Android系统以自我感知的方式著称,包括内存、处理器性能和应用程序进程的生命周期——它会自行决定是否杀死进程(活动和服务同样适用)。

这里是关于此事的官方文档。

看看它关于前台进程的说法:

在系统中只会有几个这样的进程,并且只有在内存非常低的情况下(即使这些进程无法继续运行),才会最后尝试杀死它们。一般来说,此时设备已经达到了内存分页状态,因此需要执行此操作以保持用户界面的响应性。

可见进程(前台服务是一个可见进程)

在系统中运行这些进程的数量比前台进程少得多,但仍然相对受控制。这些进程被认为是极其重要的,除非必须杀死所有前台进程,否则不会被杀死。

这意味着Android操作系统将使您的应用程序进程保持运行,只要它有足够的内存来支持所有前台进程。即使您停止它,系统也可能将其移动到缓存进程中,并以队列方式处理它。最终它将被杀死,但通常你无需担心你的应用程序进程到底发生了什么(只要所有Android生命周期方法都被调用)。Android知道得更好。

当然,您可以使用 android.os.Process.killProcess(android.os.Process.myPid()); 来杀死进程,但不建议这样做,因为它会破坏正确的Android元素生命周期并可能无法调用适当的回调函数,从而导致您的应用程序在某些情况下出现异常行为。

希望对您有所帮助。


0

在Android系统中,操作系统决定哪些应用程序被终止。

有一个优先级顺序:
具有前台服务的应用程序很少被杀死,相反,在一段时间的不活动后,其他应用程序和服务将被杀死,这就是您的应用程序所发生的情况。

当您完成前台服务时,这会增加系统终止您的应用程序的可能性,但这并不意味着它会立即被终止。


0
最近使用屏幕是一个系统级别的用户界面,列出了最近访问的活动任务
在列表中,您可以从单个应用程序中拥有多个活动或任务。它不是一个“最近使用的应用程序”列表。
因此,在“最近使用屏幕”的元素和应用程序进程之间没有直接关联。
无论如何,如果您关闭应用程序的最后一个活动并且没有其他正在进程中运行的内容(例如前台服务),则该进程将被清除。
因此,当您停止正在运行的前台服务(通过stopService()stopSelf()和取消绑定)时,系统也会清理它所在的进程。
因此,这确实是一种预期行为。

0

这取决于前台服务的实际作用。如果它使用线程、网络连接、文件 I/O 等主动消耗内存,即使你尝试停止服务,它也不会被销毁,因此进程将保持活动状态。这还包括在您尝试停止服务时仍处于活动状态的任何接口回调。特别是仍在运行(甚至被中断的)线程和绑定服务会阻塞生命周期,使服务无法正常停止。

确保您的服务没有内存泄漏,并关闭每个连接(数据库、网络等),在停止服务之前从所有接口中删除所有回调。您可以确保服务将在调用 onDestroy() 后被销毁。

对于不同的进程:我相信进程保持活动状态的原因是,系统以某种方式看待服务可能在一段时间后再次启动,因此它会保持进程活动一段时间。至少这是我观察到的,因为即使调用了 onDestroy(),进程仍然保持活动状态,我能够从调试器中看到它。

如果您想要确保(尽管这种方法不被推荐)在所有内容被销毁后进程被终止,您始终可以调用以下代码终止进程本身:

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

您可以使用问题中的示例重现此行为 - 没有连接、线程或其他任何东西在运行。onDestroy(服务)被调用,但进程仍然保持活动状态,即使一切都已经消失。即使操作系统保持进程处于活动状态以便重新启动服务,我也不明白为什么当您先停止服务,然后从最近使用的应用程序中删除应用程序时,它不会执行相同的操作。显示的行为似乎相当不直观,特别是考虑到最近对后台执行限制的更改,因此了解如何确保进程停止将是很好的。 - David Medenjak
@DavidMedenjak 请尝试使用getApplicationContext().startService(MyService.newIntent(this));代替您正在使用的内容,并告诉我结果。我认为活动仍然保持活动状态,因为使用了活动上下文而不是应用程序上下文。另外,如果您正在测试Oreo或更高版本,请尝试使用getApplicationContext().startforegroundService(MyService.newIntent(this)); - Furkan Yurdakul

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接