Reputation: 363
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.
Upvotes: 3
Views: 581
Reputation: 632
Short answer impossible.
Jetpack compose is not managed by Looper, it's managed by Composer which is managed by Coroutine
and Recomposer
. Recomposer
manages the animation sync which is handled by MonotonicFrameClock
.
onClick
is called by pointerInput
. which runs gesture inside suspend function. Which is generally managed by LaunchedEffect
.
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 Recomposer
is enough. No need to restart the Looper since the exception is caught by CoroutineExceptionHandler
.
val recomposer = window.decorView.createLifecycleAwareWindowRecomposer(
CoroutineExceptionHandler { coroutineContext, throwable -> throwable.printStackTrace() }, lifecycle)
window.decorView.compositionContext = recomposer
setContent {
Button(onClick = {
Handler(this.mainLooper).post {
// print shutdown
println(recomposer.currentState.value)
}
throw RuntimeException("Throw")
}) {
Text(text = "AAA")
}
}
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, effectCoroutineContext
where all effects(LaunchedEffect
, SideEffect
, onClick
etc), and recomposeCoroutineContext
where recompose runs.
And effectCoroutineContext
is always linked to Recomposer
's effectiveJob so there is no way to intercept the exception before is reaches Recomposer by magic.
Overidng UIComposable
recomposeCoroutineContext
is also not possible, because you have create child Composition
with UIApplier
while UIApplier
is internal.
Upvotes: 3