Android远程服务异常:Context.startForegroundService()未调用Service.startForeground()。

5

我知道,已经有很多关于这个错误的问题了。但是我尝试了许多解决方案,但都没有起作用。我的应用程序中有一个带有计时器的通知服务,并且Crashlytics报告了数百个崩溃(Android 8-12),如下所示:

ForegroundServiceDidNotStartInTimeException:

Fatal Exception: android.app.ForegroundServiceDidNotStartInTimeException
Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{cac9d0b u0 com.malmatech.eggtimer/.NotificationUtil}

android.app.ActivityThread.throwRemoteServiceException (ActivityThread.java:2134)
android.app.ActivityThread.access$2900 (ActivityThread.java:309)
android.app.ActivityThread$H.handleMessage (ActivityThread.java:2363)
android.os.Handler.dispatchMessage (Handler.java:106)
android.os.Looper.loopOnce (Looper.java:226)
android.os.Looper.loop (Looper.java:313)
android.app.ActivityThread.main (ActivityThread.java:8582)
java.lang.reflect.Method.invoke (Method.java)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:563)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1133)

以及RemoteServiceException:

Fatal Exception: android.app.RemoteServiceException
Context.startForegroundService() did not then call Service.startForeground(): ServiceRecord{d760a21 u0 com.malmatech.eggtimer/.NotificationUtil}

android.app.ActivityThread$H.handleMessage (ActivityThread.java:2313)
android.os.Handler.dispatchMessage (Handler.java:107)
android.os.Looper.loop (Looper.java:213)
android.app.ActivityThread.main (ActivityThread.java:8178)
java.lang.reflect.Method.invoke (Method.java)
com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run (RuntimeInit.java:513)
com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1101)

我仔细检查了我的代码并在网上搜索,但没有发现任何问题,并且在自己的设备或模拟器上从未遇到这些异常...


只有当计时器正在运行并且应用程序被关闭时才会显示通知。 在TimerActivity的onPause()中,我启动了服务:

if (timerState == TimerState.Running){
    timer.cancel()
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        MyContext.getContext().startForegroundService(Intent(this, NotificationUtil::class.java))
    } else {
        startService(Intent(this, NotificationUtil::class.java))
    }
}

在onResume()中,我停止服务:

try {
    MyContext.getContext().stopService(
        Intent(
            MyContext.getContext(),
            NotificationUtil::class.java
        )
    )
} catch (ex: Exception) {
    ex.printStackTrace()
}

这是我的服务类,我剪切了一些不相关的代码,但包括管理通知所需的所有内容。

class NotificationUtil : Service() {
override fun onBind(intent: Intent?): IBinder? {
    return null
}

var eggCount = 1
private var secondsRemaining: Long = 0
private lateinit var timer: CountDownTimer
// some more, irrelevant for question

override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
    showNotification()
    log("onStartCommand executed with startId: $startId")
    if (intent != null) {
        val action = intent.action
        log("using an intent with action $action")
    } else {
        log("with a null intent. It has been probably restarted by the system.")
    }
    // by returning this we make sure the service is restarted if the system kills the service
    return START_STICKY
}

override fun onCreate() {
    super.onCreate()

    //irrelevant Code - getting an array from SharedPreferences and setting up textToSpeech

    showNotification()
    startTimer()
}

override fun onDestroy() {
    super.onDestroy()
    hideNotification()
    timer.cancel()
}

fun showNotification(done: Boolean = false) {
    val nBuilder = NotificationCompat.Builder(applicationContext, "menu_timer")
        .setSmallIcon(R.drawable.ic_notification_res_specific)
        .setOngoing(true)
        .setAutoCancel(true)
        .setDefaults(0)
        .setContentIntent(
            getPendingIntentWithStack(
                applicationContext,
                TimerActivity::class.java
            )
        )
        .setPriority(PRIORITY_MAX)
        .setVisibility(VISIBILITY_PUBLIC)
        .setNotificationSilent()

    if (!done) {

        //irrelecantCode, setting ginishedStr and finishedIndividualString

        nBuilder.setContentTitle(getString(R.string.eggTimerRunning))
        if (eggCount > 1) {
            nBuilder.setStyle(
                NotificationCompat.BigTextStyle().bigText(
                    getString(R.string.remainingTimeNotification) + " $finishedStr\n" + getString(
                        R.string.nextEggNotification
                    ) + " $finishedIndividualString"
                )
            )
                .setContentText(
                    getString(R.string.remainingTimeNotification) + " $finishedStr\n" + getString(
                        R.string.nextEggNotification
                    ) + " $finishedIndividualString"
                )
        } else {
            nBuilder.setStyle(
                NotificationCompat.BigTextStyle()
                    .bigText(getString(R.string.remainingTimeNotification) + " $finishedStr")
            )
                .setContentText(getString(R.string.remainingTimeNotification) + " $finishedStr")
        }
    } else {
        nBuilder.setContentTitle(getString(R.string.eggTimerDone))
            .setStyle(
                NotificationCompat.BigTextStyle().bigText(getString(R.string.BonAppetite))
            )
            .setContentText(getString(R.string.BonAppetite))
    }

    //irrelevant code

    val nManager =
        applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    nManager.createNotificationChannel("menu_timer", "Timer App Timer", true)

    nManager.notify(135678, nBuilder.build())

    val notification: Notification = nBuilder.build()
    startForeground(135678, notification)
}


