連續啓動 crash 自修復技術實現與原理解析


Table of Contents generated with DocToc前端

 

做者:阿里雲-移動雲-大前端團隊github

前言

連續啓動 Crash 應該是 crash 類型中zui,api

在微信讀書團隊發佈的《iOS 啓動連續閃退保護方案》 一文中,給出了連續啓動crash的自修復技術的思路講解,並在GitHub上給出了技術實現,並開源了 GYBootingProtection。方案思路很好,很輕量級。微信

實現原理

在微信讀書團隊給出的文章中已經有比較詳細的闡述,在此不作贅述,實現的流程圖以下所示:app

enter image description here

但有個實現上能夠優化下,能夠下降50%以上誤報機率,監聽用戶手動劃掉 APP 這個事件,其中一些特定場景,是能夠獲取的。另外在這裏也給出對其 API 設計的建議。最後給出優化後的實現。異步

優化:下降50%以上誤報機率

用戶主動 kill 掉 APP 分爲兩種狀況:async

  • App在前臺時用戶手動劃掉APP的時候
  • APP在後臺時劃掉APP

第一種場景更爲常見,能夠經過監聽 UIApplicationWillTerminateNotification 來捕獲該動做,捕獲後恢復計數。第二種狀況,沒法監聽到。但也足以下降 50% 以上的誤報機率。函數

對原有API設計的幾點優化意見

1. 機制狀態應當用枚舉來作爲API透出

該機制當前所處的狀態,好比:NeedFix 、isFixing,建議用枚舉來作爲API透出。好比:優化

typedef NS_ENUM(NSInteger, BootingProtectionStatus) {
   BootingProtectionStatusNormal,  /**<  APP 啓動正常 */
   BootingProtectionStatusNormalChecking,  /**< 正在檢測是否會在特定時間內是否會 Crash,注意:檢測狀態下「連續啓動崩潰計數」個數小於或等於上限值 */
   BootingProtectionStatusNeedFix, /**< APP 出現連續啓動 Crash,須要採起修復措施 */
   BootingProtectionStatusFixing,   /**< APP 出現連續啓動 Crash,正在修復中... */
};

2. 關鍵數值應當作爲初始化參數供用戶設置

/*!
* 當前啓動Crash的狀態
*/
@property (nonatomic, assign, readonly) ABSBootingProtectionStatus bootingProtectionStatus;

/*!
* 達到須要執行上報操做的「連續啓動崩潰計數」個數。
*/
@property (nonatomic, assign, readonly) NSUInteger continuousCrashOnLaunchNeedToReport;

/*!
* 達到須要執行修復操做的「連續啓動崩潰計數」個數。
*/
@property (nonatomic, assign, readonly) NSUInteger continuousCrashOnLaunchNeedToFix;

/*!
* APP 啓動後通過多少秒,能夠將「連續啓動崩潰計數」清零
*/
@property (nonatomic, assign, readonly) NSTimeInterval crashOnLaunchTimeIntervalThreshold;

3. 修復、上報邏輯應當支持用戶異步操做

reportBlock 上報邏輯,
repairtBlock 修復邏輯

typedef void (^BoolCompletionHandler)(BOOL succeeded, NSError *error);
typedef void (^RepairBlock)(ABSBoolCompletionHandler completionHandler);

用戶執行 BoolCompletionHandler 後便可知道是否執行完畢,而且支持異步操做。

異步操做帶來的問題,能夠經過前面提到的枚舉API來實時監測狀態,來決定各類其餘操做。

何時會出現該異常?

連續啓動 crash 自修復技術實現與原理解析

下面給出優化後的代碼實現:

//
//  CYLBootingProtection.h
//  
//
//  Created by ChenYilong on 18/01/10.
//  Copyright © 2018年 ChenYilong. All rights reserved.
//

#import <Foundation/Foundation.h>

