Отлов не предусмотренных исключений. Как всегда поймать ошиб

Аватара пользователя
Mikhail_dev
Сообщения: 2386
Зарегистрирован: 09 янв 2012, 14:45
Откуда: Самара

Отлов не предусмотренных исключений. Как всегда поймать ошиб

Сообщение Mikhail_dev » 28 мар 2013, 23:45

(Deprecated. Используйте Flurry, Fabric и т.д.)

Здравствуйте. Вкратце: статью я разбил на 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();
	}
}
Включим логику. Есть getDefaultUncaughtExceptionHandler, значит скорее всего есть и setDefaultUncaughtExceptionHandler. И именно ему мы и передадим наш обработчик. Создадим его. Я сделал его в том же пакете. Это не принципиально.

Код: Выделить всё

public class ExceptionHandler implements Thread.UncaughtExceptionHandler {

    @Override
    public void uncaughtException(Thread thread, Throwable 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;
	}
}
Если всё сделано верно, то мы получим стек трейс ошибки при фильтрации по слуву "sample".
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. static - это что-то типа конструктора, который выполняется первым при любом, первом обращении к данному классу.
В итоге мы получим то, что всегда получали. Ошибку. И это хорошо. А еще лучше, что мы получили информацию, которую искали.
В этой заметке я пытался очень детально и просто объяснить, как можно получить ошибку, не отлавливая её. Т.е. другими словами, вы получаете информацию об ошибке всегда. Ваши друзья или знакомые, или просто сознательные граждане, могут делиться данной информацией... Хотя нет, не могут. Мы ведь еще не написали отправку ошибки на почту. Ну так сделаем это.
P.S. заострять на следующем коде внимания не буду, ибо он не относится к данной заметке. Да и кусок кода заzipовывания нагло содран с зарубежных ресурсов. Итак, алгоритм такой:
1. Создать класс логгер, который записывает ошибки в файл.
2. Создать внутренний класс Compress, который создает zip копию файла.
3. Написать простейший обработчик кнопки, которая отправляет его на почту.

Об этом я напишу во второй части.
P.P.S. пока искал более подробный материал по этой теме, попал на статью, которая по той же теме. http://habrahabr.ru/post/129582/ . Стоило один раз прочесть, как я подсознательно нагло её скопировал =) хоть я всё это и знал, но сам стиль волей-неволей похоже перенял. Да простит меня автор той статьи.
Последний раз редактировалось Mikhail_dev 01 июл 2015, 22:27, всего редактировалось 8 раз.

Аватара пользователя
Mikhail_dev
Сообщения: 2386
Зарегистрирован: 09 янв 2012, 14:45
Откуда: Самара

Re: Отлов непредусмотрен исключений. Как поймать ошибку всег

Сообщение Mikhail_dev » 28 мар 2013, 23:46

Часть вторая. Просто, но чуть дольше.
Так как эту заметку я планировал сделать исключительно по теме отлова исключений, то особо заострять внимание на отправку лога я не буду. Я просто приведу код с небольшими комментариями.
Я создам класс логгер, куда мы будем передавать наше сообщение об ошибке. Логгер просто будет записывать его в файл.
Прежде чем вы его начнёте читать скажу вкратце что он умеет. У логгера есть два основных метода и еще один вспомогательный класс Compress, который ничего не делает, как делает zip файл. Итак, два метода
public static void log(String message) { ... }
public static void log(Throwable throwable) { ... }
Первый получит просто строку и ДОзаписывает её в файл, а второй разбирает ошибку на части, преобразует в строку и просто передаёт её первому методу. Удобно? Удобно. Это называется перегрузка методов, если кто забыл. Другие методы вспомогательные. Директорию я получаю либо флешки, если она доступна, либо самого приложения. Есть еще метод public static String getLogData() { ... } , который возвращает информацию с вашего файла. Мало ли, вдруг вам понадобится, к примеру вывести ошибку в активность или еще куда.

Код: Выделить всё

package ru.startandroid.exceptionhandler;

import java.io.BufferedInputStream;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileReader;
import java.io.FileWriter;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
import android.app.Application;
import android.util.Log;

public class Logger {

    private static final SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm");
    private static Date date = new Date();

    private static final String FOLDER_NAME = "ExceptionHandler";
    private static File logFile=null;
    private static File directory = null;
    private static Application app;
    private static String sdState;
    private static File baseDir = null;

