Background / Motivation
Sometimes it is quite useful to handle/catch exceptions in a central handler
. Typical samples are:
- Handle Authorization problems e.g. with your backend
- Central dispatching of these error to an Exception Collector like Crashlytics (ur just send an email)
- Just show a message dialog to the user
Implementation
Just creating an Exception is straight forward java.lang.Thread.UncaughtExceptionHandler
. The little more tricky part is integrating it into an Android application. Two obvious ways we might choose from:
- Attach the Exception Handler in the Application
- Can’t open UI Dialogs or other Activities properly
- Will be executed for any activity
- Attach the Exception Handler in an Activity
- May start other activities
- Needs a Base class to be applied in an easy way to all activities
Exception as Application Handler
Typical here is to log the exception and later on send it to the backend, e.g. after the application was restarted.
This sample code just logs each exception into a file, which we can later on inspect and send to the backend if we like.
Exception Handler
public class LoggingExceptionHandler implements Thread.UncaughtExceptionHandler { private final static String TAG = LoggingExceptionHandler.class.getSimpleName(); private final static String ERROR_FILE = MyAuthException.class.getSimpleName() + ".error"; private final Context context; private final Thread.UncaughtExceptionHandler rootHandler; public LoggingExceptionHandler(Context context) { this.context = context; // we should store the current exception handler -- to invoke it for all not handled exceptions ... rootHandler = Thread.getDefaultUncaughtExceptionHandler(); // we replace the exception handler now with us -- we will properly dispatch the exceptions ... Thread.setDefaultUncaughtExceptionHandler(this); } @Override public void uncaughtException(final Thread thread, final Throwable ex) { try { Log.d(TAG, "called for " + ex.getClass()); // assume we would write each error in one file ... File f = new File(context.getFilesDir(), ERROR_FILE); // log this exception ... FileUtils.writeStringToFile(f, ex.getClass().getSimpleName() + " " + System.currentTimeMillis() + "\n", true); } catch (Exception e) { Log.e(TAG, "Exception Logger failed!", e); } public static final List<String> readExceptions(Context context) { List<String> exceptions = new ArrayList<>(); File f = new File(context.getFilesDir(), ERROR_FILE); if (f.exists()) { try { exceptions = FileUtils.readLines(f); } catch (IOException e) { Log.e(TAG, "readExceptions failed!", e); } } return exceptions; } }
Activation of the Exception Handler
We need now only to attach it once in our Application:
public class MyApp extends Application { @Override public void onCreate() { super.onCreate(); new LoggingExceptionHandler(this); } }
Enable the Android Application
And of course make sure it is attached in our AndroidManifest.xml
.
<application android:name=".MyApp"
Exception Handler in an Activity
Attaching an exception handler in the activity allows us to do a little more, most important here starting a different activity work — in the case of the ApplicationContext
it would just hang.
Exception Handler
In this case, we check the exceptions if we could gracefully handle them. If so the start the appropriate activity and could pass more informations to it if needed.
public class MyExceptionHandler implements Thread.UncaughtExceptionHandler { public static final String EXTRA_MY_EXCEPTION_HANDLER = "EXTRA_MY_EXCEPTION_HANDLER"; private final Activity context; private final Thread.UncaughtExceptionHandler rootHandler; public MyExceptionHandler(Activity context) { this.context = context; // we should store the current exception handler -- to invoke it for all not handled exceptions ... rootHandler = Thread.getDefaultUncaughtExceptionHandler(); // we replace the exception handler now with us -- we will properly dispatch the exceptions ... Thread.setDefaultUncaughtExceptionHandler(this); } @Override public void uncaughtException(final Thread thread, final Throwable ex) { if (ex instanceof MyAuthException) { // note we can't just open in Android an dialog etc. we have to use Intents here // http://stackoverflow.com/questions/13416879/show-a-dialog-in-thread-setdefaultuncaughtexceptionhandler Intent registerActivity = new Intent(context, AuthActivity.class); registerActivity.putExtra(EXTRA_MY_EXCEPTION_HANDLER, MyExceptionHandler.class.getName()); registerActivity.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); registerActivity.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT); context.startActivity(registerActivity); // make sure we die, otherwise the app will hang ... android.os.Process.killProcess(android.os.Process.myPid()); System.exit(0); } else { rootHandler.uncaughtException(thread, ex); } } }
Activation of the Exception Handler
Now it gets a little more messy. As we need to activate/init this handler with an Activity we have to ensure that either in each Activity or we provide a base activity all other inherit from.
public abstract class BaseActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); new MyExceptionHandler(BaseActivity.this); } }
Thank you for this illustration. Do you use this approach in production applications? If so, how effective is it.