Nicht abgefangene Ausnahme behandeln und Protokolldatei senden

Lesezeit: 10 Minuten

Nicht abgefangene Ausnahme behandeln und Protokolldatei senden
Peri Hartmann

UPDATE: Bitte sehen Sie sich unten die “akzeptierte” Lösung an

Wenn meine App eine nicht behandelte Ausnahme erstellt, anstatt sie einfach zu beenden, möchte ich dem Benutzer zunächst die Möglichkeit geben, eine Protokolldatei zu senden. Mir ist klar, dass es riskant ist, mehr Arbeit zu leisten, nachdem eine zufällige Ausnahme aufgetreten ist, aber hey, das Schlimmste ist, dass die App abstürzt und die Protokolldatei nicht gesendet wird. Das stellt sich als schwieriger heraus, als ich erwartet hatte 🙂

Was funktioniert: (1) Abfangen der nicht erfassten Ausnahme, (2) Extrahieren von Protokollinformationen und Schreiben in eine Datei.

Was noch nicht funktioniert: (3) Starten einer Aktivität zum Senden von E-Mails. Letztendlich habe ich noch eine weitere Aktivität, um die Erlaubnis des Benutzers einzuholen. Wenn ich die E-Mail-Aktivität zum Laufen bekomme, erwarte ich keine großen Probleme für den anderen.

Der Kern des Problems besteht darin, dass die unbehandelte Ausnahme in meiner Application-Klasse abgefangen wird. Da dies keine Aktivität ist, ist es nicht offensichtlich, wie man eine Aktivität mit Intent.ACTION_SEND startet. Das heißt, normalerweise ruft man zum Starten einer Aktivität startActivity auf und fährt mit onActivityResult fort. Diese Methoden werden von Activity, aber nicht von Application unterstützt.

Irgendwelche Vorschläge, wie man das macht?

Hier sind einige Codeschnipsel als Starthilfe:

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

  • Ich persönlich benutze nur ACRAobwohl es Open Source ist, sodass Sie überprüfen können, wie sie es tun …

    – David O’Meara

    11. November 2013 um 1:49 Uhr

Nicht abgefangene Ausnahme behandeln und Protokolldatei senden
Peri Hartmann

Hier ist die vollständige Lösung (fast: Ich habe das UI-Layout und die Handhabung der Schaltflächen weggelassen) – abgeleitet von vielen Experimenten und verschiedenen Beiträgen von anderen zu Problemen, die auf dem Weg aufgetreten sind.

Es gibt eine Reihe von Dingen, die Sie tun müssen:

  1. Behandeln Sie uncaughtException in Ihrer Anwendungsunterklasse.
  2. Starten Sie nach dem Abfangen einer Ausnahme eine neue Aktivität, um den Benutzer aufzufordern, ein Protokoll zu senden.
  3. Extrahieren Sie die Protokollinformationen aus den Dateien von logcat und schreiben Sie sie in Ihre eigene Datei.
  4. Starten Sie eine E-Mail-App und stellen Sie Ihre Datei als Anhang bereit.
  5. Manifest: Filtern Sie Ihre Aktivität, damit sie von Ihrem Ausnahmebehandler erkannt wird.
  6. Richten Sie optional Proguard ein, um Log.d() und Log.v() zu entfernen.

Hier nun die Details:

(1 & 2) Behandeln Sie uncaughtException, starten Sie die Sendeprotokollaktivität:

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) Protokoll extrahieren (ich habe dies in meine SendLog-Aktivität eingefügt):

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() + "https://stackoverflow.com/" + "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) Starten Sie eine E-Mail-App (auch in meiner SendLog-Aktivität):

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) So sieht SendLog aus (Sie müssen jedoch die Benutzeroberfläche hinzufügen):

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

<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) Proguard einrichten:

Ändern Sie in project.properties die Konfigurationszeile. Sie müssen “optimieren” angeben oder Proguard wird es tun nicht Log.v()- und Log.d()-Aufrufe entfernen.

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

Fügen Sie in proguard-project.txt Folgendes hinzu. Dies weist Proguard an, davon auszugehen, dass Log.v und Log.d keine Nebenwirkungen haben (obwohl sie dies tun, da sie in die Protokolle schreiben) und daher während der Optimierung entfernt werden können:

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

