13

I have integrated Firebase Crashlytics version 2.9.1 to digging out crashes to cover performance and stability of my app.

Crashes are not being logged on firebase crashlytics console if application is having own UncaughtExceptionHandler.

I have BaseActivity in my app. inside onCreate() method I have registered custom UncaughtExceptionHandler based on project requirement.

Whenever app got crashed due to any reason, user should be redirected to splash screen (MainActivity.java) .

public class BaseActivity extends FragmentActivity{ 

@Override 
protected void onCreate(Bundle arg0) { 
   // Enable global crash handler. 
   Thread.setDefaultUncaughtExceptionHandler(handleAppCrash); 
} 

/*** 
* @Purpose Called when any crash occurs in the application. 
***/ 
private Thread.UncaughtExceptionHandler handleAppCrash = new Thread.UncaughtExceptionHandler() { 
@Override 
public void uncaughtException(Thread thread, Throwable ex) { 

   Intent intent = new Intent(context, MainActivity.class); //redirect to Splash screen
   intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); 
   context.startActivity(intent); 
   System.exit(0); 
  } 
}; 

} 
Frank van Puffelen
  • 565,676
  • 79
  • 828
  • 807
Durgesh Patel
  • 1,035
  • 8
  • 15

5 Answers5

8

OK, I've investigated this question.

You should not create custom exception handler inside BaseActivity. It is better to do it in your Application class. In case of BaseActivity you will heve a new handler each time you start new activity, which extends your BaseActivity.

So, in your application class onCreate() method you can get default application handler

val defaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()

In my case it is an instance of a

com.crashlytics.android.core.CrashlyticsUncaughtExceptionHandler

This "defaultExceptionHandler" you will use to sends correct errors to firebase. Then you can create your own exception handler, but need to hold here this "defaultExceptionHandler".

class DefaultExceptionHandler (private val  defaultExceptionHandler:Thread.UncaughtExceptionHandler?) : Thread.UncaughtExceptionHandler {
override fun uncaughtException(thread: Thread, ex: Throwable) {
        try {       
            // here you restore your activity and do other things
            // and of course you deal with defaultExceptionHandler
            // without it firebase will not work       
            defaultExceptionHandler?.uncaughtException(thread, ex)
            // only after firebase dealed with an exception, you can exit
            System.exit(0)
        } catch (e: IOException) {
            // just catch
        }

        }
    }

And finally firebase will show you crashes as it is needed. Application onCreate() example

override fun onCreate() {
        super.onCreate()
        Fabric.with(this, Crashlytics())
        val defaultExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
        val customExceptionHandler = DefaultExceptionHandler(defaultExceptionHandler)
        Thread.setDefaultUncaughtExceptionHandler(customExceptionHandler)
    }
Eugene P.
  • 926
  • 9
  • 12
8

For the latest firebase crashlytics 17.x.x, they set up their uncaught exception handler inside a Content Provider so that they don't need the developer to pass a context and manually initialize. This is done to autoinitialize firebase on app launch. So, even if we set up our uncaught exception handler in Application class oncreate, our implementation overrides firebase's and crashes will not be reported to firebase exception handler.

Fix: To fix this, we have to write a custom Content Provider and set its priority to maximum in our app. Then initialize our uncaught exception handler inside the oncreate of content provider instead of doing it in the application class. This way, ours' will be initialized first and will not override firebase's.

Niyas
  • 91
  • 1
  • 4
  • If we do so (add custom ContentProvider), does Firebase handler not override our implementation and we loose it? – Maksym Oct 25 '20 at 17:47
  • If we add custom ContentProvider to init an uncaught exception handler, our exception handler will be initialized first and we will get all the exceptions first, and it will not conflict with firebase's exception handler so that the firebase exception handler also gets the same exceptions right after our handler. So the crash will be logged to firebase without issues. @Maksym – Niyas Nov 02 '20 at 09:36
5

I also had the same problem in my app. As suggested by @niyas I also used the same solution and it's working fine for me. Firebase using the content provider to initialize firebase crashlytics sdk and its uncaught exception handler. So if you register your uncaught exception handler in a custom content provider with higher priority then the firebase one. Then the order of receiving a callback to the handler is reversed. First the callback is going to Firebase handler and then to yours's handler. In this way firebase collects the stack traces for the crashes and you also get the callback in handler to relaunch the app / or any other use case. I found this solution on github Here's a link!

