Service onCreate called before Application onCreate

Viewed 2907

I'm getting crazy with some crashes reported in the Developer Console, in my most recent version of the App. They appear as java.lang.IllegalStateException and it seems that Application.onCreate is not called before Service.onCreate

It happens for about 0.3% percentage of the users and it happens ONLY on Android 8 devices. I failed to reproduce it on my devices.

I'm going to explain better what happens. The App extends Application in this way:

public class MySpecificApp  extends MyBaseApp
{
    static
    {
        AppCompatDelegate.setCompatVectorFromResourcesEnabled(true);
    }

    public void onCreate()
    {
        super.onCreate();

        ...  // Specific initializations
    }

    ...
}

public class MyBaseApp  extends Application
{

    private static MyBaseApp smApplication;

    public void onCreate()
    {
        super.onCreate();

        // Fabric (Crashlitics) initialization. It SHOULD NOT BE needed, just to be sure (tried also without this line)
        Fabric.with(this, new Crashlytics());

        MyBaseApp.smApplication = this;             // smApplication is initialized here and NEVER modified

        ... // Base initializations     
    }

    public static MyBaseApp getApp() {
        return MyBaseApp.smApplication;
    }

    ...
}

In the MyService.onCreate method a check to the MyBaseApp.smApplication reveals that the MyBaseClass.onCreate has never been called.

public class MyService extends Service
{

    public MyService()
    {
    }

    @Override
    public void onCreate()
    {
        try
        {
            if( MySpecificApp.getApp() == null )
                Crashlytics.log("MyService.onCreate: probably Application.onCreate not yet called");        <=== Here is raised the Exception
            // The line over is the line 617


            ... // Service initializations

        }
        catch( Exception e )
        {
            e.printStackTrace();

            throw e;
        }
    }

    ...
}

In fact Crashlytics crashes because it has not been initialized, but it's because the Application has not been initialized. Crashlytics is not the clue. The code is in this way, because I had several weird bugs of IllegalStateException and I supposed the lack of Application initialization: I was trying to investigate it.

Here the stack logs from the Developer Console (the crash does not reach Crashlytics)

java.lang.RuntimeException: 
  at android.app.ActivityThread.handleCreateService (ActivityThread.java:3554)
  at android.app.ActivityThread.-wrap4 (Unknown Source)
  at android.app.ActivityThread$H.handleMessage (ActivityThread.java:1786)
  at android.os.Handler.dispatchMessage (Handler.java:105)
  at android.os.Looper.loop (Looper.java:164)
  at android.app.ActivityThread.main (ActivityThread.java:6944)
  at java.lang.reflect.Method.invoke (Method.java)
  at com.android.internal.os.Zygote$MethodAndArgsCaller.run (Zygote.java:327)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1374)
Caused by: java.lang.IllegalStateException: 
  at io.fabric.sdk.android.Fabric.singleton (Fabric.java:301)
  at io.fabric.sdk.android.Fabric.getKit (Fabric.java:551)
  at com.crashlytics.android.Crashlytics.getInstance (Crashlytics.java:191)
  at com.crashlytics.android.Crashlytics.checkInitialized (Crashlytics.java:390)
  at com.crashlytics.android.Crashlytics.log (Crashlytics.java:221)
  at com.xxx.MyService.onCreate (MyService.java:617)                                <==== Here is the line that is reached only if Application.onCreate is not called
  at android.app.ActivityThread.handleCreateService (ActivityThread.java:3544)
  at android.app.ActivityThread.-wrap4 (Unknown Source)
  at android.app.ActivityThread$H.handleMessage (ActivityThread.java:1786)
  at android.os.Handler.dispatchMessage (Handler.java:105)
  at android.os.Looper.loop (Looper.java:164)
  at android.app.ActivityThread.main (ActivityThread.java:6944)
  at java.lang.reflect.Method.invoke (Method.java)
  at com.android.internal.os.Zygote$MethodAndArgsCaller.run (Zygote.java:327)
  at com.android.internal.os.ZygoteInit.main (ZygoteInit.java:1374)

