Necesita manejar una excepción no detectada y enviar un archivo de registro

ACTUALIZACIÓN: consulte la solución “aceptada” a continuación

Cuando mi aplicación crea una excepción no controlada, en lugar de simplemente cancelarla, me gustaría brindar al usuario la oportunidad de enviar un archivo de registro. Me doy cuenta de que hacer más trabajo después de obtener una excepción al azar es arriesgado, pero, oye, lo peor es que la aplicación termina colisionando y el archivo de registro no se envía. Esto está resultando ser más complicado de lo que esperaba 🙂

Lo que funciona: (1) atrapar la excepción no detectada, (2) extraer información de registro y escribir en un archivo.

Lo que aún no funciona: (3) iniciar una actividad para enviar un correo electrónico. En última instancia, tendré otra actividad para pedirle permiso al usuario. Si logro que la actividad del correo electrónico funcione, no espero muchos problemas para el otro.

La clave del problema es que la excepción no controlada queda atrapada en mi clase de aplicación. Como eso no es una actividad, no es obvio cómo iniciar una actividad con Intent.ACTION_SEND. Es decir, normalmente para iniciar una actividad que se llama startActivity y se reanuda con onActivityResult. Estos métodos son compatibles con Activity pero no con Application.

¿Alguna sugerencia sobre cómo hacer esto?

Aquí hay algunos recortes de código como una guía de inicio:

public class MyApplication extends Application { defaultUncaughtHandler = Thread.getDefaultUncaughtExceptionHandler(); public void onCreate () { Thread.setDefaultUncaughtExceptionHandler (new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException (Thread thread, Throwable e) { handleUncaughtException (thread, e); } }); } private void handleUncaughtException (Thread thread, Throwable e) { String fullFileName = extractLogToFile(); // code not shown // The following shows what I'd like, though it won't work like this. Intent intent = new Intent (Intent.ACTION_SEND); intent.setType ("plain/text"); intent.putExtra (Intent.EXTRA_EMAIL, new String[] {"me@mydomain.com"}); intent.putExtra (Intent.EXTRA_SUBJECT, "log file"); intent.putExtra (Intent.EXTRA_STREAM, Uri.parse ("file://" + fullFileName)); startActivityForResult (intent, ACTIVITY_REQUEST_SEND_LOG); } public void onActivityResult (int requestCode, int resultCode, Intent data) { if (requestCode == ACTIVITY_REQUEST_SEND_LOG) System.exit(1); } } 

Aquí está la solución completa (casi: omití el diseño de la interfaz de usuario y el manejo de los botones), derivado de una gran cantidad de experimentación y varias publicaciones de otros relacionadas con problemas que surgieron en el camino.

Hay una serie de cosas que debe hacer:

  1. Maneje uncaughtException en su subclase de Aplicación.
  2. Después de detectar una excepción, comience una nueva actividad para pedirle al usuario que envíe un registro.
  3. Extraiga la información de registro de los archivos de Logcat y escriba en su propio archivo.
  4. Inicie una aplicación de correo electrónico, proporcionando su archivo como un archivo adjunto.
  5. Manifiesto: filtra tu actividad para que sea reconocida por tu manejador de excepciones.
  6. Opcionalmente, configure Proguard para eliminar Log.d () y Log.v ().

Ahora, aquí están los detalles:

(1 y 2) Manejar uncaughtException, iniciar actividad de envío de registro:

 public class MyApplication extends Application { public void onCreate () { // Setup handler for uncaught exceptions. Thread.setDefaultUncaughtExceptionHandler (new Thread.UncaughtExceptionHandler() { @Override public void uncaughtException (Thread thread, Throwable e) { handleUncaughtException (thread, e); } }); } public void handleUncaughtException (Thread thread, Throwable e) { e.printStackTrace(); // not all Android versions will print the stack trace automatically Intent intent = new Intent (); intent.setAction ("com.mydomain.SEND_LOG"); // see step 5. intent.setFlags (Intent.FLAG_ACTIVITY_NEW_TASK); // required when starting from Application startActivity (intent); System.exit(1); // kill off the crashed app } } 

(3) Extract log (pongo esto en mi actividad SendLog):

