Using Global Exception Handling on android
Basic Example for someone who comes to this page with a solution :)
public class ChildActivity extends BaseActivity {
@SuppressWarnings("unused")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
int a=1/0;
}
}
Class for handling error:
public class BaseActivity extends Activity{
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread paramThread, Throwable paramThrowable) {
Log.e("Alert","Lets See if it Works !!!");
}
});
}
}
Here's a variant of the answer by Mohit Sharma with the following improvements:
- Doesn't cause the app/service to freeze after error handling
- Lets Android do its normal error handling after your own
Code:
public class BaseActivity extends Activity {
@Override
public void onCreate() {
super.onCreate();
final Thread.UncaughtExceptionHandler oldHandler =
Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(
new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(
Thread paramThread,
Throwable paramThrowable
) {
//Do your own error handling here
if (oldHandler != null)
oldHandler.uncaughtException(
paramThread,
paramThrowable
); //Delegates to Android's error handling
else
System.exit(2); //Prevents the service/app from freezing
}
});
}
}
For those who just want to see exception details when your app crashes on device (in debug config). This is application class:
private Thread.UncaughtExceptionHandler oldHandler;
@Override
public void onCreate() {
super.onCreate();
if (!BuildConfig.DEBUG)
return;
oldHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler((t, e) -> {
try {
StringWriter sw = new StringWriter();
e.printStackTrace(new PrintWriter(sw));
Intent intent = new Intent(Intent.ACTION_SEND);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.putExtra(Intent.EXTRA_TEXT, sw.toString());
intent.setType("text/plain");
startActivity(intent);
} catch(Exception ex) {
ex.printStackTrace();
} finally {
if (oldHandler != null)
oldHandler.uncaughtException(t, e);
else
System.exit(1);
}
});
}
It uses external app as your UI thread might not working anymore.
Keep in mind that the The RuntimePermission("setDefaultUncaughtExceptionHandler")
is checked prior to setting the handler and make sure you cause the process to halt afterwards, by throwing an uncaught exception, as things could be in an uncertain state.
Do not display anything, indeed the UI thread might have been the one that crashed, do write a log and/or send the details to a server, instead. You might want to check out this question and its answers.
I just wanted to point out my experience so far. I am using the solution suggested by https://stackoverflow.com/a/26560727/2737240 to flush the exception into my log file before giving control to the default exception handler.
However, my structure looks like this:
BaseActivity
|
_______________________
| | |
Activity A Activity B Activity C
final Thread.UncaughtExceptionHandler defaultEH = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable e) {
handleUncaughtException(thread, e, defaultEH);
}
});
where handleUncaughtException(thread, e, defaultEH);
writes to the log and hands the call over to the original UncaughtExceptionHandler.
So what happened by using this code was the following:
- Activity A is instantiated
- New Default Exception Handler (DEH) is now my log handler + the old DEH
- Activity B is instantiated
- New DEH is now my log handler + the old DEH (log handler + original DEH)
- Activity C is instantiated
- New DEH is now my log handler + the old DEH (log handler + log handler + original DEH)
So it's a chain growing infinitely causing two problems:
- The specified custom code (in my case writing to the log file) will be called multiple times, which is not the desired action.
- The reference of defaultEh is kept in the heap even after the activity has been finished, because it is used by the reference chain so the worst thing that could happen is an out of memory exception.
Therefore I added one more thing to finally make this work without issues:
private static boolean customExceptionHandlerAttached = false;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(!customExceptionHandlerAttached) {
final Thread.UncaughtExceptionHandler defaultEH = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable e) {
handleUncaughtException(thread, e, defaultEH);
}
});
customExceptionHandlerAttached = true;
}
}
With this solution we can make sure to:
- add a custom exception handler for our desired action
- ensure that this action is only triggered once
- allowing garbage collector to dispose our activity completely by calling finish()