Show a dialog in `Thread.setDefaultUncaughtExceptionHandler`

偶尔善良 提交于 2019-12-03 06:50:09

问题


When my android application throw an exception, I want to show a custom dialog to tell user there is something wrong happened, so I use Thread.setDefaultUncaughtExceptionHandler to set a global exception handler:

public class MyApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();

        Thread.setDefaultUncaughtExceptionHandler(new Thread.UncaughtExceptionHandler() {
            @Override
            public void uncaughtException(Thread thread, final Throwable ex) {
                AlertDialog.Builder builder = new AlertDialog.Builder(getApplicationContext());
                builder.setTitle("There is something wrong")
                        .setMessage("Application will exit:" + ex.toString())
                        .setPositiveButton("OK", new DialogInterface.OnClickListener() {

                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                // throw it again
                                throw (RuntimeException) ex;
                            }
                        })
                        .show();
            }
        });
    }

}

But I found it there is any exception thrown, the AlertDialog won't show, instead, the application blocks and after a while, it will show a system dialog:

X app is not responding. Would you like to close it?
Wait  |  OK

What should I do now?


UPDATE

The log:

11-16 12:54:16.017: WARN/WindowManager(90): Attempted to add window with non-application token WindowToken{b38bb6a8 token=null}.  Aborting.

It seems the error is coming from new AlertDialog.Builder(getApplicationContext());

But this is an exception handler in Application subclass, how can I set an activity instance to it?


回答1:


You cannot do any UI operation from here. Just start another activity/ splash screen. Pass an intent extra to denote crash and show dialog in that activity.

    /*
     * (non-Javadoc)
     * 
     * @see
     * java.lang.Thread.UncaughtExceptionHandler#uncaughtException(java.
     * lang.Thread, java.lang.Throwable)
     */
    @Override
    public void uncaughtException(Thread t, final Throwable e) {
        StackTraceElement[] arr = e.getStackTrace();
        final StringBuffer report = new StringBuffer(e.toString());
        final String lineSeperator = "-------------------------------\n\n";
        report.append(DOUBLE_LINE_SEP);
        report.append("--------- Stack trace ---------\n\n");
        for (int i = 0; i < arr.length; i++) {
            report.append( "    ");
            report.append(arr[i].toString());
            report.append(SINGLE_LINE_SEP);
        }
        report.append(lineSeperator);
        // If the exception was thrown in a background thread inside
        // AsyncTask, then the actual exception can be found with getCause
        report.append("--------- Cause ---------\n\n");
        Throwable cause = e.getCause();
        if (cause != null) {
            report.append(cause.toString());
            report.append(DOUBLE_LINE_SEP);
            arr = cause.getStackTrace();
            for (int i = 0; i < arr.length; i++) {
                report.append("    ");
                report.append(arr[i].toString());
                report.append(SINGLE_LINE_SEP);
            }
        }
        // Getting the Device brand,model and sdk verion details.
        report.append(lineSeperator);
        report.append("--------- Device ---------\n\n");
        report.append("Brand: ");
        report.append(Build.BRAND);
        report.append(SINGLE_LINE_SEP);
        report.append("Device: ");
        report.append(Build.DEVICE);
        report.append(SINGLE_LINE_SEP);
        report.append("Model: ");
        report.append(Build.MODEL);
        report.append(SINGLE_LINE_SEP);
        report.append("Id: ");
        report.append(Build.ID);
        report.append(SINGLE_LINE_SEP);
        report.append("Product: ");
        report.append(Build.PRODUCT);
        report.append(SINGLE_LINE_SEP);
        report.append(lineSeperator);
        report.append("--------- Firmware ---------\n\n");
        report.append("SDK: ");
        report.append(Build.VERSION.SDK);
        report.append(SINGLE_LINE_SEP);
        report.append("Release: ");
        report.append(Build.VERSION.RELEASE);
        report.append(SINGLE_LINE_SEP);
        report.append("Incremental: ");
        report.append(Build.VERSION.INCREMENTAL);
        report.append(SINGLE_LINE_SEP);
        report.append(lineSeperator);

        Log.e("Report ::", report.toString());
        Intent crashedIntent = new Intent(BaseActivity.this, SplashActivity.class);
        crashedIntent.putExtra(EXTRA_CRASHED_FLAG,  "Unexpected Error occurred.");
        crashedIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
        crashedIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
        startActivity(crashedIntent);

        System.exit(0);
        // If you don't kill the VM here the app goes into limbo

    }

