【问题标题】:App keeps running when foreground service is stopped last应用程序在前台服务最后停止时继续运行
【发布时间】:2019-10-11 17:33:43
【问题描述】:

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

停止服务的广播接收器:

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 中未标记。

因为它很容易重现,所以它可能是一种预期的行为,但我在文档中没有找到任何真正提到这一点的内容。

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

【问题讨论】:

    标签: android foreground-service


    【解决方案1】:

    Android 系统以其在内存、处理器能力和应用程序进程生命周期方面的自我意识而闻名 - 它自行决定是否杀死一个进程(与活动和服务相同)

    Here是关于此事的官方文档。

    看看它说的关于前景

    系统中只会有几个这样的进程,如果内存太低以至于这些进程都无法继续运行,这些进程只会作为最后的手段被杀死。通常,此时设备已进入内存分页状态,因此需要此操作以保持用户界面响应。

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

    系统中运行的这些进程的数量没有前台进程那么有限,但仍然相对受控。这些进程被认为非常重要,不会被杀死,除非需要这样做以保持所有前台进程的运行。

    这意味着只要有足够的内存支持所有 前台 进程,Android 操作系统就会让您的应用进程运行。即使你停止它 - 系统可能只是将它移动到 缓存 进程并以类似队列的方式处理它。最终,无论哪种方式,它都会被杀死——但通常这不是由你决定的。坦率地说,在调用所有 Android 生命周期方法之后(并且只要),您根本不应该关心您的应用程序进程发生了什么。 Android 更清楚。

    当然,您可以使用android.os.Process.killProcess(android.os.Process.myPid()); 终止该进程,但不建议这样做,因为它会破坏正确的 Android 元素生命周期并且可能不会调用正确的回调,因此您的应用在某些情况下可能会出现异常。

    希望对你有帮助。

    【讨论】:

      【解决方案2】:

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

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

      当您完成前台服务时,这增加了系统杀死您的应用程序的可能性,但绝不意味着它将立即被杀死。

      【讨论】:

        【解决方案3】:

        Recents Screen[...] 是一个系统级 UI,列出了最近访问的 activities 和 tasks。

        您可以从列表中的单个应用程序中获得多个活动或任务。它不是近期应用列表。

        因此,Recents Screen 的元素与应用进程之间没有直接关联。

        无论如何,如果您关闭应用程序的最后一个活动,并且进程中没有其他任何东西在运行(如前台服务),那么该进程就会被清理干净。

        因此,当您停止正在运行的前台(通过stopService() 或stopSelf() 并取消绑定)时,系统还将清理它正在运行的进程。

        所以这确实是一种有意的行为。

        【讨论】:

          【解决方案4】:

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

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

          对于不同的进程:我认为进程保持活跃的原因是,系统看到服务可能会在一段时间内重新启动,因此它会在短时间内保持进程活跃。至少,这是我观察到的,因为即使在调用 onDestroy() 之后,进程仍然存在,我能够从调试器中看到它。

          如果你想确保(即使不推荐这种方式)进程在所有东西都被销毁后被杀死,你总是可以调用它来终止进程本身:

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

          【讨论】:

          • 您可以使用问题中的示例重现该行为——没有连接、线程或其他任何东西继续运行。 onDestroy(服务)被调用,但在一切都消失后,该过程仍然存在很长时间。即使操作系统在服务重新启动时保持进程处于活动状态,我也不明白为什么当你先停止服务时它不会做同样的事情,然后从最近删除应用程序。显示的行为似乎相当不直观,尤其是考虑到最近对后台执行限制的更改,因此最好知道如何确保进程停止
          • @DavidMedenjak 尝试使用getApplicationContext().startService(MyService.newIntent(this)); 而不是你正在使用的,请告诉我结果。我相信活动仍然存在,因为使用的是活动上下文而不是应用程序上下文。此外,如果您在 Oreo 或更高版本上进行测试,请尝试使用 getApplicationContext().startforegroundService(MyService.newIntent(this));
          猜你喜欢
          • 2022-11-18
          • 2023-04-03
          • 1970-01-01
          • 1970-01-01
          • 2018-11-10
          • 2021-01-01
          • 2014-03-25
          • 2014-12-13
          • 1970-01-01
          相关资源
          最近更新 更多