在開發APP的過程中,崩潰等異常總是讓我們不堪其煩,不過開發階段的崩潰等問題,都是小事,可以進行處理,但是一旦發布的版本出現崩潰的問題,那就是大問題,不僅要連夜更新版本,還要為找這個bug不斷的嘗試,所以,我總結了看到的一些對于崩潰等異常的處理,以備自己日后參考。
若有可以改進的,望各位大大不吝賜教。
(注:本文只介紹使用方法,不對原理做深層解析,小弟也沒這水平,接下來閑下來了,在研究代碼的實現原理,出一篇原理篇~)
一、關于崩潰
閃退估計是我們最不想看到的,對于用戶而言,馬上就能產生一種不悅,對于投資方而言,也會產生對技術實力的不信任感,所以,我們就需要對閃退進行處理,這里介紹一個不錯的三方:AvoidCrash
,寫這個的大大也很牛逼,原文參照這里。
這個三方可以處理例如插入空值到字典中或數組中引起的崩潰、數組越界引起的崩潰、unrecognized selector sent to instance
等等的崩潰,都能捕獲并且避免閃退。
對于插入空值、越界等,原理比較簡單,就是利用Runtime
的方法交換,把普通的插入和取值的方法,替換成安全插入和安全讀取的方法,具體代碼可以去看源碼。
話不多說,先上效果:
以下是可導致崩潰的代碼:
NSString *nilStr = nil;
NSArray *array = @[@"chenfanfang", nilStr];
若有AvoidCrash來防止崩潰,則不會崩潰,并且會將原本會崩潰情況的詳細信息打印出來,如下圖:
效果不錯吧,接下來上使用步驟:
集成:
建議使用cocoapod
,僅需要pod AvoidCrash
一句話即可。(手動導入的步驟,可以參照上面所說的原文)。使用方法:(只要在
AppDelegate
的didFinishLaunchingWithOptions
方法中調用avoidCrash
方法,就可以開始監聽異常。)
- (void)avoidCrash {
/*
* 項目初期不需要對"unrecognized selector sent to instance"錯誤進行處理,因為還沒有相關的崩潰的類
* 后期出現后,再使用makeAllEffective方法,把所有對應崩潰的類添加到數組中,避免崩潰
* 對于正式線可以啟用該方法,測試線建議關閉該方法
*/
[AvoidCrash becomeEffective];
// [AvoidCrash makeAllEffective];
// NSArray *noneSelClassStrings = @[
// @"NSString"
// ];
// [AvoidCrash setupNoneSelClassStringsArr:noneSelClassStrings];
//監聽通知:AvoidCrashNotification, 獲取AvoidCrash捕獲的崩潰日志的詳細信息
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dealwithCrashMessage:) name:AvoidCrashNotification object:nil];
}
- 再監聽異常的通知:
- (void)dealwithCrashMessage:(NSNotification *)notification {
MYLog(@"\n??\n??監測到崩潰信息??\n??\n");
/*
* 在這邊對避免的異常進行一些處理,比如上傳到日志服務器等。
*/
}
以上就是避免崩潰的簡單用法,關于能處理哪些異常,可以自行查看Git中的項目介紹。
二、關于異常的統計
上述的方法,能夠避免崩潰,但是不能夠避免所有狀況的崩潰,作者也在不斷的根據用戶的使用情況進行更新,盡量對所有已知的崩潰進行避免。所以,我們還需要對異常進行其他的收集,也能有效的幫助自己改進APP。
這里僅做騰訊的Bugly進行介紹,因為其他的例如友盟、極光的,個人感覺都沒有Bugly好用,我也就不做介紹了,有興趣的可以自行了解。
這里參照的文章原文在此。
- 集成
集成很簡單,按照官方文檔來就好,我們這里建個簡單的小項目,模擬一些崩潰,測試下Bugly的bug上報及時性。
項目就取名叫NSException
了,創建好項目后,去Bugly的控制臺,添加我們的應用。
添加應用
創建完應用,進入下一個界面,我們選擇異常上報。
選擇異常上報
再到我們的APP的appDelegate
中didFinishLaunchingWithOptions
方法內調用初始化方法即可。
// 頭文件
#import <Bugly/Bugly.h>
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[Bugly startWithAppId:@"此處替換為你的AppId"];
return YES;
}
AppID可以點擊你在控制臺創建的App,然后點產品設置就能看到了。
- Bug上傳測試
接下來我們在ViewConroller中隨便創造一個閃退的bug
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSArray *arr = @[@"", @""];
arr[5];
}
運行,崩潰,刷新Bugly的控制臺,你會發現,bug已經統計到了。所以,Bugly的崩潰上傳是在崩潰后立刻上傳的。
我們點進異常問題中去看一下,崩潰信息大致是這樣的,我們可以很直觀的看到崩在哪個方法里了。
進階
如果我們就這樣使用Bugly是不是太可惜了,我們來看看Bugly還有什么功能;查看頭文件,會發現Bugly有三個類暴露出來,分別是Bugly
、BuglyConfig
和BuglyLog
。
1.BuglyConfig
類主要用于個性話配置Bugly類,由一些屬性和BuglyDelegate
代理組成。
- 屬性:
BuglyConfig
大部分屬性有設有默認值,一般不用更改,但是關于卡頓監控的屬性確是默認關閉的:
/**
* 卡頓監控開關,默認關閉
*/
@property (nonatomic) BOOL blockMonitorEnable;
/**
* 卡頓監控判斷間隔,單位為秒
*/
@property (nonatomic) NSTimeInterval blockMonitorTimeout;
如果需要上報卡頓,只需要將blockMonitorEnable
設為true
,給blockMonitorTimeout
設置一個合理的值即可;
- 代理:
BuglyConfig
可以設置一個代理,來自定義上傳崩潰的附屬信息;
@protocol BuglyDelegate <NSObject>
@optional
/**
* 發生異常時回調
* @param exception 異常信息
* @return 返回需上報記錄,隨異常上報一起上報
*/
- (NSString * BLY_NULLABLE)attachmentForException:(NSException * BLY_NULLABLE)exception;
@end
我們的初始化就改成:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
BuglyConfig *config = [[BuglyConfig alloc] init];
//監聽卡頓
config.blockMonitorEnable = YES;
config.blockMonitorTimeout = 3;
config.consolelogEnable = YES;
config.delegate = self;
[Bugly startWithAppId:@"此處替換為你的AppId" config:config];
// [self avoidCrash];
return YES;
}
- (NSString *)attachmentForException:(NSException *)exception {
NSLog(@"異常事件代理");
return [NSString stringWithFormat:@"TEST: %@",exception.userInfo];
}
再運行一次,崩潰,然后我們看看Bugly控制臺上報記錄:
文件內部記錄的就是異常的代理方法所上報的內容。
2.上傳打印日志,BuglyLog
類主要用于打印日志,有6種級別:
typedef NS_ENUM(NSUInteger, BuglyLogLevel) {
BuglyLogLevelSilent = 0,
BuglyLogLevelError = 1,
BuglyLogLevelWarn = 2,
BuglyLogLevelInfo = 3,
BuglyLogLevelDebug = 4,
BuglyLogLevelVerbose = 5,
};
BuglyLog除了控制臺打印,還有一個重要功能就是上報打印內容,內容將在崩潰時一同被上報;但是這個功能是默認不上報的,需要配置BuglyConfig的reportLogLevel屬性;如config.reportLogLevel = BuglyLogLevelWarn,將會上報BuglyLogLevelWarn和BuglyLogLevelError級別的打印日志。
3.自定義上報異常,我們再回到Bugly類,除了初始化Bugly的方法外,還有一些其他的方法:
自定義上報錯誤
/**
* 上報自定義異常
* @param exception 異常信息
*/
+ (void)reportException:(nonnull NSException *)exception;
/**
* 上報錯誤
* @param error 錯誤信息
*/
+ (void)reportError:(NSError *)error;
重點來了!!!!
配合上AvoidCrash,使用上報自定義異常方法,我們就既能避免崩潰,又能監聽異常!
//AvoidCrash異常通知監聽方法,在這里我們可以調用reportException方法進行上報
- (void)dealwithCrashMessage:(NSNotification *)notification {
NSLog(@"\n??\n??監測到崩潰信息??\n??\n");
NSException *exception = [NSException exceptionWithName:@"AvoidCrash" reason:[notification valueForKeyPath:@"userInfo.errorName"] userInfo:notification.userInfo];
[Bugly reportException:exception];
}
以上就是AvoidCrash+Bugly優化APP的運行處理。
雖然Bugly的崩潰列表中我們能看到得到代碼的崩潰信息,但想更具體的分析代碼位置,就要用到符號表了。
三、符號表
沒有符號表,我們就無法定位崩潰中的符號對應的代碼所在的類以及類中的行數位置。我們在每次構建版本、debug的時候,都會生成dSYM后綴名的符號表文件,而我們App在手機上運行的時候,崩潰后產生的崩潰信息,不可能定位到代碼的多少多少行,因為這些信息對于App運行是沒有意義的,存儲在App中勢必會增大安裝包的體積,所以App的崩潰信息都是存儲為各種符號,具體符號代表什么,需要去符號表中查找對應的含義。
我們每次debug、構建版本,都會生成dSYM文件,都對應了一個UUID(像我們的手機一樣,都有一個唯一標志),按下圖指示,我們就能找到我們所使用的App版本對應的dSYM文件的UUID,通過這個UUID,我們就能找到存儲在我們電腦中的dSYM文件,將這個文件上傳到bugly,bugly會自動幫我們找到崩潰符號的含義。
需要注意的是,構建版本會自動生成dSYM文件,但debug的時候,是沒有的,需要我們手動開啟。在build setting中搜索debug,將下面兩項內容修改為正確的設置:
有了符號表的UUID,我們打開終端,按UUID找到符號表的路徑。
mdfind "com_apple_xcode_dsym_uuids == A8E87810-70A7-3335-B638-C8B01BE15D79"
后面的一串字母數字組合,就是我們的UUID,這里需要將UUID按一定格式處理下,也就是在特定位置插入“-”,具體格式如下:
來到終端,運行上面的命令,就定位到了dSYM文件的位置:
打開文件路徑,就找到了dSYM文件:
拷貝出來,壓縮為zip文件,上傳到bugly上。
刷新頁面,再回去看剛才的問題,定位到了為ViewController.m的第24行:
這樣我們就定位到了有問題的地方。
官網文檔也提供了自動上傳dSYM文件的操作流程,有興趣的可以試試,避免以后每次新版本都要手動上傳dSYM文件。
dSYM文件也可以手動查找:
找到你構建的版本,右鍵show in finder:
然后在定位到的文件上右鍵顯示包內容就OK了:
總結
以上就是Bugly收集異常的過程,由于我也只是剛剛接觸Bugly,所以自己也有幾個問題沒有解決,例如對于Bugly的符號表的dSYM文件的上傳,每次新版本dSYM文件都會改變?那手動是有點麻煩,自動的方法也得去看看。
還有很多需要深入學習的,我也會繼續學習繼續分享,同樣的,希望各位大大能夠指出一些可以改進的或者理解有誤的,幫助小弟進步,例如AvoidCrash作者所說的“一些處理”,有的話萬分感激。