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:
- Handle uncaughtException in your Application subclass.
- After catching an exception, start a new activity to ask the user to send
a log. - Extract the log info from logcat's files and write to your
own file. - Start an email app, providing your file as an attachment.
- Manifest: filter your activity to be recognized by your exception handler.
- Optionally, setup Proguard to strip out
Log.d()
andLog.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
Best Way to Work with Dates in Android SQLite
Android Spinner with Multiple Choice
How to Handle an Asynctask During Screen Rotation
Lollipop's Backgroundtint Has No Effect on a Button
How to Determine If a Firebase User Is Signed in Using Facebook Authentication
Deprecated Gradle Features Were Used in This Build, Making It Incompatible with Gradle 5.0
Example of Code to Implement a PDF Reader
Enabling Proguard in Eclipse for Android
How to Capture the Android Device Screen Content
Android. How Does Notifydatasetchanged() Method and Listviews Work
How to Add a Textview to a Linearlayout Dynamically in Android
Meaning of Choreographer Messages in Logcat
Android Popupwindow with Tooltip Arrow
Android: Customize Application's Menu (E.G Background Color)