Ideal Way to Set Global Uncaught Exception Handler in Android

Ideal way to set global uncaught exception Handler in Android

That should be all you need to do. (Make sure you cause the process to halt afterward -- things could be in an uncertain state.)

The first thing to check is whether the Android handler is still getting called. It's possible that your version is being called but failing fatally and the system_server is showing a generic dialog when it sees the process crash.

Add some log messages at the top of your handler to see if it's getting there. Print the result from getDefaultUncaughtExceptionHandler and then throw an uncaught exception to cause a crash. Keep an eye on the logcat output to see what's going on.

Need to handle uncaught exception and send log file

Here's the complete solution (almost: I omitted the UI layout and button handling) - derived from a lot of experimentation and various posts from others related to issues that came up along the way.

There are a number of things you need to do:

  1. Handle uncaughtException in your Application subclass.
  2. After catching an exception, start a new activity to ask the user to send
    a log.
  3. Extract the log info from logcat's files and write to your
    own file.
  4. Start an email app, providing your file as an attachment.
  5. Manifest: filter your activity to be recognized by your exception handler.
  6. Optionally, setup Proguard to strip out Log.d() and Log.v().

Now, here are the details:

(1 & 2) Handle uncaughtException, start send log activity:

public class MyApplication extends Application
{
public void onCreate ()
{
// Setup handler for uncaught exceptions.
Thread.setDefaultUncaughtExceptionHandler (new Thread.UncaughtExceptionHandler()
{
@Override
public void uncaughtException (Thread thread, Throwable e)
{
handleUncaughtException (thread, e);
}
});
}

public void handleUncaughtException (Thread thread, Throwable e)
{
e.printStackTrace(); // not all Android versions will print the stack trace automatically

Intent intent = new Intent ();
intent.setAction ("com.mydomain.SEND_LOG"); // see step 5.
intent.setFlags (Intent.FLAG_ACTIVITY_NEW_TASK); // required when starting from Application
startActivity (intent);

System.exit(1); // kill off the crashed app
}
}

(3) Extract log (I put this an my SendLog Activity):

private String extractLogToFile()
{
PackageManager manager = this.getPackageManager();
PackageInfo info = null;
try {
info = manager.getPackageInfo (this.getPackageName(), 0);
} catch (NameNotFoundException e2) {
}
String model = Build.MODEL;
if (!model.startsWith(Build.MANUFACTURER))
model = Build.MANUFACTURER + " " + model;

// Make file name - file must be saved to external storage or it wont be readable by
// the email app.
String path = Environment.getExternalStorageDirectory() + "/" + "MyApp/";
String fullName = path + <some name>;

// Extract to file.
File file = new File (fullName);
InputStreamReader reader = null;
FileWriter writer = null;
try
{
// For Android 4.0 and earlier, you will get all app's log output, so filter it to
// mostly limit it to your app's output. In later versions, the filtering isn't needed.
String cmd = (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) ?
"logcat -d -v time MyApp:v dalvikvm:v System.err:v *:s" :
"logcat -d -v time";

// get input stream
Process process = Runtime.getRuntime().exec(cmd);
reader = new InputStreamReader (process.getInputStream());

// write output stream
writer = new FileWriter (file);
writer.write ("Android version: " + Build.VERSION.SDK_INT + "\n");
writer.write ("Device: " + model + "\n");
writer.write ("App version: " + (info == null ? "(null)" : info.versionCode) + "\n");

char[] buffer = new char[10000];
do
{
int n = reader.read (buffer, 0, buffer.length);
if (n == -1)
break;
writer.write (buffer, 0, n);
} while (true);

reader.close();
writer.close();
}
catch (IOException e)
{
if (writer != null)
try {
writer.close();
} catch (IOException e1) {
}
if (reader != null)
try {
reader.close();
} catch (IOException e1) {
}

// You might want to write a failure message to the log here.
return null;
}

return fullName;
}

(4) Start an email app (also in my SendLog Activity):

private void sendLogFile ()
{
String fullName = extractLogToFile();
if (fullName == null)
return;

Intent intent = new Intent (Intent.ACTION_SEND);
intent.setType ("plain/text");
intent.putExtra (Intent.EXTRA_EMAIL, new String[] {"log@mydomain.example"});
intent.putExtra (Intent.EXTRA_SUBJECT, "MyApp log file");
intent.putExtra (Intent.EXTRA_STREAM, Uri.parse ("file://" + fullName));
intent.putExtra (Intent.EXTRA_TEXT, "Log file attached."); // do this so some email clients don't complain about empty body.
startActivity (intent);
}

(3 & 4) Here's what SendLog looks like (you'll have to add the UI, though):

public class SendLog extends Activity implements OnClickListener
{
@Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
requestWindowFeature (Window.FEATURE_NO_TITLE); // make a dialog without a titlebar
setFinishOnTouchOutside (false); // prevent users from dismissing the dialog by tapping outside
setContentView (R.layout.send_log);
}