private fun hideNotification() {
    val nManager =
        applicationContext.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    nManager.cancel(135678)
}

private fun startTimer() {
    timer = object : CountDownTimer(secondsRemaining * 1000, 1000) {
        override fun onFinish() = onTimerFinished()

        override fun onTick(millisUntilFinished: Long) {
            secondsRemaining = millisUntilFinished / 1000
            showNotification()
        }
    }.start()
}

private fun onTimerFinished(ring: Boolean = true) {
    if (ring) {
        //irrelevant code, ring and vibrate if timer is finished
    }
    showNotification(true)
}

@TargetApi(26)
private fun NotificationManager.createNotificationChannel(
    channelID: String,
    channelName: String,
    playSound: Boolean
) {
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
        val channelImportance = if (playSound) NotificationManager.IMPORTANCE_DEFAULT
        else NotificationManager.IMPORTANCE_LOW
        val nChannel = NotificationChannel(channelID, channelName, channelImportance)
        nChannel.enableLights(true)
        nChannel.lightColor = Color.BLUE
        this.createNotificationChannel(nChannel)
    }
}

private fun <T> getPendingIntentWithStack(
    context: Context,
    javaClass: Class<T>
): PendingIntent {
    val resultIntent = Intent(context, javaClass)
    resultIntent.flags = Intent.FLAG_ACTIVITY_CLEAR_TOP or Intent.FLAG_ACTIVITY_SINGLE_TOP

    val stackBuilder = TaskStackBuilder.create(context)
    stackBuilder.addParentStack(javaClass)
    stackBuilder.addNextIntent(resultIntent)

    return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
        stackBuilder.getPendingIntent(0, PendingIntent.FLAG_IMMUTABLE)
    } else {
        stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT)
    }
}
}

非常感谢您的帮助,我已经尝试修复这个错误几周了,但很难解决,因为对于我自己来说一切都正常。

系统将在五秒钟后抛出异常,你是否尝试在onCreate中立即调用startForeground - georkost
谢谢您的快速评论!我还没有尝试过,但这会有什么区别吗?我立即在onCreate()中调用showNotification()。在showNotification()中没有一些复杂的计算,最终到达startForeground()肯定不需要5秒钟...或者您认为值得一试吗?测试的问题是,我必须发布Playstore中的更新并等待几天,因为错误对我自己来说并不会发生。 - Malmatth
好的,谢谢。我会尝试的... 我如何在 onCreate 中调用 startForeground() 而不必先构建通知呢? - Malmatth
所以我现在创建了一个名为“loading-notification”的虚拟元素,它与真正的内容一起在showNotification()方法中更新。接下来几天我会尝试这种方法。 - Malmatth
你总是需要提供一个通知。在我的项目中,我会构建一个初始的通知,然后从一些静态方法或者onStartCommand里面进行更新。 - georkost
显示剩余3条评论
1个回答

5

我已经几乎解决了问题,现在每周只会出现约一次宕机,而不是数百次。

解决办法是,在启动前台服务时:只需等待主线程中的主循环,然后启动它。现在往往只需要5秒钟左右,服务就能够启动了。

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    Handler(Looper.getMainLooper()).post{
        MyContext.getContext().startForegroundService(Intent(this, NotificationUtil::class.java))
    }
} else {
            startService(Intent(this, NotificationUtil::class.java))
}

这种方法对我没用。有没有什么创新?另一种方法? - Mahmut K.
1
很不幸,我的修复后这个崩溃并不经常发生了,但是stackoverflow上的其他帖子提供了一些不同的解决方案来解决这个问题 - 它们对我无效,但也许它们会对你有用 :) - Malmatth
它会因崩溃而停止,但它的副作用是在主循环器没有发挥作用之前较长时间内不启动服务。 - Ready Android

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