| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403 |
- //
- // JXCrashHandler.m
- // AICity
- //
- // Feature: 003-ios-api-https
- // 剧星平台崩溃处理器
- //
- #import "JXCrashHandler.h"
- #import "JXNetworkManager.h"
- #import <UIKit/UIKit.h>
- #import <sys/utsname.h>
- #import <mach/mach.h>
- @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<NSString *> *)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
|