    public static void init(Application _app) {
    	app = _app;
    	logFile = new File(getDir(), "log");
    }
    
    /**
     * Метод содаёт дирректорию. Если флешка есть, то в ней, иначе во внутренней памяти.
     * @return
     */
    public static File getDir() {
        sdState = android.os.Environment.getExternalStorageState();
        if (sdState.equals(android.os.Environment.MEDIA_MOUNTED)) {
            baseDir = android.os.Environment.getExternalStorageDirectory();
            directory = new File(baseDir, FOLDER_NAME);
            if (!directory.exists())
                directory.mkdirs();
        } else {
            directory = getAppDir();
        }

		return directory;
	}    
    
    public static File getAppDir() {
        directory = new File(app.getFilesDir().getParent());
        if (!directory.exists())
            directory.mkdirs();
		return directory;
	}
    
	/**
	 * Метод записи данных в лог. 
	 * @param message
	 */
    public static void log(String message) {
        BufferedWriter bw = null;
        FileWriter fw = null;
        try {
        	//true - означает ДОЗАПИСЫВАНИЕ/ДОБАВЛЕНИЕ информации в файл
        	fw = new FileWriter(logFile, true); 
        	bw = new BufferedWriter(fw);
            bw.write("Time="+getDate()+" \n"+ message);
            bw.flush();
        } catch (Exception e) {
            Log.d("ExceptionHandler", logFile.toString() + " ошибка создания лога = " + e.getMessage());
        } finally { 
            try {
            	fw.close();
				bw.close();
			} catch (Exception e) {}
        }
    }
    
    /**
     * Метод парсит данные об ошибки и приводит их к строке, после чего вызывает 
     * перегруженный метод log(String message)
     * @param throwable
     */
    public static void log(Throwable throwable) { 
        StackTraceElement[] result = throwable.getStackTrace();
        StringBuilder sb = new StringBuilder();
        sb.append("Exception: " + throwable.getClass().getName() + "\n");
        sb.append("Exception message: "+throwable.getMessage() + "\n");
        sb.append("Exception stacktrace follows:\n");
        for (int i = 0; i < result.length; i++) {
            sb.append(result[i]+" \n");
        } 
        sb.append("----------------------------------------\n");
        log(sb.toString());
        
        //печатаем информацию в консоль. Просто обычное дублирование
        throwable.printStackTrace();
    }
    
    /**
     * Метод для возврата информации с логгера для вывода куда-либо в приложение. 
     * Возвращает всё, что накопилось в файле в виде строки. Кому надо - раскомментируйте и 
     * используйте.
     * @return String
     */
//    public static String getLogData() { 
//    	File fileName = null;
//        BufferedReader in = null;
//        fileName = logFile;
//        try {
//            in = new BufferedReader(new FileReader(fileName));
//            String str = "";
//            StringBuffer output = new StringBuffer((int)fileName.length());
//            while ((str = in.readLine()) != null) {
//                output.append(str + "\n");
//            }
//            if (output.length() == 0) {
//            	return null;
//            }
//            return output.toString();
//        } catch (Exception e) {
//            Log.d("ExceptionHandler", fileName.toString() + " ошибка чтения файла = "+ e.getMessage());
//        } finally {
//            try {
//				in.close();
//			} catch (Exception e1) {}
//        }
//		return null;
//    }
    
    /**
     * Возвращает наш сжатый файл.
     * @return
     */
    public static File getLogInZip() {
    	String log = logFile.getAbsolutePath();
    	Compress compress = new Compress(log,"log.zip");
    	compress.zip(directory);
    	//возвратим наш zip
    	return new File(directory, "log.zip");
    }
 
    /**
     * Передаёт текущую дату, во сколько произошла ошибка.
     * @return
     */
    private static synchronized String getDate() { 
    	date.setTime(System.currentTimeMillis());
    	return dateFormat.format(date);
    }
}

/**
 * Класс для единственной цели - сжимать журнал ошибок в архив
 */
class Compress { 
	  private static final int BUFFER = 2048; 
	 
	  private String file; 
	  private String zipFile; 
	  private Application app;
	 
	  public Compress(String file, String zipFile) {
	    this.file = file; 
	    this.zipFile = zipFile;
	  } 
	 