typedef void (^ABSBoolCompletionHandler)(BOOL succeeded, NSError *error);
typedef void (^ABSRepairBlock)(ABSBoolCompletionHandler completionHandler);
typedef void (^ABSReportBlock)(NSUInteger crashCounts);

typedef NS_ENUM(NSInteger, BootingProtectionStatus) {
   BootingProtectionStatusNormal,  /**<  APP 啓動正常 */
   BootingProtectionStatusNormalChecking,  /**< 正在檢測是否會在特定時間內是否會 Crash,注意:檢測狀態下「連續啓動崩潰計數」個數小於或等於上限值 */
   BootingProtectionStatusNeedFix, /**< APP 出現連續啓動 Crash,須要採起修復措施 */
   BootingProtectionStatusFixing,   /**< APP 出現連續啓動 Crash,正在修復中... */
};

/**
* 啓動連續 crash 保護。
* 啓動後 `_crashOnLaunchTimeIntervalThreshold` 秒內 crash,反覆超過 `_continuousCrashOnLaunchNeedToReport` 次則上報日誌,超過 `_continuousCrashOnLaunchNeedToFix` 則啓動修復操做。
*/
@interface CYLBootingProtection : NSObject

/**
* 啓動連續 crash 保護方法。
* 前置條件:在 App 啓動時註冊 crash 處理函數,在 crash 時調用[CYLBootingProtection addCrashCountIfNeeded]。
* 啓動後必定時間內(`crashOnLaunchTimeIntervalThreshold`秒內)crash,反覆超過必定次數(`continuousCrashOnLaunchNeedToReport`次)則上報日誌,超過必定次數(`continuousCrashOnLaunchNeedToFix`次)則啓動修復程序;在必定時間內(`crashOnLaunchTimeIntervalThreshold`秒) 秒後若沒有 crash 將「連續啓動崩潰計數」計數置零。
 `reportBlock` 上報邏輯,
 `repairtBlock` 修復邏輯,完成後執行 `[self setCrashCount:0]`

*/
- (void)launchContinuousCrashProtect;

/*!
* 當前啓動Crash的狀態
*/
@property (nonatomic, assign, readonly) BootingProtectionStatus bootingProtectionStatus;

/*!
* 達到須要執行上報操做的「連續啓動崩潰計數」個數。
*/
@property (nonatomic, assign, readonly) NSUInteger continuousCrashOnLaunchNeedToReport;

/*!
* 達到須要執行修復操做的「連續啓動崩潰計數」個數。
*/
@property (nonatomic, assign, readonly) NSUInteger continuousCrashOnLaunchNeedToFix;

/*!
* APP 啓動後通過多少秒,能夠將「連續啓動崩潰計數」清零
*/
@property (nonatomic, assign, readonly) NSTimeInterval crashOnLaunchTimeIntervalThreshold;

/*!
* 藉助 context 可讓多個模塊註冊事件,而且事件 block 能獨立執行,互不干擾。
*/
@property (nonatomic, copy, readonly) NSString *context;

/*!
* @details 啓動後kCrashOnLaunchTimeIntervalThreshold秒內crash,反覆超過continuousCrashOnLaunchNeedToReport次則上報日誌,超過continuousCrashOnLaunchNeedToFix則啓動修復程序;當全部操做完成後,執行 completion。在 crashOnLaunchTimeIntervalThreshold 秒後若沒有 crash 將 kContinuousCrashOnLaunchCounterKey 計數置零。
* @param context 藉助 context 可讓多個模塊註冊事件,而且事件 block 能獨立執行,互不干擾。
*/
- (instancetype)initWithContinuousCrashOnLaunchNeedToReport:(NSUInteger)continuousCrashOnLaunchNeedToReport
                          continuousCrashOnLaunchNeedToFix:(NSUInteger)continuousCrashOnLaunchNeedToFix
                        crashOnLaunchTimeIntervalThreshold:(NSTimeInterval)crashOnLaunchTimeIntervalThreshold
                                                   context:(NSString *)context;
