暫無描述

GYBootingProtection.m 6.8KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181
  1. //
  2. // GYBootingProtection.m
  3. // GYMonitor
  4. //
  5. // Created by jasenhuang on 15/12/22.
  6. //
  7. #import "GYBootingProtection.h"
  8. #import <QuartzCore/QuartzCore.h>
  9. void (^Logger)(NSString *log);
  10. ReportBlock reportBlock;
  11. RepairBlock repairBlock;
  12. BoolCompletionBlock boolCompletionBlock;
  13. static NSString *const kStartupCrashForTest = @"StartupCrashForTest"; // 尝试制造启动 crash 彩蛋
  14. static NSString *const kContinuousCrashOnLaunchCounterKey = @"ContinuousCrashOnLaunchCounter";
  15. static NSString *const kContinuousCrashFixingKey = @"ContinuousCrashFixing"; // 是否正在修复
  16. static NSInteger const kContinuousCrashOnLaunchNeedToReport = 5;
  17. static NSInteger const kContinuousCrashOnLaunchNeedToFix = 3;
  18. static CFTimeInterval const kCrashOnLaunchTimeIntervalThreshold = 5.0;
  19. static CFTimeInterval g_startTick; // 记录启动时刻
  20. @implementation GYBootingProtection
  21. + (BOOL)launchContinuousCrashProtect
  22. {
  23. NSAssert(repairBlock, @"repairBlock is nil!");
  24. if (Logger) Logger(@"GYBootingProtection: Launch continuous crash report");
  25. [self setIsFixing:NO];
  26. NSInteger launchCrashes = [self crashCount];
  27. // 上报
  28. if (launchCrashes >= kContinuousCrashOnLaunchNeedToReport) {
  29. if (Logger) Logger([NSString stringWithFormat:@"GYBootingProtection: App has continuously crashed for %@ times. Now synchronize uploading crash report and begin fixing procedure.", @(launchCrashes)]);
  30. if (reportBlock) reportBlock(launchCrashes);
  31. }
  32. [self setCrashCount:[self crashCount]+1];
  33. // 记录启动时刻,用于计算启动连续 crash
  34. g_startTick = CACurrentMediaTime();
  35. // 重置启动 crash 计数
  36. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(kCrashOnLaunchTimeIntervalThreshold * NSEC_PER_SEC)), dispatch_get_main_queue(), ^(void){
  37. // APP活过了阈值时间,重置崩溃计数
  38. if (Logger) Logger([NSString stringWithFormat:@"GYBootingProtection: long live the app ( more than %@ seconds ), now reset crash counts", @(kCrashOnLaunchTimeIntervalThreshold)]);
  39. [self setCrashCount:0];
  40. });
  41. // 修复
  42. if (launchCrashes >= kContinuousCrashOnLaunchNeedToFix) {
  43. if (Logger) Logger(@"need to repair");
  44. [self setIsFixing:YES];
  45. if (repairBlock) {
  46. repairBlock(^BOOL(){
  47. [self setCrashCount:0];
  48. [self setIsFixing:NO];
  49. if (boolCompletionBlock) {
  50. if (Logger) Logger(@"repairBlock will execute completion block");
  51. return boolCompletionBlock();
  52. } else {
  53. if (Logger) Logger(@"repairBlock will not execute completion block (nil)");
  54. return NO;
  55. }
  56. });
  57. }
  58. } else {
  59. // 正常流程,无需修复
  60. if (Logger) Logger(@"need no repair");
  61. if (boolCompletionBlock) {
  62. if (Logger) Logger(@"will execute completion block");
  63. return boolCompletionBlock();
  64. }
  65. }
  66. return NO;
  67. }
  68. + (void)setIsFixing:(BOOL)isFixingCrash
  69. {
  70. if (Logger) Logger([NSString stringWithFormat:@"setisFixingCrash:{%@}",@(isFixingCrash)]);
  71. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  72. [defaults setBool:isFixingCrash forKey:kContinuousCrashFixingKey];
  73. [defaults synchronize];
  74. }
  75. + (void)setCrashCount:(NSInteger)count
  76. {
  77. if (Logger) Logger([NSString stringWithFormat:@"setCrashCount:%@", @(count)]);
  78. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  79. [defaults setInteger:count forKey:kContinuousCrashOnLaunchCounterKey];
  80. [defaults synchronize];
  81. }
  82. + (BOOL)isFixingCrash
  83. {
  84. BOOL isFixingCrash = [[NSUserDefaults standardUserDefaults] boolForKey:kContinuousCrashFixingKey];
  85. if (Logger) Logger([NSString stringWithFormat:@"isFixingCrash:%@", @(isFixingCrash)]);
  86. return isFixingCrash;
  87. }
  88. + (NSInteger)crashCount
  89. {
  90. NSInteger crashCount = [[NSUserDefaults standardUserDefaults] integerForKey:kContinuousCrashOnLaunchCounterKey];
  91. if (Logger) Logger([NSString stringWithFormat:@"crashCount:%@", @(crashCount)]);
  92. return crashCount;
  93. }
  94. + (void)setLogger:(void (^)(NSString *))logger
  95. {
  96. Logger = [logger copy];
  97. }
  98. + (void)setReportBlock:(ReportBlock)block
  99. {
  100. reportBlock = block;
  101. }
  102. + (void)setRepairBlock:(RepairBlock)block
  103. {
  104. repairBlock = block;
  105. }
  106. + (void)setBoolCompletionBlock:(BoolCompletionBlock)block
  107. {
  108. boolCompletionBlock = block;
  109. }
  110. + (void)setStartupCrashForTest:(BOOL)isOn
  111. {
  112. if (Logger) Logger([NSString stringWithFormat:@"setStartupCrashForTest:%@", @(isOn)]);
  113. NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
  114. [defaults setInteger:isOn forKey:kStartupCrashForTest];
  115. [defaults synchronize];
  116. }
  117. + (BOOL)startupCrashForTest
  118. {
  119. if ([GYBootingProtection crashCount] >= kContinuousCrashOnLaunchNeedToFix) {
  120. return NO;
  121. }
  122. BOOL ret = [[NSUserDefaults standardUserDefaults] boolForKey:kStartupCrashForTest];
  123. if (Logger) Logger([NSString stringWithFormat:@"startupCrashForTest:%@", @(ret)]);
  124. return ret;
  125. }
  126. + (void)deleteAllFilesUnderDocumentsLibraryCaches {
  127. NSString *documentsDirectory = [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject];
  128. NSString *libraryDirectory = [NSSearchPathForDirectoriesInDomains(NSLibraryDirectory, NSUserDomainMask, YES) firstObject];
  129. NSString *cachesDirectory = [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject];
  130. NSArray *filePathsToRemove = @[documentsDirectory, libraryDirectory, cachesDirectory];
  131. NSFileManager *fileMgr = [NSFileManager defaultManager];
  132. for (NSString *filePath in filePathsToRemove) {
  133. if ([fileMgr fileExistsAtPath:filePath]) {
  134. NSArray *subFileArray = [fileMgr contentsOfDirectoryAtPath:filePath error:nil];
  135. for (NSString *subFileName in subFileArray) {
  136. NSString *subFilePath = [filePath stringByAppendingPathComponent:subFileName];
  137. if ([fileMgr removeItemAtPath:subFilePath error:nil]) {
  138. NSLog(@"removed file path:%@", subFilePath);
  139. } else {
  140. NSLog(@"failed to remove file path:%@", subFilePath);
  141. }
  142. }
  143. } else {
  144. NSLog(@"failed to remove non-existing file path:%@", filePath);
  145. }
  146. }
  147. NSLog(@"recoverFromContinuousCrash finished, files at home:[%@]\nDocuments:[%@]\nLibrary:[%@]\nCaches:[%@]",
  148. [[NSFileManager defaultManager] contentsOfDirectoryAtPath:NSHomeDirectory() error:nil],
  149. [[NSFileManager defaultManager] contentsOfDirectoryAtPath:documentsDirectory error:nil],
  150. [[NSFileManager defaultManager] contentsOfDirectoryAtPath:libraryDirectory error:nil],
  151. [[NSFileManager defaultManager] contentsOfDirectoryAtPath:cachesDirectory error:nil]);
  152. }
  153. @end