Context.startForegroundService() did not then call Service.startForeground() - still a problem
Asked Answered
F

1

5

After checking the google issue and a lot of other issues on SO, I came to the solution I added at the bottom.

What I take care of is following:

  • in onCreate AND in onStartCommand I make sure to move the service to the foregorund (if it is not already in the foreground)
  • I don't simply stop a service, I do this with an extra command that I send to the onStartCommand handler to make sure, that the service is not stopped while it's started (eventually before it could finish going to the foreground)
  • I never stop the service directly (context.stopService(...)), I always stop the service via a command from the running service itself - so I make sure that it can only be stopped while it is running in the foreground and not during start up
  • a service can only be stopped once
  • I stop a service by cancelling it's foreground notification and only afterwards I stop the service itself

I personally use the service for overlays, so I do not handle binders inside my class, as I do not use them.

Log I got

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

This is the log of one crash report - as you can see, service is moved to foreground in line 3 (moved to foreground: true) and in line 6 it knows that it is running in foreground already.

I use this app on my android 9 device heavily (24/7, it's constantly running) and don't have problems and since I use the base class from below the problem has really minimized itself to a few crashes a month in total. Still, the log above shows, that my service is running in foreground within milliseconds, still it can crash like following:

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)

Does anyone see any issues with my base class?

Code

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
    }
}
Fecund answered 30/7, 2019 at 12:53 Comment(3)
If you found the solution to your question, please post it as an answer and accept itKelseykelsi
@Kelseykelsi try the solution from my question, it's the best I could find for now and works most of the time. If I solve it outside SO, I will post an answerFecund
It seems this is just yet another example of a major bug actively introduced by Google as a "security measure" and their total ignorance of complaints by developers. I've been trying to solve this for ages and have ended up with more than 20 different crash reports, each typically triggered 3 to 10 times a month, that all have this underlying error. [insert "this is madness!", "madness? this is GOOOOGLE!" GIF here]Pituri
V
0

I had the same issue and could solve it like this:

From Android 9 Pie if your service does not call startForeground within 5 seconds after it has been started with the command startForegroundService ... then it produces an ANR + Crash.

The solution is to add a startForeground() command, with your notification, right at the beginning of the onStartCommand method of your foreground service, like this:

final override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int 
{
    startForeground(YOUR_FOREGROUND_NOTIFICATION_ID, Notification);
    // rest of your code here...
}
Villegas answered 17/9, 2019 at 15:12 Comment(4)
If you check my question, this is already part of my solutionFecund
Sorry, indeed you're right. You might take a look if it could happen that the condition 2) into your code is not filled, then moveToForeground() not called... and at the end the system would kill your service?Villegas
Where is the 5 seconds rule specified in the documentation?Parian
It's written here, check the starred blue background note that ends with: Once the service has been created, the service must call its startForeground() method within five seconds.Villegas

© 2022 - 2024 — McMap. All rights reserved.