Das ist es! Wenn Sie diesbezüglich Verbesserungsvorschläge haben, teilen Sie mir dies bitte mit, und ich kann dies aktualisieren.

  • Nur eine Anmerkung: Rufen Sie niemals System.exit auf. Sie unterbrechen die Kette der nicht erfassten Ausnahmebehandlungsroutinen. Einfach weiterleiten an den nächsten. Sie haben bereits “defaultUncaughtHandler” von getDefaultUncaughtExceptionHandler, übergeben Sie einfach den Aufruf, wenn Sie fertig sind.

    – vergoldet

    3. März 2014 um 14:02 Uhr

  • @gilm, können Sie ein Beispiel dafür geben, wie Sie es “an den nächsten weiterleiten” und was sonst noch in der Handler-Kette passieren könnte? Es ist schon eine Weile her, aber ich habe eine Reihe von Szenarien getestet, und der Aufruf von System.exit() schien die beste Lösung zu sein. Schließlich ist die App abgestürzt und muss beendet werden.

    – Peri Hartmann

    3. März 2014 um 18:08 Uhr

  • @PeriHartman sicher: Es gibt nur einen Standardhandler. Bevor Sie setDefaultUncaughtExceptionHandler() aufrufen, müssen Sie getDefaultUncaughtExceptionHandler() aufrufen und diese Referenz beibehalten. Angenommen, Sie haben Bugsense, Crashlytics und Ihr Handler ist der zuletzt installierte, das System ruft nur Ihren an. Es ist Ihre Aufgabe, die Referenz aufzurufen, die Sie über getDefaultUncaughtExceptionHandler() erhalten haben, und den Thread und das Throwable an den nächsten in der Kette zu übergeben. Wenn Sie nur System.exit() verwenden, werden die anderen nicht aufgerufen.

    – vergoldet

    4. März 2014 um 10:25 Uhr

  • Außerdem müssen Sie Ihre Implementierung von Application im Manifest mit einem Attribut im Application-Tag registrieren, z. B.:

    – Matt

    5. November 2014 um 16:26 Uhr

  • @gilm – Ich habe diesen Code erneut besucht und versucht, ihn an den vorherigen Standard-Handler weiterzuleiten. Der Effekt besteht darin, den Systemdialog “App abgestürzt” sowie meinen benutzerdefinierten anzuzeigen. Es scheint, dass die einzige Lösung, die funktioniert, darin besteht, System.exit() aufzurufen.

    – Peri Hartmann

    17. März 2015 um 21:16 Uhr

Heutzutage gibt es viele Crash Reprting Tools, die dies problemlos tun.

  1. crashlytics – Ein kostenloses Crash-Reporting-Tool, das Ihnen grundlegende Berichte liefert. Vorteile: Kostenlos

  2. Gryphonet – Ein fortgeschritteneres Reporting-Tool erfordert eine Art Gebühr. Vorteile: Einfache Wiederherstellung von Abstürzen, ANRs, Langsamkeit…

Wenn Sie ein privater Entwickler sind, würde ich Crashlytics vorschlagen, aber wenn es sich um eine große Organisation handelt, würde ich mich für Gryphonet entscheiden.

Viel Glück!

Versuchen Sie stattdessen, ACRA zu verwenden – es übernimmt das Senden des Stack-Trace sowie Tonnen anderer nützlicher Debug-Informationen an Ihr Backend oder an das von Ihnen eingerichtete Google Docs-Dokument.

https://github.com/ACRA/acra

Nicht abgefangene Ausnahme behandeln und Protokolldatei senden
Jack Ruan

Die Antwort von @PeriHartman funktioniert gut, wenn der UI-Thread eine nicht erfasste Ausnahme auslöst. Ich habe einige Verbesserungen vorgenommen, wenn die nicht erfasste Ausnahme von einem Nicht-UI-Thread ausgelöst wird.

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
}

1647264190 938 Nicht abgefangene Ausnahme behandeln und Protokolldatei senden
Kreshnik

Umgang mit nicht abgefangenen Ausnahmen:
wie @gilm erklärte, tun Sie dies einfach (kotlin):

private val defaultUncaughtHandler = Thread.getDefaultUncaughtExceptionHandler();

override fun onCreate() {
  //...
    Thread.setDefaultUncaughtExceptionHandler { t, e ->
        Crashlytics.logException(e)
        defaultUncaughtHandler?.uncaughtException(t, e)
    }
}

Ich hoffe es hilft, bei mir hat es funktioniert.. (:y). In meinem Fall habe ich die Bibliothek „com.microsoft.appcenter.crashes.Crashes“ zur Fehlerverfolgung verwendet.

1647264192 506 Nicht abgefangene Ausnahme behandeln und Protokolldatei senden
König der Massen

Schön erklärt. Aber eine Beobachtung hier: Anstatt mit File Writer und Streaming in die Datei zu schreiben, habe ich direkt die Option logcat -f verwendet. Hier ist der 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();
        }

Dies hat mir geholfen, die neuesten Pufferinformationen zu leeren. Die Verwendung von Datei-Streaming gab mir ein Problem, dass die neuesten Protokolle nicht aus dem Puffer geleert wurden. Aber wie auch immer, das war wirklich eine hilfreiche Anleitung. Danke.

Nicht abgefangene Ausnahme behandeln und Protokolldatei senden
Osama Raddad

Sie können die nicht abgefangenen Ausnahmen mit behandeln FireCrasher Bibliothek und führen Sie eine Wiederherstellung daraus durch.

Hier können Sie mehr über die Bibliothek erfahren Mittlerer Artikel

1001700cookie-checkNicht abgefangene Ausnahme behandeln und Protokolldatei senden

This website is using cookies to improve the user-friendliness. You agree by using the website further.

Privacy policy