Handling exceptions in android

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 7: capturing signals and exceptions in Android NDK 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 final part 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.

Capturing signals and exceptions in Android NDK apps

We previously learnt how to add useful metadata to our crash reports, so that we can prioritise the most important issues and debug faster. Our goal now is to capture a different class of error entirely: signals and exceptions from Android’s NDK layer.

Error handling in the NDK

Android’s Native Development Kit allows developers to write Android apps using the C/C++ programming languages. This can be beneficial for applications which rely on functionality delivered by native code, such as image recognition, or where high performance code is required, such as in mobile gaming.

Capturing errors from an NDK app presents a host of different challenges to capturing errors from a JVM app. C++ can throw exceptions whereas C raises signals, meaning there are various different sources of errors which all require different handling. Additionally, stack tracing implementations can vary substantially across architecture and API level, meaning something that would be simple on the JVM such as obtaining a useful stacktrace can be much more involved in NDK crash reporters.

C++ Exception Handler

C++ can throw exceptions, and provides an error handling API which accepts a function. When the application is about to terminate, that function will be invoked, allowing us to generate a crash report using a similar high-level approach to the JVM by: capturing diagnostic information, writing it to disk, then making an HTTP request to deliver it on the next application launch.

In the example below, a user calls [CODE]std::terminate()[/CODE] which will end the application as it is not handled within the `foo` method:

— CODE language-c —
void foo() <
В В std::terminate(); // ends the app
>
void init() <
В В prev_handler = std::set_terminate(handle_cpp_terminate);
>
void handle_cpp_terminate() <
В В // obtain a stacktrace and create a report here
В
В В if (prev_handler != NULL) <
В В В В prev_handler();
В В >
>

Before the application terminates, the program will invoke [CODE]handle_cpp_terminate()[/CODE], where we can attempt to obtain a stacktrace by using a library such as lib corkscrew, then serialize it to JSON and save to disk in a similar way as we did in the JVM crash reporter.

Читайте также:  Gaming mod pro android

C Signal Handlers

C on the other hand, can raise a variety of signals which can interrupt the normal flow of a program so that it can attempt to handle an error. It’s possible to define a signal handler that intercepts this signal, and within the handling code generate a diagnostic report that can be written to disk. Consider the following code, which raises a SIGABRT signal in the [CODE]foo()[/CODE] method:

— CODE language-c —
void foo() <
В В abort(); // ends the app with SIGABRT
>
void handle_signal(int signum,
В В В В В В В В В siginfo_t *info,
В В В В В В В В В void *user_context) <
В В // obtain a stacktrace and create a report here
В В invoke_previous_handler(signum, info, user_context);
>

We’ll assume that the signal handler has already been installed and will be called whenever a corresponding signal is raised — bugsnag-android-ndk’s source code provides a full example of how to do this if you’re interested in the finer details. The [CODE]signum[/CODE] parameter corresponds to a particular signal type, which may affect how we capture diagnostic data — for example, [CODE]SIGABRT[/CODE] and [CODE]SIGSEGV[/CODE] will have unique integer values. The remaining parameters give additional context surrounding the signal which we can also use to capture useful information.

Communicating using the Java Native Interface

Google recommends that most Android apps are written in Kotlin, but sometimes it can make sense to write a critical bottleneck in an application in C/C++ to achieve superior performance. For this use-case, the Java Native Interface allows us to call native code directly from the JVM. Consider the [CODE]updateDeviceOrientation()[/CODE] method from the bugsnag-android repository which is called whenever the device rotates:

— CODE language-c —
public static native void updateDeviceOrientation(String newOrientation);

JNIEXPORT void JNICALL
Java_com_bugsnag_android_NativeBridge_updateDeviceOrientation(
В В В JNIEnv *env, jobject _this, jstring new_value) <
В В В bugsnag_report->device_orientation = new_value
>

You may have noticed the [CODE]native[/CODE] modifier, which indicates this Java method calls into a C method, which is defined below, with the same name prefixed with the package and several JNI symbols. The [CODE]newOrientation[/CODE] parameter from Java will be converted into a [CODE]jstring[/CODE] that can be assigned to a field on a C struct, so that the value is up to date if a C error crashes the app.

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.

Источник

Flexible way to handle exceptions in Android

May 4, 2018 · 4 min read

