Я изучал обработку исключений в 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) и способы их обработки в различных сценариях (основной поток или рабочий поток).
Я изучал обработку исключений в Android и хочу знать рекомендуемые способы перехвата исключений и их обработки. Мое приложение для Android в основном написано на C++, а остальное — на Kotlin. Я предпочитаю использовать всеобъемлющий обработчик исключений и предпочитаю избегать try-catch (если в этом нет необходимости). Исключения C++ не могут быть перехвачены Kotlin и наоборот. Итак, я излагаю свое понимание исключений в Kotlin и C++ и способов их обработки в Android. [b]Исключения Kotlin[/b]
В Котлине есть универсальный обработчик исключений, который можно зарегистрировать с помощью метода setDefaultUncaughtExceptionHandler. Мой MainActivity.kt выглядит следующим образом: [code]//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()")
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() } }
.... } [/code] После регистрации uncaughtExceptionHandler я начинаю его тестирование с деления на ноль. Как и ожидалось, мой uncaughtExceptionHandler вызывается в потоке, вызвавшем исключение. Мой класс CustomUncaughtExceptionHandler определяется следующим образом: [code]//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" }
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; } } [/code] Как показано выше, я намерен проинформировать пользователя о возникновении исключения и, если возможно, перезапустить приложение (я знаю, что в большинстве случаев это невозможно). Если рабочий поток вызвал исключение, я могу показать диалоговое окно перезапуска... рабочий поток ждет, пока поток пользовательского интерфейса не сообщит об этом. Я не уверен, нужно ли это, потому что на других платформах (Windows и Linux) при выходе из обработчика исключений происходит рекурсия, поскольку поток возвращается для выполнения предыдущей инструкции, вызвавшей исключение. Но я не наблюдал такого на Android, т. е. даже после возврата из обработчика исключений пользовательский интерфейс остается таким. Мои вопросы по исключениям Kotlin: [list] [*]Могу ли я использовать uncaughtExceptionHandler для перехвата всех исключений, возникающих на уровне Kotlin? Рекомендуемый ли это подход для Android? [*]Каковы рекомендации/ожидания Android при вызове обработчика исключений в рабочем потоке? Как объяснялось выше, я могу показать пользователю диалог перезапуска. Конечно, помимо отображения этого сообщения, я должен подготовить диагностическую информацию, например трассировку стека. Это рекомендовано Android? Если нет, то каковы рекомендуемые действия. [*]Каковы рекомендации/ожидания Android при вызове обработчика исключений в основном потоке? В этом случае я не могу показать пользователю сообщение с приведенным выше кодом. Поток пользовательского интерфейса возвращается из обработчика исключений, и затем он должен начать отображать диалоговое окно перезапуска, но я не вижу диалогового окна перезапуска. Возможно, я что-то упускаю, но в любом случае, как следует обрабатывать исключения в основном потоке? [/list] [b]Исключения C++[/b] Для исключений (таких как SIGSEGV, SIGFPE и т. д.), возникающих в коде C++, можно использовать механизм захвата сигналов Linux (поскольку не существует универсального обработчика исключений - как мне сообщалось в этот пост). Я могу зарегистрировать соответствующие сигналы, и обработчик сигнала вызывается в потоке, который вызывает исключение. Но в отличие от uncaughtExceptionHandler Kotlin, когда я возвращаюсь из обработчика сигнала C++, происходит рекурсия. У меня есть экспериментальный код для выхода из рекурсии после 10 раз, как показано ниже): [code]// ExceptionHandlingCpp.hpp using SignalHandlerFunc = void (*) (int, siginfo_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)); }
pthread_create(&thread1, nullptr, ExceptionHandlingCpp::DivisionByZero, nullptr); } [/code] Как и в Kotlin, я бы предпочел показать пользователю какое-то сообщение, завершить структуру, подготовить некоторую диагностическую информацию, а затем перейти к обработчику по умолчанию для завершения приложения. Если возможно, я хотел бы перезапустить приложение после информирования пользователя, но это будет невозможно в случае исключений, таких как SIGSEGV. Мои вопросы об исключениях C++: [list] [*]Если обработчик сигнала вызывается в основном потоке или рабочем потоке, можно ли будет сообщить об этом пользователю, завершить мои структуры, подготовить некоторую диагностическую информацию и аккуратно завершить работу, вместо того, чтобы внезапно привести к сбою приложения? (Судя по моему сообщению о Linux, я не думаю, что это возможно для такого сигнала, как SIGSEGV, но просто хотел подтвердить). [*]В моем сообщении о Linux было указано, что Вторичный процесс можно использовать для отображения диалога перезапуска пользователю и запуска нового процесса. Нечто подобное возможно и в Android — цитата из этого сообщения stackoverflow. Когда срабатывает обработчик исключений (обработчик сигнала C++ или uncaughtExceptionHandler в Kotlin), можно запустить новое действие в отдельном процессе и позволить старому процессу завершиться. Таким образом, приложение может проинформировать пользователя и перезапустить новый процесс. Рекомендуется ли это? [/list]
[list] [*]В обработчике исключений я вызову Действие, которое покажет, что что-то пошло не так. [*]Сообщите, что я вызываю обработчик исключений по умолчанию (как рекомендуется). Я также уничтожаю рабочий поток, чтобы исключить повторное возникновение исключений. [*]Процесс завершается из-за вызова обработчика по умолчанию.[*]Запущенное действие помечается как запускаемое в отдельном процессе (через файл манифеста) и, следовательно, не завершится после завершения первого процесса. [/list]
Это был большой пост, поэтому спасибо, что нашли время его прочитать. Моя цель — понять рекомендуемый в Android подход к захвату исключений (в C++ и Kotlin) и способы их обработки в различных сценариях (основной поток или рабочий поток).