Context.startForegroundService()没有调用Service.startForeground()-仍然存在问题

4

在查看Google问题和许多其他SO上的问题之后,我得出了我在底部添加的解决方案。

我要处理的是以下内容:

  • onCreateonStartCommand中,如果服务尚未在前台,则确保将其移动到前台
  • 我不仅仅停止服务,我使用额外的命令发送给onStartCommand处理程序来确保服务不会在启动时停止(在它完成前往前台之前)
  • 我从不直接停止服务(context.stopService(...)),我总是通过运行服务本身的命令来停止服务-因此我确保它只能在运行在前台时停止,而不是在启动期间
  • 一个服务只能停止一次
  • 我通过取消其前台通知并仅随后停止服务本身来停止服务

我个人使用该服务进行覆盖,因此我不在类内处理绑定器,因为我不使用它们。

我得到的日志

2019-07-29 21:41:27,146 [[BaseOverlayService:62 onCreate]]: onCreate
2019-07-29 21:41:27,146 [[BaseOverlayService:142 b]]: BEFORE moveToForeground (called by onCreate)
2019-07-29 21:41:27,152 [[BaseOverlayService:159 b]]: AFTER moveToForeground (called by onCreate) - moved to foreground: true
2019-07-29 21:41:27,176 [[BaseOverlayService:79 onStartCommand]]: onStartCommand: isForeground: true | action: null | isStopping: false
2019-07-29 21:41:27,945 [[BaseOverlayService:142 b]]: BEFORE moveToForeground (called by updateNotification [OverlayService [onInitFinished]])
2019-07-29 21:41:27,947 [[BaseOverlayService:159 b]]: AFTER moveToForeground (called by updateNotification [OverlayService [onInitFinished]]) - moved to foreground: false

这是一个崩溃报告的日志-可以看到,在第3行中,服务被移至前台(moved to foreground: true),并且在第6行中已经知道它正在前台运行。
我在我的Android 9设备上大量使用此应用程序(24/7,它一直在运行),并且没有问题。自从我使用下面的基类以来,问题已经真正减少到每月总共只有几次崩溃。尽管如此,以上日志显示,我的服务在毫秒内运行在前台,但仍可能会崩溃,如下所示:
android.app.RemoteServiceException: Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{86fa711 u0 com.my.app/com.my.app.service.OverlayService}
    at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1855)
    at android.os.Handler.dispatchMessage(Handler.java:106)
    at android.os.Looper.loop(Looper.java:214)
    at android.app.ActivityThread.main(ActivityThread.java:6986)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1445)

你们有人发现我的基类存在任何问题吗?

代码