/*!
* 當前「連續啓動崩潰「的狀態
*/
+ (BootingProtectionStatus)bootingProtectionStatusWithContext:(NSString *)context continuousCrashOnLaunchNeedToFix:(NSUInteger)continuousCrashOnLaunchNeedToFix;

/*!
* 設置上報邏輯,參數 crashCounts 爲啓動連續 crash 次數
*/
- (void)setReportBlock:(ABSReportBlock)reportBlock;

/*!
* 設置修復邏輯
*/
- (void)setRepairBlock:(ABSRepairBlock)repairtBlock;

+ (void)setLogger:(void (^)(NSString *))logger;

@end
//
//  CYLBootingProtection.m
//
//
//  Created by ChenYilong on 18/01/10.
//  Copyright © 2018年 ChenYilong. All rights reserved.
//

#import "CYLBootingProtection.h"
#import <UIKit/UIKit.h>

static dispatch_queue_t _exceptionOperationQueue = 0;
void (^Logger)(NSString *log);

@interface CYLBootingProtection ()

@property (nonatomic, assign) NSUInteger continuousCrashOnLaunchNeedToReport;
@property (nonatomic, assign) NSUInteger continuousCrashOnLaunchNeedToFix;
@property (nonatomic, assign) NSTimeInterval crashOnLaunchTimeIntervalThreshold;
@property (nonatomic, copy) NSString *context;
@property (nonatomic, copy) ABSReportBlock reportBlock;
@property (nonatomic, copy) ABSRepairBlock repairBlock;

/*!
* 設置「連續啓動崩潰計數」個數
*/
- (void)setCrashCount:(NSInteger)count;

/*!
* 設置「連續啓動崩潰計數」個數
*/
+ (void)setCrashCount:(NSUInteger)count context:(NSString *)context;

/*!
* 「連續啓動崩潰計數」個數
*/
- (NSUInteger)crashCount;

/*!
* 「連續啓動崩潰計數」個數
*/
+ (NSUInteger)crashCountWithContext:(NSString *)context;

@end

@implementation CYLBootingProtection
+ (void)initialize {
   static dispatch_once_t onceToken;
   dispatch_once(&onceToken, ^{
       _exceptionOperationQueue = dispatch_queue_create("com.ChenYilong.CYLBootingProtection.fileCacheQueue", DISPATCH_QUEUE_SERIAL);
   });
}
- (instancetype)initWithContinuousCrashOnLaunchNeedToReport:(NSUInteger)continuousCrashOnLaunchNeedToReport
                          continuousCrashOnLaunchNeedToFix:(NSUInteger)continuousCrashOnLaunchNeedToFix
                        crashOnLaunchTimeIntervalThreshold:(NSTimeInterval)crashOnLaunchTimeIntervalThreshold
                                                   context:(NSString *)context {
   if (!(self = [super init])) {
       return nil;
   }
   _continuousCrashOnLaunchNeedToReport = continuousCrashOnLaunchNeedToReport;
   _continuousCrashOnLaunchNeedToFix = continuousCrashOnLaunchNeedToFix;
   _crashOnLaunchTimeIntervalThreshold = crashOnLaunchTimeIntervalThreshold;
   _context = [context copy];
   [[NSNotificationCenter defaultCenter] addObserver:self
                                            selector:@selector(applicationWillTerminate:)
                                                name:UIApplicationWillTerminateNotification
                                              object:[UIApplication sharedApplication]];
   return self;
}

/*!
* App在前臺時用戶手動劃掉APP的時候,不計入檢測。
* 可是APP在後臺時劃掉APP,沒法檢測出來。
* 見:https://stackoverflow.com/a/35041565/3395008
*/
- (void)applicationWillTerminate:(NSNotification *)note {
   BOOL isNormalChecking = [self isNormalChecking];
   if (isNormalChecking) {
       [self decreaseCrashCount];
   }
}