	  public void zip(File directory) { 
	    try  { 
	        BufferedInputStream origin = null;
	        FileOutputStream dest = new FileOutputStream(directory.getAbsolutePath()+"/"+zipFile);
	        ZipOutputStream out = new ZipOutputStream(new BufferedOutputStream(dest)); 
	        byte data[] = new byte[BUFFER]; 
	        FileInputStream fi = new FileInputStream(file); 
	        origin = new BufferedInputStream(fi, BUFFER); 
	        ZipEntry entry = new ZipEntry(file.substring(file.lastIndexOf("/") + 1)); 
	        out.putNextEntry(entry); 
	        int count; 
	        while ((count = origin.read(data, 0, BUFFER)) != -1) { 
	            out.write(data, 0, count); 
	        } 
	        origin.close(); 
	        out.close(); 
	    } catch(Exception e) { 
	      e.printStackTrace(); 
	    } 
	 
	  } 
}
Итак, это логгер. Теперь давайте в нашем классе, в котором отлавливаем исключение, добавим отправку исключения в наш логгер.

Код: Выделить всё

public class ExceptionHandler implements Thread.UncaughtExceptionHandler {
    Thread.UncaughtExceptionHandler oldHandler;

    public ExceptionHandler() {
        oldHandler = Thread.getDefaultUncaughtExceptionHandler();
    }

    @Override
    public void uncaughtException(Thread thread, Throwable throwable) {
        Logger.log(throwable); // - отправляем данные в логгер. 
        
        if(oldHandler != null) // если есть ранее установленный...
            oldHandler.uncaughtException(thread, throwable); // ...вызовем его
    }
}
Замечательно. Теперь у нас есть всё для отправки. В завершение надо сделать следующее:
1. Добавить в активность две кнопки. Первая будет делать ошибку, вторая её отправлять на почту.
2. Добавить в манифест разрешение на запись на флешку.
Код ресурса активити.

Код: Выделить всё

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    tools:context=".MainActivity" >

    <Button
        android:id="@+id/btnSendLog"
        style="?android:attr/buttonStyleSmall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentLeft="true"
        android:layout_alignParentTop="true"
        android:text="Отправить отчёт" />

    <Button
        android:id="@+id/btnException"
        style="?android:attr/buttonStyleSmall"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_alignParentRight="true"
        android:layout_alignParentTop="true"
        android:text="Ошибка!" />

</RelativeLayout>
Исходный код активности

Код: Выделить всё

package ru.startandroid.exceptionhandler;


import android.net.Uri;
import android.os.Bundle;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.Button;
import android.app.Activity;
import android.content.Intent;

public class MainActivity extends Activity implements OnClickListener {

	private Button btnException;
	private Button btnSendLog;
	@Override
	protected void onCreate(Bundle savedInstanceState) {
		Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler());
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		//инициализация переменной в логгере.
		Logger.init(this.getApplication());
		btnException = (Button) findViewById(R.id.btnException);
		btnSendLog   = (Button) findViewById(R.id.btnSendLog);
		btnException.setOnClickListener(this);
		btnSendLog.setOnClickListener(this);
	}

    public void getNewException() {
        new Thread(new Runnable() {
            public void run() {
        		Thread.setDefaultUncaughtExceptionHandler(new ExceptionHandler());            	
            	Integer a = 2;
            	Integer b = null;
            	a = a/b;
            }
        }).start();
    }
    
    @Override 
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.btnException: {
            	getNewException();
                break;
                
            }
            case R.id.btnSendLog: {
            	Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND); 
                emailIntent.setType("text/html");
                emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL, new String[] 
                {"your_email@gmail.com"}); 
                emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, 
                "Журнал"); 
                emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, 
                "Вы также можете нам описать то событие, " +
                "вследствие которого вы решили отправить журнал отчета. ");
                emailIntent.putExtra(Intent.EXTRA_STREAM, 
                		Uri.parse("file://"+Logger.getLogInZip().getAbsolutePath()));
                startActivity(Intent.createChooser(emailIntent, "Отправка письма..."));
                break;
            }
        }
    }

}
Думаю комментарии к коду выше излишни.
И напоследок, добавьте разрешение на запись на флешку в манифест
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
В итоге, вы должны будете получить полностью рабочий проект. Прикладываю архивом данный проект. может кому пригодится.
P.S. как показывает практика, пользователи действительно отправляют отчеты. Правда я делал через меню, где делал отдельную активность для вывода информации об ошибках. Пользователи не ленились нажать пару кнопок что бы отправить отчет.
Есть еще пару нюансов, в дальнейшем надеюсь дополню. А пока что с радостью отвечу на ваши вопросы, если кого заинтересует.
Вложения
ExceptionHandler.zip
(954.06 КБ) 505 скачиваний
Последний раз редактировалось Mikhail_dev 29 мар 2013, 18:43, всего редактировалось 2 раза.

