Подтвердить что ты не робот

Показать диалог в `Thread.setDefaultUncaughtExceptionHandler`

Когда мое приложение для Android выдает исключение, я хочу показать пользовательское диалоговое окно, чтобы сообщить пользователю, что что-то случилось неправильно, поэтому я использую Thread.setDefaultUncaughtExceptionHandler для установки глобального обработчика исключений:

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();
            }
        });
    }

}

Но я обнаружил, что есть любое исключение, AlertDialog не будет отображать вместо этого блоки приложений и через некоторое время отобразит системный диалог:

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

Что мне теперь делать?


UPDATE

Журнал:

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

Кажется, что ошибка исходит от new AlertDialog.Builder(getApplicationContext());

Но это обработчик исключений в подклассе Application, как я могу установить для него экземпляр действия?

4b9b3361

Ответ 1

Вы не можете выполнять какую-либо операцию пользовательского интерфейса здесь. Просто запустите другой экран активности/заставки. Передайте намерение дополнительно, чтобы обозначить крушение и показать диалог в этом действии.

    /*
     * (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

    }

Также смотрите:

Android UncaughtExceptionHandler, создающий разрывы AlertDialog

Тост не отображается в UnCaughtExceptionHandler

Как начать работу с UncaughtExceptionHandler, если это основной поток разбился?

Как я это делаю:

У меня есть BaseActivity, который расширяет Activity, а в onCreate активности я устанавливаю UncaughtExceptionHandler. Все мои действия расширяют BaseActivity вместо Activity.

Клавиши

  • Вы не можете установить обработчик исключений в Application.onCreate, вместо этого вы должны создать BaseActivity и установить его в методе onCreate.
  • После запуска SplashActivity мы должны позвонить System.exit(0)
  • Мы не можем содержать экземпляр ошибки, чтобы передать его в SplashActivity, так как он будет уничтожен, вместо этого мы можем передать некоторое сообщение об ошибке или сохранить его в файле.

Ответ 2

Кажется, что предоставленное решение не работает (по крайней мере, для Android 4.0 и выше). Для всех, кто может быть заинтересован, открытие Activity или использование каких-либо элементов UI, таких как Dialogs, невозможно. После некоторых исследований я понял, что максимум, который вы можете предоставить, - это сообщение Toast, сообщающее о доставке журнала на сервер. Опционально SharedPreferences может использоваться для указания сбоя приложения и при перезапуске приложения Dialog может отображаться на основе значения атрибута SharedPreferences и оттуда доставлять ранее пойманное исключение (очевидно, Accra использует тот же подход ):

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();
    }
}

Исключение можно сохранить как строку, когда приложение разбилось со следующим фрагментом кода:

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

Подводя итог, чтобы доставить неперехваченное исключение на удаленный сервер, создайте пользовательский UncaughtExceptionHandler и, самое главное, сохраните ссылку на значение по умолчанию UncaughtExceptionHandler. Вместо того, чтобы резко закрыть виртуальную машину, вызвав System.exit(), более разумно позволить Android обрабатывать исключение после выполнения пользовательских операций. Я предпочитаю установить обработчик исключений со стороны Application:

public class CustomApp extends Application {

    @Override
    public void onCreate() {

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

Внутри CustomExceptionHandler после выполнения пользовательского поведения Android обрабатывает исключение по умолчанию:

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);
    }

}

И вот пример, как отправить асинхронно сообщение серверу:

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();
}