在 Xamarin 跨平台开发中的全局异常处理

42

请问如何处理Xamarin跨平台项目中的全局异常(避免应用程序崩溃)?


请查看HockeyApp https://hockeyapp.net/ - Milen
2个回答

82

据我所知,没有一种“Xamarin.Forms”方式来实现它。你需要钩入Android和iOS,你可以创建一个方法以相同的方式处理它们。

关于这个问题,Peter Norman写了一篇不错的文章,他描述如何在Android中实现,你可以在MainActivity.cs中完成。

// In MainActivity
protected override void OnCreate(Bundle bundle)
{
    base.OnCreate(bundle);  

    AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException;
    TaskScheduler.UnobservedTaskException += TaskSchedulerOnUnobservedTaskException;  

    Xamarin.Forms.Forms.Init(this, bundle);  
    DisplayCrashReport();  

    var app = new App();  
    LoadApplication(app);
}  

‪#‎region‬ Error handling
private static void TaskSchedulerOnUnobservedTaskException(object sender, UnobservedTaskExceptionEventArgs unobservedTaskExceptionEventArgs)
{
    var newExc = new Exception("TaskSchedulerOnUnobservedTaskException", unobservedTaskExceptionEventArgs.Exception);
    LogUnhandledException(newExc);
}  

private static void CurrentDomainOnUnhandledException(object sender, UnhandledExceptionEventArgs unhandledExceptionEventArgs)
{
    var newExc = new Exception("CurrentDomainOnUnhandledException", unhandledExceptionEventArgs.ExceptionObject as Exception);
    LogUnhandledException(newExc);
}  

internal static void LogUnhandledException(Exception exception)
{
    try
    {
        const string errorFileName = "Fatal.log";
        var libraryPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal); // iOS: Environment.SpecialFolder.Resources
        var errorFilePath = Path.Combine(libraryPath, errorFileName);  
        var errorMessage = String.Format("Time: {0}\r\nError: Unhandled Exception\r\n{1}",
        DateTime.Now, exception.ToString());
        File.WriteAllText(errorFilePath, errorMessage);  

        // Log to Android Device Logging.
        Android.Util.Log.Error("Crash Report", errorMessage);
    }
    catch
    {
        // just suppress any error logging exceptions
    }
}  

/// <summary>
// If there is an unhandled exception, the exception information is diplayed 
// on screen the next time the app is started (only in debug configuration)
/// </summary>
[Conditional("DEBUG")]
private void DisplayCrashReport()
{
    const string errorFilename = "Fatal.log";
    var libraryPath = Environment.GetFolderPath(Environment.SpecialFolder.Personal);
    var errorFilePath = Path.Combine(libraryPath, errorFilename);

    if (!File.Exists(errorFilePath))
    {
        return; 
    }

    var errorText = File.ReadAllText(errorFilePath);
    new AlertDialog.Builder(this)
        .SetPositiveButton("Clear", (sender, args) =>
        {
            File.Delete(errorFilePath);
        })
        .SetNegativeButton("Close", (sender, args) =>
        {
            // User pressed Close.
        })
        .SetMessage(errorText)
        .SetTitle("Crash Report")
        .Show();
} 

‪#‎endregion

对于iOS,您可以在AppDelegate.cs中添加类似以下的代码。

//iOS: Different than Android. Must be in FinishedLaunching, not in Main.
// In AppDelegate
public override bool FinishedLaunching(UIApplication uiApplication, NSDictionary options)
{
    AppDomain.CurrentDomain.UnhandledException += CurrentDomainOnUnhandledException;
    TaskScheduler.UnobservedTaskException += TaskSchedulerOnUnobservedTaskException;  
    // Rest of your code...
}  

