50

iOS 中有很多 iOS 崩溃报告库,包括TestFlightHockeyApp。如果您不想依赖服务,您仍然可以使用PLCrashReporter 之类的库。绑定这些库相当简单,因为它们的公共 API 通常由几个类和几个初始化方法组成。

但是,当尝试在我们的应用程序中使用 TestFlight 和后来的 HockeyApp 时,我们的应用程序开始随机崩溃。事实证明,这是一个多次报告 的已知问题但 Xamarin 并没有对此发出警告,它相对晦涩难懂,我们很难找到它。

我们了解到,所有 iOS 崩溃报告器都会阻止 Mono 捕获空引用异常:

try {
    object o = null;
    o.GetHashCode ();
} catch {
    // Catch block isn't called with crash reporting enabled.
    // Instead, the app will crash.
}

为什么会这样?引用 Xamarin 开发人员 Rolf 的话,

空引用异常最初实际上是一个 SIGSEGV 信号。通常,mono 运行时会处理此问题并将其转换为空引用异常,从而允许继续执行。问题是 SIGSEGV 信号在 ObjC 应用程序中是一件非常糟糕的事情(并且当它发生在托管代码之外时),因此任何崩溃报告解决方案都会将其报告为崩溃(并终止应用程序)——这发生在 MonoTouch 获得机会之前处理 SIGSEGV,所以 MonoTouch 对此无能为力。

我敢肯定,很多人在 MonoTouch 应用程序中使用 TestFlight,却不知道它会导致崩溃。
不是很讽刺吗?

如何使崩溃报告库不会导致 MonoTouch 应用程序崩溃?

4

2 回答 2

57

把这个放在AppDelegate.cs

[DllImport ("libc")]
private static extern int sigaction (Signal sig, IntPtr act, IntPtr oact);

enum Signal {
    SIGBUS = 10,
    SIGSEGV = 11
}

static void EnableCrashReporting ()
{
    IntPtr sigbus = Marshal.AllocHGlobal (512);
    IntPtr sigsegv = Marshal.AllocHGlobal (512);

    // Store Mono SIGSEGV and SIGBUS handlers
    sigaction (Signal.SIGBUS, IntPtr.Zero, sigbus);
    sigaction (Signal.SIGSEGV, IntPtr.Zero, sigsegv);

    // Enable crash reporting libraries
    EnableCrashReportingUnsafe ();

    // Restore Mono SIGSEGV and SIGBUS handlers            
    sigaction (Signal.SIGBUS, sigbus, IntPtr.Zero);
    sigaction (Signal.SIGSEGV, sigsegv, IntPtr.Zero);

    Marshal.FreeHGlobal (sigbus);
    Marshal.FreeHGlobal (sigsegv);
}

static void EnableCrashReportingUnsafe ()
{
    // Run your crash reporting library initialization code here--
    // this example uses HockeyApp but it should work well
    // with TestFlight or other libraries.

    // Verify in documentation that your library of choice
    // installs its sigaction hooks before leaving this method.

    var manager = BITHockeyManager.SharedHockeyManager;
    manager.Configure (HockeyAppId, null);
    manager.StartManager ();
}

EnableCrashReporting ()在方法的开头调用FinishedLaunching。如果需要,请
将此调用包装在指令中。#if !DEBUG


它是如何工作的?

我听从了 Rolf 的建议:

一种可能的解决方案是允许 mono 处理所有 SIGSEGV 信号(从技术上讲,崩溃报告库不应该处理 SIGSEGV 信号,或者它应该链接到 mono 的处理程序而不自己做任何处理)。如果 mono 确定 SIGSEGV 信号不是来自托管代码(即发生了非常糟糕的事情),它将引发 SIGABORT 信号(崩溃报告库应该已经处理并视为崩溃)。正如您所理解的,这是必须在崩溃报告库中完成的事情。

Landon Fuller的 Objective C 实现:

#import <signal.h>

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    /* Save Mono's signal handler actions */
    struct sigaction sigbus_action, sigsegv_action;
    sigaction(SIGBUS, NULL, &sigbus_action);
    sigaction(SIGSEGV, NULL, &sigsegv_action);

    // Enable the crash reporter here. Ie, [[PLCrashReporter sharedReporter] enableCrashReporterAndReturnError:],
    // or whatever is the correct initialization mechanism for the crash reporting service you're using

    /* Restore Mono's signal handlers */
    sigaction(SIGBUS, &sigbus_action, NULL);
    sigaction(SIGSEGV, &sigsegv_action, NULL);

    return YES;
}

我使用Banshee 源代码作为如何sigaction从 MonoTouch 调用的参考点。

希望能帮助到你!

于 2013-01-24T10:40:33.727 回答
4

从 Xamarin.iOS 10.4 开始,现在有一种受支持的方式来执行此操作:

static void EnableCrashReporting ()
{
    try {
    } finally {
        Mono.Runtime.RemoveSignalHandlers ();
        try {
            EnableCrashReportingUnsafe ();
        } finally {
            Mono.Runtime.InstallSignalHandlers ();
        }
    }
}

static void EnableCrashReportingUnsafe ()
{
    // Run your crash reporting library initialization code here--
    // this example uses HockeyApp but it should work well
    // with TestFlight or other libraries.

    // Verify in documentation that your library of choice
    // installs its sigaction hooks before leaving this method.

    // Have in mind that at this point Mono will not handle
    // any NullReferenceExceptions, if there are any 
    // NullReferenceExceptions on any thread (not just the current one),
    // then the app will crash.

    var manager = BITHockeyManager.SharedHockeyManager;
    manager.Configure (HockeyAppId, null);
    manager.StartManager ();
}
于 2017-02-23T16:21:51.603 回答