Crashlytics Android SDK - custom UncaughtExceptionHandler
Since recent versions of Crashlytics perform initialization asynchronously, it's better to use Fabric's initialization callback:
private static Thread.UncaughtExceptionHandler mDefaultUEH;
private static Thread.UncaughtExceptionHandler mCaughtExceptionHandler =
new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread thread, Throwable ex) {
// Custom logic goes here
// This will make Crashlytics do its job
mDefaultUEH.uncaughtException(thread, ex);
}
};
CrashlyticsCore core = new CrashlyticsCore.Builder()
.disabled(BuildConfig.DEBUG)
.build();
Fabric.with(new Fabric.Builder(this).kits(new Crashlytics.Builder()
.core(core)
.build())
.initializationCallback(new InitializationCallback<Fabric>() {
@Override
public void success(Fabric fabric) {
mDefaultUEH = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(mCaughtExceptionHandler);
}
@Override
public void failure(Exception e) {
}
})
.build());
Upgrade Firebase Crashlytics with custom UncaughtExceptionHandler
The solution is posted here. I'll paste my modified code here for anyone who looking for it:
Below code is written in Java. For Kotlin, please visit the original post.
public class NewCrashHandlerContentProvider extends ContentProvider {
public static final String TAG = "NewCrashHandler";
static long PRE_DELAY_MILLIS = 3000L;
static long POST_DELAY_MILLIS = 3000L;
public static void initializeAfterFirebaseContentProvider() {
Thread.setDefaultUncaughtExceptionHandler(new PreFirebaseCrashHandler(Thread.getDefaultUncaughtExceptionHandler()));
}
@Override
public boolean onCreate() {
try {
Log.i(TAG, "+onCreate()");
Thread.setDefaultUncaughtExceptionHandler(new PostFirebaseCrashHandler(Thread.getDefaultUncaughtExceptionHandler()));
return true;
}finally {
Log.i(TAG, "-onCreate()");
}
}
@Nullable
@Override
public Cursor query(@NonNull Uri uri, @Nullable String[] projection, @Nullable String selection, @Nullable String[] selectionArgs, @Nullable String sortOrder) {
return null;
}
@Nullable
@Override
public String getType(@NonNull Uri uri) {
return null;
}
@Nullable
@Override
public Uri insert(@NonNull Uri uri, @Nullable ContentValues values) {
return null;
}
@Override
public int delete(@NonNull Uri uri, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
@Override
public int update(@NonNull Uri uri, @Nullable ContentValues values, @Nullable String selection, @Nullable String[] selectionArgs) {
return 0;
}
static class PreFirebaseCrashHandler implements Thread.UncaughtExceptionHandler {
Thread.UncaughtExceptionHandler previousUncaughtExceptionHandler;
public static final String TAG = "PreFirebaseCrashHandler";
public PreFirebaseCrashHandler(Thread.UncaughtExceptionHandler previousUncaughtExceptionHandler) {
this.previousUncaughtExceptionHandler = previousUncaughtExceptionHandler;
}
@Override
public void uncaughtException(@NonNull Thread t, @NonNull Throwable e) {
LogWrapper.logException(e);
try {
// my own logic
Thread.sleep(PRE_DELAY_MILLIS);
}catch (Throwable throwable) {
LogWrapper.e(TAG, throwable.getLocalizedMessage());
} finally {
previousUncaughtExceptionHandler.uncaughtException(t,e);
}
}
}
static class PostFirebaseCrashHandler implements Thread.UncaughtExceptionHandler {
Thread.UncaughtExceptionHandler previousUncaughtExceptionHandler;
public static final String TAG = "PostFirebaseCrashHandler";
public PostFirebaseCrashHandler(Thread.UncaughtExceptionHandler previousUncaughtExceptionHandler) {
this.previousUncaughtExceptionHandler = previousUncaughtExceptionHandler;
}
@Override
public void uncaughtException(@NonNull Thread t, @NonNull Throwable e) {
LogWrapper.e(TAG, "+uncaughtException($thread, $throwable)");
try {
Thread.sleep(POST_DELAY_MILLIS);
}catch (Throwable throwable) {
LogWrapper.e(TAG, throwable.getLocalizedMessage());
}finally {
previousUncaughtExceptionHandler.uncaughtException(t,e);
}
LogWrapper.e(TAG, e.getLocalizedMessage());
}
}
}
In Application file, write in onCreate()
method:
NewCrashHandlerContentProvider.initializeAfterFirebaseContentProvider();
Also don't forget to update manifests
<provider
android:authorities="${applicationId}"
android:name=".utils.NewCrashHandlerContentProvider"
android:exported="false"
android:initOrder="101"
android:grantUriPermissions="false"/>
Crashlytics and default exception handler
Thanks. This is not officially supported by Crashlytics. If you do find a workaround we cannot ensure that it will continue to work over time with updates to our SDK.
Deal with Firebase crash reporting and custom Application class with custom UncaughtExceptionHandler
After some testing, I finally found a way to both ensure my app restarts properly after an UncaughtException
.
I attempted three different approaches, but only the first, which is my original code, with just a little tweak to pass the uncaught Throwable
to `FirebaseCrash, and ensure it is considered as a FATAL error.
The code that works:
final UncaughtExceptionHandler crashShield = new UncaughtExceptionHandler() {
private static final int RESTART_APP_REQUEST = 2;
@Override
public void uncaughtException(Thread thread, Throwable ex) {
if (BuildConfig.DEBUG) ex.printStackTrace();
reportFatalCrash(ex);
restartApp(MyApp.this, 5000L);
}
private void reportFatalCrash(Throwable exception) {
FirebaseApp firebaseApp = FirebaseApp.getInstance();
if (firebaseApp != null) {
try {
FirebaseCrash.getInstance(firebaseApp)
.zzg(exception); // Reports the exception as fatal.
} catch (com.google.firebase.crash.internal.zzb zzb) {
Timber.wtf(zzb, "Internal firebase crash reporting error");
} catch (Throwable t) {
Timber.wtf(t, "Unknown error during firebase crash reporting");
}
} else Timber.wtf("no FirebaseApp!!");
}
/**
* Schedules an app restart with {@link AlarmManager} and exit the process.
* @param restartDelay in milliseconds. Min 3s to let the user got in settings force
* close the app manually if needed.
*/
private void restartApp(Context context, @IntRange(from = 3000) long restartDelay) {
Intent restartReceiver = new Intent(context, StartReceiver_.class)
.setAction(StartReceiver.ACTION_RESTART_AFTER_CRASH);
PendingIntent restartApp = PendingIntent.getBroadcast(
context,
RESTART_APP_REQUEST,
restartReceiver,
PendingIntent.FLAG_ONE_SHOT
);
final long now = SystemClock.elapsedRealtime();
// Line below schedules an app restart 5s from now.
mAlarmManager.set(ELAPSED_REALTIME_WAKEUP, now + restartDelay, restartApp);
Timber.i("just requested app restart, killing process");
System.exit(2);
}
};
Thread.setDefaultUncaughtExceptionHandler(crashShield);
Explanation of why and unsuccessful attempts
It's weird that the hypothetically named reportFatal(Throwable ex)
method from FirebaseCrash
class has it's name proguarded while being still (and thankfully) public
, giving it the following signature: zzg(Throwable ex)
.
This method should stay public, but not being obfuscated IMHO.
To ensure my app works properly with multi-process introduced by Firebase Crash Report library, I had to move code away from the application class (which was a great thing) and put it in lazily loaded singletons instead, following Doug Stevenson's advice, and it is now multi-process ready.
You can see that nowhere in my code, I called/delegated to the default UncaughtExceptionHandler
, which would be Firebase Crash Reporting one here. I didn't do so because it always calls the default one, which is Android's one, which has the following issue:
All code written after the line where I pass the exception to Android's default UncaughtExceptionHandler
will never be executed, because the call is blocking, and process termination is the only thing that can happen after, except already running threads.
The only way to let the app die and restart is by killing the process programmatically with System.exit(int whatever)
or Process.kill(Process.myPid())
after having scheduled a restart with AlarmManager
in the very near future.
Given this, I started a new Thread
before calling the default UncaughtExceptionHandler
, which would kill the running process after Firebase Crash Reporting library would have got the exception but before the scheduled restart fires (requires magic numbers). It worked on the first time, removing the Force Close dialog when the background thread killed the process, and then, the AlarmManager
waked up my app, letting it know that it crashed and has a chance to restart.
The problem is that the second time didn't worked for some obscure and absolutely undocumented reasons. The app would never restart even though the code that schedules a restart calling the AlarmManager
was properly run.
Also, the Force Close popup would never show up. After seeing that whether Firebase Crash reporting was included (thus automatically enabled) or not didn't change anything about this behavior, it was tied to Android (I tested on a Kyocera KC-S701 running Android 4.4.2).
So I finally searched what Firebase own UncaughtExceptionHandler
called to report the throwable and saw that I could call the code myself and manage myself how my app behaves on an uncaught Throwable
.
How Firebase could improve such scenarios
Making the hypothetically named reportFatal(Throwable ex)
method non name-obfuscated and documented, or letting us decide what happens after Firebase catches the Throwable
in it's UncaughtExceptionHandler
instead of delegating inflexibly to the dumb Android's default UncaughtExceptionHandler
would help a lot.
It would allow developers which develop critical apps that run on Android to ensure their app keep running if the user is not able to it (think about medical monitoring apps, monitoring apps, etc).
It would also allow developers to launch a custom activity to ask users to explain how it occurred, with the ability to post screenshots, etc.
BTW, my app is meant to monitor humans well-being in critical situations, hence cannot tolerate to stop running. All exceptions must be recovered to ensure user safety.
Using a custom crash handler with Firebase installed
I found my answer thanks to @BobSnyder's comment - it lead me to search and find this previous question/answer:
How to disable default Firebase crash reporting?
and ultimately I am using this answer to solve my issue:
If you want to disable the Firebase Analytics Crash Reporting, use the below
code inside app.gradle file.
configurations {
all*.exclude group: 'com.google.firebase', module: 'firebase-crash'
}
Related Topics
How to Make Edittext Not Editable Through Xml in Android
How to Use the Proguard in Android Studio
Google Cloud Messaging - Messages Sometimes Not Received Until Network State Changed
Libz.So.1: Cannot Open Shared Object File
Android Tabwidget Detect Click on Current Tab
Get My Phone Number in Android
Android Listview Setselection() Does Not Seem to Work
How to Get File Path from Sd Card in Android
How to Rename a File on Sdcard with Android Application
Android Viewpager Setcurrentitem Not Working After Onresume
How to Use a Timertask to Run a Thread
Android Tabhost Change Text Color Style
Arrayadapter.Notifydatasetchanged() Is Not Working
Installing Ionic Npm Err! Tar.Unpack Error