So when your app crashes on your users' phones, it's a good idea to collect the details of the crash and prompt the user to send them to you. I recently wrote one such scheme for ReaderScope and it has been very helpful. Here is how I am doing it.
I found the code snippets after googling around. Mainly from this stackoverflow article. This morning however, I added few more bits to work well with exceptions that take place in background threads.
The way to do this is to implement the Thread.UncaughtExceptionHandler interface and pass it to Thread.setDefaultUncaughtExceptionHandler() at the beginning of your Activity's onCreate(). Here is the implementation class TopExceptionHandler.
package com.altcanvas.readerscope; import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Context; import java.io.*; public class TopExceptionHandler implements Thread.UncaughtExceptionHandler { private Thread.UncaughtExceptionHandler defaultUEH; private Activity app = null; public TopExceptionHandler(Activity app) { this.defaultUEH = Thread.getDefaultUncaughtExceptionHandler(); this.app = app; } public void uncaughtException(Thread t, Throwable e) { StackTraceElement[] arr = e.getStackTrace(); String report = e.toString()+"\n\n"; report += "--------- Stack trace ---------\n\n"; for (int i=0; i<arr.length; i++) { report += " "+arr[i].toString()+"\n"; } report += "-------------------------------\n\n"; // If the exception was thrown in a background thread inside // AsyncTask, then the actual exception can be found with getCause report += "--------- Cause ---------\n\n"; Throwable cause = e.getCause(); if(cause != null) { report += cause.toString() + "\n\n"; arr = cause.getStackTrace(); for (int i=0; i<arr.length; i++) { report += " "+arr[i].toString()+"\n"; } } report += "-------------------------------\n\n"; try { FileOutputStream trace = app.openFileOutput( "stack.trace", Context.MODE_PRIVATE); trace.write(report.getBytes()); trace.close(); } catch(IOException ioe) { // ... } defaultUEH.uncaughtException(t, e); } }
Note that we are not consuming the exception. We let the Android framework's defaultUEH to handle it. If you don't do this, bad things may happen.
@Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); Thread.setDefaultUncaughtExceptionHandler(new TopExceptionHandler(this));
...
This exception handler saves the trace in a file. When ReaderScope restarts next time, it detects the file and prompts the user if he/she wants to email it to the developer.
Emailing the stack trace with user's consent is so much easier on Android, than setting up a crash report server. Somewhere near the beginning of your activity just check if stack.trace file is present. If so, execute following code to pack it in an email.
try { BufferedReader reader = new BufferedReader( new InputStreamReader(ReaderScopeActivity.this .openFileInput("stack.trace"))); while((line = reader.readLine()) != null) { trace += line+"\n"; } } catch(FileNotFoundException fnfe) { // ... } catch(IOException ioe) { // ... } Intent sendIntent = new Intent(Intent.ACTION_SEND); String subject = "Error report"; String body = "Mail this to readerscope@altcanvas.com: "+ "\n\n"+ trace+ "\n\n"; sendIntent.putExtra(Intent.EXTRA_EMAIL, new String[] {"readerscope@altcanvas.com"}); sendIntent.putExtra(Intent.EXTRA_TEXT, body); sendIntent.putExtra(Intent.EXTRA_SUBJECT, subject); sendIntent.setType("message/rfc822"); ReaderScopeActivity.this.startActivity( Intent.createChooser(sendIntent, "Title:")); ReaderScopeActivity.this.deleteFile("stack.trace");
Hope this helps! If you have suggestions for improvements, please do let me know in comments.
code syntax highlighting by GVIM, using the "delek" theme.
Ads:
* Professional Android 2 Application Development
* Android Application Development: Programming with the Google SDK
* The Busy Coder's Guide to Advanced Android Development
Ads:
* Professional Android 2 Application Development
* Android Application Development: Programming with the Google SDK
* The Busy Coder's Guide to Advanced Android Development
14 comments:
Hi Jayesh.
Great article!
Just I wanted to know. The "Thread.setDefaultUncaughtExceptionHandler..." line have to be placed at the beggining of every Activity you have in the App?
> The "Thread.setDefaultUncaughtExceptionHandler..." line have to be placed at the beggining of every Activity you have in the App?
I think this call directly affects the VM. (I recall reading it somewhere, but can't find where). What that means is, any activity or service in your app that runs after this call, will have your default uncaught exception handler to fall back to, provided your app's process (and hence the VM running inside it) is not killed by the framework.
If your app has multiple entry points, i.e. if you have two activities, but user may use only one of them and never invoke the other, then you will have to do this registration in both activities. However in that scenario, to not leak the TopExceptionHandler instance, you may want to make it singleton.
Yes, I have set it in the main Activity and works for all App. I have realized something, I dont know if the same happens to you:
When an exception is uncaught and goes trough your handler, if a second uncaught exception happens in your app, this second one is not get by the handler...
Its like "Ok, this thread is dead, and no longer used"...
Hi Jayesh,
Great Job, really useful.
May be of benefit to add the following to the report as it's often helpful to know:
report += "-------------------------------\n\n";
report += "--------- Device ---------\n\n";
report += "Brand: " + Build.BRAND + "\n";
report += "Device: " + Build.DEVICE + "\n";
report += "Model: " + Build.MODEL + "\n";
report += "Id: " + Build.ID + "\n";
report += "Product: " + Build.PRODUCT + "\n";
report += "-------------------------------\n\n";
report += "--------- Firmware ---------\n\n";
report += "SDK: " + Build.VERSION.SDK + "\n";
report += "Release: " + Build.VERSION.RELEASE + "\n";
report += "Incremental: " + Build.VERSION.INCREMENTAL + "\n";
report += "-------------------------------\n\n";
There is one loop-hole with the solution, that is if you have an exception with your app when it is being created (i.e. in onCreate or onStart, etc), the email intent will not get sent!
Can you please confirm this? Is there any way around it?
Cheers,
Don
> When an exception is uncaught and goes trough your handler, if a second uncaught exception happens in your app, this second one is not get by the handler...
That's true. Once you get report of first exception, fix it, release new version. The other exception might be reported next time.
Ideally we should test our app against all of the crash scenarios before releasing it. But practially that's not possible. This crash report is a safety net for that. You will be saved if one crash scenario slips your testing, but you shouldn't rely on it to catch all your crashes.
> There is one loop-hole with the solution, that is if you have an exception with your app when it is being created (i.e. in onCreate or onStart, etc), the email intent will not get sent!
That's true. The check if stack.trace exists or not should be done as early in onCreate() as possible. And whatever little code that runs before that check can be well tested to not have any bug leading to crash. I think that's not very difficult to ensure.
I've found a 'hack' to get around this:
@Override
public void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);
this.gEH = new GlobalExceptionHandler(this);
//Check if there is a crash report and if so, send email intent.
Boolean hasCrashed = this.gEH.checkForCrashAndHandle();
if(hasCrashed == true)
{
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
Thread.setDefaultUncaughtExceptionHandler(this.gEH);
//Cause exception
int var = 10 / 0;
// Do Ui Stuff
...
...
}
If I don't put in the Thread sleep, then the exception will cause the UI thread to stop, resulting in the Intent to send the email not being carried out. Even though I know that my uncaughtException handler is being called and the intent for the email started.
Not a neat solution, but as Intents can't block the UI thread and I don't think you can start an 'independent' Intent, then I can't think of any other possible solutions.
Don
I loved your article. I have used something similar in my apps and have had wonderful results. Thanks for sharing!
My tweak to this method of error reporting was to make the handler send out the email intent immediately after writing the file to disk. The trick is to make the class implement Runnable and put the code necessary to launch the email intent inside a run() method. Then you call runOnUiThread(this) and while the dialog with the Force Close button is being shown to the user, 99% of the time the email app will launch behind it ready to send the crash report.
e.g.
public class pmreHandler implements UncaughtExceptionHandler, Runnable {
public pmreHandler(Activity aApp) {
mDefaultUEH = Thread.getDefaultUncaughtExceptionHandler();
mApp = aApp;
}
@Override
public void uncaughtException(Thread t, Throwable e) {
submit(e);
//do not forget to pass this exception through up the chain
mDefaultUEH.uncaughtException(t,e);
}
@Override
public void run() {
sendDebugReportToAuthor();
}
public void submit(Throwable e) {
String theErrReport = getDebugReport(e);
saveDebugReport(theErrReport);
//try to send file contents via email (need to do so via the UI thread)
mApp.runOnUiThread(this);
}
}
You can check out my full source code (with link back to here) at
http://blog.blackmoonit.com/2010/02/android-postmortem-reports-via-email.html
Latest reviews and news about android phones, free droid app and android mobile.
You could use BugSense, it's really easy to setup and the reports are stunning.
How can i kill the thread because when it gives the exception and when it go back to class files it hangs my application what can i do ?
but how can i get specified exception name..?
like java.lang.ArrayOutOfBounsException
hey,
in above comments I see a statement : " When an exception is uncaught and goes trough your handler, if a second uncaught exception happens in your app, this second one is not get by the handler..."
Is there a way to reproduce this? Basically I want to see the case when exception is not caught by the handler. This is important for robustness in some cases.
Good one
but exception should be send from background instead of user configuration email id
Post a Comment