/// <summary>
// If there is an unhandled exception, the exception information is diplayed 
// on screen the next time the app is started (only in debug configuration)
/// </summary>
[Conditional("DEBUG")]
private static void DisplayCrashReport()
{
    const string errorFilename = "Fatal.log";
    var libraryPath = Environment.GetFolderPath(Environment.SpecialFolder.Resources);
    var errorFilePath = Path.Combine(libraryPath, errorFilename);

    if (!File.Exists(errorFilePath))
    {
        return;
    }

    var errorText = File.ReadAllText(errorFilePath);
    var alertView = new UIAlertView("Crash Report", errorText, null, "Close", "Clear") { UserInteractionEnabled = true };
    alertView.Clicked += (sender, args) =>
    {
        if (args.ButtonIndex != 0)
        {
            File.Delete(errorFilePath);
        }
    };
    alertView.Show();
}

当您调试应用程序时,它还包括显示日志的功能。当然,您可以实现自己的记录或处理方法。您可以查看 HockeyApp 等工具。默认情况下,此工具会处理未经处理的异常并将它们发送回给您。

更新,因为这仍然在谷歌上找到: 对于崩溃报告和分析,您现在需要开始查看 App Center。这是 HockeyApp 和 Xamarin Insights 的发展(以及其他功能,如构建、分发和推送通知),现在作为与应用程序相关的所有事物的任务仪表板,而不仅仅是 Xamarin。

对于 UWP 和 WinPhone 8.1,Application 对象中应该有一个 UnhandledException 处理程序。查看 此答案 获取更多信息。我引用:

对于基于 XAML 的应用程序,您可以使用 UnhandledException;但是,那只捕获通过 XAML (UI) 框架引起的异常,并且您并不总是会获得有关根本原因的大量信息,即使在 InnerException 中也是如此。

更新 Windows 8.1:: UnhandledException 还将捕获由 async void 方法创建的异常。在 Windows 8 中,这类异常会导致应用程序崩溃。LunarFrog 在其网站上进行了很好的 讨论.

基本上,您应该在您的 App.xaml.cs 的构造函数中添加一个事件处理程序: this.UnhandledException += (o, s) => {}


谢谢Gerald的回复,我会尝试一下。只是确认一下,HockeyApp不会阻止应用程序崩溃,它只会在我们没有正确处理异常时发送错误报告。 - Ashaar
1
感谢您的及时回复。 很抱歉我的描述有误,我现在注意到错误已经出现了,但应用程序将会最小化。 请问是否有任何方法可以保持应用程序的焦点而不是最小化。 - Ashaar
1
我认为现在不可能。这些处理程序并不是设计用于从错误中恢复的,它们是最后一道防线,用于记录更多的错误信息,以便您作为开发人员可以看到更多正在发生的情况。如果您的应用程序达到了这个级别,那么一定出了什么大问题,最好重新启动应用程序。 - Gerald Versluis
1
大家好。我仍然有同样的问题。没有办法简单地捕获所有错误,以便应用程序不会崩溃吗?谁有一个想法,不是每个网络调用或其他后台错误都应该使整个应用程序崩溃,而不是在我的整个代码库中添加数百个try-catch。我的意思是这很基本。谢谢! - Yisroel M. Olewski
3
对我来说在iOS上无法工作。除了两个事件OnUnhandledException和OnUnobservedTaskException,我需要做些什么吗? - Chucky
显示剩余14条评论

1

以下是我为Xamarin Forms Android全局异常处理和应用程序崩溃所做的工作。

此解决方案将处理前台和后台异常。

AppDomain.CurrentDomain.UnhandledException += (s,e)=>
{
    System.Diagnostics.Debug.WriteLine("AppDomain.CurrentDomain.UnhandledException: {0}. IsTerminating: {1}", e.ExceptionObject, e.IsTerminating);
};

AndroidEnvironment.UnhandledExceptionRaiser += (s, e) =>
{
    System.Diagnostics.Debug.WriteLine("AndroidEnvironment.UnhandledExceptionRaiser: {0}. IsTerminating: {1}", e.Exception, e.Handled);
    e.Handled = true;
};

作为结果,我对这两种类型的异常有不同的处理方式:
前台:
后台:

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接