Как не закрывать iOS приложение на .NET MAUI при необработанном исключении?
Я реализовал глобальный обработчик исключений, который в случае любого необработанного исключения не закрывает приложение, а отображает сообщение пользователю, что произошла какая-нибудь ошибка, после чего приложение продолжает работать.
Это очень удобно, ведь не надо всё оборачивать в try/catch.
На Android и Windows всё работает как надо, но вот на iOS (наверное и на MacOS) приложение просто закрывается, а не продолжает работать.
У меня есть вот такой код, который я подглядел здесь:
AppDomain.CurrentDomain.UnhandledException += (_, args) => (args.ExceptionObject as Exception).DisplayError();
#if ANDROID
Android.Runtime.AndroidEnvironment.UnhandledExceptionRaiser += (sender, args) =>
{
args.Handled = true;
args.Exception.DisplayError();
};
#elif WINDOWS
AppDomain.CurrentDomain.FirstChanceException += (_, args) => lastFirstChanceException = args.Exception;
Microsoft.UI.Xaml.Application.Current.UnhandledException += (sender, args) =>
{
Exception exception = args.Exception;
if (exception.StackTrace == null)
{
exception = lastFirstChanceException;
}
args.Handled = true;
args.Exception.DisplayError();
};
#endif
Но вот только на iOS это не работает так, как надо, и приложение в любом случае закрывается при любом необработанном исключении.
Немного покопавшись, я нашел нечто, что помогло мне приблизится к ожидаемому результату:
AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
{
(args.ExceptionObject as Exception).DisplayError();
#if IOS || MACCATALYST
Foundation.NSRunLoop.Current.RunUntil(Foundation.NSDate.DistantFuture);
#endif
};
#if ANDROID
Android.Runtime.AndroidEnvironment.UnhandledExceptionRaiser += (sender, args) =>
{
args.Handled = true;
args.Exception.DisplayError();
};
#elif WINDOWS
AppDomain.CurrentDomain.FirstChanceException += (_, args) => lastFirstChanceException = args.Exception;
Microsoft.UI.Xaml.Application.Current.UnhandledException += (sender, args) =>
{
Exception exception = args.Exception;
if (exception.StackTrace == null)
{
exception = lastFirstChanceException;
}
args.Handled = true;
args.Exception.DisplayError();
};
#endif
После добавления Foundation.NSRunLoop.Current.RunUntil(Foundation.NSDate.DistantFuture) всё стало работать почти так, как надо. При необработанном исключении действительно отображается сообщение с ошибкой, при этом приложение продолжает работать!
Но есть один нюанс... Это работает лишь один раз! Если произойдёт второе необработанное исключение, то приложение просто закроется.
Способ, который я нашёл, это просто какой-то костыль, который я нашел здесь. Причём другие способы не оказали никакого эффекта. Вот я и решил спросить у вас, знает ли кто, что мне делать в такой ситуации.
Ответы (1 шт):
Я ничего толкового не нашёл в интернете, а если и находил, то там лишь говорили, что приложение в любом случае закроется.
После небольших тестов я понял, что Foundation.NSRunLoop.Current.RunUntil(Foundation.NSDate.DistantFuture) можно заменить на Foundation.NSRunLoop.Current.Run(), а так же, что этот метод не завершает свою работу, пока не произойдёт другая ошибка.
То есть метод, который вызывается в событии AppDomain.CurrentDomain.UnhandledException будет работать вечно, вызывая самого себя, будет что-то типа рекурсии, из-за чего ошибка во второй раз не будет отображена пользователю и приложение закроется.
Ну и я решил зайти в Platforms/IOS/Program.cs и написать там это:
public sealed class Program
{
private static void Main(string[] args)
{
bool launching = true;
while (true)
{
try
{
if (launching)
{
launching = false;
UIApplication.Main(args, null, typeof(AppDelegate));
}
else NSRunLoop.Current.Run();
}
catch (Exception ex)
{
ex.DisplayError();
}
}
}
}
То есть, когда приложение только запускается, то вызывается UIApplication.Main, который отрисовывает приложение, а когда происходит исключение, то мы его ловим, затем отображаем пользователю, после чего отрисовкой приложения занимается NSRunLoop.Current.Run и так идёт по кругу.
Это единственный рабочий вариант, но не без подвоха!
Подвох заключается в том, что исключение может произойти в момент того, как отрисовывается какой-нибудь UI-элемент, из-за чего он просто сломается, например: если во время навигации на какую-нибудь страницу где-нибудь произойдёт исключение, то кнопка навигации назад перестанет работать правильно и при нажатии на неё приложение зависнет. Это значит, что придётся проводить дополнительные махинации, чтобы всё это вернуть на круги своя.
На Windows и Android это не происходит, то есть это побочный эффект данного обходного путя.
Ну а подпись на событие AppDomain.CurrentDomain.UnhandledException в билдах для iOS и MacOS можно исключить из-за ненадобности.