Аватара пользователя
rezak90
Сообщения: 3422
Зарегистрирован: 26 июн 2012, 13:22
Откуда: UA
Контактная информация:

Re: Отлов непредусмотреных исключений. Как всегда поймать ош

Сообщение rezak90 » 29 мар 2013, 00:25

много букав а читать я много не люблю))) по этому увидел setDefaultUncaughtExceptionHandler, это ещё с джавы пришло в андроид и на сколько я помню этот метод будет отлавливать только падения главного потока, или я ошибаюсь?
R.id.team
Политика на форуме запрещена

Аватара пользователя
neoksi
Сообщения: 712
Зарегистрирован: 26 июл 2012, 10:42
Контактная информация:

Re: Отлов непредусмотрен исключений. Как поймать ошибку всег

Сообщение neoksi » 29 мар 2013, 00:42

no--
Очень полезная штука, но вот накидывать обработчик на каждый поток приложения как-то муторно, может можно накинуть его сразу на все потоки приложения к примеру через Application?

Аватара пользователя
Mikhail_dev
Сообщения: 2386
Зарегистрирован: 09 янв 2012, 14:45
Откуда: Самара

Re: Отлов непредусмотреных исключений. Как всегда поймать ош

Сообщение Mikhail_dev » 29 мар 2013, 00:49

много букав а читать я много не люблю))) по этому увидел setDefaultUncaughtExceptionHandler, это ещё с джавы пришло в андроид и на сколько я помню этот метод будет отлавливать только падения главного потока, или я ошибаюсь?
Нет, не основного. Любого!)
Очень полезная штука, но вот накидывать обработчик на каждый поток приложения как-то муторно, может можно накинуть его сразу на все потоки приложения к примеру через Application?
Вот вот, забыл добавить, что если сделать свой класс, наследуемый от Application и сменить там, то это по дефолту распространится на все. Сейчас допишу, спасибо.

Аватара пользователя
rezak90
Сообщения: 3422
Зарегистрирован: 26 июн 2012, 13:22
Откуда: UA
Контактная информация:

Re: Отлов непредусмотреных исключений. Как всегда поймать ош

Сообщение rezak90 » 29 мар 2013, 00:54

Нет, не основного. Любого!)
ну это я имел в виду только отлавливать будет того потока в котором реализован обработчик. А как на счёт сервисов?
R.id.team
Политика на форуме запрещена

Аватара пользователя
neoksi
Сообщения: 712
Зарегистрирован: 26 июл 2012, 10:42
Контактная информация:

Re: Отлов непредусмотреных исключений. Как всегда поймать ош

Сообщение neoksi » 29 мар 2013, 00:59

rezak90 писал(а):
Нет, не основного. Любого!)
ну это я имел в виду только отлавливать будет того потока в котором реализован обработчик. А как на счёт сервисов?
Ну сервис является элементом Application, и если через него установить, то и на них должно распространиться.

Аватара пользователя
Mikhail_dev
Сообщения: 2386
Зарегистрирован: 09 янв 2012, 14:45
Откуда: Самара

Re: Отлов непредусмотреных исключений. Как всегда поймать ош

Сообщение Mikhail_dev » 29 мар 2013, 01:00

Отлов производится во всём приложении. Добавил кусочек кода для этого дела

