【问题标题】:Show a dialog in `Thread.setDefaultUncaughtExceptionHandler`在 `Thread.setDefaultUncaughtExceptionHandler` 中显示一个对话框
【发布时间】:2012-11-16 12:47:04
【问题描述】:

当我的 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

我现在该怎么办?


更新

日志:

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子类中的异常处理程序,我该如何为其设置活动实例?

【问题讨论】:

  • 当应用程序花费太多时间时会出现该消息(在这种情况下不会引发异常)。我的建议是尝试将繁重的任务转移到 AsyncTask。

标签: android exception-handling android-alertdialog android-dialog


【解决方案1】:

您不能从这里进行任何 UI 操作。只需开始另一个活动/启动屏幕。传递一个额外的意图来表示崩溃并在该活动中显示对话框。

    /*
     * (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 that instantiates an AlertDialog breaks

Toast not showing up in UnCaughtExceptionHandler

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

我是如何做到的:

我有一个扩展 Activity 的 BaseActivity,并在该 Activity 的 onCreate 中设置了 UncaughtExceptionHandler。我所有的活动都扩展了 BaseActivity 而不是 Activity。

按键

  1. 您不能在Application.onCreate 中设置异常处理程序,而是应该创建一个BaseActivity 并将其设置在它的onCreate 方法上。
  2. 启动SplashActivity后,我们应该调用System.exit(0)
  3. 我们不能将错误实例共享给SplashActivity,因为它将被销毁,相反,我们可以传递一些错误消息或将其保存在文件中。

【讨论】:

  • 为什么不能在Application.onCreate中设置异常处理程序?看到这个:intertech.com/Blog/android-handling-the-unexpected
  • System.exit(1)(或任何非零值)会更好,因为它会指示异常退出。
  • @infranoise 从 java 应用程序的角度来看是的,从 android 的角度来看它没有任何效果。您甚至无法访问大多数情况下崩溃的设备。
【解决方案2】:

似乎提供的解决方案不起作用(至少对于 Android 4.0 及更高版本)。对于任何可能感兴趣的人,不可能打开Activity 或涉及某种UI 元素,例如Dialogs。经过一些研究,我意识到您可以提供的最大值是Toast 消息,通知日志传递到服务器。可选地,SharedPreferences 可以用于指示应用程序崩溃,并且在应用程序重新启动时,可以根据SharedPreferences 属性值显示Dialog,并从那里传递先前捕获的异常(显然 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();
    }
}

应用崩溃时可以将异常保存为字符串,使用如下代码sn-p:

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() 来突然关闭VM,不如让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();
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2011-08-05
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多