abstract class BaseOverlayService<T : BaseOverlayService<T>>(
        val foregroundNotificationId: Int,
        val notificationCreator: ((service: T) -> Notification)
) : Service() {

    companion object {

        val DEBUG = true

        // helper function, simply checks if this service is already running by checking the ActivityManager
        inline fun <reified T : BaseOverlayService<T>> isRunning(context: Context): Boolean {
            return Tools.isServiceRunning(context, T::class.java)
        }

        inline fun <reified T : BaseOverlayService<T>> start(context: Context, checkIfServiceIsRunning: Boolean) {
            if (checkIfServiceIsRunning && isRunning<T>(context)) {
                L.logIf { DEBUG }?.d { "IGNORED start intent" }
                return
            }

            L.logIf { DEBUG }?.d { "send start intent" }
            val intent = Intent(context, T::class.java)
            ContextCompat.startForegroundService(context, intent)
        }

        inline fun <reified T : BaseOverlayService<T>> sendAction(context: Context, checkIfServiceIsRunning: Boolean, action: String, intentUpdater: ((Intent) -> Unit) = {}) {
            if (checkIfServiceIsRunning && !isRunning<T>(context)) {
                L.logIf { DEBUG }?.d { "IGNORED action intent - action: $action" }
                return
            }

            L.logIf { DEBUG }?.d { "send action intent - action: $action" }
            val intent = Intent(context, T::class.java)
            intent.action = action
            intentUpdater(intent)
            ContextCompat.startForegroundService(context, intent)
        }
    }

    protected var isForeground = false
        private set
    protected var isStopping: Boolean = false
        private set

    // ------------------------
    // service events
    // ------------------------

    final override fun onCreate() {

        L.logIf { DEBUG }?.d { "onCreate" }

        super.onCreate()

        if (foregroundNotificationId <= 0) {
            throw RuntimeException("foregroundNotificationId must be > 0!")
        }

        moveToForeground("onCreate")

        onCreateEvent()
    }

    final override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {

        val returnValue = START_STICKY

        L.logIf { DEBUG }?.d { "onStartCommand: isForeground: $isForeground | action: ${intent?.action} | isStopping: $isStopping" }

        // 1) if service is stopping, we ignore the event
        if (isStopping) {
            return returnValue
        }

        // 2) if service is not running in foreground we make it run in the foreground
        if (!isForeground) {
            moveToForeground("onStartCommand")
        }

        onStartCommandEvent(intent, flags, startId)

        return returnValue
    }

    final override fun onBind(intent: Intent): IBinder? {
        // overlay service is never bound!
        return null
    }

    // ------------------------
    // Forwarded abstract events
    // ------------------------

    abstract fun onCreateEvent()

    abstract fun onStartCommandEvent(intent: Intent?, flags: Int, startId: Int)

    abstract fun onStopEvent()

    // ------------------------
    // protected functions
    // ------------------------

    protected fun stopService() {

        L.logIf { DEBUG }?.d { "stopService | isStopping: $isStopping" }

        if (isStopping) {
            L.logIf { DEBUG }?.d { "IGNORED stopService" }
            return
        }

        onStopEvent()
        isStopping = true
        moveToBackground(true)
        stopSelf()

        L.logIf { DEBUG }?.d { "stopService finished" }
    }

    protected fun updateNotification(caller: String) {
        moveToForeground("updateNotification [$caller]")
    }

    // ------------------------
    // private foreground/background functions
    // ------------------------

    private fun moveToForeground(caller: String): Boolean {

        L.logIf { DEBUG }?.d { "BEFORE moveToForeground (called by $caller)" }

        // 1) Create notification
        val notification = notificationCreator(this as T)

        // 2.1) Create foreground notification
        val result = if (!isForeground) {
            isForeground = true
            startForeground(foregroundNotificationId, notification)
            true
        }
        // 2.2) Update foreground notification
        else {
            notificationManager.notify(foregroundNotificationId, notification)
            false
        }

        L.logIf { DEBUG }?.d { "AFTER moveToForeground (called by $caller) - moved to foreground: $result" }

        return result
    }

    private fun moveToBackground(cancelNotification: Boolean) {
        isForeground = false
        super.stopForeground(cancelNotification)
    }

    // ------------------------
    // private helper functions
    // ------------------------

    private val notificationManager by lazy {
        getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    }
}

如果您找到了解决问题的方法,请将其发布为答案并接受它。 - matdev
@matdev 你可以尝试一下我在问题中提供的解决方案,目前这是我能找到的最好的方法,而且大部分时间都有效。如果我在 Stack Overflow 之外解决了它,我会发布一个答案。 - prom85
1
看起来这只是谷歌作为“安全措施”积极引入的又一个重大错误的例子,以及他们对开发人员投诉的完全无视。我已经试图解决这个问题很长时间了,最终得到了20多个不同的崩溃报告,每个报告通常会在一个月内触发3到10次,并且都有这个潜在的错误。[在此处插入“这是疯狂的!”,“疯狂?这是谷歌!”的GIF] - 0101100101
1个回答

0

我曾经遇到过同样的问题,但是我通过以下方式解决了它:

从Android 9 Pie开始,如果您的服务在使用startForegroundService命令启动后5秒内没有调用startForeground,那么它会产生ANR +崩溃。

解决方案是在前台服务的onStartCommand方法开头添加一个startForeground()命令,并带有您的通知,就像这样:

final override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int 
{
    startForeground(YOUR_FOREGROUND_NOTIFICATION_ID, Notification);
    // rest of your code here...
}

如果您查看我的问题,这已经是我的解决方案的一部分了。 - prom85
抱歉,你说得对。如果你的代码中没有满足条件2),那么moveToForeground()就不会被调用……最终系统可能会杀掉你的服务,你可以看一下这个问题。 - JBA
文档中在哪里指定了5秒规则? - IgorGanapolsky
这里写着(https://developer.android.com/guide/components/services#StartingAService),请查看以星号标记的蓝色背景注释,该注释以以下内容结尾:_服务一旦创建,必须在五秒内调用其startForeground()方法。_ - JBA

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