You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

154 lines
6.1 KiB

import Flutter
import MachO
import UIKit
@main
@objc class AppDelegate: FlutterAppDelegate {
override func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
) -> Bool {
application.applicationIconBadgeNumber = 0
NativeCrashRecorder.install()
GeneratedPluginRegistrant.register(with: self)
// 第三方 SDK(高德/杰理等)初始化时可能装自己的信号处理器把我们顶掉,
// 过一会儿再装一次;装的时候会把当时在位的处理器记下来链式调用,两边都不丢。
DispatchQueue.main.asyncAfter(deadline: .now() + 15) { NativeCrashRecorder.install() }
return super.application(application, didFinishLaunchingWithOptions: launchOptions)
}
override func applicationDidBecomeActive(_ application: UIApplication) {
application.applicationIconBadgeNumber = 0
super.applicationDidBecomeActive(application)
}
// 添加URL回调监听
override func application(
_ app: UIApplication,
open url: URL,
options: [UIApplication.OpenURLOptionsKey : Any] = [:]
) -> Bool {
print("AppDelegate收到URL回调: \(url.absoluteString)")
// 首先尝试让父类处理
if super.application(app, open: url, options: options) {
return true
}
return false
}
}
// MARK: - 原生崩溃记录(2026-09-26)
//
// 起因:车载香薰对话里 App 两次 SIGSEGV 退出,但这台 iPhone 上**一份系统崩溃报告都没留下**
// (「分析数据」里最新的还是 9-21),devicectl 只能看到 "terminated due to signal 11",
// 没有调用栈就没法定位。怀疑是某个 SDK 装了信号处理器却没把信号交还系统。
//
// 这里在崩溃那一刻把调用栈写进 Documents/logs/native_crash.log(和 App 日志同目录,
// devicectl 能直接拉),然后把信号交还给之前在位的处理器 / 系统默认行为,不改变崩溃本身。
// 调用栈是「镜像 + 偏移」形式,拿同一次构建的 dSYM 用 atos 还原成函数名。
//
// 信号处理函数里只用 write / backtrace_symbols_fd 这类 async-signal-safe 的调用,
// 不分配内存、不碰 Swift 运行时的锁。
private var crashFD: Int32 = -1
/// 调用栈缓冲:安装时就分配好,信号处理里不再申请内存
private let crashFrames = UnsafeMutablePointer<UnsafeMutableRawPointer?>.allocate(capacity: 128)
private let crashSignals: [Int32] = [SIGSEGV, SIGBUS, SIGABRT, SIGILL, SIGFPE, SIGTRAP]
/// 安装时在位的旧处理器(按信号号索引),崩溃时链式调用
private let previousHandlers = UnsafeMutablePointer<sig_t?>.allocate(capacity: 32)
private var previousExceptionHandler: (@convention(c) (NSException) -> Void)?
private func crashWrite(_ s: StaticString) {
guard crashFD >= 0 else { return }
_ = s.withUTF8Buffer { write(crashFD, $0.baseAddress, $0.count) }
}
private func crashWriteInt(_ value: Int) {
guard crashFD >= 0 else { return }
// 用栈上的元组当缓冲,不走堆分配
var buf: (UInt64, UInt64, UInt64) = (0, 0, 0)
withUnsafeMutableBytes(of: &buf) { raw in
let p = raw.bindMemory(to: UInt8.self)
var v = value < 0 ? -value : value
var i = p.count
repeat { i -= 1; p[i] = UInt8(48 + v % 10); v /= 10 } while v > 0 && i > 0
if value < 0 && i > 0 { i -= 1; p[i] = 45 }
_ = write(crashFD, p.baseAddress! + i, p.count - i)
}
}
private let crashSignalHandler: @convention(c) (Int32) -> Void = { sig in
crashWrite("\n=== native crash signal=")
crashWriteInt(Int(sig))
crashWrite(" time=")
crashWriteInt(Int(time(nil)))
crashWrite(" ===\n")
let n = backtrace(crashFrames, 128)
backtrace_symbols_fd(crashFrames, n, crashFD)
crashWrite("=== images ===\n")
// 各镜像的加载基址:atos 还原时要用(-l <基址>)
let count = _dyld_image_count()
for idx in 0..<count {
if let name = _dyld_get_image_name(idx) {
let header = _dyld_get_image_header(idx)
crashWriteInt(Int(bitPattern: header))
crashWrite(" ")
_ = write(crashFD, name, strlen(name))
crashWrite("\n")
}
}
fsync(crashFD)
// 交还:之前有人装过处理器就调它(SIG_DFL=0 / SIG_IGN=1 不算),否则恢复默认并重发信号
if let prev = previousHandlers[Int(sig)] {
let raw = unsafeBitCast(prev, to: Int.self)
if raw != 0 && raw != 1 {
prev(sig)
}
}
signal(sig, SIG_DFL)
raise(sig)
}
enum NativeCrashRecorder {
private static var initialized = false
static func install() {
if !initialized {
initialized = true
previousHandlers.initialize(repeating: nil, count: 32)
}
if crashFD < 0 {
let docs = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)[0]
let dir = docs.appendingPathComponent("logs")
try? FileManager.default.createDirectory(at: dir, withIntermediateDirectories: true)
crashFD = open(dir.appendingPathComponent("native_crash.log").path,
O_WRONLY | O_CREAT | O_APPEND, 0o644)
}
let mine = unsafeBitCast(crashSignalHandler, to: Int.self)
for sig in crashSignals {
let prev = signal(sig, crashSignalHandler)
// 在位的已经是我们自己(第二次 install):别记成「旧处理器」,否则崩溃时会调到自己、无限递归
if let p = prev, unsafeBitCast(p, to: Int.self) == mine { continue }
previousHandlers[Int(sig)] = prev
}
let current = NSGetUncaughtExceptionHandler()
if current.map({ unsafeBitCast($0, to: Int.self) }) != unsafeBitCast(exceptionHandler, to: Int.self) {
previousExceptionHandler = current
NSSetUncaughtExceptionHandler(exceptionHandler)
}
}
private static let exceptionHandler: @convention(c) (NSException) -> Void = { e in
let text = "\n=== uncaught exception \(Date()) \(e.name.rawValue): \(e.reason ?? "")\n"
+ e.callStackSymbols.joined(separator: "\n") + "\n"
if crashFD >= 0, let d = text.data(using: .utf8) {
d.withUnsafeBytes { _ = write(crashFD, $0.baseAddress, d.count) }
fsync(crashFD)
}
previousExceptionHandler?(e)
}
}