I'm setting the default uncaught exception handler using setDefaultUncaughtExceptionHandler interface, as shown below:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
LOG("MainActivity.onCreate(Bundle?)")
super.onCreate(savedInstanceState)
// Register the 'catch-all' exception handler
Thread.setDefaultUncaughtExceptionHandler(CustomUncaughtExceptionHandler())
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// Example of a call to a native method
binding.sampleText.text = stringFromJNI()
testExceptionHandler()
LOG("Returning from Activity.onCreate()")
}
}
I explicitly throw an exception from onCreate
to test the exception handler:
private fun testExceptionHandler() {
LOG("testExceptionHandler()")
LOG("Raising exception...")
throw NullPointerException()
}
The exception is thrown and caught by the uncaught exception handler. My CustomUncaughtExceptionHandler, which I registered earlier, is defined as follows:
class CustomUncaughtExceptionHandler: Thread.UncaughtExceptionHandler {
override fun uncaughtException(p0: Thread, p1: Throwable) {
LOG("uncaughtException(Thread, Throwable)")
var exceptionType: String = "(null)"
when (p1) {
is IndexOutOfBoundsException -> {
exceptionType = "IndexOutOFBoundsException"
}
is NullPointerException -> {
exceptionType = "NullPointerException"
}
is ArithmeticException -> {
exceptionType = "ArithmeticException"
}
is RuntimeException -> {
exceptionType = "RuntimeException"
}
else -> {
exceptionType = "Unknown"
}
}
LOG("Exception type = $exceptionType")
LOG("Already in main thread... Preparing to display alert...")
displayAlert(exceptionType)
LOG("Returning from default uncaught exception handler...")
}
private fun displayAlert(exceptionType: String) {
LOG("Displaying error on foreground activity...")
val builder : AlertDialog.Builder = AlertDialog.Builder(ContextMgr.activityCtx!!)
builder.setTitle("Something went wrong!!")
builder.setMessage(exceptionType)
builder.setCancelable(false)
builder.setPositiveButton("Restart") { _, _ ->
LOG("Restart the app...")
}
val alertdialog : AlertDialog = builder.create()
alertdialog.show()
}
}
In the exception handler, I try to capture the kind of exception thrown, and then show an AlertDialog
to the user.
The problem is, the UI is stuck like this:
The normal flow without throwing any exception would have resulted a minimal UI with a text - "Hello from C++". This is the UI of the Android native project template.
Looking at the logs,
MainActivity.onCreate(Bundle?)
testExceptionHandler()
Raising exception...
....
// stack trace
....
uncaughtException(Thread, Throwable)
Exception type = RuntimeException
Already in main thread... Preparing to display alert...
Returning from default uncaught exception handler...
As shown in the logs, in onCreate
, a NullPointerException
is thrown, invokes the uncaught exception handler which tries to display an alert... but the UI looks as attached above.
The logs also indicate that the flow never returned from onCreate
, and onResume
, onStart
never got invoked.
So, my AlertDialog
failed because an exception happened, the Activity
never completed its lifecycle and therefore cannot show anything to user? If true, how can I remedy this? If possible, I'd like to show my AlertDialog
, or atleast the process should terminate instead of showing... whatever this UI is.
Update1 - My environment:
Link to my project - AndroidExceptionHandling
AS details: Android Studio Iguana | 2023.2.1 Patch 2
Emulator details:
You are throwing an exception in onCreate()
which is running on the main (UI) thread, then you are "handling" that exception in your UncaughtExceptionHandler
. You can't really do what you want to do, because Android has detected an uncaught exception, and expects to terminate the current thread, but you are "handling" the exception and NOT terminating the current thread.
Generally speaking, in an UncaughtExceptionHandler
all you can/should do is to collect information about the exception, try to write this data to a file and then terminate the app. You can do this by calling System.exit()
, or you can try the following:
MyApplication
that extends Application
MyApplication.onCreate()
save a reference to the DefaultUncaughtExceptionHandler
in a property that is publically accessable<application>
declarationMainActivity.onCreate()
set the DefaultUncaughtExceptionHandler
to your exception handler.ucaughtException()
on the original DefaultExceptionHandler
which you saved a reference to in MyApplication
, passing the arguments through that you got in your exception handler.This should properly close the app.
You cannot show a Dialog or do anything with the UI in an UncaughtExceptionHandler
if the uncaught exception was thrown on the Main (UI) thread, as the main thread is the one that handles the UI and it is going to terminate (one way or another).
When your app starts again, it should check if there is a file with an uncaught exception info in it, and if so, it can show the user the file, or send it to a server or whatever before starting up.
Here are a few relevant links to similar solutions: