连续启动 crash 自修复技术实现与原理解析

本文涉及的产品
日志服务 SLS,月写入数据量 50GB 1个月
云解析 DNS,旗舰版 1个月
全局流量管理 GTM,标准版 1个月
简介: **Table of Contents** *generated with [DocToc](https://github.com/thlorenz/doctoc)* - [前言](#%E5%89%8D%E8%A8%80) - [实现原理](#%E5%AE%9E%E7%8E%B0%E5%8E%9F%E7%90%86) - [优化:降低50%以上误报机率](#%E4%BC%98%



Table of Contents generated with DocToc

作者:阿里云-移动云-大前端团队

前言

连续启动 Crash 应该是 crash 类型中zui,

在微信读书团队发布的《iOS 启动连续闪退保护方案》 一文中,给出了连续启动crash的自修复技术的思路讲解,并在GitHub上给出了技术实现,并开源了 GYBootingProtection。方案思路很好,很轻量级。

实现原理

在微信读书团队给出的文章中已经有比较详细的阐述,在此不做赘述,实现的流程图如下所示:

enter image description here

但有个实现上可以优化下,可以降低50%以上误报机率,监听用户手动划掉 APP 这个事件,其中一些特定场景,是可以获取的。另外在这里也给出对其 API 设计的建议。最后给出优化后的实现。

优化:降低50%以上误报机率

用户主动 kill 掉 APP 分为两种情况:

  • 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}
相关文章
|
7天前
|
机器学习/深度学习 算法 数据挖掘
解析静态代理IP改善游戏体验的原理
静态代理IP通过提高网络稳定性和降低延迟,优化游戏体验。具体表现在加快游戏网络速度、实时玩家数据分析、优化游戏设计、简化更新流程、维护网络稳定性、提高连接可靠性、支持地区特性及提升访问速度等方面,确保更流畅、高效的游戏体验。
53 22
解析静态代理IP改善游戏体验的原理
|
4天前
|
编解码 缓存 Prometheus
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
本期内容为「ximagine」频道《显示器测试流程》的规范及标准,我们主要使用Calman、DisplayCAL、i1Profiler等软件及CA410、Spyder X、i1Pro 2等设备,是我们目前制作内容数据的重要来源,我们深知所做的仍是比较表面的活儿,和工程师、科研人员相比有着不小的差距,测试并不复杂,但是相当繁琐,收集整理测试无不花费大量时间精力,内容不完善或者有错误的地方,希望大佬指出我们好改进!
48 16
「ximagine」业余爱好者的非专业显示器测试流程规范,同时也是本账号输出内容的数据来源!如何测试显示器?荒岛整理总结出多种测试方法和注意事项,以及粗浅的原理解析!
|
12天前
|
机器学习/深度学习 人工智能 算法
DeepSeek技术报告解析:为什么DeepSeek-R1 可以用低成本训练出高效的模型
DeepSeek-R1 通过创新的训练策略实现了显著的成本降低,同时保持了卓越的模型性能。本文将详细分析其核心训练方法。
310 11
DeepSeek技术报告解析:为什么DeepSeek-R1 可以用低成本训练出高效的模型
|
1月前
|
机器学习/深度学习 自然语言处理 搜索推荐
自注意力机制全解析:从原理到计算细节,一文尽览!
自注意力机制(Self-Attention)最早可追溯至20世纪70年代的神经网络研究,但直到2017年Google Brain团队提出Transformer架构后才广泛应用于深度学习。它通过计算序列内部元素间的相关性,捕捉复杂依赖关系,并支持并行化训练,显著提升了处理长文本和序列数据的能力。相比传统的RNN、LSTM和GRU,自注意力机制在自然语言处理(NLP)、计算机视觉、语音识别及推荐系统等领域展现出卓越性能。其核心步骤包括生成查询(Q)、键(K)和值(V)向量,计算缩放点积注意力得分,应用Softmax归一化,以及加权求和生成输出。自注意力机制提高了模型的表达能力,带来了更精准的服务。
|
5天前
|
人工智能 自然语言处理 算法
DeepSeek模型的突破:性能超越R1满血版的关键技术解析
上海AI实验室周伯文团队的最新研究显示,7B版本的DeepSeek模型在性能上超越了R1满血版。该成果强调了计算最优Test-Time Scaling的重要性,并提出了一种创新的“弱到强”优化监督机制的研究思路,区别于传统的“从强到弱”策略。这一方法不仅提升了模型性能,还为未来AI研究提供了新方向。
241 5
|
1月前
|
缓存 算法 Oracle
深度干货 如何兼顾性能与可靠性?一文解析YashanDB主备高可用技术
数据库高可用(High Availability,HA)是指在系统遇到故障或异常情况时,能够自动快速地恢复并保持服务可用性的能力。如果数据库只有一个实例,该实例所在的服务器一旦发生故障,那就很难在短时间内恢复服务。长时间的服务中断会造成很大的损失,因此数据库高可用一般通过多实例副本冗余实现,如果一个实例发生故障,则可以将业务转移到另一个实例,快速恢复服务。
深度干货  如何兼顾性能与可靠性?一文解析YashanDB主备高可用技术
|
1月前
|
Serverless 对象存储 人工智能
智能文件解析:体验阿里云多模态信息提取解决方案
在当今数据驱动的时代,信息的获取和处理效率直接影响着企业决策的速度和质量。然而,面对日益多样化的文件格式(文本、图像、音频、视频),传统的处理方法显然已经无法满足需求。
94 4
智能文件解析:体验阿里云多模态信息提取解决方案
|
1月前
|
Kubernetes Linux 虚拟化
入门级容器技术解析:Docker和K8s的区别与关系
本文介绍了容器技术的发展历程及其重要组成部分Docker和Kubernetes。从传统物理机到虚拟机,再到容器化,每一步都旨在更高效地利用服务器资源并简化应用部署。容器技术通过隔离环境、减少依赖冲突和提高可移植性,解决了传统部署方式中的诸多问题。Docker作为容器化平台,专注于创建和管理容器;而Kubernetes则是一个强大的容器编排系统,用于自动化部署、扩展和管理容器化应用。两者相辅相成,共同推动了现代云原生应用的快速发展。
193 11
|
2月前
|
存储 物联网 大数据
探索阿里云 Flink 物化表:原理、优势与应用场景全解析
阿里云Flink的物化表是流批一体化平台中的关键特性,支持低延迟实时更新、灵活查询性能、无缝流批处理和高容错性。它广泛应用于电商、物联网和金融等领域,助力企业高效处理实时数据,提升业务决策能力。实践案例表明,物化表显著提高了交易欺诈损失率的控制和信贷审批效率,推动企业在数字化转型中取得竞争优势。
120 16
|
2月前
|
域名解析 负载均衡 安全
DNS技术标准趋势和安全研究
本文探讨了互联网域名基础设施的结构性安全风险,由清华大学段教授团队多年研究总结。文章指出,DNS系统的安全性不仅受代码实现影响,更源于其设计、实现、运营及治理中的固有缺陷。主要风险包括协议设计缺陷(如明文传输)、生态演进隐患(如单点故障增加)和薄弱的信任关系(如威胁情报被操纵)。团队通过多项研究揭示了这些深层次问题,并呼吁构建更加可信的DNS基础设施,以保障全球互联网的安全稳定运行。

热门文章

最新文章

推荐镜像

更多