Mostrar un diálogo en `Thread.setDefaultUncaughtExceptionHandler`
Cuando mi aplicación androide lance una excepción, quiero mostrar un diálogo personalizado para decirle al usuario que hay algo mal sucedido, así que utilizo Thread.setDefaultUncaughtExceptionHandler
para establecer un manejador de excepciones global:
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(); } }); } }
Pero he encontrado que hay alguna excepción lanzada, el AlertDialog
no se mostrará, en su lugar, los bloques de aplicación y después de un tiempo, se mostrará un diálogo del sistema:
- Múltiples cuadros de diálogo OK / Cancelar, cómo decir, en onClick (), ¿qué diálogo?
- DialogFragment mostrado desde onContextItemSelected no sobrevive onPause / onResume
- ¿Qué hay de malo con el tema del diálogo ICS Holo?
- ¿Cómo administrar los diálogos de pantalla completa en Android?
- Actividad de Android como diálogo
X app is not responding. Would you like to close it? Wait | OK
¿Qué debería hacer ahora?
ACTUALIZAR
El registro:
11-16 12:54:16.017: WARN/WindowManager(90): Attempted to add window with non-application token WindowToken{b38bb6a8 token=null}. Aborting.
Parece que el error viene de new AlertDialog.Builder(getApplicationContext());
Pero esto es un manejador de excepciones en la subclase de Application
, ¿cómo puedo establecer una instancia de actividad para él?
- Cómo eliminar el fondo oscuro transparente fuera del cuadro de diálogo
- Cambiar el fondo de ProgressDialog
- Agregar botón positivo / negativo al cuadro de diálogo de DialogFragment
- ¿Cómo mostrar Cuadro de diálogo de la clase que extiende Aplicación en android?
- Android: Mostrar diálogo personalizado en el centro del contenedor
- Cómo cancelar o descartar un cuadro de diálogo personalizado en su método onCreate?
- ¿Cómo puedo añadir un tercer botón a un cuadro de diálogo de alerta de Android?
- ¿Cómo se crea una actividad transparente que puede superponerse a la pantalla de inicio, pero no se descarta cuando se presiona el botón de inicio o de retroceso?
No se puede hacer ninguna operación de interfaz de usuario desde aquí. Simplemente inicie otra actividad / pantalla de bienvenida. Pase un intento extra para denotar crash y mostrar diálogo en esa actividad.
/* * (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 }
Ver también:
Android UncaughtExceptionHandler que instancia un AlertDialog se rompe
Tostadas no aparecen en UnCaughtExceptionHandler
Cómo iniciar la actividad de UncaughtExceptionHandler si este es el hilo principal se estrelló?
Cómo lo hago:
Tengo un BaseActivity que extiende la actividad, y en el onCreate de la actividad fijé el UncaughtExceptionHandler. Todas mis actividades amplían la BaseActivity en lugar de Actividad.
Llaves
- No puede establecer el manejador de excepciones en
Application.onCreate
, sino que debe crear unaBaseActivity
y establecerla en el métodoonCreate
de la misma. - Después de iniciar SplashActivity, debemos llamar a
System.exit(0)
- No podemos mantener la instancia de error para compartirla con
SplashActivity
, ya que será destruida, en su lugar, podemos pasar algún mensaje de error o persistir en el archivo.
Parece que la solución proporcionada no funciona (al menos para Android 4.0 y superiores). Para cualquier persona que pueda estar interesado, la apertura de una Activity
o la participación de algún tipo de elementos de la UI
de UI
, tales como Dialogs
no es posible. Después de algunas investigaciones me di cuenta de que el máximo que puede proporcionar es un mensaje Toast
notificando la entrega de registro al servidor. Opcionalmente, se puede usar SharedPreferences
para indicar el bloqueo de la aplicación y, en el reinicio de la aplicación, se puede mostrar un Dialog
basado en el valor del atributo SharedPreferences
y desde allí entregar la excepción previamente detectada (aparentemente, Accra utiliza el mismo enfoque):
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(); } }
La excepción se puede guardar como una cadena cuando la aplicación se estrelló con el fragmento de código siguiente:
StringWriter sw = new StringWriter(); PrintWriter pw = new PrintWriter(sw); exception.printStackTrace(pw); String stStr = sw.toString(); prefEditor.putString("EXCEPTION_CAUGHT", stStr);
Para resumir, para entregar la excepción no captada a un servidor remoto crear un UncaughtExceptionHandler
personalizado y lo más importante mantener una referencia a la UncaughtExceptionHandler
predeterminado. En lugar de cerrar de forma abrupta la VM llamando a System.exit()
, es más razonable permitir que Android maneje la excepción después de realizar las operaciones personalizadas. Prefiero establecer el controlador de excepciones en el lado de la Application
:
public class CustomApp extends Application { @Override public void onCreate() { super.onCreate(); Thread.setDefaultUncaughtExceptionHandler(new CustomExceptionHandler(this)); } }
Dentro de CustomExceptionHandler
después de ejecutar el comportamiento personalizado, deje que Android maneje la excepción de la manera predeterminada:
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); } }
Y aquí está un ejemplo de cómo entregar de forma asíncrona un mensaje al servidor:
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(); }