I wan`t to make a global crash handler, so I extend Thread.UncaughtExceptionHandler and set this as default uncaught exception handler. I restart the main thread looper in this handler:
class JavaUncaughtExceptionHandler : Thread.UncaughtExceptionHandler {
init {
Thread.setDefaultUncaughtExceptionHandler(this)
}
override fun uncaughtException(t: Thread, e: Throwable) {
saveCrashMessageToLocal(t, e)
handleException(t, e)
restartLooper(t)
}
private fun restartLooper(thread: Thread) {
// restart looper when this thread is main thread
if (thread == Looper.getMainLooper().thread) {
while (true) {
try {
Looper.loop()
} catch (e: Throwable) {
handleException(Thread.currentThread(), e)
}
}
}
}
}
It works as I expect when I use Xml view, such as:
setContentView(R.layout.layout_main)
findViewById<Button>(R.id.crash).setOnClickListener { throwExc() }
findViewById<Button>(R.id.flow_crash).setOnClickListener { throwFlowExe() }
when exception happen, this handler catch it and restart the main looper, and then the app can run as normal.
But when using Compose View and throwing exception, app cannot run normally:
@Composable
fun Greeting(name: String) {
Button(onClick = { throwExc() }) {
Text(text = "Hello NewActivity!", modifier = Modifier)
}
Button(onClick = { throwFlowExe() }) {
Text(text = "Hello NewProcessActivity!", modifier = Modifier)
}
}
I guess the reason is the main looper is not be restarting, but I don`t know the difference when clicking view between Xml View and Compose View.
Short answer impossible.
Jetpack compose is not managed by Looper, it's managed by Composer which is managed by
CoroutineandRecomposer.Recomposermanages the animation sync which is handled byMonotonicFrameClock.onClickis called bypointerInput. which runs gesture inside suspend function. Which is generally managed byLaunchedEffect.So generally Speaking if you throw exception normally in compose. It will first reach the coroutine context of composer and shutdown the composer (because job is destroyed) and then call
UncaughtExceptionHandler.Just preventing app being crashed when using jetpack compose providing coroutineExceptionHandler to the
Recomposeris enough. No need to restart the Looper since the exception is caught byCoroutineExceptionHandler.So what really matter for your case is how to caught exception before it reaches the root level composition context.
However that is not possible. CompositionContext have two context,
effectCoroutineContextwhere all effects(LaunchedEffect,SideEffect,onClicketc), andrecomposeCoroutineContextwhere recompose runs.And
effectCoroutineContextis always linked toRecomposer's effectiveJob so there is no way to intercept the exception before is reaches Recomposer by magic.Overidng
UIComposablerecomposeCoroutineContextis also not possible, because you have create childCompositionwithUIApplierwhileUIApplieris internal.