Здравствуйте. Вкратце: статью я разбил на 2 части, дабы не напугать большим количеством буковок:
1. отлов исключений.
2. отправка их на почту.
Кого интересует только отлов исключений, тем хватит только первого сообщения.
Часть первая. Очень простая
Данную статейку я решил написать после того, как начал замечать людей, которые задавались вопросами, как же понять что за ошибку словил кто-то из знакомых или просто незнакомый человек, ведь не все жмут отправить отчет об ошибке, либо приложение вообще еще не дошло до стадии релиза в маркете. Я приведу простой пример как отловить любую ошибку в два клика. Во второй части я покажу как сделать отправку лога посредством zip архива на почтовый ящик.
Для простоты понимания, я разобью информацию на небольшие итерации, что-то вроде коротких истин.
1. Существует стандартный обработчик исключений, который можно получить, вызвав метод . Thread.getUncaughtExceptionHandler()
2. Его можно поменять, вызвав setDefaultUncaughtExceptionHandler(Thread.UncaughtExceptionHandler eh)
3. Базовый обработчик реализует интерфейс Thread.UncaughtExceptionHandler. Это интерфейс, который вызывается в потоке, когда происходит НЕОТЛАВЛИВАЕМОЕ исключение, которое мы не отлавливаем обычными средствами (try catch, throws).
4. Исходя из логики 3 пункта, мы можем создать свой, реализуя интерфейс Thread.UncaughtExceptionHandler.
Итак, давайте создадим пример. Пусть у нас будет активность с внутренним потоком, в котором мы будем отлавливать наши исключения. Создадим активность.
Код: Выделить всё
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
Код: Выделить всё
public class MainActivity extends Activity {
Thread.UncaughtExceptionHandler standartHandler;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
standartHandler = Thread.getDefaultUncaughtExceptionHandler();
}
}
Код: Выделить всё
public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
// throwable - это и есть наше неотловленное исключение.
}
}
Код: Выделить всё
public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
// throwable - это и есть наше неотловленное исключение.
Log.d("sample", "catched", throwable);
}
}
Код: Выделить всё
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler());
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
int i = 2/0;
}
}
И тут мы заметим, что программа не выбросила ошибку. Хорошо ли это? Нет! Никогда не игнорируйте ошибки. Это очень чревато. Путь решения проблемы:03-17 22:56:33.152: D/sample(4397): catched
03-17 22:56:33.152: D/sample(4397): java.lang.RuntimeException: Unable to start activity ComponentInfo{ru.startandroid.exceptionhandler/ru.startandroid.exceptionhandler.MainActivity}: java.lang.ArithmeticException: divide by zero
03-17 22:56:33.152: D/sample(4397): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2503)
03-17 22:56:33.152: D/sample(4397): at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2519)
03-17 22:56:33.152: D/sample(4397): at android.app.ActivityThread.access$2200(ActivityThread.java:123)
03-17 22:56:33.152: D/sample(4397): at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1870)
03-17 22:56:33.152: D/sample(4397): at android.os.Handler.dispatchMessage(Handler.java:99)
03-17 22:56:33.152: D/sample(4397): at android.os.Looper.loop(Looper.java:123)
03-17 22:56:33.152: D/sample(4397): at android.app.ActivityThread.main(ActivityThread.java:4370)
03-17 22:56:33.152: D/sample(4397): at java.lang.reflect.Method.invokeNative(Native Method)
03-17 22:56:33.152: D/sample(4397): at java.lang.reflect.Method.invoke(Method.java:521)
03-17 22:56:33.152: D/sample(4397): at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868)
03-17 22:56:33.152: D/sample(4397): at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:626)
03-17 22:56:33.152: D/sample(4397): at dalvik.system.NativeStart.main(Native Method)
03-17 22:56:33.152: D/sample(4397): Caused by: java.lang.ArithmeticException: divide by zero
03-17 22:56:33.152: D/sample(4397): at ru.startandroid.exceptionhandler.MainActivity.onCreate(MainActivity.java:13)
03-17 22:56:33.152: D/sample(4397): at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1047)
03-17 22:56:33.152: D/sample(4397): at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2466)
1. Сохранить стандартный обработчик;
2. Заменить его на наш;
3. Вывести ошибку;
4. Вернуть на место стандартный обработчик.
Переделаем немного наш обработчик
Код: Выделить всё
public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
Thread.UncaughtExceptionHandler oldHandler;
public ExceptionHandler() {
oldHandler = Thread.getDefaultUncaughtExceptionHandler();
}
@Override
public void uncaughtException(Thread thread, Throwable throwable) {
Log.d("sample", "catched", throwable);
if(oldHandler != null) // если есть ранее установленный...
oldHandler.uncaughtException(thread, throwable); // ...вызовем его
}
}
Код: Выделить всё
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler());
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// int i = 2/0;
getNewException();
}
public void getNewException() {
new Thread(new Runnable() {
public void run() {
Integer a = 2;
Integer b = null;
a = a/b;
}
}).start();
}
}
Код: Выделить всё
public class MainActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler());
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// int i = 2/0;
getNewException();
}
public void getNewException() {
new Thread(new Runnable() {
public void run() {
Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler());
Integer a = 2;
Integer b = null;
a = a/b;
}
}).start();
}
}
Хороший. Стоит унаследовать еще один класс от Application и там поменять обработчик. Он будет работать во всех потоках!
Код: Выделить всё
public class App extends android.app.Application {
private static App singleton;
static {
Thread.setDefaultUncaughtExceptionHandler(new CustomExceptionHandler());
}
public static App getInstance() {
return singleton;
}
@Override
public void onCreate() {
super.onCreate();
singleton = this;
}
}
В итоге мы получим то, что всегда получали. Ошибку. И это хорошо. А еще лучше, что мы получили информацию, которую искали.
В этой заметке я пытался очень детально и просто объяснить, как можно получить ошибку, не отлавливая её. Т.е. другими словами, вы получаете информацию об ошибке всегда. Ваши друзья или знакомые, или просто сознательные граждане, могут делиться данной информацией... Хотя нет, не могут. Мы ведь еще не написали отправку ошибки на почту. Ну так сделаем это.
P.S. заострять на следующем коде внимания не буду, ибо он не относится к данной заметке. Да и кусок кода заzipовывания нагло содран с зарубежных ресурсов. Итак, алгоритм такой:
1. Создать класс логгер, который записывает ошибки в файл.
2. Создать внутренний класс Compress, который создает zip копию файла.
3. Написать простейший обработчик кнопки, которая отправляет его на почту.
Об этом я напишу во второй части.
P.P.S. пока искал более подробный материал по этой теме, попал на статью, которая по той же теме. http://habrahabr.ru/post/129582/ . Стоило один раз прочесть, как я подсознательно нагло её скопировал =) хоть я всё это и знал, но сам стиль волей-неволей похоже перенял. Да простит меня автор той статьи.