- (void)dealloc {
   [[NSNotificationCenter defaultCenter] removeObserver:self];
}

/*
支持同步修復、異步修復,兩種修復方式
- 異步修復,不卡頓主UI,但有修復未完成就被再次觸發crash、或者用戶kill掉的可能。須要用戶手動根據修復狀態,來選擇性地進行操做,應該有回掉。
- 同步修復,最簡單直觀,在主線程刪除或者下載修復包。
*/
- (void)launchContinuousCrashProtect {
   NSAssert(_repairBlock, @"_repairBlock is nil!");
   [[self class] Logger:@"CYLBootingProtection: Launch continuous crash report"];
   [self resetBootingProtectionStatus];
   
   NSUInteger launchCrashes = [self crashCount];
   // 上報
   if (launchCrashes >= self.continuousCrashOnLaunchNeedToReport) {
       NSString *logString = [NSString stringWithFormat:@"CYLBootingProtection: App has continuously crashed for %@ times. Now synchronize uploading crash report and begin fixing procedure.", @(launchCrashes)];
       [[self class] Logger:logString];
       if (_reportBlock) {
           dispatch_async(dispatch_get_main_queue(),^{
               _reportBlock(launchCrashes);
           });
       }
   }
   
   // 修復
   if ([self isUpToBootingProtectionCount]) {
       [[self class] Logger:@"need to repair"];
       [self setIsFixing:YES];
       if (_repairBlock) {
           ABSBoolCompletionHandler completionHandler = ^(BOOL succeeded, NSError *__nullable error){
               if (succeeded) {
                   [self resetCrashCount];
               } else {
                   [[self class] Logger:error.description];
               }
           };
           dispatch_async(dispatch_get_main_queue(),^{
               _repairBlock(completionHandler);
           });
       }
   } else {
       [self increaseCrashCount:launchCrashes];
       // 正常流程,無需修復
       [[self class] Logger:@"need no repair"];
       
       // 記錄啓動時刻,用於計算啓動連續 crash
       // 重置啓動 crash 計數
       dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(self.crashOnLaunchTimeIntervalThreshold * NSEC_PER_SEC)), dispatch_get_main_queue(), ^(void){
           // APP活過了閾值時間,重置崩潰計數
           NSString *logString = [NSString stringWithFormat:@"CYLBootingProtection: long live the app ( more than %@ seconds ), now reset crash counts", @(self.crashOnLaunchTimeIntervalThreshold)];
           [[self class] Logger:logString];
           [self resetCrashCount];
       });
   }
}

//減小計數的時機:用戶手動劃掉APP
- (void)decreaseCrashCount {
   NSUInteger oldCrashCount = [self crashCount];
   [self decreaseCrashCountWithOldCrashCount:oldCrashCount];
}

- (void)decreaseCrashCountWithOldCrashCount:(NSUInteger)oldCrashCount {
   dispatch_sync(_exceptionOperationQueue, ^{
       if (oldCrashCount > 0) {
           [self setCrashCount:oldCrashCount-1];
       }
       [self resetBootingProtectionStatus];
   });
}

//重製計數的時機:修復完成、或者用戶手動劃掉APP
- (void)resetCrashCount {
   [self setCrashCount:0];
   [self resetBootingProtectionStatus];
}

//只在未達到計數上限時纔會增長計數
- (void)increaseCrashCount:(NSUInteger)oldCrashCount {
   dispatch_sync(_exceptionOperationQueue, ^{
       [self setIsNormalChecking:YES];
       [self setCrashCount:oldCrashCount+1];
   });
}

- (void)resetBootingProtectionStatus {
   [self setIsNormalChecking:NO];
   [self setIsFixing:NO];
}

- (BootingProtectionStatus)bootingProtectionStatus {
   return [[self class] bootingProtectionStatusWithContext:_context continuousCrashOnLaunchNeedToFix:_continuousCrashOnLaunchNeedToFix];
}

