- Error handling on Android part 1: how exceptions work for JVM and Android apps
- Error handling on Android
- How do exceptions work for JVM and Android apps?
- What is an Exception object?
- Anatomy of the Throwable class
- Exception handling hierarchy
- Would you like to know more?
- Error handling on Android part 2: implementing an UncaughtExceptionHandler in a JVM app
- Error handling on Android
- How do I implement a custom UncaughtExceptionHandler for a JVM app?
- Implementing a basic UncaughtExceptionHandler
- Capturing stacktrace information for error reports
- Delivering an error report on the JVM
- Would you like to know more?
- Coroutines do not log uncaught exceptions in Android #148
- Comments
- wardellbagby commented Oct 18, 2017
- elizarov commented Oct 18, 2017
- wardellbagby commented Oct 18, 2017
- elizarov commented Oct 19, 2017
- wardellbagby commented Oct 19, 2017
- elizarov commented Oct 20, 2017 •
- wardellbagby commented Oct 20, 2017
Error handling on Android part 1: how exceptions work for JVM and Android apps
Error handling on Android
How many times have you been in the middle of using a new shiny app, only to have it crash on you?
This is the first in a series of posts that will investigate how the exception handling mechanism works in Java and Android, and how crash reporting SDKs can capture diagnostic information, so that you’re not flying blind in production.
How do exceptions work for JVM and Android apps?
Exceptions should be thrown in exceptional circumstances where the calling code needs to decide how to recover from an error condition.
What is an Exception object?
A [CODE]Throwable[/CODE] is a special type of object which can be thrown and alter the execution path of a JVM application. For example, the code snippet below throws an [CODE]IllegalStateException[/CODE]:
— CODE language-kotlin —
fun main() <
В В В try <
В В В В В В В throw IllegalStateException()
В В В В В В В println(«Hello World»)
В В В > catch (exc: Throwable) <
В В В В В В В println(«Something went wrong»)
В В В >
>
Throwing an exception means that the execution flow changes, and ‘Hello World’ is never printed. Instead, the program counter will jump to the nearest catch block, and executes the error recovery code within, meaning our program prints вЂSomething went wrong’ instead.
Of course, it doesn’t always make sense to recover from a failure — for example, if an [CODE]OutOfMemoryError[/CODE] is thrown by the JVM, there is very little prospect of ever recovering from this condition. In this case it makes sense to leave the [CODE]Throwable as unhandled[/CODE], and allow the process to terminate so the user can restart the app from a fresh state.
Anatomy of the Throwable class
[CODE]Throwable[/CODE] has two direct subclasses: [CODE]Exception[/CODE], and [CODE]Error[/CODE]. Typically an [CODE]Error[/CODE] is thrown in conditions where recovery is not possible, and an [CODE]Exception[/CODE] where recovery is possible. Additionally, there are many subclasses of [CODE]Exception[/CODE] which convey additional meaning — for example, an [CODE]IllegalArgumentException[/CODE] would indicate the programmer passed an invalid argument, and an [CODE]IllegalStateException[/CODE] would indicate that the program encountered an unanticipated state.
— CODE language-kotlin —
fun main() <
В В В try <
В В В В В В В throw IllegalStateException(«This should never happen!»)
В В В В В В В println(«Hello World»)
В В В > catch (exc: Throwable) <
В В В В В В В println(«Something went wrong»)
В В В >
>
Let’s consider the above snippet again. The constructed [CODE]IllegalStateException[/CODE] object captures a snapshot of the application at the time of the error condition:
— CODE language-kotlin —
java.lang.IllegalStateException: This should never happen!
at com.example.myapplication.Exceptions101Kt.foo(Exceptions101.kt:12)
at com.example.myapplication.Exceptions101Kt.main(Exceptions101.kt:5)
at com.example.myapplication.Exceptions101Kt.main(Exceptions101.kt)
This is commonly called a stacktrace. Each line represents a single frame in the application’s call stack at the time of the error, which match the filename, method name, and line number of our original code snippet.
A stacktrace can also contain other useful information, such as program state, which in this case is a static error message, but we could equally pass in arbitrary variables.
Exception handling hierarchy
After throwing an exception, an exception handler must be found to handle the exception, or the app will terminate. In the JVM, this is a well-defined hierarchy, which we’ll run through here.
First up in the exception handling hierarchy is a catch block:
— CODE language-kotlin —
try <
В В В crashyCode()
> catch (exc: IllegalStateException) <
В В В // handle throwables of type IllegalStateException
>
If a catch block isn’t available in the current stack frame, but is defined further down the call stack, then the exception will be handled there.
Next in the hierarchy is implementations of [CODE]UncaughtExceptionHandler[/CODE]. This interface contains a single method which is invoked whenever a [CODE]Throwable[/CODE] is thrown, after the handler has been set:
— CODE language-kotlin —
val currentThread = Thread.currentThread()
currentThread.setUncaughtExceptionHandler < thread, exc ->
В В В // handle all uncaught JVM exceptions in the current Thread
>
It’s possible to set an [CODE]UncaughtExceptionHandler[/CODE] in a few different places; the JVM has a defined hierarchy for these. First, if a handler has been set on the current [CODE]Thread[/CODE], this will be invoked. Next up will be a handler on the [CODE]ThreadGroup[/CODE], before finally, the default handler is invoked, which will handle all uncaught JVM exceptions by printing a stacktrace, and then terminating the app.
— CODE language-kotlin —
Thread.setDefaultUncaughtExceptionHandler < thread, exc ->
В В В // handle all uncaught JVM exceptions
>
It’s the default [CODE]UncaughtExceptionHandler[/CODE] that is most interesting from an error reporting point-of-view, and it’s the default [CODE]UncaughtExceptionHandler[/CODE] that is responsible for showing that all too familiar crash dialog on Android.
The [CODE]UncaughtExceptionHandler[/CODE] interface is the building block of all crash reporting SDKs on the JVM, such as bugsnag-android or bugsnag-java. Read on in part two to learn how we can define a custom handler for uncaught exceptions, and use it to create a crash reporting SDK.
Would you like to know more?
Hopefully this has helped you learn a bit more about Error Handling on Android. If you have any questions or feedback, please feel free to get in touch.
Источник
Error handling on Android part 2: implementing an UncaughtExceptionHandler in a JVM app
Error handling on Android
How many times have you been in the middle of using a new shiny app, only to have it crash on you?
This is the second in a series of posts that will investigate how the exception handling mechanism works in Java and Android, and how crash reporting SDKs can capture diagnostic information, so that you’re not flying blind in production.
How do I implement a custom UncaughtExceptionHandler for a JVM app?
We previously learnt how an [CODE]UncaughtExceptionHandler[/CODE] allows us to handle uncaught exceptions in JVM applications. Our goal now is to create a simple handler that captures the stacktrace for every unhandled error, and generates a diagnostic report that could be sent to an error reporting API.
Implementing a basic UncaughtExceptionHandler
We’ll start by implementing an [CODE]UncaughtExceptionHandler[/CODE], and setting it as the default handler for all exceptions in the JVM. We’ll implement the [CODE]UncaughtExceptionHandler[/CODE] interface, then call [CODE]Thread.setDefaultUncaughtExceptionHandler[/CODE] to override the JVM’s default implementation:
— CODE language-kotlin —
fun main() <
В В В val exceptionHandler = SimpleExceptionHandler()
В В В Thread.setDefaultUncaughtExceptionHandler(exceptionHandler)
В В В throw RuntimeException(«Whoops!»)
>
class SimpleExceptionHandler : Thread.UncaughtExceptionHandler <
В В В override fun uncaughtException(thread: Thread, exc: Throwable) <
В В В В В В В // TODO generate a diagnostic report
В В В >
>
Of course, [CODE]SimpleExceptionHandler[/CODE] isn’t much use in its current form, as there isn’t currently any handling code in our handler. Our next step will be to obtain a stacktrace from the [CODE]Throwable[/CODE] object, and gather other information to form a diagnostic report.
Capturing stacktrace information for error reports
Right off the bat we’ll start off by encapsulating the stacktrace, so that we can capture additional metadata that may be useful in debugging our error. We’ll do this by creating a [CODE]Report[/CODE] class which can hold many arbitrary fields:
— CODE language-kotlin —
fun main() <
В В В val exceptionHandler = SimpleExceptionHandler()
В В В Thread.setDefaultUncaughtExceptionHandler(exceptionHandler)
В В В throw RuntimeException(«Whoops!»)
>
override fun uncaughtException(thread: Thread, exc: Throwable) <
В В В val report = Report(exc)
In the example above, our [CODE]UncaughtExceptionHandler[/CODE] will now generate a [CODE]Report[/CODE] object that contains a stacktrace for each unhandled error. We’ll also call [CODE]Thread.getAllStackTraces()[/CODE] to obtain stacktraces for all running threads in our application, which can be immensely useful for tracking down those tricky concurrency bugs.
Finally, we’ll add a field of type [CODE]Foo[/CODE], to demonstrate that we can capture arbitrary information about the application at this point.
Delivering an error report on the JVM
After generating a basic error report, the next step in a crash reporting SDK would be to serialise the report to JSON. If all goes well, we’ll then make a request to an error reporting API, so that we can quickly be altered that our app is crashing in production. We’ll achieve this by adding a [CODE]Delivery[/CODE] interface that delivers a Report to an arbitrary location:
— CODE language-kotlin —
override fun uncaughtException(thread: Thread, exc: Throwable) <
В В В val report = Report(exc)
В В В delivery.deliver(report)
>
interface Delivery <
В В В fun deliver(report: Report)
>
There are a surprising amount of error conditions that we need to account for within the [CODE]Delivery[/CODE], such as caching reports locally when there’s no network connectivity, and ensuring the handler doesn’t make long-lived requests, which can be killed by later versions of the OS. We’ll cover this in more depth in our next post.
Would you like to know more?
Hopefully this has helped you learn a bit more about Error Handling on Android. If you have any questions or feedback, please feel free to get in touch.
Источник
Coroutines do not log uncaught exceptions in Android #148
Comments
wardellbagby commented Oct 18, 2017
I created a sample app to show what I mean.
When a coroutine has an uncaught exception, it’s manually calling the Thread UncaughtExceptionHandler . A consequence of that is Android’s uncaught exception pre-handler that handles logging isn’t invoked.
The README of the linked project goes more in depth.
The text was updated successfully, but these errors were encountered:
elizarov commented Oct 18, 2017
You correctly describe the current behavior of kotlinx.coroutines . By default, on Android they use the default Android policy on uncaught exception handling.
What is your question or suggestion?
wardellbagby commented Oct 18, 2017
My suggestion is that, for Android, I believe it’s more beneficial to have the uncaught exception pre-handler be invoked as well as the Thread’s UncaughtExceptionHandler . I imagine most developers use the information posted to the log by the pre-handler for debugging issues, and with the way coroutines are now on Android, uncaught exceptions will crash the app without actually posting the logs.
While it is possible for developers to manually fix this, I believe it would be better for coroutines, on Android, to either rethrow the exception so the system will log it, or reimplement Android’s pre-handler so that the exception is easier to track down.
elizarov commented Oct 19, 2017
@wardellbagby How this can be implemented, given the constraint that kotlinx-coroutines-core can depend only on Java 6 APIs? How this «pre-handler» can be invoked?
We can provide some ready-to-use implementation as a part of kotlinx-coroutines-androind module, but, frankly, I cannot find any mention of «pre-handler» in Android APIs either. Can you give a link?
wardellbagby commented Oct 19, 2017
The pre-handler concept is completely hidden by the Android system, and it can’t be invoked directly. You can take a look at where it’s set by RuntimeInit and where the methods are in the Thread class in which it’s calling:
I think in a perfect world, the default coroutine contexts (e.g., CommonPool , Unconfined , and others) would include a coroutine exception handler by default that either logs the exception, or rethrows it so the Android system will catch it and make sure the pre-handler is called. Or there would be a new coroutine context included in the kotlinx-coroutines-android module that would be CommonPool with a default coroutine exception handler that would be recommended for use.
elizarov commented Oct 20, 2017 •
@wardellbagby Would you suggest as to how this can be implement? How do we know what logging framework to use given there are so many different logging frameworks out there?
Actually, the variety of logging frameworks was the chief reason to support CoroutineExceptionHandler so that you can log they way you want it in your app. That is also the reason why the default exception handler uses getUncaughtExceptionHandler and not simply dumps exception to console or does something else of that nature. You can install whatever default uncaught exception handler you want in your application, and it will be used by all the default contexts automatically.
wardellbagby commented Oct 20, 2017
Hmm. That’s a good point; I didn’t think much of the different logging frameworks.
I think in this case, and only this case specifically where the Thread ‘s uncaught exception will be invoked on Android, it’s okay to just dump it straight to console via Android’s android.util.Log . Since it would be using Android specific functionality, this would likely be best off inside of kotlinx-coroutines-android as a new coroutine with a CoroutineExceptionHandler , since it would only be giving it the same functionality other exceptions get when caught by the system.
However, the downsides would be that developers would have to manually specify that coroutine context and might still be confused if they aren’t aware of it. So while it’s a solution, it’ll have similar pitfalls to what is currently happening.
I would say it could be made so that the DefaultDispatcher is changed only on Android, but it doesn’t look like there’s any platform specific code in core (which is a great thing) and this isn’t major enough to warrant the introduction of that, since this isn’t technically incorrect behavior.
In light of all of this information, I’m changing my mind. The best solution might just be to add this quirk into the documentation so that fellow developers are aware of it. There are at least two ways to fix this developer side, and neither of them are difficult to implement. For the sake of any future web crawlers who come across this looking for a code sample, I’ll provide the two I’m aware of.
- Create a new UncaughtExceptionHandler (best if placed in #onCreate inside of the developer’s Application subclass) that logs the issue before passing it on to the old UncaughtExceptionHandler
Источник