web-dev-qa-db-fra.com

Besoin de gérer une exception non capturée et d'envoyer un fichier journal

MISE À JOUR: Voir la solution "acceptée" ci-dessous

Lorsque mon application crée une exception non gérée, plutôt que de simplement terminer, j'aimerais tout d'abord donner à l'utilisateur la possibilité d'envoyer un fichier journal. Je me rends compte que faire plus de travail après avoir obtenu une exception aléatoire est risqué, mais le pire est que l'application finisse par planter et que le fichier journal ne soit pas envoyé. Cela s'avère être plus compliqué que ce à quoi je m'attendais :)

Ce qui fonctionne: (1) capturer l’exception non capturée, (2) extraire les informations du journal et écrire dans un fichier.

Ce qui ne fonctionne pas encore: (3) démarrer une activité pour envoyer un email. En fin de compte, j'aurai encore une autre activité pour demander la permission de l'utilisateur. Si l'activité de la messagerie électronique fonctionne, je ne m'attends pas à beaucoup de problèmes pour les autres.

Le nœud du problème est que l'exception non gérée est capturée dans ma classe Application. Comme il ne s'agit pas d'une activité, la manière de démarrer une activité avec Intent.ACTION_SEND n'est pas évidente. C'est-à-dire que normalement, pour démarrer une activité, on appelle startActivity et le reprend avec onActivityResult. Ces méthodes sont supportées par Activity mais pas par Application.

Des suggestions sur la façon de faire ceci?

Voici quelques extraits de code comme guide de départ:

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[] {"[email protected]"});
    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);
  }
}
104
Peri Hartman

Voici la solution complète (presque: j'ai omis la disposition de l'interface utilisateur et la gestion des boutons) - résultant de nombreuses expériences et de divers messages publiés sur d'autres sujets liés aux problèmes survenus au cours du processus.

Il y a un certain nombre de choses que vous devez faire:

  1. Gérez uncaughtException dans votre sous-classe Application.
  2. Après avoir intercepté une exception, démarrez une nouvelle activité pour demander à l'utilisateur d'envoyer un journal.
  3. Extrayez les informations du journal à partir des fichiers de logcat et écrivez dans votre propre fichier.
  4. Lancez une application de messagerie en fournissant votre fichier en pièce jointe.
  5. Manifeste: filtrez votre activité pour qu'elle soit reconnue par votre gestionnaire d'exceptions.
  6. En option, configurez Proguard pour supprimer Log.d () et Log.v ().

Maintenant, voici les détails:

(1 & 2) Gérez uncaughtException, démarrez l’activité d’envoi du journal:

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) Extrait du journal (je mets ceci dans mon activité 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 + <some name>;

  // 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) Démarrer une application de messagerie (également dans mon activité 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[] {"[email protected]"});
  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 & 4) Voici à quoi ressemble SendLog (vous devrez cependant ajouter l'interface utilisateur):

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) manifeste:

<manifest xmlns:Android="http://schemas.Android.com/apk/res/Android" ... >
    <!-- needed for Android 4.0.x and eariler -->
    <uses-permission Android:name="Android.permission.READ_LOGS" /> 

    <application ... >
        <activity
            Android:name="com.mydomain.SendLog"
            Android:theme="@Android:style/Theme.Dialog"
            Android:textAppearance="@Android:style/TextAppearance.Large"
            Android:windowSoftInputMode="stateHidden">
            <intent-filter>
              <action Android:name="com.mydomain.SEND_LOG" />
              <category Android:name="Android.intent.category.DEFAULT" />
            </intent-filter>
        </activity>
     </application>
</manifest>

(6) programme d'installation:

Dans project.properties, modifiez la ligne de configuration. Vous devez spécifier "optimiser" sinon Proguard not supprimera les appels Log.v () et Log.d ().

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

Dans proguard-project.txt, ajoutez ce qui suit. Ceci indique à Proguard de supposer que Log.v et Log.d n'ont aucun effet secondaire (même s'ils le font depuis qu'ils écrivent dans les journaux) et peuvent donc être supprimés lors de l'optimisation:

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

C'est ça! Si vous avez des suggestions d’améliorations à faire, faites-le moi savoir et je pourrai le mettre à jour.

229
Peri Hartman

Aujourd'hui, il existe de nombreux outils de reprise sur incident qui le font facilement.

  1. crashlytics - Un outil de compte rendu de panne gratuit, mais qui vous donne des rapports basiques Avantages: Gratuit

  2. Gryphonet - Un outil de reporting plus avancé, nécessite des frais. Avantages: Recréation facile des collisions, ANR, lenteur ...

Si vous êtes un développeur privé, je suggérerais Crashlytics, mais s'il s'agit d'une grande organisation, je choisirais Gryphonet.

Bonne chance!

8
Ariel Bell

Essayez plutôt d’utiliser ACRA, car il gère l’envoi de la trace de la pile ainsi que de nombreuses autres informations utiles pour le débogage à votre backend ou au document Google Docs que vous avez configuré.

https://github.com/ACRA/acra

5
Martin Konecny

La réponse de @ PeriHartman fonctionne bien lorsque le fil de l'interface utilisateur lève une exception non interceptée. J'ai apporté quelques améliorations pour le moment où l'exception non capturée est renvoyée par un thread non 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
}
4
Jack Ruan

Joliment expliqué. Mais une observation ici, au lieu d'écrire dans un fichier en utilisant File Writer et Streaming, j'ai utilisé directement l'option logcat -f. Voici le code

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

Cela m'a aidé à éliminer les dernières informations sur le tampon. L'utilisation de la diffusion en continu de fichiers m'a posé un problème, à savoir qu'elle ne vidait pas les derniers journaux du tampon. Mais de toute façon, ce guide était vraiment utile. Je vous remercie.

2
schow

Vous pouvez gérer les exceptions non capturées à l'aide de la bibliothèque FireCrasher et en effectuer une récupération.

vous pouvez en savoir plus sur la bibliothèque dans cet article de média

0
Osama Raddad