Here an extract of the manifest:

<application
    android:name=".MySpecificApp"
    android:allowBackup="true"
    android:fullBackupContent="true"
    android:icon="@mipmap/ic_launcher"
    android:label="@string/app_name"
    android:theme="@style/AppTheme"
    android:largeHeap="true">

    ...

    <service
        android:name=".MyService"
        android:enabled="true"
        android:exported="false"
        android:stopWithTask="false">
    </service>

    ...
</application>

Does anyone have any idea of what happens and how I could fix the bug?

4 Answers

I can't tell you why your service is started before the Application's onCreate was called, especially because there is a lot of code missing from how you implemented the service. When/how do you start it? What's your target API? Is there a certain device/manufacturer which causes this exclusively? It's not the weirdest thing I've seen on Android, though. It might very well also be a bug in the OS if it's only Android 8 and zero occurrences on other versions.

Maybe there's something we can do to fix it:

Approach A

Firstly, the Application is not the first thing being created when your app starts (a little counter intuitive). As far as I'm aware, ContentProviders are the first components being created when your app starts (that's why certain services like Firebase use them to set things up, e.g. crash reporting).

onCreate is also not the first thing being called when you Application is created (a little more counter intuitive). Right after creation the init function / constructor is invoked. Any work which does not require a Context can already be done in the default constructor. The next best thing being called is the attachBaseContext function. That's the earliest you can use a Context to run initializations. At this point ContentProviders get created (constructor + onCreate). And only now the Application's onCreate get called.

Check this little sample out:

class MyApp : Application() {

    init {
        Log.i("MyApp", "init")
    }

    override fun attachBaseContext(base: Context?) {
        super.attachBaseContext(base)
        Log.i("MyApp", "attachBaseContext")
    }

    override fun onCreate() {
        super.onCreate()
        Log.i("MyApp", "onCreate")
    }
}
class MyContentProvider : ContentProvider() {

    init {
        Log.i("MyContentProvider", "init")
    }

    override fun onCreate(): Boolean {
        Log.i("MyContentProvider", "onCreate")
        return true
    }

    ...

}

If you start this app, you get this output:

I/MyApp: init
I/MyApp: attachBaseContext
I/MyContentProvider: init
I/MyContentProvider: onCreate
I/MyApp: onCreate

You could use this fact to move your critical initializations to e.g. a ContentProvider or to the attachBaseContext method and try to out run the early created Service instances.

Approach B

An other idea would be to delay the Service initialization manually in case of an error. As this would only happen in 0.3% of all cases, I think a little delay should be fine to prevent a crash. You could do something like this (sorry for my Kotlin):

fun onCreate() {
    super.onCreate()
    onCreate0()
}

private fun onCreate0() {
    if (MySpecificApp.getApp() == null) {
        Log.w("MyService", "Delaying service initialization due to race condition")
        Handler(Looper.getMainLooper()).postDelayed(this::onCreate0, 100)
        return
    } 

    Log.i("MyService", "Application init verified, initializing service now")
    // Do your stuff here. Application is initialized
}


So basically you do your check and if the app is not ready you try again after 100ms. This is not perfect but better than a crash. I'd recommend to still collect data on this behviour to A. see how many loops are required and B. how often this occurs and on which devices. It's maybe also a good idea to gate this by something like Firebase Remote Config or control the maximum number of attempts through it. Getting the cloud values may fail because your app is in a weird init state, though...

By doing this you can also collect more insights about this behavior. Is the application initialized at all (that's why I'd limit the number of loops)?

Approach B v2 To address your concerns from the comments to this answer, here is a updated version handling the intents.

In your Service do the same check but cache Intents you could not handle. The service will stop itself

var isInitialized = false

// static in Java
companion object {
    private val pendingIntents = mutableListOf<Intent>()

    fun hasPendingIntents() = pendingIntents.size > 0
}

