willowtreeapps / Hyperion-Android

App Debugging & Inspection Tool for Android
MIT License
1.95k stars 144 forks source link

[Bug] Crash plugin doesn't play nice with Application class init logic #195

Open frushlabo opened 4 years ago

frushlabo commented 4 years ago

Crash plugin seems to try to create its own application process when a crash is detected, this triggers all of my application class init logic to run, such as firebase then crashes the crash plugin :D

Not sure if there's a straight forward solution but interested to hear your thoughts

  Process: com.myapp:crash, PID: 13013
    java.lang.RuntimeException: Unable to create application com.myapp.MyApplication: java.lang.IllegalStateException: Default FirebaseApp is not initialized in this process com.myapp:crash. Make sure to call FirebaseApp.initializeApp(Context) first.
        at android.app.ActivityThread.handleBindApplication(ActivityThread.java:6846)
        at android.app.ActivityThread.access$1400(ActivityThread.java:267)
        at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1981)
        at android.os.Handler.dispatchMessage(Handler.java:107)
        at android.os.Looper.loop(Looper.java:237)
        at android.app.ActivityThread.main(ActivityThread.java:7770)
        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:1047)
     Caused by: java.lang.IllegalStateException: Default FirebaseApp is not initialized in this process com.myapp:crash. Make sure to call FirebaseApp.initializeApp(Context) first.
CaptnBlubber commented 3 years ago

I just ran into the same Problem. The Code that causes the application to crash lives in one of my dagger (using Hilt) modules.

AlexKrupa commented 3 years ago

Correct, Hyperion launches its crash activity in a separate :crash process, which causes Application class recreation.

You can work around this issue by initializing your dependencies in that same process.

Here's how you can do it using ContentProvider trick to initialize Firebase before Application.onCreate:

class HyperionCrashProcessWorkaround : ContentProvider() {

    private companion object {

        var isCreated = false

        // Workaround for ContentProvider being created twice:
        // https://issuetracker.google.com/issues/37045392
        fun ifFirstOnCreate(action: () -> Unit) {
            if (isCreated) return
            action()
            isCreated = true
        }
    }

    override fun onCreate(): Boolean {
        ifFirstOnCreate {
            initializeFirebase()
        }
        return false
    }

    private fun initializeFirebase() {
        FirebaseApp.initializeApp(checkNotNull(context))
    }

    // Default overrides to satisfy ContentProvider's API.
    override fun query(uri: Uri, projection: Array<out String>?, selection: String?, selectionArgs: Array<out String>?, sortOrder: String?): Cursor? = null
    override fun insert(uri: Uri, values: ContentValues?): Uri? = null
    override fun update(uri: Uri, values: ContentValues?, selection: String?, selectionArgs: Array<out String>?) = 0
    override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?) = 0
    override fun getType(uri: Uri): String? = null
}

Add the provider AndroidManifest.xml:

<application>
    <provider
        android:name=".HyperionCrashProcessWorkaround"
        android:authorities="${applicationId}.HyperionCrashProcessWorkaround"
        android:exported="false"
        android:process=":crash"
        android:syncable="false"/>
</application>

Note the android:process value matching Hyperion's process here: https://github.com/willowtreeapps/Hyperion-Android/blob/8bb1e83adfcb0e349681b372e89bd9c23c832123/hyperion-crash/src/main/AndroidManifest.xml#L1-L13

Keep in mind that both of these can (and probably should) be defined only in debug sources, since Hyperion itself is usually added as debugImplementation.