При разработке приложения неизбежно приходится сталкиваться с ошибками в коде и/или окружении. И очень печально когда подобные ошибки встречаются не на тестовом телефоне/эмуляторе а у живых пользователей. Еще печальнее если это не ваш друг бета-тестер и толком никто не может объяснить что и где свалилось.
Обычно при внезапном падении приложения Android предлагает отправить отчет об ошибке, где будет и подробный стэк-трейс и информация о версии вашего приложения. К сожалению пользователи не всегда нажимают кнопку «отправить отчет» а для дебаг-приложений или приложений не из маркета такая функциональность и вовсе недоступна.
Что же делать? На помощь приедет возможность языка Java обрабатывать исключения (Exceptions), в том числе и непойманные (unhandled).
Класс Thread имеет статический метод setDefaultUncaughtExceptionHandler. Данный метод позволяет установить собственный класс-обработчик непойманных исключений. Класс-обработчик должен имплементировать интерфейс Thread.UncaughtExceptionHandler. Каркас обработчика может выглядеть примерно так:
Единственный метод принимает на вход Thread — поток, в котором произошло исключение, и Throwable — само исключение. Приведенная выше реализация просто выводит в лог сообщение без каких либо деталей… Попробуем воспользоваться…
После запуска вышеприведенного кода мы (ура!) получим сообщение в логе… и черный экран. Установив наш собственный обработчик мы удалил штатный обработчик ОС Android и теперь нам больше не предлагают закрыть приложение.
Исправим положение
Теперь мы видим и сообщение в логе, и привычное системное сообщение.
Неудобно устанавливать обработчик в Activity. Хоть он и будет установлен а все потоки, но Activity может быть несколько и несколько же стартовых. А еще могут быть сервисы… В этом случае лучше всего устанавливать обработчик при инициализации приложения. Примерно вот так:
При этом нужно не забыть прописать новый класс приложения в манифест. Примерно вот так:
Теперь при старте приложения (не важно какого его компонента) будет установлен обработчик исключений.
Конечно выводить сообщение в лог это не серьезно. Нужно собирать больше информации. Какая версия приложения? Какое исключение не обработано? Какое другое исключение привело к выбросу фатального? В каком потоке? Какой был стэк? Всю эту информацию можно получить. Код простейшего обработчика исключений получающий и сохраняющий на 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
Обычно при внезапном падении приложения 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