Код: Выделить всё

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;
    }
}
куда же проще =)
На счет сервисов? Да запросто. У нас сервис вообще в другом процессе (не путать с потоком). У него м манифесте стоит :remote , который запускает его в другой далвик машине. И всё отлично работает. Мы ловили такие ужасные баги от юзеров, что в жизнь такое бы не отловили. Вот к примеру
2013-03-14 22:45:41+0400 [main] Exception: java.lang.UnsupportedOperationException
Exception message: null
Exception stacktrace follows:
android.view.GLES20Canvas.drawPicture(GLES20Canvas.java:916)
org.osmdroid.views.overlay.ScaleBarOverlay.draw(ScaleBarOverlay.java:266)
org.osmdroid.views.overlay.OverlayManager.onDraw(OverlayManager.java:136)
org.osmdroid.views.MapView.dispatchDraw(MapView.java:885)
android.view.View.getDisplayList(View.java:10433)
android.view.ViewGroup.drawChild(ViewGroup.java:2850)
android.view.ViewGroup.dispatchDraw(ViewGroup.java:2489)
android.view.View.getDisplayList(View.java:10433)
android.view.ViewGroup.drawChild(ViewGroup.java:2850)
android.view.ViewGroup.dispatchDraw(ViewGroup.java:2489)
android.view.View.getDisplayList(View.java:10433)
android.view.ViewGroup.drawChild(ViewGroup.java:2850)
android.view.ViewGroup.dispatchDraw(ViewGroup.java:2489)
android.view.View.getDisplayList(View.java:10433)
android.view.ViewGroup.drawChild(ViewGroup.java:2850)
android.view.ViewGroup.dispatchDraw(ViewGroup.java:2489)
android.view.View.getDisplayList(View.java:10433)
android.view.ViewGroup.drawChild(ViewGroup.java:2850)
android.view.ViewGroup.dispatchDraw(ViewGroup.java:2489)
android.view.View.draw(View.java:10997)
android.widget.FrameLayout.draw(FrameLayout.java:450)
com.android.internal.policy.impl.PhoneWindow$DecorView.draw(PhoneWindow.java:2126)
android.view.View.getDisplayList(View.java:10435)
android.view.HardwareRenderer$GlRenderer.draw(HardwareRenderer.java:873)
android.view.ViewRootImpl.draw(ViewRootImpl.java:1910)
android.view.ViewRootImpl.performTraversals(ViewRootImpl.java:1634)
android.view.ViewRootImpl.handleMessage(ViewRootImpl.java:2445)
android.os.Handler.dispatchMessage(Handler.java:99)
android.os.Looper.loop(Looper.java:137)
android.app.ActivityThread.main(ActivityThread.java:4424)
java.lang.reflect.Method.invokeNative(Native Method)
java.lang.reflect.Method.invoke(Method.java:511)
com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:787)
com.android.internal.os.ZygoteInit.main(ZygoteInit.java:554)
dalvik.system.NativeStart.main(Native Method)
Баг подвержен версиям андроида выше Android 4.1. Они там ускоритель по дефолту отключили 2D графики, вот вылезла такая беда. Решения пока особого нету.

Аватара пользователя
Foenix
Сообщения: 4201
Зарегистрирован: 20 окт 2012, 12:01

Re: Отлов непредусмотреных исключений. Как всегда поймать ош

Сообщение Foenix » 29 мар 2013, 17:55

как раз на это неделе сделала себе аналогичное.
Вчера лог так здорово отсылался, сегодня виснет, собака, и все тут.
R.id.team

NullPointerException - что делать???
viewtopic.php?f=33&t=3899&p=28952#p28952
Где моя ошибка?
viewtopic.php?f=60&t=3198

Аватара пользователя
Mikhail_dev
Сообщения: 2386
Зарегистрирован: 09 янв 2012, 14:45
Откуда: Самара

Re: Отлов непредусмотреных исключений. Как всегда поймать ош

Сообщение Mikhail_dev » 29 мар 2013, 18:36

1. Где создаётся лог? Флешка или внутренняя память? Там есть некоторые нюансы.
2. Какой вес лога?

Аватара пользователя
Foenix
Сообщения: 4201
Зарегистрирован: 20 окт 2012, 12:01

Re: Отлов непредусмотреных исключений. Как всегда поймать ош

Сообщение Foenix » 29 мар 2013, 19:02

no-- писал(а):1. Где создаётся лог? Флешка или внутренняя память? Там есть некоторые нюансы.
2. Какой вес лога?
чуть позже добавлю все коды.

Сейчас вот заметила, что перед запуском программы из эклипса на устройстве пишет
03-29 20:00:02.388: E/Trace(2296): error opening trace file: No such file or directory (2)
Может в этом все дело, я раньше не замечала этой строчки.
R.id.team

NullPointerException - что делать???
viewtopic.php?f=33&t=3899&p=28952#p28952
Где моя ошибка?
viewtopic.php?f=60&t=3198

Аватара пользователя
Foenix
Сообщения: 4201
Зарегистрирован: 20 окт 2012, 12:01