Also see:

Android UncaughtExceptionHandler that instantiates an AlertDialog breaks

Toast not showing up in UnCaughtExceptionHandler

How to start activity from UncaughtExceptionHandler if this is main thread crashed?

How i do it:

I have a BaseActivity which extends Activity, and in onCreate of the activity I set the UncaughtExceptionHandler. All my activities extend the BaseActivity instead of Activity.

Keys

  1. You can't set the exception handler in Application.onCreate, instead, you should create a BaseActivity and set it on the onCreate method of it.
  2. After starting the SplashActivity, we should call System.exit(0)
  3. We can't hold the error instance to share it to SplashActivity, since it will be destroyed, instead, we can pass some error message or persist it in file.



回答2:


It seems that the solution provided does not work (at least for Android 4.0 and above). For anyone who might be interested, opening an Activity or involving some sort of UI elements such as Dialogs is not possible. After some research i realized that the maximum you can provide is a Toast message notifying the log delivery to the server. Optionally a SharedPreferences can be used to indicate the application crash, and on the application restart, a Dialog can be displayed based on the SharedPreferences attribute value and from there deliver the previously caught exception (apparently Accra uses the same approach):

public class FirstAc extends Activity{
    @Override
    protected void onCreate(Bundle savedInstanceState) {

        super.onCreate(savedInstanceState);
        setContentView();

        sharedPrefValue = pref.getBoolean("DID_APP_CRASH", false);
        if(sharedPrefValue)
           dialog.show();
    }
}

The exception can be saved as a string when the app crashed with the following code snippet:

StringWriter sw = new StringWriter();
PrintWriter pw  = new PrintWriter(sw);
exception.printStackTrace(pw);
String stStr = sw.toString();
prefEditor.putString("EXCEPTION_CAUGHT", stStr);

To sum up, in order to deliver the uncaught exception to a remote server create a custom UncaughtExceptionHandler and most importantly keep a reference to the default UncaughtExceptionHandler. Instead of abruptly shutting down the VM by calling System.exit() it is more reasonable to let Android handle the exception after the custom operations are performed. I prefer set the exception handler on the Application side:

public class CustomApp extends Application {

    @Override
    public void onCreate() {

        super.onCreate();
        Thread.setDefaultUncaughtExceptionHandler(new CustomExceptionHandler(this));
    }
}

Within the CustomExceptionHandler after executing the custom behaviour let Android handle the exception in the default manner:

public class CustomExceptionHandler implements UncaughtExceptionHandler {

    private CustomApp                _app;
    private UncaughtExceptionHandler _defaultEH; 

    public YolbilExceptionHandler(YolbilApp ac){

        _defaultEH = Thread.getDefaultUncaughtExceptionHandler();
        _app = ac;
    }

    @Override
    public void uncaughtException(Thread thread, final Throwable ex) {

        Toast.makeText(_app, "Delivering log...", Toast.LENGTH_LONG).show();
        // obtain the Exception info as a String
        StringWriter sw = new StringWriter();
        PrintWriter pw  = new PrintWriter(sw);
        ex.printStackTrace(pw);
        String exStr    = sw.toString();
        ExceptionServer.getInstance().deliverMessageAsync(exStr, _app);
        _defaultEH.uncaughtException(thread, ex);
    }

}

And here is a sample how to deliver asynchronously a message to server:

public void deliverMessageAsync(final String msg, final YolbilApp app){

    new Thread(new Runnable() {

        @Override
        public void run() {

            HttpClient httpclient = new DefaultHttpClient();
            HttpPost httppost = new HttpPost(SERVER_ADDR); 
            try {
                Looper.prepare();
                Toast.makeText(app, R.string.msg_delivering_log, Toast.LENGTH_LONG).show();
                httppost.setHeader("Content-Type","application/x-www-form-urlencoded;charset=UTF-8");                   
                httpclient.execute(httppost); 
                Toast.makeText(app, "Log delivered ...", Toast.LENGTH_SHORT).show();
                Looper.loop();
            } catch (ClientProtocolException e) {
                e.printStackTrace();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }).start();
}


来源:https://stackoverflow.com/questions/13416879/show-a-dialog-in-thread-setdefaultuncaughtexceptionhandler

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!