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?
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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With