/*!
*
@attention 注意之因此要檢查 `BootingProtectionStatusNormalChecking` 緣由以下:

`-launchContinuousCrashProtect` 方法與 `-bootingProtectionStatus` 方法,若是 `-launchContinuousCrashProtect` 先執行,那麼會形成以下問題:
假設n爲上限,但crash(n-1)次,可是用 `-bootingProtectionStatus` 判斷出來,當前已經處於n次了。緣由以下:

crash(n-1)次,正常流程,計數+1,變成n次,
隨後在檢查 `-bootingProtectionStatus` 時,發現已經處於異常狀態了,實際是正常狀態。因此須要使用`BootingProtectionStatusNormalChecking` 來進行區分。
*/
+ (BootingProtectionStatus)bootingProtectionStatusWithContext:(NSString *)context continuousCrashOnLaunchNeedToFix:(NSUInteger)continuousCrashOnLaunchNeedToFix {
   
   BOOL isNormalChecking = [self isNormalCheckingWithContext:context];
   if (isNormalChecking) {
       return BootingProtectionStatusNormalChecking;
   }
   
   BOOL isUpToBootingProtectionCount = [self isUpToBootingProtectionCountWithContext:context
                                                    continuousCrashOnLaunchNeedToFix:continuousCrashOnLaunchNeedToFix];
   if (!isUpToBootingProtectionCount) {
       return BootingProtectionStatusNormal;
   }
   
   BootingProtectionStatus type;
   BOOL isFixingCrash = [self isFixingCrashWithContext:context];
   if (isFixingCrash) {
       type = BootingProtectionStatusFixing;
   } else {
       type = BootingProtectionStatusNeedFix;
   }
   return type;
}

- (NSUInteger)crashCount {
   return [[self class] crashCountWithContext:_context];
}

- (void)setCrashCount:(NSInteger)count {
   if (count >=0) {
       [[self class] setCrashCount:count context:_context];
   }
}

- (void)setIsFixing:(BOOL)isFixingCrash {
   [[self class] setIsFixing:isFixingCrash context:_context];
}

/*!
* 是否正在修復
*/
- (BOOL)isFixingCrash {
   return [[self class] isFixingCrashWithContext:_context];
}

- (void)setIsNormalChecking:(BOOL)isNormalChecking {
   [[self class] setIsNormalChecking:isNormalChecking context:_context];
}

/*!
* 是否正在檢查
*/
- (BOOL)isNormalChecking {
   return [[self class] isNormalCheckingWithContext:_context];
}

+ (NSUInteger)crashCountWithContext:(NSString *)context {
   NSString *continuousCrashOnLaunchCounterKey = [self continuousCrashOnLaunchCounterKeyWithContext:context];
   NSUInteger crashCount = [[NSUserDefaults standardUserDefaults] integerForKey:continuousCrashOnLaunchCounterKey];
   NSString *logString = [NSString stringWithFormat:@"crashCount:%@", @(crashCount)];
   [[self class] Logger:logString];
   return crashCount;
}

+ (void)setCrashCount:(NSUInteger)count context:(NSString *)context {
   NSString *continuousCrashOnLaunchCounterKey = [self continuousCrashOnLaunchCounterKeyWithContext:context];
   NSString *logString = [NSString stringWithFormat:@"setCrashCount:%@", @(count)];
   [[self class] Logger:logString];
   NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
   [defaults setInteger:count forKey:continuousCrashOnLaunchCounterKey];
   [defaults synchronize];
}

+ (void)setIsFixing:(BOOL)isFixingCrash context:(NSString *)context {
   NSString *continuousCrashFixingKey = [[self class] continuousCrashFixingKeyWithContext:context];
   NSString *logString = [NSString stringWithFormat:@"setisFixingCrash:{%@}", @(isFixingCrash)];
   [[self class] Logger:logString];
   NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
   [defaults setBool:isFixingCrash forKey:continuousCrashFixingKey];
   [defaults synchronize];
}