Re: Отлов непредусмотреных исключений. Как всегда поймать ош

Сообщение Foenix » 29 мар 2013, 21:38

Вот лог от начала ошибки

Код: Выделить всё

03-29 22:34:56.573: W/dalvikvm(3723): threadid=1: thread exiting with uncaught exception (group=0x40e26300)
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): Error while sendErrorMailError Report collected on : Fri Mar 29 22:34:56 GMT+03:00 2013
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): Informations :
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): Locale: ru_RU
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): Version: 1.0.266
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): Package: ru.***.***online
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): Phone Model: DIGMA iDsD8 3G
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): Android Version: 4.1.1
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): Board: rk30sdk
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): Brand: rk30sdk
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): Device: rk30sdk
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): Host: test-desktop
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): ID: JRO03H
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): Model: DIGMA iDsD8 3G
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): Product: rk30sdk
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): Type: eng
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): Total Internal memory: 1056858112
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): Available Internal memory: 726147072
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): Stack:
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): java.lang.RuntimeException: Unable to start activity ComponentInfo{ru.***.***online/ru.***.***online.ASpisok***}: java.lang.ArithmeticException: divide by zero
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): 	at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2059)
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): 	at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2084)
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): 	at android.app.ActivityThread.access$600(ActivityThread.java:130)
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): 	at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1195)
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): 	at android.os.Handler.dispatchMessage(Handler.java:99)
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): 	at android.os.Looper.loop(Looper.java:137)
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): 	at android.app.ActivityThread.main(ActivityThread.java:4745)
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): 	at java.lang.reflect.Method.invokeNative(Native Method)
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): 	at java.lang.reflect.Method.invoke(Method.java:511)
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): 	at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:786)
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): 	at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:553)
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): 	at dalvik.system.NativeStart.main(Native Method)
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): Caused by: java.lang.ArithmeticException: divide by zero
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): 	at ru.***.***online.ASpisok***.onCreate(ASpisok***.java:135)
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): 	at android.app.Activity.performCreate(Activity.java:5008)
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): 	at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1079)
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): 	at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2023)
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): 	... 11 more
03-29 22:34:56.573: E/ru.***.***online.CUnCaughtException(3723): **** End of current Report ***
03-29 22:34:56.593: W/HardwareRenderer(3723): Attempting to initialize hardware acceleration outside of the main thread, aborting
03-29 22:35:09.723: I/dalvikvm(3723): threadid=3: reacting to signal 3
03-29 22:35:09.743: I/dalvikvm(3723): Wrote stack traces to '/data/anr/traces.txt'

Программа доходит до вопроса - послать ли отчет, и после него (я ничего не нажимаю, на кнопки фактически не реагирует) экран гаснет - черный.
Последний раз редактировалось Foenix 29 мар 2013, 21:46, всего редактировалось 1 раз.
R.id.team

NullPointerException - что делать???
viewtopic.php?f=33&t=3899&p=28952#p28952
Где моя ошибка?
viewtopic.php?f=60&t=3198

Аватара пользователя
Foenix
Сообщения: 4201
Зарегистрирован: 20 окт 2012, 12:01

Re: Отлов непредусмотреных исключений. Как всегда поймать ош

Сообщение Foenix » 29 мар 2013, 21:41

Код

Код: Выделить всё

import java.io.File;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.io.Writer;
import java.lang.Thread.UncaughtExceptionHandler;
import java.util.Date;
import java.util.Locale;

import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Environment;
import android.os.Looper;
import android.os.StatFs;
import android.util.Log;

/**
 * {@link UncaughtExceptionHandler} send an e-mail with some debug information
 * to the developer.
 * 
 * @author VIJAYAKUMAR
 */
public class CUnCaughtException implements UncaughtExceptionHandler {
	private static final String RECIPIENT = "***@***.ru";
	private Thread.UncaughtExceptionHandler previousHandler;
	private Context context;
	private static Context context1;

	public CUnCaughtException(Context ctx) {
		context = ctx;
		context1 = ctx;
	}

	private StatFs getStatFs() {
		File path = Environment.getDataDirectory();
		return new StatFs(path.getPath());
	}

	private long getAvailableInternalMemorySize(StatFs stat) {
		long blockSize = stat.getBlockSize();
		long availableBlocks = stat.getAvailableBlocks();
		return availableBlocks * blockSize;
	}

