Обработка исключений в AndroidAndroid

Форум для тех, кто программирует под Android
Ответить
Anonymous
 Обработка исключений в Android

Сообщение Anonymous »

Я изучал обработку исключений в Android и хочу знать рекомендуемые способы перехвата исключений и их обработки. Мое приложение для Android в основном написано на C++, а остальное — на Kotlin. Я предпочитаю использовать всеобъемлющий обработчик исключений и предпочитаю избегать try-catch (если в этом нет необходимости). Исключения C++ не могут быть перехвачены Kotlin и наоборот. Итак, я излагаю свое понимание исключений в Kotlin и C++ и способов их обработки в Android.
Исключения Kotlin

В Котлине есть универсальный обработчик исключений, который можно зарегистрировать с помощью метода setDefaultUncaughtExceptionHandler. Мой MainActivity.kt выглядит следующим образом:

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

//MainActivity.kt
class MainActivity : AppCompatActivity() {

....

override fun onCreate(savedInstanceState: Bundle?) {
LOG("MainActivity.onCreate(Bundle?)")

super.onCreate(savedInstanceState)

// Register the 'catch-all' exception handler
Thread.setDefaultUncaughtExceptionHandler(CustomUncaughtExceptionHandler())

....

// Start testing the exception handler
testExceptionHandler()
//testExceptionHandlerFromCpp()
}

private fun testExceptionHandler() {
LOG("testExceptionHandler()")

LOG("**** Start exception handling testing (Kotlin) ****")

divisionByZeroInWorkerThread()
//divisionByZero()
//throwExceptionInWorkerThread()
//throwException()

LOG("****End exception handling testing (Kotlin) ****")

}

private fun divisionByZero() {
LOG("divisionByZero()")
val num1: Int = 5
val num2: Int = 0
val result: Int

LOG("Raising exception...")
result = num1/num2
}

private fun divisionByZeroInWorkerThread() {
LOG("divisionByZeroInWorkerThread()")
thread {
divisionByZero()
}
}

....
}
После регистрации uncaughtExceptionHandler я начинаю его тестирование с деления на ноль. Как и ожидалось, мой uncaughtExceptionHandler вызывается в потоке, вызвавшем исключение. Мой класс CustomUncaughtExceptionHandler определяется следующим образом:

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