@Override
public void onClick (View v)
{
// respond to button clicks in your UI
}

private void sendLogFile ()
{
// method as shown above
}

private String extractLogToFile()
{
// method as shown above
}
}

(5) Manifest:

<manifest xmlns:android="http://schemas.android.com/apk/res/android" ... >
<!-- needed for Android 4.0.x and eariler -->
<uses-permission android:name="android.permission.READ_LOGS" />

<application ... >
<activity
android:name="com.mydomain.SendLog"
android:theme="@android:style/Theme.Dialog"
android:textAppearance="@android:style/TextAppearance.Large"
android:windowSoftInputMode="stateHidden">
<intent-filter>
<action android:name="com.mydomain.SEND_LOG" />
<category android:name="android.intent.category.DEFAULT" />
</intent-filter>
</activity>
</application>
</manifest>

(6) Setup Proguard:

In project.properties, change the config line. You must specify "optimize" or Proguard will not remove Log.v() and Log.d() calls.

proguard.config=${sdk.dir}/tools/proguard/proguard-android-optimize.txt:proguard-project.txt

In proguard-project.txt, add the following. This tell Proguard to assume Log.v and Log.d have no side effects (even though they do since they write to the logs) and thus can be removed during optimization:

-assumenosideeffects class android.util.Log {
public static int v(...);
public static int d(...);
}

That's it! If you have any suggestions for improvements to this, please let me know and I may update this.

How do I handle uncaught exceptions and then delegate handling back to the system?

The uncaught exception can be delegated back to system by storing the old exception handler and passing uncaught exceptions to it.

First create an Application class as below:

public class Controller extends Application {

private static Thread.UncaughtExceptionHandler defaultHandler;

@Override
public void onCreate() {
super.onCreate();
if (defaultHandler == null) {
defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
}
Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread t, Throwable e) {
FirebaseCrash.report(e); //or whatever
defaultHandler.uncaughtException(t, e); //this will show crash dialog.
}
});
}

}

Then use this class as application in the manifest as:

<application
android:name=".Controller"
... />

Android unhandled exception

Those are two separate utilities that are only tangentially related.

The UncaughtExceptionHandler is for any Exception percolated up through the system that isn't caught. Uncaught exceptions are what crashes your app, so people often use the default UncaughtExceptionHandler for crash tracking.

The RxJavaErrorHandler is just for RxJava logging purposes. Every time onError is called for any subscription, it calls this method. It calls it whether there's already onError handling or not, nor should you use it for any sort of flow control, so it's really only useful for logging.

It sounds like what you want is to use the UncaughtExceptionHandler. It will catch anything that the app didn't handle itself (including RxJava).

Catch all possible android exception globally and reload application

In your onCreate

Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
@Override
public void uncaughtException(Thread paramThread, Throwable paramThrowable) {
//Catch your exception
// Without System.exit() this will not work.
System.exit(2);
}
});

Use UncaughtExceptionHandler to do what Android usually does plus extra functionality I need

I had a similar problem when tracking to GoogleAnalytics - most Exceptions got lost when i tried to report them in the default handler. So i cached them and reported them on the next startup.

The second trick (and this hopefully answers your question) to let the Application crash as it should is to store the 'old' DefaultUncaughtExceptionHandler and pass the exception to it.

The onCreate method below is the one of my Application class

@Override
public void onCreate()
{
super.onCreate();
trackCachedException();
setupExceptionHandling();
}


private void setupExceptionHandling()
{
_systemExceptionHandler = Thread.getDefaultUncaughtExceptionHandler();
Thread.UncaughtExceptionHandler myHandler = new Thread.UncaughtExceptionHandler()
{

@Override
public void uncaughtException(Thread thread, Throwable ex)
{
cacheException(thread.getName(), ex);
if (_systemExceptionHandler != null)
{
_systemExceptionHandler.uncaughtException(thread, ex);
}
}
};
// Make myHandler the new default uncaught exception handler.
Thread.setDefaultUncaughtExceptionHandler(myHandler);
}

Java uncaught global exception handler for ALL threads

Use Thread.setDefaultUncaughtExceptionHandler. As the javadoc says:

"By setting the default uncaught exception handler, an application can change the way in which uncaught exceptions are handled (such as logging to a specific device, or file) for those threads that would already accept whatever "default" behavior the system provided."

Obviously, if a Thread already has a (non-default) handler, then it won't be affected by a change to the default behavior.


I think it should be possible to get all threads in some way, then bind them the exception handler ?

That's not necessary ... unless you want to change a thread's non-default handler. If you really need to do that, you can find all threads by traversing the application's ThreadGroup hierarchy. (Unless your app is sandboxed ...)

Edit

The thread list of the running app can be found using this answer :

Set<Thread> threadSet = Thread.getAllStackTraces().keySet();


Related Topics



Leave a reply



Submit