3 October 2011

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

Development for 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
Tags:androidexception handlingthreadобработка ошибок
Hubs: Development for Android
+46
22.4k 174
Comments 13