//CustomUncaughtExceptionHandler.kt
class CustomUncaughtExceptionHandler: Thread.UncaughtExceptionHandler {

override fun uncaughtException(p0: Thread, p1: Throwable) {

LOG("uncaughtException(Thread, Throwable)")

var exceptionType: String = "(null)"
when (p1) {
is IndexOutOfBoundsException -> {
exceptionType = "IndexOutOFBoundsException"
}

is NullPointerException -> {
exceptionType = "NullPointerException"
}

is ArithmeticException -> {
exceptionType = "ArithmeticException"
}

is RuntimeException -> {
exceptionType = "RuntimeException"
}

else -> {
exceptionType = "Unknown"
}
}
LOG("Exception type = $exceptionType")

if(Thread.currentThread().id.toInt() == 2) {
LOG("Already in main thread...  Preparing to display alert...")
displayAlert(exceptionType)
} else {
Handler (Looper.getMainLooper ()).post {

LOG("Back to main thread!!")
displayAlert(exceptionType)
}

// Block worker thread from exiting the event loop.
// UI thread will relieve the worker thread from this loop.
while(blockExceptionThread) {

LOG("${Calendar.getInstance().time}")
Thread.sleep(10000)
}
}

LOG("Returning from default uncaught exception handler...")
}

private fun displayAlert(exceptionType: String) {

if (ContextMgr.activityCtx == null) {
LOG("There's no foreground activity!")
} else {
LOG("Displaying error on foreground activity...")

val builder: AlertDialog.Builder = AlertDialog.Builder(ContextMgr.activityCtx!!)
builder.setTitle("Something went wrong!!")
builder.setMessage(exceptionType)
builder.setCancelable(false)
builder.setPositiveButton("Restart") { _, _ ->
LOG("Restart the app...")
blockExceptionThread = false;
}

val alertdialog : AlertDialog = builder.create()
alertdialog.show()
}
}

companion object {
var blockExceptionThread: Boolean = true;
}
}
Как показано выше, я намерен проинформировать пользователя о возникновении исключения и, если возможно, перезапустить приложение (я знаю, что в большинстве случаев это невозможно). Если рабочий поток вызвал исключение, я могу показать диалоговое окно перезапуска... рабочий поток ждет, пока поток пользовательского интерфейса не сообщит об этом. Я не уверен, нужно ли это, потому что на других платформах (Windows и Linux) при выходе из обработчика исключений происходит рекурсия, поскольку поток возвращается для выполнения предыдущей инструкции, вызвавшей исключение. Но я не наблюдал такого на Android, т. е. даже после возврата из обработчика исключений пользовательский интерфейс остается таким.
Мои вопросы по исключениям Kotlin:
  • Могу ли я использовать uncaughtExceptionHandler для перехвата всех исключений, возникающих на уровне Kotlin? Рекомендуемый ли это подход для Android?
  • Каковы рекомендации/ожидания Android при вызове обработчика исключений в рабочем потоке? Как объяснялось выше, я могу показать пользователю диалог перезапуска. Конечно, помимо отображения этого сообщения, я должен подготовить диагностическую информацию, например трассировку стека. Это рекомендовано Android? Если нет, то каковы рекомендуемые действия.
  • Каковы рекомендации/ожидания Android при вызове обработчика исключений в основном потоке? В этом случае я не могу показать пользователю сообщение с приведенным выше кодом. Поток пользовательского интерфейса возвращается из обработчика исключений, и затем он должен начать отображать диалоговое окно перезапуска, но я не вижу диалогового окна перезапуска. Возможно, я что-то упускаю, но в любом случае, как следует обрабатывать исключения в основном потоке?
Исключения C++
Для исключений (таких как SIGSEGV, SIGFPE и т. д.), возникающих в коде C++, можно использовать механизм захвата сигналов Linux (поскольку не существует универсального обработчика исключений - как мне сообщалось в этот пост). Я могу зарегистрировать соответствующие сигналы, и обработчик сигнала вызывается в потоке, который вызывает исключение. Но в отличие от uncaughtExceptionHandler Kotlin, когда я возвращаюсь из обработчика сигнала C++, происходит рекурсия. У меня есть экспериментальный код для выхода из рекурсии после 10 раз, как показано ниже):

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

// ExceptionHandlingCpp.hpp
using SignalHandlerFunc = void (*) (int, siginfo_t *, void *);

struct tSignals {

SignalHandlerFunc signalHandlerFunc;
uint32_t          signal;
[[maybe_unused]]
uint8_t           reserved[4];
};

class ExceptionHandlingCpp {

public:

static tSignals unixSignals[];

private:
static uint8_t numOfRecursions;
static thread_local uint8_t currentIteration;

public:

static void RegisterSignals();
static void HandleSignals(int pSignals, siginfo_t* pInfo, void* pContext);
static void UnregisterSignals(int pSignal);

static void *DivisionByZero(void *pParms);
static void DivisionByZeroInWorkerThread();

....
};

// ExceptionHandlingCpp.cpp
tSignals ExceptionHandlingCpp::unixSignals[] = {

{HandleSignals, SIGFPE, {0}},
{HandleSignals, SIGSEGV, {0}},
{HandleSignals, SIGKILL, {0}},
};

uint8_t ExceptionHandlingCpp::numOfRecursions {10};
thread_local uint8_t ExceptionHandlingCpp::currentIteration {0};

void ExceptionHandlingCpp::RegisterSignals() {
LOG("ExceptionHandlingCpp::RegisterSignals()");

struct sigaction sa;

sa.sa_flags = SA_SIGINFO;

for(int i = 0; i < sizeof(unixSignals)/sizeof(tSignals); ++i) {

sa.sa_sigaction = unixSignals[i].signalHandlerFunc;
sigaction(unixSignals[i].signal, &sa, nullptr);
}
}