Code snippet:

 <application>
        ...
        <!-- Firebase SDK initOrder is 100. Higher order init first -->
        <provider
            android:name=".UncaughtExceptionHandlerContentProvider"
            android:authorities="${applicationId}"
            android:exported="false"
            android:initOrder="101"
            android:grantUriPermissions="false" />
        ... 
    </application>

 public class UncaughtExceptionHandlerContentProvider extends ContentProvider {
    @Override
    public boolean onCreate() {
        MyCustomCrashHandler myHandler = new MyCustomCrashHandler(Thread.getDefaultUncaughtExceptionHandler());
        Thread.setDefaultUncaughtExceptionHandler(myHandler);
        return true;
    }

    @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; }
}


public class MyCustomCrashHandler implements UncaughtExceptionHandler {
    @Nullable 
    private final UncaughtExceptionHandler defaultHandler;
    
    public MyCustomCrashHandler(@Nullable UncaughtExceptionHandler defaultHandler)(){
         this.defaultHandler = defaultHandler;
    }

    @Override
    public void uncaughtException(@NonNull Thread thread, @NonNull Throwable ex) {
        // We are now safely being called after Crashlytics does its own thing. 
        // Whoever is the last handler on Thread.getDefaultUncaughtExceptionHandler() will execute first on uncaught exceptions.
        // Firebase Crashlytics will handle its own behavior first before calling ours in its own 'finally' block.
        // You can choose to propagate upwards (it will kill the app by default) or do your own thing and propagate if needed.
        
        try { 
            //do your own thing.
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (defaultHandler != null) {
                defaultHandler.uncaughtException(thread, ex) 
                // propagate upwards. With this workaround (and also without any other similar UncaughtExceptionHandler based on ContentProvider), 
                // defaultHandler should now be an instance of com.android.internal.os.RuntimeInit.KillApplicationHandler
                // hence properly killing the app via framework calls.
            }
        }
    }
android user
  • 81
  • 1
  • 4
2

Set your custom exception handler inside InitializationCallback of fabric like below code.

    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) {
                // Get default exception handler
                final Thread.UncaughtExceptionHandler defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
                // Set your custom exception handler   
                Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
                    @Override
                    public void uncaughtException(Thread thread, Throwable ex) {
                        // redirect to Splash screen
                        Intent intent = new Intent(context, MainActivity.class);
                        intent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
                        context.startActivity(intent);
                        // pass exception to default handler
                        defaultHandler.uncaughtException(thread, ex);
                    }
                };);
            }

            @Override
            public void failure(Exception e) {

            }
        }).build());
Priyank Patel
  • 12,244
  • 8
  • 65
  • 85
  • I dont want to integrate firebase crashlytics only my own uncaught Exception. so i can achieve this? – Kevan Aghera May 30 '18 at 03:45
  • @Priyank Patel, as i have put this code is in onCreate() of BaseActivity.java but there is no callback of success or failure :) – Durgesh Patel May 30 '18 at 05:58
  • @kevanaghera if you want to use only your own exception handler instead of crashlytics, then you just need to use `Thread.setDefaultUncaughtExceptionHandler(UncaughtExceptionHandler);`' in `onCreate` method of application class to catch uncaught exception. – Priyank Patel May 30 '18 at 06:01
  • @kevanaghera i knew this thing already. :) see my posted question description. :) – Durgesh Patel May 30 '18 at 06:07
  • This doesn't work for me. My requirement is to launch a feedback activity post a crash and simultaneously log a fatal exception on Crashlytics, without the app showing the default crash dialog box and asking for restarting the app. – Rahul Gurnani Oct 10 '18 at 06:33
1

Have you try to upgrade in latest version:

implementation 'com.crashlytics.sdk.android:crashlytics:2.9.3'

And google services (Project level):

classpath 'com.google.gms:google-services:4.0.1'

Make sure that everything is up-to-date. This is the latest update in firebase libraries based on this link, you could check below:

implementation 'com.google.firebase:firebase-core:16.0.0'
implementation 'com.google.firebase:firebase-ads:15.0.1'
implementation 'com.google.firebase:firebase-analytics:16.0.0'
implementation 'com.google.firebase:firebase-appindexing:15.0.1'
implementation 'com.google.firebase:firebase-auth:16.0.1'
implementation 'com.google.firebase:firebase-firestore:17.0.1'
implementation 'com.google.firebase:firebase-functions:16.0.1'
implementation 'com.google.firebase:firebase-messaging:17.0.0'
implementation 'com.google.firebase:firebase-storage:16.0.1'
implementation 'com.google.firebase:firebase-crash:16.0.0'
implementation 'com.google.firebase:firebase-invites:16.0.0'
implementation 'com.google.firebase:firebase-perf:16.0.0'
implementation 'com.google.firebase:firebase-database:16.0.1'
implementation 'com.google.firebase:firebase-config:16.0.0'
Paraskevas Ntsounos
  • 1,755
  • 2
  • 18
  • 34