13

Quando la mia applicazione Android un'eccezione, voglio mostrare una finestra personalizzata di dire all'utente che c'è qualcosa di sbagliato è successo, per cui uso Thread.setDefaultUncaughtExceptionHandler per impostare un gestore di eccezioni globale:Mostra una finestra di dialogo in `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(); 
      } 
     }); 
    } 

} 

Ma ho scoperto che c'è qualche eccezione generata, la AlertDialog non mostrerà, invece, i blocchi applicativi e dopo un po ', si mostreranno una finestra di dialogo di sistema:

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

Cosa devo fare ora ?


UPDATE

Il registro:

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

Sembra l'errore viene da new AlertDialog.Builder(getApplicationContext());

Ma questo è un gestore di eccezioni in Application sottoclasse, come posso impostare un'istanza di attività ad esso?

+0

incollare il logcat troppo – waqaslam

+0

appare Quel messaggio quando l'appli richiede troppo tempo (non c'è eccezione in questo caso). La mia raccomandazione è di provare a spostare le tue attività pesanti su un AsyncTask. – richardtz

risposta

28

Non è possibile eseguire alcuna operazione dell'interfaccia utente da qui. Basta avviare un'altra attività/schermata iniziale. Passa un'intenzione extra per denotare crash e mostrare il dialogo in quella attività.

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

    } 

vedi anche:

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?

come lo faccio:

Ho una BaseActivity che estende Activity, e in onCreate dell'attività ho impostato UncaughtExceptionHandler. Tutte le mie attività estendono BaseActivity anziché Activity.

Keys

  1. Non è possibile impostare il gestore di eccezioni in Application.onCreate, invece, è necessario creare un BaseActivity e impostarlo sul metodo di esso onCreate.
  2. Dopo aver avviato lo SplashActivity, dovremmo chiamare System.exit(0)
  3. Non possiamo tenere l'istanza errore di condividerlo a SplashActivity, dal momento che sarà distrutto, invece, siamo in grado di passare qualche messaggio di errore o persistono nel file.
+0

Perché non è possibile impostare il gestore di eccezioni in Application.onCreate? vedere questo: http://www.intertech.com/Blog/android-handling-the-unexpected/ – Rotem

+0

'System.exit (1)' (o qualsiasi valore diverso da zero) sarà migliore in quanto indicherà un'uscita anomala. – infranoise

+0

@infranoise da una prospettiva di applicazione java sì dalla prospettiva di Android non ha alcun effetto. Non avrai nemmeno accesso al dispositivo dove si blocca il più delle volte. – Atrix1987

4

Sembra che la soluzione fornita non funzioni (almeno per Android 4.0 e versioni successive). Per chiunque sia interessato, l'apertura di Activity o di una specie di elemento UI come Dialogs non è possibile. Dopo alcune ricerche mi sono reso conto che il massimo che puoi fornire è un messaggio Toast che notifica la consegna del registro al server.Opzionalmente un SharedPreferences può essere utilizzato per indicare la crash dell'applicazione, e il riavvio dell'applicazione, un Dialog possono essere visualizzati in base al valore dell'attributo SharedPreferences e da lì consegnare l'eccezione precedentemente catturati (a quanto pare Accra utilizza lo stesso approccio):

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

l'eccezione possono essere salvati come una stringa quando l'applicazione si è schiantato con il seguente frammento di codice:

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

in sintesi, al fine di fornire l'eccezione non rilevata a un server remoto creare un costume UncaughtExceptionHandler e, soprattutto, mantenere un riferimento al valore predefinito UncaughtExceptionHandler. Invece di arrestare bruscamente la VM chiamando System.exit(), è più ragionevole lasciare che Android gestisca l'eccezione dopo l'esecuzione delle operazioni personalizzate. Io preferisco impostato il gestore di eccezioni sul lato Application:

public class CustomApp extends Application { 

    @Override 
    public void onCreate() { 

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

All'interno del CustomExceptionHandler dopo l'esecuzione del comportamento personalizzato lasciare Android gestisce l'eccezione nel modo predefinito:

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

} 

Ed ecco un esempio di come fornire in modo asincrono un messaggio al 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(); 
}