Fix: prevent black screen after failed initialization.

If a crash occurs before feedback is started then attempting to report that crash will, itself, crash because the lateinit feedback instance is not initialized. The result is a black screen on launch! This fixes that by catching everything while trying to report an error.
This commit is contained in:
Kevin Gorham 2020-08-27 20:24:01 -04:00
parent 953aeb32ea
commit 1577b3223d
No known key found for this signature in database
GPG Key ID: CCA55602DF49FC38
2 changed files with 38 additions and 14 deletions

View File

@ -7,10 +7,8 @@ import androidx.camera.camera2.Camera2Config
import androidx.camera.core.CameraXConfig import androidx.camera.core.CameraXConfig
import cash.z.ecc.android.di.component.AppComponent import cash.z.ecc.android.di.component.AppComponent
import cash.z.ecc.android.di.component.DaggerAppComponent import cash.z.ecc.android.di.component.DaggerAppComponent
import cash.z.ecc.android.ext.tryWithWarning
import cash.z.ecc.android.feedback.FeedbackCoordinator import cash.z.ecc.android.feedback.FeedbackCoordinator
import cash.z.ecc.android.sdk.ext.SilentTwig
import cash.z.ecc.android.sdk.ext.TroubleshootingTwig
import cash.z.ecc.android.sdk.ext.Twig
import cash.z.ecc.android.sdk.ext.twig import cash.z.ecc.android.sdk.ext.twig
import kotlinx.coroutines.* import kotlinx.coroutines.*
import javax.inject.Inject import javax.inject.Inject
@ -68,19 +66,34 @@ class ZcashWalletApp : Application(), CameraXConfig.Provider {
* is complete, we can lazily initialize all the feedback objects at this moment so that we * is complete, we can lazily initialize all the feedback objects at this moment so that we
* don't have to add any time to startup. * don't have to add any time to startup.
*/ */
inner class ExceptionReporter(private val ogHandler: Thread.UncaughtExceptionHandler) : Thread.UncaughtExceptionHandler { inner class ExceptionReporter(private val ogHandler: Thread.UncaughtExceptionHandler) :
Thread.UncaughtExceptionHandler {
override fun uncaughtException(t: Thread?, e: Throwable?) { override fun uncaughtException(t: Thread?, e: Throwable?) {
twig("Uncaught Exception: $e caused by: ${e?.cause}") twig("Uncaught Exception: $e caused by: ${e?.cause}")
// these are the only reported crashes that are considered fatal // Things can get pretty crazy during a fatal exception
coordinator.feedback.report(e, true) // so be cautious here to avoid freezing the app
coordinator.flush() tryWithWarning("Unable to report fatal crash") {
// can do this if necessary but first verify that we need it // note: these are the only reported crashes that set isFatal=true
runBlocking { coordinator.feedback.report(e, true)
coordinator.await() }
coordinator.feedback.stop() tryWithWarning("Unable to flush the feedback coordinator") {
coordinator.flush()
}
try {
// can do this if necessary but first verify that we need it
runBlocking {
coordinator.await()
coordinator.feedback.stop()
}
} catch (t: Throwable) {
twig("WARNING: failed to wait for the feedback observers to complete.")
} finally {
// it's important that this always runs so we use the finally clause here
// rather than another tryWithWarning block
ogHandler.uncaughtException(t, e)
Thread.sleep(2000L)
} }
ogHandler.uncaughtException(t, e)
Thread.sleep(2000L)
} }
} }
} }

View File

@ -1,3 +1,14 @@
package cash.z.ecc.android.ext package cash.z.ecc.android.ext
fun Boolean.asString(ifTrue: String = "", ifFalse: String = "") = if(this) ifTrue else ifFalse import cash.z.ecc.android.sdk.ext.twig
fun Boolean.asString(ifTrue: String = "", ifFalse: String = "") = if(this) ifTrue else ifFalse
inline fun <R> tryWithWarning(message: String = "", block: () -> R): R? {
return try {
block()
} catch (error: Throwable) {
twig("WARNING: $message")
null
}
}