I know, there are already many questions about this error. But I tried many solutions and nothing worked for me. I have a Notification Service in my application with a timer in it and crashlytics reports hundreds of crashes (android 8 - 12) like this:
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)
and 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)
I double checked my code and searched the web but didn't find anything and never get these exceptions on my own devices or emulators...
The Notification is only shown, when a timer is running and the application is closed. In the onPause() of my TimerActivity I start the service:
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))
}
}
In onResume() I stop the service:
try {
MyContext.getContext().stopService(
Intent(
MyContext.getContext(),
NotificationUtil::class.java
)
)
} catch (ex: Exception) {
ex.printStackTrace()
}
And here is my service class, I cut out some irrelevant code but everything for managing the notification should be included.
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)
}
}
}
Thank you so much in advance, I tried fixing the bug for weeks now but it's hard to fix it, because everything works for myself.
So I've almost fixed the problem and no longer get hundreds of crashes every week, but only about one every two days.
Solution is this, when starting the foreground service: Just wait for the main looper in the main thread and then start it. It is much more common, that the 5s until the service needs to be started are enough now.
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))
}