	private long getTotalInternalMemorySize(StatFs stat) {
		long blockSize = stat.getBlockSize();
		long totalBlocks = stat.getBlockCount();
		return totalBlocks * blockSize;
	}

	private void addInformation(StringBuilder message) {
		message.append("Locale: ").append(Locale.getDefault()).append('\n');
		try {
			PackageManager pm = context.getPackageManager();
			PackageInfo pi;
			pi = pm.getPackageInfo(context.getPackageName(), 0);
			message.append("Version: ").append(pi.versionName).append('\n');
			message.append("Package: ").append(pi.packageName).append('\n');
		} catch (Exception e) {
			Log.e("CustomExceptionHandler", "Error", e);
			message.append("Could not get Version information for ").append(context.getPackageName());
		}
		message.append("Phone Model: ").append(android.os.Build.MODEL).append('\n');
		message.append("Android Version: ").append(android.os.Build.VERSION.RELEASE).append('\n');
		message.append("Board: ").append(android.os.Build.BOARD).append('\n');
		message.append("Brand: ").append(android.os.Build.BRAND).append('\n');
		message.append("Device: ").append(android.os.Build.DEVICE).append('\n');
		message.append("Host: ").append(android.os.Build.HOST).append('\n');
		message.append("ID: ").append(android.os.Build.ID).append('\n');
		message.append("Model: ").append(android.os.Build.MODEL).append('\n');
		message.append("Product: ").append(android.os.Build.PRODUCT).append('\n');
		message.append("Type: ").append(android.os.Build.TYPE).append('\n');
		StatFs stat = getStatFs();
		message.append("Total Internal memory: ").append(getTotalInternalMemorySize(stat)).append('\n');
		message.append("Available Internal memory: ").append(getAvailableInternalMemorySize(stat)).append('\n');
	}

	public void uncaughtException(Thread t, Throwable e) {
		try {
			StringBuilder report = new StringBuilder();
			Date curDate = new Date();
			report.append("Error Report collected on : ").append(curDate.toString()).append('\n').append('\n');
			report.append("Informations :").append('\n');
			addInformation(report);
			report.append('\n').append('\n');
			report.append("Stack:\n");
			final Writer result = new StringWriter();
			final PrintWriter printWriter = new PrintWriter(result);
			e.printStackTrace(printWriter);
			report.append(result.toString());
			printWriter.close();
			report.append('\n');
			report.append("**** End of current Report ***");
			Log.e(CUnCaughtException.class.getName(), "Error while sendErrorMail" + report);
			sendErrorMail(report);
		} catch (Throwable ignore) {
			Log.e(CUnCaughtException.class.getName(), "Error while sending error e-mail", ignore);
		}
		// previousHandler.uncaughtException(t, e);
	}

	/**
	 * This method for call alert dialog when application crashed!
	 * 
	 * @author vijayakumar
	 */
	public void sendErrorMail(final StringBuilder errorContent) {
		final AlertDialog.Builder builder = new AlertDialog.Builder(context);
		new Thread() {
			@Override
			public void run() {
				Looper.prepare();
				builder.setTitle("Ошибка!");
				builder.create();
				builder.setNegativeButton("Отмена", new DialogInterface.OnClickListener() {
					@Override
					public void onClick(DialogInterface dialog, int which) {
						System.exit(0);
					}
				});
				builder.setPositiveButton("Отправить отчет", new DialogInterface.OnClickListener() {
					@Override
					public void onClick(DialogInterface dialog, int which) {
						Intent sendIntent = new Intent(Intent.ACTION_SEND);
						String subject = "Отчет об ошибке приложения ***Online";
						StringBuilder body = new StringBuilder("Yoddle");
						body.append('\n').append('\n');
						body.append(errorContent).append('\n').append('\n');
						// sendIntent.setType("text/plain");
						sendIntent.setType("message/rfc822");
						sendIntent.putExtra(Intent.EXTRA_EMAIL, new String[] { RECIPIENT });
						sendIntent.putExtra(Intent.EXTRA_TEXT, body.toString());
						sendIntent.putExtra(Intent.EXTRA_SUBJECT, subject);
						sendIntent.setType("message/rfc822");
						// context.startActivity(Intent.createChooser(sendIntent,
						// "Error Report"));
						context1.startActivity(sendIntent);
						System.exit(0);
					}
				});
				builder.setMessage("К сожалению, работа программы прервана из-за ошибки. " +
						"Пожалуйста, отправьте отчет об ошибке разработчикам");
				builder.show();
				Looper.loop();
			}
		}.start();
	}
}
R.id.team

