Исключения 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()
}
}
....
}
Код: Выделить всё
//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;
}
}
Мои вопросы по исключениям Kotlin:
- Могу ли я использовать uncaughtExceptionHandler для перехвата всех исключений, возникающих на уровне Kotlin? Рекомендуемый ли это подход для Android?
- Каковы рекомендации/ожидания Android при вызове обработчика исключений в рабочем потоке? Как объяснялось выше, я могу показать пользователю диалог перезапуска. Конечно, помимо отображения этого сообщения, я должен подготовить диагностическую информацию, например трассировку стека. Это рекомендовано Android? Если нет, то каковы рекомендуемые действия.
- Каковы рекомендации/ожидания Android при вызове обработчика исключений в основном потоке? В этом случае я не могу показать пользователю сообщение с приведенным выше кодом. Поток пользовательского интерфейса возвращается из обработчика исключений, и затем он должен начать отображать диалоговое окно перезапуска, но я не вижу диалогового окна перезапуска. Возможно, я что-то упускаю, но в любом случае, как следует обрабатывать исключения в основном потоке?
Для исключений (таких как 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);
}
Мои вопросы об исключениях C++:
- Если обработчик сигнала вызывается в основном потоке или рабочем потоке, можно ли будет сообщить об этом пользователю, завершить мои структуры, подготовить некоторую диагностическую информацию и аккуратно завершить работу, вместо того, чтобы внезапно привести к сбою приложения? (Судя по моему сообщению о Linux, я не думаю, что это возможно для такого сигнала, как SIGSEGV, но просто хотел подтвердить).
- В моем сообщении о Linux было указано, что Вторичный процесс можно использовать для отображения диалога перезапуска пользователю и запуска нового процесса. Нечто подобное возможно и в Android — цитата из этого сообщения stackoverflow. Когда срабатывает обработчик исключений (обработчик сигнала C++ или uncaughtExceptionHandler в Kotlin), можно запустить новое действие в отдельном процессе и позволить старому процессу завершиться. Таким образом, приложение может проинформировать пользователя и перезапустить новый процесс. Рекомендуется ли это?
- В обработчике исключений я вызову Действие, которое покажет, что что-то пошло не так.
- Сообщите, что я вызываю обработчик исключений по умолчанию (как рекомендуется). Я также уничтожаю рабочий поток, чтобы исключить
повторное возникновение исключений. - Процесс завершается из-за вызова обработчика по умолчанию.
- Запущенное действие помечается как запускаемое в отдельном процессе (через файл манифеста) и, следовательно, не завершится после завершения первого
процесса.
Подробнее здесь: https://stackoverflow.com/questions/791 ... in-android