// // JXCrashHandler.m // AICity // // Feature: 003-ios-api-https // 剧星平台崩溃处理器 // #import "JXCrashHandler.h" #import "JXNetworkManager.h" #import #import #import @implementation JXCrashInfo @end @interface JXCrashHandler () @property (nonatomic, assign) BOOL isInstalled; @property (nonatomic, strong) NSDateFormatter *dateFormatter; @end @implementation JXCrashHandler + (instancetype)sharedHandler { static JXCrashHandler *sharedInstance = nil; static dispatch_once_t onceToken; dispatch_once(&onceToken, ^{ sharedInstance = [[JXCrashHandler alloc] init]; }); return sharedInstance; } - (instancetype)init { self = [super init]; if (self) { _isInstalled = NO; _dateFormatter = [[NSDateFormatter alloc] init]; _dateFormatter.dateFormat = @"yyyy-MM-dd HH:mm:ss"; _dateFormatter.locale = [NSLocale localeWithLocaleIdentifier:@"en_US_POSIX"]; } return self; } #pragma mark - 崩溃处理器安装/卸载 - (void)installCrashHandler { if (self.isInstalled) { NSLog(@"JXCrashHandler already installed"); return; } // 安装异常处理器 NSSetUncaughtExceptionHandler(&JXUncaughtExceptionHandler); // 安装信号处理器 [self installSignalHandler]; self.isInstalled = YES; NSLog(@"JXCrashHandler installed successfully"); } - (void)uninstallCrashHandler { if (!self.isInstalled) { return; } // 移除异常处理器 NSSetUncaughtExceptionHandler(NULL); // 移除信号处理器 [self uninstallSignalHandler]; self.isInstalled = NO; NSLog(@"JXCrashHandler uninstalled"); } - (void)installSignalHandler { signal(SIGABRT, JXSignalHandler); signal(SIGILL, JXSignalHandler); signal(SIGSEGV, JXSignalHandler); signal(SIGFPE, JXSignalHandler); signal(SIGBUS, JXSignalHandler); signal(SIGPIPE, JXSignalHandler); } - (void)uninstallSignalHandler { signal(SIGABRT, SIG_DFL); signal(SIGILL, SIG_DFL); signal(SIGSEGV, SIG_DFL); signal(SIGFPE, SIG_DFL); signal(SIGBUS, SIG_DFL); signal(SIGPIPE, SIG_DFL); } #pragma mark - 崩溃处理函数 void JXUncaughtExceptionHandler(NSException *exception) { JXCrashHandler *handler = [JXCrashHandler sharedHandler]; [handler handleException:exception]; } void JXSignalHandler(int signal) { JXCrashHandler *handler = [JXCrashHandler sharedHandler]; [handler handleSignal:signal]; } - (void)handleException:(NSException *)exception { @try { JXCrashInfo *crashInfo = [[JXCrashInfo alloc] init]; crashInfo.timestamp = [[NSDate date] timeIntervalSince1970]; crashInfo.exceptionName = exception.name ?: @"Unknown"; crashInfo.exceptionReason = exception.reason ?: @"Unknown"; crashInfo.callStack = exception.callStackSymbols ?: @[]; crashInfo.deviceInfo = [self collectDeviceInfo]; crashInfo.appInfo = [self collectAppInfo]; // 保存到本地 [self saveCrashInfoToLocal:crashInfo]; // 异步上报 dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ [self reportCrashInfo:crashInfo]; }); NSLog(@"JXCrashHandler: Exception handled - %@", exception.name); } @catch (NSException *e) { NSLog(@"JXCrashHandler: Error handling exception - %@", e); } } - (void)handleSignal:(int)sig { @try { NSString *signalName = [self signalNameForSignal:sig]; JXCrashInfo *crashInfo = [[JXCrashInfo alloc] init]; crashInfo.timestamp = [[NSDate date] timeIntervalSince1970]; crashInfo.exceptionName = [NSString stringWithFormat:@"Signal %d", sig]; crashInfo.exceptionReason = signalName; crashInfo.callStack = [NSThread callStackSymbols]; crashInfo.deviceInfo = [self collectDeviceInfo]; crashInfo.appInfo = [self collectAppInfo]; // 保存到本地 [self saveCrashInfoToLocal:crashInfo]; // 同步上报(信号处理中不能使用异步) [self reportCrashInfo:crashInfo]; NSLog(@"JXCrashHandler: Signal handled - %@", signalName); } @catch (NSException *e) { NSLog(@"JXCrashHandler: Error handling signal - %@", e); } // 恢复默认信号处理并重新发送信号 signal(sig, SIG_DFL); raise(sig); } - (NSString *)signalNameForSignal:(int)signal { switch (signal) { case SIGABRT: return @"SIGABRT"; case SIGILL: return @"SIGILL"; case SIGSEGV: return @"SIGSEGV"; case SIGFPE: return @"SIGFPE"; case SIGBUS: return @"SIGBUS"; case SIGPIPE: return @"SIGPIPE"; default: return [NSString stringWithFormat:@"Signal %d", signal]; } } #pragma mark - 信息收集 - (NSDictionary *)collectDeviceInfo { struct utsname systemInfo; uname(&systemInfo); UIDevice *device = [UIDevice currentDevice]; // 获取内存信息 mach_port_t host_port = mach_host_self(); mach_msg_type_number_t host_size = sizeof(vm_statistics_data_t) / sizeof(natural_t); vm_size_t pagesize; vm_statistics_data_t vm_stat; host_page_size(host_port, &pagesize); host_statistics(host_port, HOST_VM_INFO, (host_info_t)&vm_stat, &host_size); unsigned long long totalMemory = [NSProcessInfo processInfo].physicalMemory; unsigned long long freeMemory = (unsigned long long)vm_stat.free_count * pagesize; return @{ @"device_model": [NSString stringWithCString:systemInfo.machine encoding:NSUTF8StringEncoding], @"system_name": device.systemName, @"system_version": device.systemVersion, @"device_name": device.name, @"identifier_for_vendor": device.identifierForVendor.UUIDString ?: @"unknown", @"total_memory": @(totalMemory), @"free_memory": @(freeMemory), @"used_memory": @(totalMemory - freeMemory), @"battery_level": @(device.batteryLevel), @"battery_state": @(device.batteryState) }; } - (NSDictionary *)collectAppInfo { NSBundle *mainBundle = [NSBundle mainBundle]; NSDictionary *infoDictionary = mainBundle.infoDictionary; return @{ @"bundle_identifier": mainBundle.bundleIdentifier ?: @"unknown", @"app_version": infoDictionary[@"CFBundleShortVersionString"] ?: @"unknown", @"build_number": infoDictionary[@"CFBundleVersion"] ?: @"unknown", @"executable_name": infoDictionary[@"CFBundleExecutable"] ?: @"unknown", @"bundle_name": infoDictionary[@"CFBundleName"] ?: @"unknown", @"process_id": @([[NSProcessInfo processInfo] processIdentifier]), @"process_name": [[NSProcessInfo processInfo] processName], @"launch_time": @([[NSProcessInfo processInfo] systemUptime]) }; } #pragma mark - 本地存储 - (void)saveCrashInfoToLocal:(JXCrashInfo *)crashInfo { @try { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; NSString *crashDirectory = [documentsDirectory stringByAppendingPathComponent:@"JXCrashes"]; // 创建崩溃日志目录 NSFileManager *fileManager = [NSFileManager defaultManager]; if (![fileManager fileExistsAtPath:crashDirectory]) { [fileManager createDirectoryAtPath:crashDirectory withIntermediateDirectories:YES attributes:nil error:nil]; } // 生成文件名 NSDate *crashDate = [NSDate dateWithTimeIntervalSince1970:crashInfo.timestamp]; NSString *dateString = [self.dateFormatter stringFromDate:crashDate]; dateString = [dateString stringByReplacingOccurrencesOfString:@" " withString:@"_"]; dateString = [dateString stringByReplacingOccurrencesOfString:@":" withString:@"-"]; NSString *fileName = [NSString stringWithFormat:@"crash_%@.txt", dateString]; NSString *filePath = [crashDirectory stringByAppendingPathComponent:fileName]; // 格式化崩溃信息 NSString *crashReport = [self formatCrashInfo:crashInfo]; // 写入文件 [crashReport writeToFile:filePath atomically:YES encoding:NSUTF8StringEncoding error:nil]; NSLog(@"JXCrashHandler: Crash info saved to %@", filePath); } @catch (NSException *e) { NSLog(@"JXCrashHandler: Failed to save crash info - %@", e); } } - (NSString *)formatCrashInfo:(JXCrashInfo *)crashInfo { NSMutableString *report = [NSMutableString string]; [report appendString:@"=== JuXing Crash Report ===\n"]; NSDate *crashDate = [NSDate dateWithTimeIntervalSince1970:crashInfo.timestamp]; [report appendFormat:@"Timestamp: %@\n", [self.dateFormatter stringFromDate:crashDate]]; [report appendFormat:@"Exception: %@\n", crashInfo.exceptionName]; [report appendFormat:@"Reason: %@\n", crashInfo.exceptionReason]; [report appendString:@"\n"]; [report appendString:@"=== Device Info ===\n"]; for (NSString *key in crashInfo.deviceInfo) { [report appendFormat:@"%@: %@\n", key, crashInfo.deviceInfo[key]]; } [report appendString:@"\n"]; [report appendString:@"=== App Info ===\n"]; for (NSString *key in crashInfo.appInfo) { [report appendFormat:@"%@: %@\n", key, crashInfo.appInfo[key]]; } [report appendString:@"\n"]; [report appendString:@"=== Call Stack ===\n"]; for (NSString *symbol in crashInfo.callStack) { [report appendFormat:@"%@\n", symbol]; } return report; } #pragma mark - 崩溃上报 - (void)reportCrashInfo:(JXCrashInfo *)crashInfo { @try { NSDictionary *crashReport = @{ @"timestamp": @(crashInfo.timestamp), @"exception_name": crashInfo.exceptionName, @"exception_reason": crashInfo.exceptionReason, @"call_stack": crashInfo.callStack, @"device_info": crashInfo.deviceInfo, @"app_info": crashInfo.appInfo, @"platform": @"ios" }; // 这里应该调用实际的API接口上报崩溃信息 // JXNetworkManager *networkManager = [JXNetworkManager sharedManager]; // [networkManager POST:@"monitor/crash" parameters:crashReport success:^(id responseObject) { // NSLog(@"JXCrashHandler: Crash report sent successfully"); // } failure:^(NSError *error) { // NSLog(@"JXCrashHandler: Failed to send crash report - %@", error); // }]; NSLog(@"JXCrashHandler: Crash report prepared for upload"); } @catch (NSException *e) { NSLog(@"JXCrashHandler: Failed to report crash info - %@", e); } } #pragma mark - 崩溃日志管理 - (NSArray *)getLocalCrashLogs { @try { NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString *documentsDirectory = [paths objectAtIndex:0]; NSString *crashDirectory = [documentsDirectory stringByAppendingPathComponent:@"JXCrashes"]; NSFileManager *fileManager = [NSFileManager defaultManager]; if (![fileManager fileExistsAtPath:crashDirectory]) { return @[]; } NSArray *files = [fileManager contentsOfDirectoryAtPath:crashDirectory error:nil]; NSMutableArray *crashLogs = [NSMutableArray array]; for (NSString *fileName in files) { if ([fileName hasPrefix:@"crash_"] && [fileName hasSuffix:@".txt"]) { NSString *filePath = [crashDirectory stringByAppendingPathComponent:fileName]; [crashLogs addObject:filePath]; } } // 按修改时间排序 [crashLogs sortUsingComparator:^NSComparisonResult(NSString *path1, NSString *path2) { NSDictionary *attrs1 = [fileManager attributesOfItemAtPath:path1 error:nil]; NSDictionary *attrs2 = [fileManager attributesOfItemAtPath:path2 error:nil]; NSDate *date1 = attrs1[NSFileModificationDate]; NSDate *date2 = attrs2[NSFileModificationDate]; return [date2 compare:date1]; // 降序排列 }]; return [crashLogs copy]; } @catch (NSException *e) { NSLog(@"JXCrashHandler: Failed to get local crash logs - %@", e); return @[]; } } - (void)cleanupOldCrashLogs:(NSTimeInterval)maxAge { @try { NSArray *crashLogs = [self getLocalCrashLogs]; NSFileManager *fileManager = [NSFileManager defaultManager]; NSTimeInterval currentTime = [[NSDate date] timeIntervalSince1970]; NSInteger deletedCount = 0; for (NSString *filePath in crashLogs) { NSDictionary *attributes = [fileManager attributesOfItemAtPath:filePath error:nil]; NSDate *modificationDate = attributes[NSFileModificationDate]; NSTimeInterval fileAge = currentTime - [modificationDate timeIntervalSince1970]; if (fileAge > maxAge) { if ([fileManager removeItemAtPath:filePath error:nil]) { deletedCount++; } } } NSLog(@"JXCrashHandler: Cleaned up %ld old crash logs", (long)deletedCount); } @catch (NSException *e) { NSLog(@"JXCrashHandler: Failed to cleanup old crash logs - %@", e); } } @end