void ExceptionHandlingCpp::HandleSignals(int pSignal, siginfo_t *pInfo, void *pContext) {
LOG("ExceptionHandlingCpp::HandleSignals(int, signinfo_t*, void*)");

LOG("signal = " + std::to_string(pSignal));
// TODO: Handle the exception/signal.

currentIteration++;

if(currentIteration > numOfRecursions) {

// After repeating for said times, update the action for the signal and do nothing
LOG("Since iteration limit is reached (10), unsetting the signal handler...");
UnregisterSignals(pSignal);
} else {
LOG("Number of recursions so far = " + std::to_string(currentIteration));
}

LOG("Returning from exception handler...");
}

void ExceptionHandlingCpp::UnregisterSignals(int pSignal) {

LOG("UnregisterSignals(int)");

struct sigaction defaultAction {};
defaultAction.sa_handler = SIG_DFL;

if(sigaction(pSignal, &defaultAction, nullptr) == -1) {
LOG("Error in resetting action for " + std::to_string(pSignal));
} else {
LOG("Successfully reset " + std::to_string(pSignal) + "'s action to default!");
}
}

void* ExceptionHandlingCpp::DivisionByZero ([[maybe_unused]] void* pParms)
{
LOG("DivisionByZero()");

int num1;
int num2;
int result;

num1 = 5;
num2 = 0;

LOG("Raising exception...");

result = num1 / num2;

LOG("Returning from DivisionByZero() method");

return nullptr;
}

void ExceptionHandlingCpp::DivisionByZeroInWorkerThread ()
{
pthread_t thread1;

LOG("DivisionByZeroInWorkerThread()");

pthread_create(&thread1, nullptr, ExceptionHandlingCpp::DivisionByZero, nullptr);
}
Как и в Kotlin, я бы предпочел показать пользователю какое-то сообщение, завершить структуру, подготовить некоторую диагностическую информацию, а затем перейти к обработчику по умолчанию для завершения приложения. Если возможно, я хотел бы перезапустить приложение после информирования пользователя, но это будет невозможно в случае исключений, таких как SIGSEGV.
Мои вопросы об исключениях C++:
  • Если обработчик сигнала вызывается в основном потоке или рабочем потоке, можно ли будет сообщить об этом пользователю, завершить мои структуры, подготовить некоторую диагностическую информацию и аккуратно завершить работу, вместо того, чтобы внезапно привести к сбою приложения? (Судя по моему сообщению о Linux, я не думаю, что это возможно для такого сигнала, как SIGSEGV, но просто хотел подтвердить).
  • В моем сообщении о Linux было указано, что Вторичный процесс можно использовать для отображения диалога перезапуска пользователю и запуска нового процесса. Нечто подобное возможно и в Android — цитата из этого сообщения stackoverflow. Когда срабатывает обработчик исключений (обработчик сигнала C++ или uncaughtExceptionHandler в Kotlin), можно запустить новое действие в отдельном процессе и позволить старому процессу завершиться. Таким образом, приложение может проинформировать пользователя и перезапустить новый процесс. Рекомендуется ли это?
  • В обработчике исключений я вызову Действие, которое покажет, что что-то пошло не так.
  • Сообщите, что я вызываю обработчик исключений по умолчанию (как рекомендуется). Я также уничтожаю рабочий поток, чтобы исключить
    повторное возникновение исключений.
  • Процесс завершается из-за вызова обработчика по умолчанию.
  • Запущенное действие помечается как запускаемое в отдельном процессе (через файл манифеста) и, следовательно, не завершится после завершения первого
    процесса.
Это был большой пост, поэтому спасибо, что нашли время его прочитать. Моя цель — понять рекомендуемый в Android подход к захвату исключений (в C++ и Kotlin) и способы их обработки в различных сценариях (основной поток или рабочий поток).

Подробнее здесь: https://stackoverflow.com/questions/791 ... in-android
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «Android»