在開(kāi)發(fā)APP的過(guò)程中泪酱,崩潰等異常總是讓我們不堪其煩,不過(guò)開(kāi)發(fā)階段的崩潰等問(wèn)題墓阀,都是小事毡惜,可以進(jìn)行處理,但是一旦發(fā)布的版本出現(xiàn)崩潰的問(wèn)題斯撮,那就是大問(wèn)題经伙,不僅要連夜更新版本,還要為找這個(gè)bug不斷的嘗試勿锅,所以帕膜,我總結(jié)了看到的一些對(duì)于崩潰等異常的處理,以備自己日后參考溢十。
若有可以改進(jìn)的垮刹,望各位大大不吝賜教。
(注:本文只介紹使用方法茶宵,不對(duì)原理做深層解析危纫,小弟也沒(méi)這水平,接下來(lái)閑下來(lái)了乌庶,在研究代碼的實(shí)現(xiàn)原理种蝶,出一篇原理篇~)
一、關(guān)于崩潰
閃退估計(jì)是我們最不想看到的瞒大,對(duì)于用戶而言螃征,馬上就能產(chǎn)生一種不悅,對(duì)于投資方而言透敌,也會(huì)產(chǎn)生對(duì)技術(shù)實(shí)力的不信任感盯滚,所以,我們就需要對(duì)閃退進(jìn)行處理酗电,這里介紹一個(gè)不錯(cuò)的三方:AvoidCrash
魄藕,寫這個(gè)的大大也很牛逼,原文參照這里撵术。
這個(gè)三方可以處理例如插入空值到字典中或數(shù)組中引起的崩潰背率、數(shù)組越界引起的崩潰、unrecognized selector sent to instance
等等的崩潰嫩与,都能捕獲并且避免閃退寝姿。
對(duì)于插入空值、越界等划滋,原理比較簡(jiǎn)單饵筑,就是利用Runtime
的方法交換,把普通的插入和取值的方法处坪,替換成安全插入和安全讀取的方法根资,具體代碼可以去看源碼架专。
話不多說(shuō),先上效果:
以下是可導(dǎo)致崩潰的代碼:
NSString *nilStr = nil;
NSArray *array = @[@"chenfanfang", nilStr];
若有AvoidCrash來(lái)防止崩潰嫂冻,則不會(huì)崩潰胶征,并且會(huì)將原本會(huì)崩潰情況的詳細(xì)信息打印出來(lái),如下圖:
效果不錯(cuò)吧桨仿,接下來(lái)上使用步驟:
集成:
建議使用cocoapod
睛低,僅需要pod AvoidCrash
一句話即可。(手動(dòng)導(dǎo)入的步驟服傍,可以參照上面所說(shuō)的原文)钱雷。使用方法:(只要在
AppDelegate
的didFinishLaunchingWithOptions
方法中調(diào)用avoidCrash
方法,就可以開(kāi)始監(jiān)聽(tīng)異常吹零。)
- (void)avoidCrash {
/*
* 項(xiàng)目初期不需要對(duì)"unrecognized selector sent to instance"錯(cuò)誤進(jìn)行處理罩抗,因?yàn)檫€沒(méi)有相關(guān)的崩潰的類
* 后期出現(xiàn)后,再使用makeAllEffective方法灿椅,把所有對(duì)應(yīng)崩潰的類添加到數(shù)組中套蒂,避免崩潰
* 對(duì)于正式線可以啟用該方法,測(cè)試線建議關(guān)閉該方法
*/
[AvoidCrash becomeEffective];
// [AvoidCrash makeAllEffective];
// NSArray *noneSelClassStrings = @[
// @"NSString"
// ];
// [AvoidCrash setupNoneSelClassStringsArr:noneSelClassStrings];
//監(jiān)聽(tīng)通知:AvoidCrashNotification, 獲取AvoidCrash捕獲的崩潰日志的詳細(xì)信息
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(dealwithCrashMessage:) name:AvoidCrashNotification object:nil];
}
- 再監(jiān)聽(tīng)異常的通知:
- (void)dealwithCrashMessage:(NSNotification *)notification {
MYLog(@"\n??\n??監(jiān)測(cè)到崩潰信息??\n??\n");
/*
* 在這邊對(duì)避免的異常進(jìn)行一些處理茫蛹,比如上傳到日志服務(wù)器等操刀。
*/
}
以上就是避免崩潰的簡(jiǎn)單用法,關(guān)于能處理哪些異常婴洼,可以自行查看Git中的項(xiàng)目介紹骨坑。
二、關(guān)于異常的統(tǒng)計(jì)
上述的方法柬采,能夠避免崩潰欢唾,但是不能夠避免所有狀況的崩潰,作者也在不斷的根據(jù)用戶的使用情況進(jìn)行更新粉捻,盡量對(duì)所有已知的崩潰進(jìn)行避免礁遣。所以,我們還需要對(duì)異常進(jìn)行其他的收集肩刃,也能有效的幫助自己改進(jìn)APP亡脸。
這里僅做騰訊的Bugly進(jìn)行介紹,因?yàn)槠渌睦缬衙耸骼摇O光的,個(gè)人感覺(jué)都沒(méi)有Bugly好用大州,我也就不做介紹了续语,有興趣的可以自行了解。
這里參照的文章原文在此厦画。
- 集成
集成很簡(jiǎn)單疮茄,按照官方文檔來(lái)就好滥朱,我們這里建個(gè)簡(jiǎn)單的小項(xiàng)目,模擬一些崩潰力试,測(cè)試下Bugly的bug上報(bào)及時(shí)性徙邻。
項(xiàng)目就取名叫NSException
了,創(chuàng)建好項(xiàng)目后畸裳,去Bugly的控制臺(tái)缰犁,添加我們的應(yīng)用。
添加應(yīng)用
創(chuàng)建完應(yīng)用怖糊,進(jìn)入下一個(gè)界面帅容,我們選擇異常上報(bào)。
選擇異常上報(bào)
再到我們的APP的appDelegate
中didFinishLaunchingWithOptions
方法內(nèi)調(diào)用初始化方法即可伍伤。
// 頭文件
#import <Bugly/Bugly.h>
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
[Bugly startWithAppId:@"此處替換為你的AppId"];
return YES;
}
AppID可以點(diǎn)擊你在控制臺(tái)創(chuàng)建的App并徘,然后點(diǎn)產(chǎn)品設(shè)置就能看到了。
- Bug上傳測(cè)試
接下來(lái)我們?cè)赩iewConroller中隨便創(chuàng)造一個(gè)閃退的bug
- (void)touchesBegan:(NSSet<UITouch *> *)touches withEvent:(UIEvent *)event {
NSArray *arr = @[@"", @""];
arr[5];
}
運(yùn)行扰魂,崩潰麦乞,刷新Bugly的控制臺(tái),你會(huì)發(fā)現(xiàn)劝评,bug已經(jīng)統(tǒng)計(jì)到了姐直。所以,Bugly的崩潰上傳是在崩潰后立刻上傳的付翁。
我們點(diǎn)進(jìn)異常問(wèn)題中去看一下简肴,崩潰信息大致是這樣的,我們可以很直觀的看到崩在哪個(gè)方法里了百侧。
進(jìn)階
如果我們就這樣使用Bugly是不是太可惜了砰识,我們來(lái)看看Bugly還有什么功能;查看頭文件佣渴,會(huì)發(fā)現(xiàn)Bugly有三個(gè)類暴露出來(lái)辫狼,分別是Bugly
、BuglyConfig
和BuglyLog
辛润。
1.BuglyConfig
類主要用于個(gè)性話配置Bugly類膨处,由一些屬性和BuglyDelegate
代理組成。
- 屬性:
BuglyConfig
大部分屬性有設(shè)有默認(rèn)值砂竖,一般不用更改真椿,但是關(guān)于卡頓監(jiān)控的屬性確是默認(rèn)關(guān)閉的:
/**
* 卡頓監(jiān)控開(kāi)關(guān),默認(rèn)關(guān)閉
*/
@property (nonatomic) BOOL blockMonitorEnable;
/**
* 卡頓監(jiān)控判斷間隔乎澄,單位為秒
*/
@property (nonatomic) NSTimeInterval blockMonitorTimeout;
如果需要上報(bào)卡頓突硝,只需要將blockMonitorEnable
設(shè)為true
,給blockMonitorTimeout
設(shè)置一個(gè)合理的值即可置济;
- 代理:
BuglyConfig
可以設(shè)置一個(gè)代理解恰,來(lái)自定義上傳崩潰的附屬信息锋八;
@protocol BuglyDelegate <NSObject>
@optional
/**
* 發(fā)生異常時(shí)回調(diào)
* @param exception 異常信息
* @return 返回需上報(bào)記錄,隨異常上報(bào)一起上報(bào)
*/
- (NSString * BLY_NULLABLE)attachmentForException:(NSException * BLY_NULLABLE)exception;
@end
我們的初始化就改成:
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
BuglyConfig *config = [[BuglyConfig alloc] init];
//監(jiān)聽(tīng)卡頓
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];
}
再運(yùn)行一次护盈,崩潰挟纱,然后我們看看Bugly控制臺(tái)上報(bào)記錄:
文件內(nèi)部記錄的就是異常的代理方法所上報(bào)的內(nèi)容。
2.上傳打印日志腐宋,BuglyLog
類主要用于打印日志紊服,有6種級(jí)別:
typedef NS_ENUM(NSUInteger, BuglyLogLevel) {
BuglyLogLevelSilent = 0,
BuglyLogLevelError = 1,
BuglyLogLevelWarn = 2,
BuglyLogLevelInfo = 3,
BuglyLogLevelDebug = 4,
BuglyLogLevelVerbose = 5,
};
BuglyLog除了控制臺(tái)打印,還有一個(gè)重要功能就是上報(bào)打印內(nèi)容脏款,內(nèi)容將在崩潰時(shí)一同被上報(bào);但是這個(gè)功能是默認(rèn)不上報(bào)的围苫,需要配置BuglyConfig的reportLogLevel屬性;如config.reportLogLevel = BuglyLogLevelWarn撤师,將會(huì)上報(bào)BuglyLogLevelWarn和BuglyLogLevelError級(jí)別的打印日志剂府。
3.自定義上報(bào)異常,我們?cè)倩氐紹ugly類剃盾,除了初始化Bugly的方法外腺占,還有一些其他的方法:
自定義上報(bào)錯(cuò)誤
/**
* 上報(bào)自定義異常
* @param exception 異常信息
*/
+ (void)reportException:(nonnull NSException *)exception;
/**
* 上報(bào)錯(cuò)誤
* @param error 錯(cuò)誤信息
*/
+ (void)reportError:(NSError *)error;
重點(diǎn)來(lái)了!Q髑础Kゲ!
配合上AvoidCrash积蔚,使用上報(bào)自定義異常方法意鲸,我們就既能避免崩潰,又能監(jiān)聽(tīng)異常尽爆!
//AvoidCrash異常通知監(jiān)聽(tīng)方法怎顾,在這里我們可以調(diào)用reportException方法進(jìn)行上報(bào)
- (void)dealwithCrashMessage:(NSNotification *)notification {
NSLog(@"\n??\n??監(jiān)測(cè)到崩潰信息??\n??\n");
NSException *exception = [NSException exceptionWithName:@"AvoidCrash" reason:[notification valueForKeyPath:@"userInfo.errorName"] userInfo:notification.userInfo];
[Bugly reportException:exception];
}
以上就是AvoidCrash+Bugly優(yōu)化APP的運(yùn)行處理。
雖然Bugly的崩潰列表中我們能看到得到代碼的崩潰信息漱贱,但想更具體的分析代碼位置槐雾,就要用到符號(hào)表了。
三幅狮、符號(hào)表
沒(méi)有符號(hào)表募强,我們就無(wú)法定位崩潰中的符號(hào)對(duì)應(yīng)的代碼所在的類以及類中的行數(shù)位置。我們?cè)诿看螛?gòu)建版本崇摄、debug的時(shí)候擎值,都會(huì)生成dSYM后綴名的符號(hào)表文件,而我們App在手機(jī)上運(yùn)行的時(shí)候逐抑,崩潰后產(chǎn)生的崩潰信息幅恋,不可能定位到代碼的多少多少行,因?yàn)檫@些信息對(duì)于App運(yùn)行是沒(méi)有意義的泵肄,存儲(chǔ)在App中勢(shì)必會(huì)增大安裝包的體積捆交,所以App的崩潰信息都是存儲(chǔ)為各種符號(hào),具體符號(hào)代表什么腐巢,需要去符號(hào)表中查找對(duì)應(yīng)的含義品追。
我們每次debug、構(gòu)建版本冯丙,都會(huì)生成dSYM文件肉瓦,都對(duì)應(yīng)了一個(gè)UUID(像我們的手機(jī)一樣,都有一個(gè)唯一標(biāo)志)胃惜,按下圖指示泞莉,我們就能找到我們所使用的App版本對(duì)應(yīng)的dSYM文件的UUID,通過(guò)這個(gè)UUID船殉,我們就能找到存儲(chǔ)在我們電腦中的dSYM文件鲫趁,將這個(gè)文件上傳到bugly,bugly會(huì)自動(dòng)幫我們找到崩潰符號(hào)的含義利虫。
需要注意的是挨厚,構(gòu)建版本會(huì)自動(dòng)生成dSYM文件,但debug的時(shí)候糠惫,是沒(méi)有的疫剃,需要我們手動(dòng)開(kāi)啟。在build setting中搜索debug硼讽,將下面兩項(xiàng)內(nèi)容修改為正確的設(shè)置:
有了符號(hào)表的UUID巢价,我們打開(kāi)終端,按UUID找到符號(hào)表的路徑固阁。
mdfind "com_apple_xcode_dsym_uuids == A8E87810-70A7-3335-B638-C8B01BE15D79"
后面的一串字母數(shù)字組合壤躲,就是我們的UUID,這里需要將UUID按一定格式處理下您炉,也就是在特定位置插入“-”柒爵,具體格式如下:
來(lái)到終端,運(yùn)行上面的命令赚爵,就定位到了dSYM文件的位置:
打開(kāi)文件路徑棉胀,就找到了dSYM文件:
拷貝出來(lái),壓縮為zip文件冀膝,上傳到bugly上唁奢。
刷新頁(yè)面,再回去看剛才的問(wèn)題窝剖,定位到了為ViewController.m的第24行:
這樣我們就定位到了有問(wèn)題的地方麻掸。
官網(wǎng)文檔也提供了自動(dòng)上傳dSYM文件的操作流程,有興趣的可以試試赐纱,避免以后每次新版本都要手動(dòng)上傳dSYM文件脊奋。
dSYM文件也可以手動(dòng)查找:
找到你構(gòu)建的版本熬北,右鍵show in finder:
然后在定位到的文件上右鍵顯示包內(nèi)容就OK了:
總結(jié)
以上就是Bugly收集異常的過(guò)程,由于我也只是剛剛接觸Bugly诚隙,所以自己也有幾個(gè)問(wèn)題沒(méi)有解決讶隐,例如對(duì)于Bugly的符號(hào)表的dSYM文件的上傳,每次新版本dSYM文件都會(huì)改變久又?那手動(dòng)是有點(diǎn)麻煩巫延,自動(dòng)的方法也得去看看。
還有很多需要深入學(xué)習(xí)的地消,我也會(huì)繼續(xù)學(xué)習(xí)繼續(xù)分享炉峰,同樣的,希望各位大大能夠指出一些可以改進(jìn)的或者理解有誤的脉执,幫助小弟進(jìn)步疼阔,例如AvoidCrash作者所說(shuō)的“一些處理”,有的話萬(wàn)分感激适瓦。