+ (BOOL)isFixingCrashWithContext:(NSString *)context {
   NSString *continuousCrashFixingKey = [[self class] continuousCrashFixingKeyWithContext:context];
   BOOL isFixingCrash = [[NSUserDefaults standardUserDefaults] boolForKey:continuousCrashFixingKey];
   NSString *logString = [NSString stringWithFormat:@"isFixingCrash:%@", @(isFixingCrash)];
   [[self class] Logger:logString];
   return isFixingCrash;
}

+ (void)setIsNormalChecking:(BOOL)isNormalChecking context:(NSString *)context {
   NSString *continuousCrashNormalCheckingKey = [[self class] continuousCrashNormalCheckingKeyWithContext:context];
   NSString *logString = [NSString stringWithFormat:@"setIsNormalChecking:{%@}", @(isNormalChecking)];
   [[self class] Logger:logString];
   NSUserDefaults *defaults = [NSUserDefaults standardUserDefaults];
   [defaults setBool:isNormalChecking forKey:continuousCrashNormalCheckingKey];
   [defaults synchronize];
}

+ (BOOL)isNormalCheckingWithContext:(NSString *)context {
   NSString *continuousCrashFixingKey = [[self class] continuousCrashNormalCheckingKeyWithContext:context];
   BOOL isFixingCrash = [[NSUserDefaults standardUserDefaults] boolForKey:continuousCrashFixingKey];
   NSString *logString = [NSString stringWithFormat:@"isIsNormalChecking:%@", @(isFixingCrash)];
   [[self class] Logger:logString];
   return isFixingCrash;
}

- (BOOL)isUpToBootingProtectionCount {
   return [[self class] isUpToBootingProtectionCountWithContext:_context continuousCrashOnLaunchNeedToFix:_continuousCrashOnLaunchNeedToFix];
}

+ (BOOL)isUpToBootingProtectionCountWithContext:(NSString *)context continuousCrashOnLaunchNeedToFix:(NSUInteger)continuousCrashOnLaunchNeedToFix {
   BOOL isUpToCount = [self crashCountWithContext:context] >= continuousCrashOnLaunchNeedToFix;
   if (isUpToCount) {
       return YES;
   }
   return NO;
}

- (void)setReportBlock:(ABSReportBlock)block {
   _reportBlock = block;
}

- (void)setRepairBlock:(ABSRepairBlock)block {
   _repairBlock = block;
}

/*!
*  「連續啓動崩潰計數」個數,對應的Key
*  默認爲 "_CONTINUOUS_CRASH_COUNTER_KEY"
*/
+ (NSString *)continuousCrashOnLaunchCounterKeyWithContext:(NSString *)context {
   BOOL isValid = [[self class] isValidString:context];
   NSString *validContext = isValid ? context : @"";
   NSString *continuousCrashOnLaunchCounterKey = [NSString stringWithFormat:@"%@_CONTINUOUS_CRASH_COUNTER_KEY", validContext];
   return continuousCrashOnLaunchCounterKey;
}

/*!
*  是否正在修復記錄,對應的Key
*  默認爲 "_CONTINUOUS_CRASH_FIXING_KEY"
*/
+ (NSString *)continuousCrashFixingKeyWithContext:(NSString *)context {
   BOOL isValid = [[self class] isValidString:context];
   NSString *validContext = isValid ? context : @"";
   NSString *continuousCrashFixingKey = [NSString stringWithFormat:@"%@_CONTINUOUS_CRASH_FIXING_KEY", validContext];
   return continuousCrashFixingKey;
}

