-
-
Save mattjohnsonpint/7b385b7a2da7059c4a16562bc5ddb3b7 to your computer and use it in GitHub Desktop.
MIT License | |
Copyright (c) 2022 Matt Johnson-Pint | |
Permission is hereby granted, free of charge, to any person obtaining a copy | |
of this software and associated documentation files (the "Software"), to deal | |
in the Software without restriction, including without limitation the rights | |
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
copies of the Software, and to permit persons to whom the Software is | |
furnished to do so, subject to the following conditions: | |
The above copyright notice and this permission notice shall be included in all | |
copies or substantial portions of the Software. | |
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE | |
SOFTWARE. |
public static class MauiExceptions | |
{ | |
#if WINDOWS | |
private static Exception _lastFirstChanceException; | |
#endif | |
// We'll route all unhandled exceptions through this one event. | |
public static event UnhandledExceptionEventHandler UnhandledException; | |
static MauiExceptions() | |
{ | |
// This is the normal event expected, and should still be used. | |
// It will fire for exceptions from iOS and Mac Catalyst, | |
// and for exceptions on background threads from WinUI 3. | |
AppDomain.CurrentDomain.UnhandledException += (sender, args) => | |
{ | |
UnhandledException?.Invoke(sender, args); | |
}; | |
#if IOS || MACCATALYST | |
// For iOS and Mac Catalyst | |
// Exceptions will flow through AppDomain.CurrentDomain.UnhandledException, | |
// but we need to set UnwindNativeCode to get it to work correctly. | |
// | |
// See: https://github.com/xamarin/xamarin-macios/issues/15252 | |
ObjCRuntime.Runtime.MarshalManagedException += (_, args) => | |
{ | |
args.ExceptionMode = ObjCRuntime.MarshalManagedExceptionMode.UnwindNativeCode; | |
}; | |
#elif ANDROID | |
// For Android: | |
// All exceptions will flow through Android.Runtime.AndroidEnvironment.UnhandledExceptionRaiser, | |
// and NOT through AppDomain.CurrentDomain.UnhandledException | |
Android.Runtime.AndroidEnvironment.UnhandledExceptionRaiser += (sender, args) => | |
{ | |
UnhandledException?.Invoke(sender, new UnhandledExceptionEventArgs(args.Exception, true)); | |
}; | |
#elif WINDOWS | |
// For WinUI 3: | |
// | |
// * Exceptions on background threads are caught by AppDomain.CurrentDomain.UnhandledException, | |
// not by Microsoft.UI.Xaml.Application.Current.UnhandledException | |
// See: https://github.com/microsoft/microsoft-ui-xaml/issues/5221 | |
// | |
// * Exceptions caught by Microsoft.UI.Xaml.Application.Current.UnhandledException have details removed, | |
// but that can be worked around by saved by trapping first chance exceptions | |
// See: https://github.com/microsoft/microsoft-ui-xaml/issues/7160 | |
// | |
AppDomain.CurrentDomain.FirstChanceException += (_, args) => | |
{ | |
_lastFirstChanceException = args.Exception; | |
}; | |
Microsoft.UI.Xaml.Application.Current.UnhandledException += (sender, args) => | |
{ | |
var exception = args.Exception; | |
if (exception.StackTrace is null) | |
{ | |
exception = _lastFirstChanceException; | |
} | |
UnhandledException?.Invoke(sender, new UnhandledExceptionEventArgs(exception, true)); | |
}; | |
#endif | |
} | |
} |
@Danielku15, @rumbledot: you shouldn't need to set Thread.DefaultUncaughtExceptionHandler
, as .NET for Android already sets that property and forwards exceptions to the AppDomain.UnhandledException
event:
- https://github.com/dotnet/android/blob/fc88da7f8bbbed08ca449e3e1c37d99904151ed8/src/java-runtime/java/mono/android/Runtime.java#L14
- https://github.com/dotnet/android/blob/fc88da7f8bbbed08ca449e3e1c37d99904151ed8/src/java-runtime/java/mono/android/Runtime.java#L50
- https://github.com/dotnet/android/blob/fc88da7f8bbbed08ca449e3e1c37d99904151ed8/src/Mono.Android/Android.Runtime/JNIEnv.cs#L140
- https://github.com/dotnet/runtime/blob/731a96b0018cda0c67bb3fab7a4d06538fe2ead5/src/mono/mono/metadata/object.c#L4447-L4462
@jonpryor Interesting, in my case none of the other exception handlers were invoked in scenarios like ClassNotFoundException
(related to broken bindings) so I had to register Thread.DefaultUncaughtExceptionHandler
where I got the callback and details I needed.
I have followed what everybody has mentioned here. Though the customer exception handler code is getting executed which i can verify from the console during debug run on android emulator as well as on real device, app still closes down. I can't get to handle the error and keep the app open. Is that the expected behaviour? Is there a way i can keep the app open and show a error alert?
this is my code
` public static class GlobalExceptions
{
#if WINDOWS
private static Exception _lastFirstChanceException;
#endif
// We'll route all unhandled exceptions through this one event.
public static event UnhandledExceptionEventHandler UnhandledException;
static GlobalExceptions()
{
// This is the normal event expected, and should still be used.
// It will fire for exceptions from iOS and Mac Catalyst,
// and for exceptions on background threads from WinUI 3.
AppDomain.CurrentDomain.UnhandledException += (sender, args) =>
{
UnhandledException?.Invoke(sender, args);
};
// Events fired by the TaskScheduler. That is calls like Task.Run(...)
TaskScheduler.UnobservedTaskException += (sender, args) =>
{
UnhandledException?.Invoke(sender, new UnhandledExceptionEventArgs(args.Exception, false));
};
#if IOS
// For iOS and Mac Catalyst
// Exceptions will flow through AppDomain.CurrentDomain.UnhandledException,
// but we need to set UnwindNativeCode to get it to work correctly.
//
// See: dotnet/macios#15252
ObjCRuntime.Runtime.MarshalManagedException += (_, args) =>
{
args.ExceptionMode = ObjCRuntime.MarshalManagedExceptionMode.UnwindNativeCode;
};
#elif ANDROID
// For Android:
// All exceptions will flow through Android.Runtime.AndroidEnvironment.UnhandledExceptionRaiser,
// and NOT through AppDomain.CurrentDomain.UnhandledException
Android.Runtime.AndroidEnvironment.UnhandledExceptionRaiser += (sender, args) =>
{
args.Handled = true;
UnhandledException?.Invoke(sender, new UnhandledExceptionEventArgs(args.Exception, true));
};
Java.Lang.Thread.DefaultUncaughtExceptionHandler = new CustomUncaughtExceptionHandler(e =>
UnhandledException?.Invoke(null, new UnhandledExceptionEventArgs(e, false)));
#elif WINDOWS
// For WinUI 3:
//
// * Exceptions on background threads are caught by AppDomain.CurrentDomain.UnhandledException,
// not by Microsoft.UI.Xaml.Application.Current.UnhandledException
// See: https://github.com/microsoft/microsoft-ui-xaml/issues/5221
//
// * Exceptions caught by Microsoft.UI.Xaml.Application.Current.UnhandledException have details removed,
// but that can be worked around by saved by trapping first chance exceptions
// See: https://github.com/microsoft/microsoft-ui-xaml/issues/7160
//
AppDomain.CurrentDomain.FirstChanceException += (_, args) =>
{
args.Handled = true;
_lastFirstChanceException = args.Exception;
};
Microsoft.UI.Xaml.Application.Current.UnhandledException += (sender, args) =>
{
var exception = args.Exception;
if (exception.StackTrace is null)
{
exception = _lastFirstChanceException;
}
args.Handled = true;
UnhandledException?.Invoke(sender, new UnhandledExceptionEventArgs(exception, true));
};
#endif
}
}
#if ANDROID
public class CustomUncaughtExceptionHandler(Action<Java.Lang.Throwable> callback)
: Java.Lang.Object, Java.Lang.Thread.IUncaughtExceptionHandler
{
public void UncaughtException(Java.Lang.Thread t, Java.Lang.Throwable e)
{
callback(e);
}
}
#endif
}`
Hi @Danielku15,
Thank you for sharing.
Can you advise how to implement this?