I'm currently stuck with UncaughtExceptionHandler and Firebase Database in my Android Kotlin App.
My plan:
In the case of an uncaught exception, log the stack trace to Google Firebase.
I already have an BaseCompatActivity with an UncaughtExceptionHandler. All activities inherit from this class.
The only and the interesting part from BaseCompatActivity follows:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val currentUncaughtExceptionHandler = Thread.getDefaultUncaughtExceptionHandler()
Thread.setDefaultUncaughtExceptionHandler({ thread, exception ->
val exStackTraceString = StringWriter()
exception.printStackTrace(PrintWriter(exStackTraceString))
FirebaseDatabase.getInstance().getReference("/exception-log").push().setValue(exStackTraceString.toString()).addOnCompleteListener {
currentUncaughtExceptionHandler.uncaughtException(thread, exception)
}
})
}
Everything is working except the the default method call for currentUncaughtExceptionHandler.uncaughtException(thread, exception) because this line is never called. It seems that the complete listener never gets called. This leads to an non-closed, unusable App after the exception occured :-(
The logging into the Firebase Database is working. The only broken thing is the listener callback method.
I don't want to use System.exit() because this breaks the Exception Chain.
Can you help me?
From the documentation of Thread.setDefaultUncaughtExceptionHandler:
"Set the default handler invoked when a thread abruptly terminates due to an uncaught exception"
Your call to your Firebase Database uses an asynchronous callback. Without more information, it's hard to be sure, but considering your UI is getting stuck, something off of your Main thread is getting killed. You're able to make the call, but the result doesn't have a thread to return to because the thread has been terminated.
If you're using Firebase, I'd suggest looking at Crashlytics, they have a method for handling this.
Related
I'm trying kotlinx.coroutines (version: 1.2.0). Here is a simple test code block:
GlobalScope.launch {
Logger.i("${Thread.currentThread()}, ${Looper.myLooper() == Looper.getMainLooper()}")
text_view.text = "test"
}
The printed log is:
Thread[DefaultDispatcher-worker-2,5,main], false
As the log shows, we are not on the Android main thread, i.e UI thread. However, the code above won't throw an exception after we set text to text_view on this worker thread, and "test" is set to text_view correctly. What's the reason?
Update 1:
Adding delay(10000L) before setText() will cause the exception while shorter time (like 1000L in my test for a debug run with cold startup) won't. So it seems like an Android issue. But still that question, what's the reason?
Update 2:
Now I realized this behavior is related to Android instead of kotlinx.coroutines. The code above is executed in onCreate() when ViewRootImpl may not have called performTraversals() or initialize all Views. In this situation, checkThread() before UI operation isn't called, either.
The default dispatcher, that is used when coroutines are launched in GlobalScope, is represented by Dispatchers.Default and uses shared background pool of threads, so launch(Dispatchers.Default) { ... } uses the same dispatcher as GlobalScope.launch { ... }.
Hence, When launch { ... } is used without parameters, it inherits the context (and thus dispatcher) from the CoroutineScope that it is being launched from.
In this case, it inherits the context of the main thread.
So, unless we define context & dispatcher, Coroutine will work on main thread creating new worker thread from DefaultDispatcher (in our case is main again).
Nothing to do with Kotlin coroutines really.
Even though you should not be calling UI functions from non-UI threads, not every Android UI function actually checks that you're on the UI thread. TextView#setText() is one of them and you can get away with calling it from a background thread without getting an exception.
GlobalScope.launch(Dispatchers.Main) {
mTvText?.text = "text" // exemple: set text in main thread
... // write also your code here
}
Whenever I start the app I want to know if the app recovered from a crash. Can I store it in a flag ?
Do the crash and regular app exit scenario go through the same steps(lifecycle) in android ?
You can override your crash exception by using Thread.setDefaultUncaughtExceptionHandler. But do not forget, If you would not close your application it will freeze the screen by OS.
Example code :
//variable that inside the application class
private Thread.UncaughtExceptionHandler defaultUEH;
public void onCreate(){
super.onCreate();
defaultUEH = Thread.getDefaultUncaughtExceptionHandler();
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler(){
#Override
public void uncaughtException(Thread t, Throwable e) {
handleUncaughtException(t,e);
defaultUEH.uncaughtException(thread,e);
}
});
}
private void handleUncaughtException(Thread thread,Throwable e){
//do whatever you like!
}
NOTE : There is no way to understand how is your program is opened
You don't get this information at all from the Android SDK. There are two options you could try, first is would be to have extensive logging/tracking of the apps life-cycle. In all of your activities keep track of when you activities are started, resumed, paused, stopped, and destroyed. (Also handle special usecase of when the device is rotated and the activity will experience a tear-down and restart). From here you will have information of when an activity has been stopped and you check the last state in the onCreate, onStart, or onResume of you activities to make sure that you're expected life-cycles where hit. If not, then you more than likely experienced a crash. Also note, on older version of Android certain teardown life-cycle callbacks weren't guaranteed to be called.
Second option would be to try using Thread.setDefaultUncaughtExceptionHandler. I have never personally used it but it may help in the usecase where an uncaught exception occurs. You could log such an event and check that flag once, the app is resumed. I am not sure of all of the side effects of using this mechanism such as, will this override the default uncaught exception behavior and remove the default stack trace that gets printed out, which would be very very very bad.
There is no way for the app to know whether it started from a crash; this is indistinguishable from starting the application for the first time after booting. In both cases you will see the onCreate lifecycle event for your activity.
If your application crashes, it stops processing lifecycle events. So no, lifecycle events will not be processed in the same way.
If you want to know whether your application is crashing, perhaps a better approach is to directly record when the application crashes. There are tools like Errbit that help record this when the application is running on general users' devices.
Try to add Exception handling to the code to whatever is causing a crash.
try{
//code causing the crash
} catch (Exception e){
//code to set flags whenever an event causing crash occurs.
}
I have a general error handling solution in my app which should be invoked whenever onError is called. Instead of implementing onError for every subscribe I have done this inside the Application class:
RxJavaPlugins.getInstance().registerErrorHandler(new RxJavaErrorHandler() {
#Override
public void handleError(final Throwable throwable) {
new ErrorHandler().call(throwable);
}
});
However I would like to have the ability to override this by implementing onError, and according to:
https://github.com/ReactiveX/RxJava/wiki/Plugins#rxjavaerrorhandler
This plugin allows you to register a function that will handle errors that are raised by RxJava but that cannot be handled by the ordinary RxJava onError notification process (for instance, if RxJava tries to propagate an error to a subscriber that has not implemented an onError handler).
This should have been the case where I have implemented onError. However when running the code handleError inside RxJavaErrorHandler still gets invoked first, even though I have implemented onError.
Update:
According to zsxwing, the wiki has been updated with the correct description of RxJavaErrorHandler.
What we did at work was make a subclass of observer that has a default implementation of onError that you can still override if need be. Seems to solve your use case.
In my application, i want to set the uncaught exception handler so that i can do stuff in the event that of an unforseen crash. (i want to do stuff like close out sockets, clear notification...etc.)
Thread.currentThread().setDefaultUncaughtExceptionHandler(sDefaultThreadHandler);
where
private static UncaughtExceptionHandler sDefaultThreadHandler = new UncaughtExceptionHandler() {
#Override
public void uncaughtException(Thread thread, Throwable ex) {
// i want ACRA to log here, then clear notifications, close out connections, cancel asynctasks...etc.
// DO NOT REMOVE or else your app will hang when there is a crash.
android.os.Process.killProcess(android.os.Process.myPid());
System.exit(10);
}
};
the problem is, i want ACRA to also report before the process exits. how do i accomplish this?
Oh wait, nvm. i found out that ACRA uses the default exception handler (according to https://github.com/ACRA/acra/blob/master/src/main/java/org/acra/ErrorReporter.java#L201), so that means that if your own thread has a thread exception handler, that will be used first.
Thread.currentThread().setUncaughtExceptionHandler(mYourOwnThreadHandler);
and if you really need to use ACRA, then inside the uncaughtException() method that you override, just delegate it upwards by calling Thread.getDefaultUncaughtExceptionHandler().uncaughtException(thread, ex);
I'm using ACRA in my android application.
But I find that when exception happens within Application#onCreate() method,it only save the report file,rather than raising the dialog to send it.
It there something wrong with my code?
#ReportsCrashes(formKey="")
public class MyAndroidApplication extends Application
{
public void onCreate()
{
ACRAConfiguration config = ACRA.getConfig();
config.setMailTo("test#test.com");
config.setResToastText(R.string.acra_toast);
config.setResDialogText(R.string.acra_dlg_txt);
config.setResDialogCommentPrompt(R.string.acra_dlg_comment_prpmpt);
try
{
config.setMode(ReportingInteractionMode.DIALOG);
}
catch (ACRAConfigurationException e)
{
logger.error("fail to config ACRA", e);
return;
}
ACRA.setConfig(config);
ACRA.init(this);
someMethodThrowsException();
}
}
The onCreate of the Application is called before any Activity is created and does not have a UI, therefore ACRA cannot display a dialog. From the android docs for onCreate
Called when the application is starting, before any activity, service,
or receiver objects (excluding content providers) have been created.
Implementations should be as quick as possible (for example using lazy
initialization of state) since the time spent in this function
directly impacts the performance of starting the first activity,
service, or receiver in a process. If you override this method, be
sure to call super.onCreate().
So, be sure to call super.onCreate(), which your example above is missing, and you should not be doing a whole lot in there that would cause exceptions.
I'm seeing two problems with your code.
You don't call super.onCreate() after initializing ACRA
Your class should have tha annotation #Reportscrashes even if the parameters are set at runtime. Otherwise you will get an error in logcat saying ACRA#init called but no ReportsCrashes annotation on Application
Also, I'm not sure if the Application can show a dialog because it has no UI layout associated with it. Toast reporting works fine if you change both points above.