/*!
*  是否正在檢查是否在特定時間內會Crash,對應的Key
*  默認爲 "_CONTINUOUS_CRASH_CHECKING_KEY"
*/
+ (NSString *)continuousCrashNormalCheckingKeyWithContext:(NSString *)context {
   BOOL isValid = [[self class] isValidString:context];
   NSString *validContext = isValid ? context : @"";
   NSString *continuousCrashFixingKey = [NSString stringWithFormat:@"%@_CONTINUOUS_CRASH_CHECKING_KEY", validContext];
   return continuousCrashFixingKey;
}

#pragma mark -
#pragma mark - log and util Methods

+ (void)setLogger:(void (^)(NSString *))logger {
   Logger = [logger copy];
}

+ (void)Logger:(NSString *)log {
   if (Logger) Logger(log);
}

+ (BOOL)isValidString:(id)notValidString {
   if (!notValidString) {
       return NO;
   }
   if (![notValidString isKindOfClass:[NSString class]]) {
       return NO;
   }
   NSInteger stringLength = 0;
   @try {
       stringLength = [notValidString length];
   } @catch (NSException *exception) {}
   if (stringLength == 0) {
       return NO;
   }
   return YES;
}

@end

下面是相應的驗證步驟:

等待15秒會有對應計數清零的操做日誌輸出:

2018-01-18 16:25:37.162980+0800 BootingProtection[89773:15553277] ?類名與方法名:-[AppDelegate onBeforeBootingProtection]_block_invoke(在第45行),描述:CYLBootingProtection: Launch continuous crash report
2018-01-18 16:25:37.163140+0800 BootingProtection[89773:15553277] ?類名與方法名:-[AppDelegate onBeforeBootingProtection]_block_invoke(在第45行),描述:setIsNormalChecking:{0}
2018-01-18 16:25:37.165738+0800 BootingProtection[89773:15553277] ?類名與方法名:-[AppDelegate onBeforeBootingProtection]_block_invoke(在第45行),描述:setisFixingCrash:{0}
2018-01-18 16:25:37.166883+0800 BootingProtection[89773:15553277] ?類名與方法名:-[AppDelegate onBeforeBootingProtection]_block_invoke(在第45行),描述:crashCount:0
2018-01-18 16:25:37.167102+0800 BootingProtection[89773:15553277] ?類名與方法名:-[AppDelegate onBeforeBootingProtection]_block_invoke(在第45行),描述:crashCount:0
2018-01-18 16:25:37.167253+0800 BootingProtection[89773:15553277] ?類名與方法名:-[AppDelegate onBeforeBootingProtection]_block_invoke(在第45行),描述:setIsNormalChecking:{1}
2018-01-18 16:25:37.167938+0800 BootingProtection[89773:15553277] ?類名與方法名:-[AppDelegate onBeforeBootingProtection]_block_invoke(在第45行),描述:setCrashCount:1
2018-01-18 16:25:37.168806+0800 BootingProtection[89773:15553277] ?類名與方法名:-[AppDelegate onBeforeBootingProtection]_block_invoke(在第45行),描述:need no repair










2018-01-18 16:25:52.225197+0800 BootingProtection[89773:15553277] ?類名與方法名:-[AppDelegate onBeforeBootingProtection]_block_invoke(在第45行),描述:CYLBootingProtection: long live the app ( more than 15 seconds ), now reset crash counts
2018-01-18 16:25:52.225378+0800 BootingProtection[89773:15553277] ?類名與方法名:-[AppDelegate onBeforeBootingProtection]_block_invoke(在第45行),描述:setCrashCount:0
2018-01-18 16:25:52.226234+0800 BootingProtection[89773:15553277] ?類名與方法名:-[AppDelegate onBeforeBootingProtection]_block_invoke(在第45行),描述:setIsNormalChecking:{0}
2018-01-18 16:25:52.226595+0800 BootingProtection[89773:15553277] ?類名與方法名:-[AppDelegate onBeforeBootingProtection]_block_invoke(在第45行),描述:setisFixingCrash:{0}

閱讀原文

http://click.aliyun.com/m/40957/

相關文章
相關標籤/搜索