fun onCreate() {
    super.onCreate()
    if (MySpecificApp.getApp() == null) {
        Log.w("MyService", "Application not ready")
        stopSelf()
        return
    } 

    Log.i("MyService", "Application init verified, initializing service now")
    isInitialized = true
    // Do your stuff here. Application is initialized
}

fun onStartCommand(intent: Intent, flags: Int, startId: Int) {
    pendingIntents.add(intent)

    if (!isInitialized) {
        return   
    }

    pendingIntents.forEach {
        // Handle intents which could not be satisfied before including the latest
    }

    pendingIntents.clear()

    return super.onStartCommand(intent, flags, startId)
}

In your Application check whether the service encountered an issue and manually start it again if so:


fun onCreate() {
    super.onCreate()
    // init app
    if (MyService.hasPendingIntents()) {
       startService(Intent(this, MyService::class).putExtra("RECOVER_AFTER_FAILURE", true)
    }
}

Approach C

This is not a solution but might bring you closer to a "clean" solutions: You can try to cache the last 100 logs on disk (app creation, activity states, ...) and when the service is started append those 100 logs to the crash report. This might give valuable insights, e.g. whether the app was recently used or was idle/closed for a long time. Maybe you find something leading you into the right direction. It would be interesting to know more about the circumstances in which this behavior occurs.

You have below line your service:

android:stopWithTask="false"

If set to true, this service with be automatically stopped when the user removes a task rooted in an activity owned by the application. The default is false.

I would suggest setting it true because you don't want to run the service even if the task owned by the application is closed. Also check if your Service is STICKY.

Background execution limits

Android 8.0 Behavior Changes

As one of the changes that Android 8.0 (API level 26) introduces to improve battery life, when your app enters the cached state, with no active components, the system releases any wakelocks that the app holds.

In addition, to improve device performance, the system limits certain behaviors by apps that are not running in the foreground. Specifically:

Apps that are running in the background now have limits on how freely they can access background services. Apps cannot use their manifests to register for most implicit broadcasts (that is, broadcasts that are not targeted specifically at the app). By default, these restrictions only apply to apps that target O. However, users can enable these restrictions for any app from the Settings screen, even if the app has not targetted O.

Android 8.0

(API level 26) also includes the following changes to specific methods:

The startService() method now throws an IllegalStateException if an app targeting Android 8.0 tries to use that method in a situation when it isn't permitted to create background services. The new Context.startForegroundService() method starts a foreground service. The system allows apps to call Context.startForegroundService() even while the app is in the background. However, the app must call that service's startForeground() method within five seconds after the service is created. For more information, see Background Execution Limits.

Starting on Android 8, any Not Sticky Service will be destroyed after going to background for more than few minutes (tipically 1 minute). Once the user cames back again to foreground as explained in https://developer.android.com/about/versions/oreo/background#services it will be recreated (service's onCreate will be called)

While an app is in the foreground, it can create and run both foreground and background services freely. When an app goes into the background, it has a window of several minutes in which it is still allowed to create and use services. At the end of that window, the app is considered to be idle. At this time, the system stops the app's background services, just as if the app had called the services' Service.stopSelf() methods.

I can't remember if its just the service which is killed or the entire app, anyway after going to background for more than 1 minute and service becomes destroyed, there's already a change your app might die as a consequence of any other app requesting for more memory RAM. And although your app is killed it stays in recent app list.

When user comes back to your app, application class's onCreate() will be called and your service's onCreate will also be called to recreate it. I can't say which of both onCreate() methods will be fired first.

If you don't know how to kill an app IN BACKGROUND, just use: enter image description here

Regardless of all that, if this weren't your problem you can access you application from Service by using getApplicationContext() and cast it to MyBaseApp. No need to access an static method on MyBaseApp. This way won't fail, as Service extends Context and therefore you can always cast it to your Application

public class MyService extends Service {
    private MyBaseApp application;
    @Override
    public void onCreate() {
        application = ((MyBaseApp) getApplicationContext())
    }
    ...
}
Related