NullPointerException - что делать???
viewtopic.php?f=33&t=3899&p=28952#p28952
Где моя ошибка?
viewtopic.php?f=60&t=3198

Аватара пользователя
Foenix
Сообщения: 4201
Зарегистрирован: 20 окт 2012, 12:01

Re: Отлов непредусмотреных исключений. Как всегда поймать ош

Сообщение Foenix » 29 мар 2013, 22:11

Переместила исключение в Mainactivity - работает. В другой активити - ошибка. Но вчера работало!! много раз проверяла!!
R.id.team

NullPointerException - что делать???
viewtopic.php?f=33&t=3899&p=28952#p28952
Где моя ошибка?
viewtopic.php?f=60&t=3198


Аватара пользователя
Foenix
Сообщения: 4201
Зарегистрирован: 20 окт 2012, 12:01

Re: Отлов непредусмотреных исключений. Как всегда поймать ош

Сообщение Foenix » 29 мар 2013, 22:43

нет, только в первой.
R.id.team

NullPointerException - что делать???
viewtopic.php?f=33&t=3899&p=28952#p28952
Где моя ошибка?
viewtopic.php?f=60&t=3198

Аватара пользователя
Foenix
Сообщения: 4201
Зарегистрирован: 20 окт 2012, 12:01

Re: Отлов непредусмотреных исключений. Как всегда поймать ош

Сообщение Foenix » 29 мар 2013, 22:46

Поставила еще и во вторую, работает.
Я не могу в application поставить, там же передается контекст.. что с ним делать-то..?

Из-за него скорее всего и ошибка. Контекст не тот или какой-нибудь нулл получается..
R.id.team

NullPointerException - что делать???
viewtopic.php?f=33&t=3899&p=28952#p28952
Где моя ошибка?
viewtopic.php?f=60&t=3198

Аватара пользователя
Mikhail_dev
Сообщения: 2386
Зарегистрирован: 09 янв 2012, 14:45
Откуда: Самара

Re: Отлов непредусмотреных исключений. Как всегда поймать ош

Сообщение Mikhail_dev » 29 мар 2013, 22:50

Тут вот как получается:
1. Вы меняете обработчик на новый, записываете информацию, но дальше НЕ меняете его на старый и оставляете всё как есть. В такой ситуации программа переходит в неадекватное состояние. Другими словами, если было исключение необработанное, то вы можете отправить отчет, но после завершите работу приложения. Другими словами, расскоментируйте
// previousHandler.uncaughtException(t, e);
2. Обработчик исключений надо менять в каждой активности, потому как активности это разные потоки. Отсюда вывод, что во второй активности старый обработчик. Короче каша получается.

Что лучше сделать:
унаследуйтесь от Application, измените там обработчик. Он будет влиять на все потоки приложения, а не только на тот, в котором вы поменяли. Взгляните сами на первый аргумент того основного метода. Это и есть тот поток, где было исключение.

Код: Выделить всё

public void uncaughtException(Thread t, Throwable e) {...}

Аватара пользователя
Mikhail_dev
Сообщения: 2386
Зарегистрирован: 09 янв 2012, 14:45
Откуда: Самара

Re: Отлов непредусмотреных исключений. Как всегда поймать ош

Сообщение Mikhail_dev » 29 мар 2013, 22:52

Я не могу в application поставить, там же передается контекст.. что с ним делать-то..?
Не понял. какой контекст? Вот я приводил пример
viewtopic.php?f=31&t=1781&p=10601#p10524

Аватара пользователя
Foenix
Сообщения: 4201
Зарегистрирован: 20 окт 2012, 12:01

Re: Отлов непредусмотреных исключений. Как всегда поймать ош

Сообщение Foenix » 29 мар 2013, 23:03

У меня вот так обработчик меняется
Thread.setDefaultUncaughtExceptionHandler(new CUnCaughtException(AMainActivity.this));
в конструктор передается контекст..
R.id.team

NullPointerException - что делать???
viewtopic.php?f=33&t=3899&p=28952#p28952
Где моя ошибка?
viewtopic.php?f=60&t=3198

Ответить