 private String extractLogToFile() { PackageManager manager = this.getPackageManager(); PackageInfo info = null; try { info = manager.getPackageInfo (this.getPackageName(), 0); } catch (NameNotFoundException e2) { } String model = Build.MODEL; if (!model.startsWith(Build.MANUFACTURER)) model = Build.MANUFACTURER + " " + model; // Make file name - file must be saved to external storage or it wont be readable by // the email app. String path = Environment.getExternalStorageDirectory() + "/" + "MyApp/"; String fullName = path + ; // Extract to file. File file = new File (fullName); InputStreamReader reader = null; FileWriter writer = null; try { // For Android 4.0 and earlier, you will get all app's log output, so filter it to // mostly limit it to your app's output. In later versions, the filtering isn't needed. String cmd = (Build.VERSION.SDK_INT <= Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) ? "logcat -d -v time MyApp:v dalvikvm:v System.err:v *:s" : "logcat -d -v time"; // get input stream Process process = Runtime.getRuntime().exec(cmd); reader = new InputStreamReader (process.getInputStream()); // write output stream writer = new FileWriter (file); writer.write ("Android version: " + Build.VERSION.SDK_INT + "\n"); writer.write ("Device: " + model + "\n"); writer.write ("App version: " + (info == null ? "(null)" : info.versionCode) + "\n"); char[] buffer = new char[10000]; do { int n = reader.read (buffer, 0, buffer.length); if (n == -1) break; writer.write (buffer, 0, n); } while (true); reader.close(); writer.close(); } catch (IOException e) { if (writer != null) try { writer.close(); } catch (IOException e1) { } if (reader != null) try { reader.close(); } catch (IOException e1) { } // You might want to write a failure message to the log here. return null; } return fullName; } 

(4) Inicie una aplicación de correo electrónico (también en mi actividad SendLog):

 private void sendLogFile () { String fullName = extractLogToFile(); if (fullName == null) return; Intent intent = new Intent (Intent.ACTION_SEND); intent.setType ("plain/text"); intent.putExtra (Intent.EXTRA_EMAIL, new String[] {"log@mydomain.com"}); intent.putExtra (Intent.EXTRA_SUBJECT, "MyApp log file"); intent.putExtra (Intent.EXTRA_STREAM, Uri.parse ("file://" + fullName)); intent.putExtra (Intent.EXTRA_TEXT, "Log file attached."); // do this so some email clients don't complain about empty body. startActivity (intent); } 

(3 y 4) Esto es lo que parece SendLog (sin embargo, tendrá que agregar la interfaz de usuario):

 public class SendLog extends Activity implements OnClickListener { @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature (Window.FEATURE_NO_TITLE); // make a dialog without a titlebar setFinishOnTouchOutside (false); // prevent users from dismissing the dialog by tapping outside setContentView (R.layout.send_log); } @Override public void onClick (View v) { // respond to button clicks in your UI } private void sendLogFile () { // method as shown above } private String extractLogToFile() { // method as shown above } } 

(5) Manifiesto:

             

(6) Setup Proguard:

En project.properties, cambie la línea de configuración. Debe especificar "optimizar" o Proguard no eliminará las llamadas Log.v () y Log.d ().

 proguard.config=${sdk.dir}/tools/proguard/proguard-android-optimize.txt:proguard-project.txt 

En proguard-project.txt, agregue lo siguiente. Esto le indica a Proguard que asum que Log.v y Log.d no tienen efectos secundarios (aunque lo hagan porque escriben en los registros) y, por lo tanto, pueden eliminarse durante la optimización:

 -assumenosideeffects class android.util.Log { public static int v(...); public static int d(...); } 

¡Eso es! Si tiene alguna sugerencia para mejorar esto, hágamelo saber y puedo actualizar esto.

Hoy en día existen muchas herramientas de repuesta de locking que hacen esto fácilmente.

  1. crashlytics : una herramienta de informe de fallos , sin cargo pero que ofrece informes básicos Ventajas: Gratis

  2. Gryphonet : una herramienta de informes más avanzada que requiere algún tipo de tarifa. Ventajas: recreación fácil de accidentes, ANR, lentitud …

Si eres un desarrollador privado, te sugiero Crashlytics, pero si se trata de una gran organización, iría por Gryphonet.

¡Buena suerte!

Intente utilizar ACRA en su lugar: maneja el envío del rastreo de la stack, así como toneladas de otra información de depuración útil a su back-end, o al documento de Google Docs que ha configurado.

https://github.com/ACRA/acra

La respuesta de @ PeriHartman funciona bien cuando el hilo de la interfaz de usuario arroja una excepción no detectada. Hice algunas mejoras para cuando la excepción no detectada es lanzada por un hilo no UI.

 public boolean isUIThread(){ return Looper.getMainLooper().getThread() == Thread.currentThread(); } public void handleUncaughtException(Thread thread, Throwable e) { e.printStackTrace(); // not all Android versions will print the stack trace automatically if(isUIThread()) { invokeLogActivity(); }else{ //handle non UI thread throw uncaught exception new Handler(Looper.getMainLooper()).post(new Runnable() { @Override public void run() { invokeLogActivity(); } }); } } private void invokeLogActivity(){ Intent intent = new Intent (); intent.setAction ("com.mydomain.SEND_LOG"); // see step 5. intent.setFlags (Intent.FLAG_ACTIVITY_NEW_TASK); // required when starting from Application startActivity (intent); System.exit(1); // kill off the crashed app } 

Bien explicado. Pero una observación aquí, en lugar de escribir en un archivo usando File Writer y Streaming, hice uso de la opción logcat -f directamente. Aquí está el código

 String[] cmd = new String[] {"logcat","-f",filePath,"-v","time",":D","*:S"}; try { Runtime.getRuntime().exec(cmd); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } 

Esto me ayudó a descargar la última información de buffer. El uso de la transmisión de archivos me dio un problema: no estaba descargando los últimos registros del búfer. Pero de todos modos, esta fue una guía realmente útil. Gracias.