Introduction

As your project becomes bigger, you will inevitably face problems of error handling in different parts of your app. Error handling can be tricky — in some cases you should simply show a message to the user, while some errors require to perform navigation to a screen, etc…

The majority of developers use some kind of a struct u re pattern in the presentation layer of any project — MVP, MVVM, some guys use MVI… In most cases the exceptions which occurred in the data layer should be raised to the presentation layer for appropriate handling and showing message to the user. But it is not true for every case.

What do we expect from the ErrorHandler in our project?

  • We need to be able to handle various types of exceptions.
  • We want to use it not only in the presenters.
  • The code should be reusable.

Although, I will be talking in terms of MVP, but this approach will work for any kind of presentation pattern. And one more thing: it will work even at iOS

DIRTY IMPLEMENTATION

So, you are using MVP. I am pretty sure that most of you:

1. Use some kind of an MVP library for reducing boilerplate code(Moxy, Mosby, EasyMvp and etc.)

2. Have a BasePresenter that looks like this :

It also may contain code for attaching and detaching views and some code for surviving orientation changes. But in my case MvpPresenter will do it for me.

Читайте также:  Не определяет имя звонящего android

So I am with you, guys. =)

How does specific implementation of a BasePresenter actually look like?

Good. Let’s imagine, that you are not getting particular messages in error responses from backend and you have to handle it on the client side. Almost all presenters should be able to handle errors, so maybe you will have an idea to make something like this :

P.S. ResourceManager is just a wrapper around the application context, since we do not want to have Android dependencies in the presenters to ensure they are convenient for unit testing.

At this point, you can come up with a lot of details related to the implementation. For example, you can pass a lambda to the handleError function, or create a new abstract class ErrorHandlingPresenter which will extend BasePresenter and move the handleError function there. There is actually a lot of space for improvement. But this approach has some serious drawbacks:

  1. Your code is not SOLID. Your presenters are responsible for error handling.
  2. Your code is not reusable. Imagine that you need to process errors in the Retrofit Interceptor while you are trying to refresh your access token? What should you do? Just copy and paste the handleError function?

Let’s try to fix it.

THE RIGHT WAY

Let’s assume that the most common way to handle any error is to show a message to the user. Note: that is true almost for any project, but feel free to adjust it to your project requirements.

Further, I will show you a complete hierarchy of classes with explanations.

First, we need an interface for the classes, which is able to show the error. In most cases it will be implemented by activity/fragments:

Interface for error handler :

Since our error handlers live in the presenters, and the presenters survive orientation changes, we cannot pass instance of the CanShowError view to the error handler constructor.

The following is the most common implementation of the ErrorHandler :

The implementation has a weak reference of the view, that is able to show errors to the user. It has a resource manager for fetching strings and it has the most simple and common logic of error processing. The DefaultErrorHandler will be a global singleton, and the views will be attached and detached from the ErrorHandler depending on what the screen had presented to the user.

Good. Now we need to inject ErrorHandler implementation to every presenter that is able to handle errors and not forget to attach and detach view to it. For this purpose I prefer using inheritance, so I have two base presenters: the most common one has CompositeSubscription and ErrorHandlingPresenter, that attaches and detaches view to error handler automatically.

Please pay attention to the generic constraints of the view types.

Now our specific presenters that are able to process errors will look like this:

With the help of the Dagger2 we can pass the DefaultErrorHandler as implementation of the ErrorHandler interface to constructor. But if you remember, the DefaultErrorHandler can only show errors, so in my case the business rules were: if you get a 404 Not found error from the backend, then perform navigation to another screen, if something else — show the error to the user.

So, I create a new implementation of the ErrorHandler :

Here I used composition and injecting the DefaultErrorHandler to the SpecificErrorHandler. Also, a router for performing screen navigation. In proceed function we try to catch 404 error and navigate to another screen, if we cannot — we delegate the control to the defaultErrorHandler. The same is with the ‘attach’ and ‘detach’ methods — the control is delegated to the defaultErrorHandler.

Since the DefaultErrorHandler is a global singleton, we should use Qualifier for injecting SpecificErrorHandler implementation.

That is it. Now it should work as expected.

As you can see, it does not depend on the MVP details, it is flexible, SOLID and we can use it in any class that can spawn errors.

Источник

Оцените статью