Делаем простейший сборщик ошибок для Android

    При разработке приложения неизбежно приходится сталкиваться с ошибками в коде и/или окружении. И очень печально когда подобные ошибки встречаются не на тестовом телефоне/эмуляторе а у живых пользователей. Еще печальнее если это не ваш друг бета-тестер и толком никто не может объяснить что и где свалилось.

    Обычно при внезапном падении приложения Android предлагает отправить отчет об ошибке, где будет и подробный стэк-трейс и информация о версии вашего приложения. К сожалению пользователи не всегда нажимают кнопку «отправить отчет» а для дебаг-приложений или приложений не из маркета такая функциональность и вовсе недоступна.

    Что же делать? На помощь приедет возможность языка Java обрабатывать исключения (Exceptions), в том числе и непойманные (unhandled).



    Класс Thread имеет статический метод setDefaultUncaughtExceptionHandler. Данный метод позволяет установить собственный класс-обработчик непойманных исключений. Класс-обработчик должен имплементировать интерфейс Thread.UncaughtExceptionHandler. Каркас обработчика может выглядеть примерно так:
    public class TryMe implements Thread.UncaughtExceptionHandler {
        @Override
        public void uncaughtException(Thread thread, Throwable throwable) {
            Log.d("TryMe", "Something wrong happened!");
        }
    }

    Единственный метод принимает на вход Thread — поток, в котором произошло исключение, и Throwable — само исключение. Приведенная выше реализация просто выводит в лог сообщение без каких либо деталей… Попробуем воспользоваться…
    public class MainActivity extends MapActivity {
        @Override
        public void onCreate(Bundle savedInstanceState) {
            Thread.setDefaultUncaughtExceptionHandler(new TryMe());
    
            Integer a=1;
            if(true)
                a=null;
            int x = 6;
            x=x/a;  // Exception here!
        }
    }

    После запуска вышеприведенного кода мы (ура!) получим сообщение в логе… и черный экран. Установив наш собственный обработчик мы удалил штатный обработчик ОС Android и теперь нам больше не предлагают закрыть приложение.

    Исправим положение
    public class TryMe implements Thread.UncaughtExceptionHandler {
    
        Thread.UncaughtExceptionHandler oldHandler;
    
        public TryMe() {
            oldHandler = Thread.getDefaultUncaughtExceptionHandler(); // сохраним ранее установленный обработчик
        }
    
        @Override
        public void uncaughtException(Thread thread, Throwable throwable) {
            Log.d("TryMe", "Something wrong happened!");
            if(oldHandler != null) // если есть ранее установленный...
                oldHandler.uncaughtException(thread, throwable); // ...вызовем его
        }
    }

    Теперь мы видим и сообщение в логе, и привычное системное сообщение.

    Неудобно устанавливать обработчик в Activity. Хоть он и будет установлен а все потоки, но Activity может быть несколько и несколько же стартовых. А еще могут быть сервисы… В этом случае лучше всего устанавливать обработчик при инициализации приложения. Примерно вот так:
    public class MyApplication extends Application {
        @Override
        public void onCreate() {
            Thread.setDefaultUncaughtExceptionHandler(new TryMe());
            super.onCreate();
        }
    }

    При этом нужно не забыть прописать новый класс приложения в манифест. Примерно вот так:
    <manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="my.package">
        <application
            android:name="MyApplication" ...

    Теперь при старте приложения (не важно какого его компонента) будет установлен обработчик исключений.

    Конечно выводить сообщение в лог это не серьезно. Нужно собирать больше информации. Какая версия приложения? Какое исключение не обработано? Какое другое исключение привело к выбросу фатального? В каком потоке? Какой был стэк? Всю эту информацию можно получить. Код простейшего обработчика исключений получающий и сохраняющий на SD-карту всю вышеуказанную информацию размещен на GitHub.

    Приведенная реализация сохраняет информацию об необработанном исключении в файл на SD-карте в папку /Android/data/your.app.package.name/files/ (так велит Dev Guide) в файлах вида stacktrace-dd-MM-yy.txt. Для работы в манифесте приложения требуется разрешение WRITE_EXTERNAL_STORAGE.

    Естественно это не единственное подобное решение.

    Flurry — аналитика для мобильных приложений, содержит свой обработчик ошибок. ACRA — библиотека для Android, собирает данные об ошибках и постит их на GoogleDocs. Android-remote-stacktrace — аналогичная библиотека, шлет данные на пользовательский скрипт-приемник. Также много полезного можно получить в этом вопросе на StackOverflow
    AdBlock похитил этот баннер, но баннеры не зубы — отрастут

    Подробнее
    Реклама

    Комментарии 13

      0
      Однозначно в избранное. Спасибо!
        –1
        IDE и SDK поставил уже давно, а вот приложение сесть писать все не соберусь. Мне как новичку в разработке под андроид будет полезно. Спасибо за статью.
          0
          Ещё бы из JNI ошибки/стектрейсы логгить. 8) У меня аппа на 98% С++, так что явашных ошибок нету вообще.
            0
            так можете парсить logcat
              0
              плохой вариант т.к. не вытащите длинный стектрейс + нужно право на чтение логов.
              0
              На самом деле это возможно, только код будет платформозависимым. Копайте в сторону ptrace. Я свой подобный код начал оформлять в виде библиотеки, но пока как-то не закончил. А на статью материал не тянет, ибо там самого текста с теорией мало но очень много исходного кода
                0
                мы отдельным приложением трейсим, а потом с помощью addr2line (сейчас уже есть ndk-stack) декодим
                0
                Установил RoboErrorReporter почему то не заработал.
                Объясни плиз, зачем нужен метод RoboErrorReporter.reportError?
                  0
                  1. RoboErrorReporter.bindReporter(Context) сделали
                  2. Права на запись на SD у приложения есть?

                  reportError используется для ручного логгирования ошибки в общий файл. Например в каком-то куске кода вы использовали try + catch и внутри catch можно использовать reportError чтобы зарепортить ошибку.
                    0
                    сделал так
                    public class App extends Application{

                    @Override
                    public void onCreate()
                    {
                    appContext = this.getApplicationContext();
                    RoboErrorReporter.bindReporter(appContext);
                    super.onCreate();


                    Запустил на эмуляторе, приложение крешиться штатно в файло ниче не пишет
                      0
                      А покажите пожалуйста ваш манифест?
                  0
                  <?xml version=«1.0» encoding=«utf-8»?>
                  <manifest xmlns:android=«schemas.android.com/apk/res/android»
                  package=«com.roboxchange.arobo»
                  android:versionCode=«1»
                  android:versionName=«1.0» >
                  <uses-sdk android:minSdkVersion=«7» />
                  <application
                  android:icon="@drawable/icon"
                  android:label="@string/app_name"
                  android:name=".App"
                  android:theme="@android:style/Theme.NoTitleBar" >

                  <activity
                  android:excludeFromRecents=«true»
                  android:finishOnTaskLaunch=«true»
                  android:launchMode=«singleInstance»
                  android:name=«org.acra.CrashReportDialog»
                  android:theme="@android:style/Theme.Dialog" />
                  <activity
                  android:label="@string/app_name"
                  android:name=".ActivityBackground"
                  android:screenOrientation=«portrait» >
                  <intent-filter
                  android:icon="@drawable/icon"
                  android:label="@string/app_name" >
                  <action android:name=«android.intent.action.MAIN» />
                  <category android:name=«android.intent.category.LAUNCHER» />
                  </intent-filter>

                  <activity
                  android:label="@string/app_name"
                  android:name=".ActivityMain"
                  android:screenOrientation=«portrait» >



                  <uses-permission android:name=«android.permission.INTERNET» />
                  <uses-permission android:name=«android.permission.READ_PHONE_STATE» />
                  <uses-permission android:name=«android.permission.ACCESS_NETWORK_STATE» />
                  <uses-permission android:name=«android.permission.WRITE_EXTERNAL_STORAGE» />

                    0
                    хмм… а что насчет модулей? работает ли такой подход для перехвата exception в модулях? например, в canvas-библиотеке?

                    Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                    Самое читаемое