Необходимо обрабатывать неперехваченное исключение и отправлять файл журнала

ОБНОВЛЕНИЕ: см. «Принятое» решение ниже

Когда мое приложение создает необработанное исключение, а не просто завершает работу, я хотел бы сначала дать пользователю возможность отправить файл журнала. Я понимаю, что делать больше работы после получения случайного исключения является рискованным, но, эй, худшее, что приложение заканчивает сбой, и файл журнала не отправляется. Это оказалось сложнее, чем я ожидал 🙂

Что работает: (1) захват неперехваченного исключения, (2) извлечение информации журнала и запись в файл.

Что еще не работает: (3) начало действия для отправки электронной почты. В конечном счете, у меня будет еще одна активность, чтобы спросить разрешения пользователя. Если я получаю работу по электронной почте, я не ожидаю больших проблем для другого.

Суть проблемы в том, что необработанное исключение попадает в мой класс приложения. Поскольку это не мероприятие, не очевидно, как начать работу с Intent.ACTION_SEND. То есть, как правило, для запуска активности вызывается startActivity и возобновляется с onActivityResult. Эти методы поддерживаются Activity, но не Application.

Любые предложения о том, как это сделать?

В качестве исходного руководства вы найдете несколько сокращений кода:

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

Solutions Collecting From Web of "Необходимо обрабатывать неперехваченное исключение и отправлять файл журнала"

Вот полное решение (почти: я опустил макет интерфейса и управление кнопками) – это результат много экспериментов и различных сообщений от других, связанных с проблемами, которые возникли на этом пути.

Вам нужно сделать несколько вещей:

  1. Обработать uncaughtException в подклассе Application.
  2. После обнаружения исключения запустите новое действие, чтобы попросить пользователя отправить журнал.
  3. Извлеките информацию журнала из файлов logcat и напишите в свой собственный файл.
  4. Запустите приложение электронной почты, предоставив файл в качестве вложения.
  5. Манифест: фильтруйте свою активность, чтобы ее распознал обработчик исключений.
  6. При желании, установите Proguard, чтобы вырезать Log.d () и Log.v ().

Теперь, вот подробности:

(1 & 2) Обработать uncaughtException, начать работу с журналом отправки:

 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) Извлечь журнал (я ставлю это своей обработкой 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) Запустите приложение электронной почты (также в моей операции 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 и 4) Вот как выглядит SendLog (вам придется добавить пользовательский интерфейс):

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

В project.properties измените строку конфигурации. Вы должны указать «оптимизировать», или Proguard не будет удалять вызовы Log.v () и Log.d ().

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

В proguard-project.txt добавьте следующее. Это говорит Proguard о том, что Log.v и Log.d не имеют побочных эффектов (даже если они работают с тех пор, как они записываются в журналы) и поэтому могут быть удалены во время оптимизации:

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

Это оно! Если у вас есть предложения по улучшению этого, сообщите мне, и я могу это обновить.

Сегодня есть много инструментов для устранения сбоев, которые делают это легко.

  1. Crashlytics – инструмент для создания отчетов о сбоях , бесплатно, но дает вам базовые отчеты. Преимущества: Бесплатно

  2. Gryphonet – более продвинутый инструмент отчетности, требует какой-то платы. Преимущества: Легкий отдых аварий, ANR, медлительность …

Если вы частный разработчик, я бы предложил Crashlytics, но если это большая организация, я бы пошел на Gryphonet.

Удачи!

Вместо этого попробуйте использовать ACRA – он обрабатывает отправку трассировки стека, а также массу другой полезной отладочной информации на ваш сервер или документ Google Docs, который вы настроили.

https://github.com/ACRA/acra

@ Ответ PeriHartman работает хорошо, когда нить пользовательского интерфейса генерирует неперехваченное исключение. Я сделал некоторые улучшения для того, когда непокрытое исключение вызывается потоком без 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 } 

Красиво объяснено. Но одно наблюдение здесь, вместо того, чтобы записывать в файл с помощью File Writer и Streaming, я напрямую использовал параметр logcat -f. Вот код

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

Это помогло мне очистить последнюю информацию о буфере. Использование потоковой передачи файлов дало мне одну проблему: он не смывал последние журналы из буфера. Но в любом случае это было действительно полезным руководством. Спасибо.