iOS App 穩(wěn)定性指標(biāo)及監(jiān)測(cè)

2017-07-29李運(yùn)鋒Cocoa開(kāi)發(fā)者社區(qū)

作者介紹:李運(yùn)鋒,美團(tuán)點(diǎn)評(píng)iOS工程師,5年iOS開(kāi)發(fā)經(jīng)驗(yàn),現(xiàn)在是美團(tuán)點(diǎn)評(píng)點(diǎn)餐團(tuán)隊(duì)的一員。

前言

本文較長(zhǎng)(5000字左右),建議閱讀時(shí)間: 20min+

一個(gè)iOS App的穩(wěn)定性,主要決定于整體的系統(tǒng)架構(gòu)設(shè)計(jì),同時(shí)也不可忽略編程的細(xì)節(jié),正所謂“千里之堤,潰于蟻穴”,一旦考慮不周,看似無(wú)關(guān)緊要的代碼片段可能會(huì)帶來(lái)整體軟件系統(tǒng)的崩潰。尤其因?yàn)樘O(píng)果限制了熱更新機(jī)制,App本身的穩(wěn)定性及容錯(cuò)性就顯的更加重要,之前可以通過(guò)發(fā)布熱補(bǔ)丁的方式解決線上代碼問(wèn)題,現(xiàn)在就需要在提交之前對(duì)App開(kāi)發(fā)周期內(nèi)的各個(gè)指標(biāo)進(jìn)行實(shí)時(shí)監(jiān)測(cè),盡量讓問(wèn)題暴漏在開(kāi)發(fā)階段,然后及時(shí)修復(fù),減少線上出問(wèn)題的幾率。針對(duì)一個(gè)App的開(kāi)發(fā)周期,它的穩(wěn)定性指標(biāo)主要有以下幾個(gè)環(huán)節(jié)構(gòu)成,用一個(gè)腦圖表示如下:

穩(wěn)定性指標(biāo)

1

開(kāi)發(fā)過(guò)程

開(kāi)發(fā)過(guò)程中,主要是通過(guò)監(jiān)控內(nèi)存使用及泄露,CPU使用率,F(xiàn)PS,啟動(dòng)時(shí)間等指標(biāo),以及常見(jiàn)的UI的主線程監(jiān)測(cè),NSAssert斷言等,最好能在Debug模式下,實(shí)時(shí)顯示在界面上,針對(duì)出現(xiàn)的問(wèn)題及早解決。

內(nèi)存問(wèn)題

內(nèi)存問(wèn)題主要包括兩個(gè)部分,一個(gè)是iOS中常見(jiàn)循環(huán)引用導(dǎo)致的內(nèi)存泄露 ,另外就是大量數(shù)據(jù)加載及使用導(dǎo)致的內(nèi)存警告。

mmap

雖然蘋(píng)果并沒(méi)有明確每個(gè)App在運(yùn)行期間可以使用的內(nèi)存最大值,但是有開(kāi)發(fā)者進(jìn)行了實(shí)驗(yàn)和統(tǒng)計(jì),一般在占用系統(tǒng)內(nèi)存超過(guò)20%的時(shí)候會(huì)有內(nèi)存警告,而超過(guò)50%的時(shí)候,就很容易Crash了,所以內(nèi)存使用率還是盡量要少,對(duì)于數(shù)據(jù)量比較大的應(yīng)用,可以采用分步加載數(shù)據(jù)的方式,或者采用mmap方式。mmap 是使用邏輯內(nèi)存對(duì)磁盤(pán)文件進(jìn)行映射,中間只是進(jìn)行映射沒(méi)有任何拷貝操作,避免了寫(xiě)文件的數(shù)據(jù)拷貝。 操作內(nèi)存就相當(dāng)于在操作文件,避免了內(nèi)核空間和用戶空間的頻繁切換。之前在開(kāi)發(fā)輸入法的時(shí)候 ,詞庫(kù)的加載也是使用mmap方式,可以有效降低App的內(nèi)存占用率,具體使用可以參考鏈接第一篇文章。

循環(huán)引用

循環(huán)引用是iOS開(kāi)發(fā)中經(jīng)常遇到的問(wèn)題,尤其對(duì)于新手來(lái)說(shuō)是個(gè)頭疼的問(wèn)題。循環(huán)引用對(duì)App有潛在的危害,會(huì)使內(nèi)存消耗過(guò)高,性能變差和Crash等,iOS常見(jiàn)的內(nèi)存主要以下三種情況:

Delegate

代理協(xié)議是一個(gè)最典型的場(chǎng)景,需要你使用弱引用來(lái)避免循環(huán)引用。ARC時(shí)代,需要將代理聲明為weak是一個(gè)即好又安全的做法:

@property (nonatomic, weak) id delegate;

NSTimer

NSTimer我們開(kāi)發(fā)中會(huì)用到很多,比如下面一段代碼

- (void)viewDidLoad { ? ?[super viewDidLoad]; ? ?self.timer = [NSTimer scheduledTimerWithTimeInterval:1 target:self ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?selector:@selector(doSomeThing) ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?userInfo:nil ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ? ?repeats:YES]; } - (void)doSomeThing { } - (void)dealloc { ? ? [self.timer invalidate]; ? ? self.timer = nil; }

這是典型的循環(huán)引用,因?yàn)閠imer會(huì)強(qiáng)引用self,而self又持有了timer,所有就造成了循環(huán)引用。那有人可能會(huì)說(shuō),我使用一個(gè)weak指針,比如

__weak typeof(self) weakSelf = self; self.mytimer = [NSTimer scheduledTimerWithTimeInterval:1 target:weakSelf selector:@selector(doSomeThing) userInfo:nil repeats:YES];

但是其實(shí)并沒(méi)有用,因?yàn)椴还苁莣eakSelf還是strongSelf,最終在NSTimer內(nèi)部都會(huì)重新生成一個(gè)新的指針指向self,這是一個(gè)強(qiáng)引用的指針,結(jié)果就會(huì)導(dǎo)致循環(huán)引用。那怎么解決呢?主要有如下三種方式:

使用類方法

使用weakProxy

使用GCD timer

具體如何使用,我就不做具體的介紹,網(wǎng)上有很多可以參考。

Block

Block的循環(huán)引用,主要是發(fā)生在ViewController中持有了block,比如:

@property (nonatomic, copy) LFCallbackBlock callbackBlock;

同時(shí)在對(duì)callbackBlock進(jìn)行賦值的時(shí)候又調(diào)用了ViewController的方法,比如:

self.callbackBlock = ^{

[self doSomething];

}];

就會(huì)發(fā)生循環(huán)引用,因?yàn)椋篤iewController->強(qiáng)引用了callback->強(qiáng)引用了ViewController,解決方法也很簡(jiǎn)單:

__weak __typeof(self) weakSelf = self;

self.callbackBlock = ^{

[weakSelf doSomething];

}];

原因是使用MRC管理內(nèi)存時(shí),Block的內(nèi)存管理需要區(qū)分是Global(全局)、Stack(棧)還是Heap(堆),而在使用了ARC之后,蘋(píng)果自動(dòng)會(huì)將所有原本應(yīng)該放在棧中的Block全部放到堆中。全局的Block比較簡(jiǎn)單,凡是沒(méi)有引用到Block作用域外面的參數(shù)的Block都會(huì)放到全局內(nèi)存塊中,在全局內(nèi)存塊的Block不用考慮內(nèi)存管理問(wèn)題。(放在全局內(nèi)存塊是為了在之后再次調(diào)用該Block時(shí)能快速反應(yīng),當(dāng)然沒(méi)有調(diào)用外部參數(shù)的Block根本不會(huì)出現(xiàn)內(nèi)存管理問(wèn)題)。

所以Block的內(nèi)存管理出現(xiàn)問(wèn)題的,絕大部分都是在堆內(nèi)存中的Block出現(xiàn)了問(wèn)題。默認(rèn)情況下,Block初始化都是在棧上的,但可能隨時(shí)被收回,通過(guò)將Block類型聲明為copy類型,這樣對(duì)Block賦值的時(shí)候,會(huì)進(jìn)行copy操作,copy到堆上,如果里面有對(duì)self的引用,則會(huì)有一個(gè)強(qiáng)引用的指針指向self,就會(huì)發(fā)生循環(huán)引用,如果采用weakSelf,內(nèi)部不會(huì)有強(qiáng)類型的指針,所以可以解決循環(huán)引用問(wèn)題。

那是不是所有的block都會(huì)發(fā)生循環(huán)引用呢?其實(shí)不然,比如UIView的類方法Block動(dòng)畫(huà),NSArray等的類的遍歷方法,也都不會(huì)發(fā)生循環(huán)引用,因?yàn)楫?dāng)前控制器一般不會(huì)強(qiáng)引用一個(gè)類。

其他內(nèi)存問(wèn)題

NSNotification addObserver之后,記得在dealloc里面添加remove;

動(dòng)畫(huà)的repeat count無(wú)限大,而且也不主動(dòng)停止動(dòng)畫(huà),基本就等于無(wú)限循環(huán)了;

forwardingTargetForSelector返回了self。

內(nèi)存解決思路:

通過(guò)Instruments來(lái)查看leaks

集成Facebook開(kāi)源的FBRetainCycleDetector

集成MLeaksFinder

具體原理及使用,可以參考鏈接。

CPU使用率

CPU的使用也可以通過(guò)兩種方式來(lái)查看,一種是在調(diào)試的時(shí)候Xcode會(huì)有展示,具體詳細(xì)信息可以進(jìn)入Instruments內(nèi)查看,通過(guò)查看Instruments的time profile來(lái)定位并解決問(wèn)題。另一種常見(jiàn)的方法是通過(guò)代碼讀取CPU使用率,然后顯示在App的調(diào)試面板上,可以在Debug環(huán)境下顯示信息,具體代碼如下:

int result; mib[0] = CTL_HW; mib[1] = HW_CPU_FREQ; length = sizeof(result);if (sysctl(mib, 2, &result, &length, NULL, 0) < 0) { ? ? perror("getting cpu frequency"); }printf("CPU Frequency = %u hz\n", result);

FPS監(jiān)控

目前主要使用CADisplayLink來(lái)監(jiān)控FPS,CADisplayLink是一個(gè)能讓我們以和屏幕刷新率相同的頻率將內(nèi)容畫(huà)到屏幕上的定時(shí)器。我們?cè)趹?yīng)用中創(chuàng)建一個(gè)新的 CADisplayLink 對(duì)象,把它添加到一個(gè)runloop中,并給它提供一個(gè) target 和selector 在屏幕刷新的時(shí)候調(diào)用,需要注意的是添加到runloop的common mode里面,代碼如下:

- (void)setupDisplayLink { ? ?_displayLink = [CADisplayLink displayLinkWithTarget:self selector:@selector(linkTicks:)]; ? ?[_displayLink addToRunLoop:[NSRunLoop currentRunLoop] forMode:NSRunLoopCommonModes]; } - (void)linkTicks:(CADisplayLink *)link { ? ?//執(zhí)行次數(shù) ? ?_scheduleTimes ++; ? ?//當(dāng)前時(shí)間戳 ? ?if(_timestamp == 0){ ? ? ? ?_timestamp = link.timestamp; ? ?} ? ?CFTimeInterval timePassed = link.timestamp - _timestamp; ? ?if(timePassed >= 1.f) ? ? ? ?//fps ? ? ? ?CGFloat fps = _scheduleTimes/timePassed; ? ? ? ?printf("fps:%.1f, timePassed:%f\n", fps, timePassed); ? ?} }

啟動(dòng)時(shí)間

點(diǎn)評(píng)App里面本身就包含了很多復(fù)雜的業(yè)務(wù),比如外賣、團(tuán)購(gòu)、到綜和酒店等,同時(shí)還引入了很多第三方SDK比如微信、QQ、微博等,在App初始化的時(shí)候,很多SDK及業(yè)務(wù)也開(kāi)始初始化,這就會(huì)拖慢應(yīng)用的啟動(dòng)時(shí)間。

App的啟動(dòng)時(shí)間t(App總啟動(dòng)時(shí)間) = t1(main()之前的加載時(shí)間) + t2(main()之后的加載時(shí)間)。 t1 = 系統(tǒng)dylib(動(dòng)態(tài)鏈接庫(kù))和自身App可執(zhí)行文件的加載; t2 = main方法執(zhí)行之后到AppDelegate類中的- (BOOL)Application:(UIApplication *)Application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions方法執(zhí)行結(jié)束前這段時(shí)間,主要是構(gòu)建第一個(gè)界面,并完成渲染展示。

針對(duì)t1的優(yōu)化,優(yōu)化主要有如下:

減少不必要的framework,因?yàn)閯?dòng)態(tài)鏈接比較耗時(shí);

檢查framework應(yīng)當(dāng)設(shè)為optional和required,如果該framework在當(dāng)前App支持的所有iOS系統(tǒng)版本都存在,那么就設(shè)為required,否則就設(shè)為optional,因?yàn)閛ptional會(huì)有些額外的檢查;

合并或者刪減一些OC類,這些我會(huì)在后續(xù)的靜態(tài)檢查中進(jìn)行詳解;

針對(duì)t2的時(shí)間優(yōu)化,可以采用:

異步初始化部分操作,比如網(wǎng)絡(luò),數(shù)據(jù)讀??;

采用延遲加載或者懶加載某些視圖,圖片等的初始化操作;

對(duì)與圖片展示類的App,可以將解碼的圖片保存到本地,下次啟動(dòng)時(shí)直接加載解碼后的圖片;

對(duì)實(shí)現(xiàn)了+load()方法的類進(jìn)行分析,盡量將load里的代碼延后調(diào)用。

UI的主線程監(jiān)測(cè)

我們都知道iOS的UI的操作一定是在主線程進(jìn)行,該監(jiān)測(cè)可以通過(guò)hook UIView的如下三個(gè)方法

-setNeedsLayout,

-setNeedsDisplay,

-setNeedsDisplayInRect

確保它們都是在主線程執(zhí)行。子線程操作UI可能會(huì)引起什么問(wèn)題,蘋(píng)果說(shuō)得并不清楚,但是在實(shí)際開(kāi)發(fā)中,我們經(jīng)常會(huì)遇到整個(gè)App的動(dòng)畫(huà)丟失,很大原因就是UI操作不是在主線程導(dǎo)致。

2

靜態(tài)分析過(guò)程

靜態(tài)分析在這里,我主要介紹兩方面,一個(gè)是正常的code review機(jī)制,另外一個(gè)就是代碼靜態(tài)檢查工具

code review

組內(nèi)的code review機(jī)制,可以參考團(tuán)隊(duì)之前的OpenDoc - 前端團(tuán)隊(duì)CodeReview制度,iOS客戶端開(kāi)發(fā),會(huì)在此基礎(chǔ)上進(jìn)行一些常見(jiàn)手誤及Crash情況的重點(diǎn)標(biāo)記,比如:

我們開(kāi)發(fā)中首先都是在測(cè)試環(huán)境開(kāi)發(fā),開(kāi)發(fā)時(shí)可以將測(cè)試環(huán)境的url寫(xiě)死到代碼中,但是在提交代碼的時(shí)候一定要將他改為線上環(huán)境的url,這個(gè)就可以通過(guò)gitlab中的重點(diǎn)比較部分字符串,給提交者一個(gè)強(qiáng)力的提示;

其他常見(jiàn)Crash的重點(diǎn)檢查,比如NSMutableString/NSMutableArray/NSMutableDictionary/NSMutableSet 等類下標(biāo)越界判斷保護(hù),或者 append/insert/add nil對(duì)象的保護(hù);

ARC下的release操作,UITableViewCell返回nil,以及前面介紹的常見(jiàn)的循環(huán)引用等。

code review機(jī)制,一方面是依賴寫(xiě)代碼者的代碼習(xí)慣及質(zhì)量,另一名依賴審查者的經(jīng)驗(yàn)和細(xì)心程度,即使讓多人revew,也可能會(huì)漏過(guò)一些錯(cuò)誤,所以我們又添加了代碼的靜態(tài)檢查。

代碼靜態(tài)檢查

代碼靜態(tài)分析(Static Program Analysis)是指在不運(yùn)行程序的條件下,由代碼靜態(tài)分析工具自動(dòng)對(duì)程序進(jìn)行分析的方法. iOS常見(jiàn)的靜態(tài)掃描工具有Clang Static Analyzer、OCLint、Infer,這些主要是用來(lái)檢查可能存在的問(wèn)題,還有Deploymate用來(lái)檢查api的兼容性。

Clang Static Analyzer

Clang Static Analyzer是一款靜態(tài)代碼掃描工具,專門(mén)用于針對(duì)C,C++和Objective-C的程序進(jìn)行分析。已經(jīng)被Xcode集成,可以直接使用Xcode進(jìn)行靜態(tài)代碼掃描分析,Clang默認(rèn)的配置主要是空指針檢測(cè),類型轉(zhuǎn)換檢測(cè),空判斷檢測(cè),內(nèi)存泄漏檢測(cè)這種等問(wèn)題。如果需要更多的配置,可以使用開(kāi)源的Clang項(xiàng)目,然后集成到自己的CI上。

OCLint

OCLint是一個(gè)強(qiáng)大的靜態(tài)代碼分析工具,可以用來(lái)提高代碼質(zhì)量,查找潛在的bug,主要針對(duì) C、C++和Objective-C的靜態(tài)分析。功能非常強(qiáng)大,而且是出自國(guó)人之手。OCLint基于 Clang 輸出的抽象語(yǔ)法樹(shù)對(duì)代碼進(jìn)行靜態(tài)分析,支持與現(xiàn)有的CI集成,部署之后基本不需要維護(hù),簡(jiǎn)單方便。

OCLint可以發(fā)現(xiàn)這些問(wèn)題

可能的bug - 空的 if / else / try / catch / finally 語(yǔ)句

未使用的代碼 - 未使用的局部變量和參數(shù)

復(fù)雜的代碼 - 高圈復(fù)雜度, NPath復(fù)雜, 高NCSS

冗余代碼 - 多余的if語(yǔ)句和無(wú)用的括號(hào)

壞味道的代碼 - 過(guò)長(zhǎng)的方法和過(guò)長(zhǎng)的參數(shù)列表

不好的使用 - 倒邏輯和入?yún)⒅匦沦x值

對(duì)于OCLint的與原理和部署方法,可以參考團(tuán)隊(duì)成員之前的文章:靜態(tài)代碼分析之OCLint的那些事兒,每次提交代碼后,可以在打包的過(guò)程中進(jìn)行代碼檢查,及早發(fā)現(xiàn)有問(wèn)題的代碼。當(dāng)然也可以在合并代碼之前執(zhí)行對(duì)應(yīng)的檢查,如果檢查不通過(guò),不能合并代碼,這樣檢查的力度更大。

Infer

Infer facebook開(kāi)源的靜態(tài)分析工具,Infer可以分析 Objective-C, Java 或者 C 代碼,報(bào)告潛在的問(wèn)題。Infer效率高,規(guī)模大,幾分鐘能掃描數(shù)千行代碼;

C/OC中捕捉的bug類型主要有:

1:Resource leak

2:Memory leak

3:Null dereference

4:Premature nil termination argument

只在 OC中捕捉的bug類型

1:Retain cycle

2:Parameter not null checked

3:Ivar not null checked

結(jié)論

Clang Static Analyzer和Xcode集成度更高、更好用,支持命令行形式,并且能夠用于持續(xù)集成。OCLint有更多的檢查規(guī)則和定制,和很多工具集成,也同樣可用于持續(xù)集成。Infer效率高,規(guī)模大,幾分鐘能掃描數(shù)千行代碼;支持增量及非增量分析;分解分析,整合輸出結(jié)果。infer能將代碼分解,小范圍分析后再將結(jié)果整合在一起,兼顧分析的深度和速度,所以根據(jù)自己的項(xiàng)目特點(diǎn),選擇合適的檢查工具對(duì)代碼進(jìn)行檢查,減少人力review成本,保證代碼質(zhì)量,最大限度的避免運(yùn)行錯(cuò)誤。

2

測(cè)試過(guò)程

前面介紹了很多指標(biāo)的監(jiān)測(cè),代碼靜態(tài)檢查,這些都是性能相關(guān)的,真正決定一個(gè)App功能穩(wěn)定是否的是測(cè)試環(huán)節(jié)。測(cè)試是發(fā)布之前的最后一道卡,如果bug不能在測(cè)試中發(fā)現(xiàn),那么最終就會(huì)觸達(dá)用戶,所以一個(gè)App的穩(wěn)定性,很大程度決定它的測(cè)試過(guò)程。iOS App的測(cè)試包括以下幾個(gè)層次:?jiǎn)卧獪y(cè)試,UI測(cè)試,功能測(cè)試,異常測(cè)試。

單元測(cè)試

XCTest是蘋(píng)果官方提供的單元測(cè)試框架,與Xcode集成在一起,由此蘋(píng)果提供了很詳細(xì)的文檔XCTest。

Xcode單元測(cè)試包含在一個(gè)XCTestCase的子類中。依據(jù)約束,每一個(gè) XCTestCase 子類封裝一個(gè)特殊的有關(guān)聯(lián)的集合,例如一個(gè)功能、用例或者一個(gè)程序流。同時(shí)還提供了XCTestExpectation來(lái)處理異步任務(wù)的測(cè)試,以及性能測(cè)試measureBlock(),還包括很多第三方測(cè)試框架比如:KiWi,Quick,Specta等,以及常用的mock框架OCMock。

單元測(cè)試的目的是將程序中所有的源代碼,隔離成最小的可測(cè)試單元,以確保每個(gè)單元的正確性,如果每個(gè)單元都能保證正確,就能保證應(yīng)用程序整體相當(dāng)程度的正確性。但是在實(shí)際的操作過(guò)程中,很多公司都很難徹底執(zhí)行單元測(cè)試,主要就是單元測(cè)試代碼量甚至大于功能開(kāi)發(fā),比較難于維護(hù)。

對(duì)于測(cè)試用例覆蓋度多少合適這個(gè)話題,也是仁者見(jiàn)仁智者見(jiàn)智,其實(shí)一個(gè)軟件覆蓋度在50%以上就可以稱為一個(gè)健壯的軟件了,要達(dá)到70,80這些已經(jīng)是非常難了,不過(guò)我們常見(jiàn)的一些第三方開(kāi)源框架的測(cè)試用例覆蓋率還是非常高的,讓人咋舌。例如,AFNNetWorking的覆蓋率高達(dá)87%,SDWebImage的覆蓋率高達(dá)77%。

UI測(cè)試

Xcode7中新增了UITest測(cè)試,UI測(cè)試是模擬用戶操作,進(jìn)而從業(yè)務(wù)處層面測(cè)試,常用第三方庫(kù)有KIF,appium。關(guān)于XCTest的UI測(cè)試,建議看看WWDC 2015的視頻UI Testing in Xcode。 UI測(cè)試還有一個(gè)核心功能是UI Recording。選中一個(gè)UI測(cè)試用例,然后點(diǎn)擊圖中的小紅點(diǎn)既可以開(kāi)始UI Recoding。你會(huì)發(fā)現(xiàn):隨著點(diǎn)擊模擬器,自動(dòng)合成了測(cè)試代碼。(通常自動(dòng)合成代碼后,還需要手動(dòng)的去調(diào)整)

UI測(cè)試

功能測(cè)試

功能測(cè)試跟上述的UT和UI測(cè)試有一些相通的地方,首先針對(duì)各個(gè)模塊設(shè)計(jì)的功能,測(cè)試是否達(dá)到產(chǎn)品的目的,通常功能測(cè)試主要是測(cè)試及產(chǎn)品人員,然后還需要進(jìn)行專項(xiàng)測(cè)試,比如我們公司的云測(cè)平臺(tái),會(huì)對(duì)整個(gè)App的性能,穩(wěn)定性,UI等都進(jìn)行整體評(píng)測(cè),看是否達(dá)到標(biāo)準(zhǔn),對(duì)于大規(guī)模的活動(dòng),還需要進(jìn)行服務(wù)端的壓力測(cè)試,確保整個(gè)功能無(wú)異常。測(cè)試通過(guò)后,可以進(jìn)行estFlight測(cè)試,到最后正式發(fā)布。

功能測(cè)試還包括如下場(chǎng)景:系統(tǒng)兼容性測(cè)試,屏幕分辨率兼容性測(cè)試,覆蓋安裝測(cè)試,UI是否符合設(shè)計(jì),消息推送等,以及前面開(kāi)發(fā)過(guò)程中需要監(jiān)控的內(nèi)存、cpu、電量、網(wǎng)絡(luò)流量、冷啟動(dòng)時(shí)間、熱啟動(dòng)時(shí)間、存儲(chǔ)、安裝包的大小等測(cè)試。

異常測(cè)試

異常測(cè)試主要是針對(duì)一些不常規(guī)的操作

使用過(guò)程中的來(lái)電時(shí)及結(jié)束后,界面顯示是否正常;

狀態(tài)欄為兩倍高度時(shí),界面是否顯示正常;

意外斷電后,數(shù)據(jù)是否保存,數(shù)據(jù)是否有損害等;

設(shè)備充電時(shí),不同電量時(shí)的App響應(yīng)速度及操作流暢度等;

其他App的相互切換,前后臺(tái)轉(zhuǎn)換時(shí),是否正常;

網(wǎng)絡(luò)變化時(shí)的提示,弱網(wǎng)環(huán)境下的網(wǎng)絡(luò)請(qǐng)求成功率等;

各種monkey的隨機(jī)點(diǎn)擊,多點(diǎn)觸摸測(cè)試等是否正常;

更改系統(tǒng)時(shí)間,字體大小,語(yǔ)言等顯示是否正常;

設(shè)備存儲(chǔ)不夠時(shí),是否能正常操作;

...

異常測(cè)試有很多,App針對(duì)自身的特點(diǎn),可以選擇性的進(jìn)行邊界和異常測(cè)試,也是保證App穩(wěn)定行的一個(gè)重要方面。

4

發(fā)布及監(jiān)控

因?yàn)橐苿?dòng)App的特點(diǎn),即使我們通過(guò)了各種測(cè)試,產(chǎn)品最終發(fā)布后,還是會(huì)遇到很多問(wèn)題,比如Crash,網(wǎng)絡(luò)失敗,數(shù)據(jù)損壞,賬號(hào)異常等等。針對(duì)已經(jīng)發(fā)布的App,主要有一下方式保證穩(wěn)定性:

熱修復(fù)

目前比較流行的熱修復(fù)方案都是基于JSPatch、React Native、Weex、lua+wax。

JSPatch能做到通過(guò)js調(diào)用和改寫(xiě)OC方法。最根本的原因是 Objective-C 是動(dòng)態(tài)語(yǔ)言,OC上所有方法的調(diào)用/類的生成都通過(guò) objective-c Runtime 在運(yùn)行時(shí)進(jìn)行,我們可以通過(guò)類名和方法名反射得到相應(yīng)的類和方法,也可以替換某個(gè)類的方法為新的實(shí)現(xiàn),還可以新注冊(cè)一個(gè)類,為類添加方法。JSPatch 的原理就是:JS傳遞字符串給OC,OC通過(guò) Runtime 接口調(diào)用和替換OC方法。

React Native 是從 Web 前端開(kāi)發(fā)框架 React 延伸出來(lái)的解決方案,主要解決的問(wèn)題是 Web 頁(yè)面在移動(dòng)端性能低的問(wèn)題,React Native 讓開(kāi)發(fā)者可以像開(kāi)發(fā) Web 頁(yè)面那樣用 React 的方式開(kāi)發(fā)功能,同時(shí)框架會(huì)通過(guò) JavaScript 與 Objective-C 的通信讓界面使用原生組件渲染,讓開(kāi)發(fā)出來(lái)的功能擁有原生App的性能和體驗(yàn)。

Weex阿里開(kāi)源的,基于Vue+Native的開(kāi)發(fā)模式,跟RN的主要區(qū)別就在React和Vue的區(qū)別,同時(shí)在RN的基礎(chǔ)上進(jìn)行了部分性能優(yōu)化,總體開(kāi)發(fā)思路跟RN是比較像的。

但是在今年上半年,蘋(píng)果以安全為理由,開(kāi)始拒絕有熱修復(fù)功能的應(yīng)用,但其實(shí)蘋(píng)果拒的不是熱更新,拒的是從網(wǎng)絡(luò)下載代碼并修改應(yīng)用行為,蘋(píng)果禁止的是“基于反射的熱更新“,而不是 “基于沙盒接口的熱更新”。而大部分框架(如 React Native、weex)和游戲引擎(比如 Unity、Cocos2d-x等)都屬于后者,所以不在被警告范圍內(nèi)。而JSPatch因?yàn)樵趪?guó)內(nèi)大部分應(yīng)用來(lái)做熱更新修復(fù)bug的行為,所以才回被蘋(píng)果禁止。

降級(jí)

用戶使用App一段時(shí)間后,可能會(huì)遇到這樣的情況:每次打開(kāi)App時(shí)閃退,或者正常操作到某個(gè)界面時(shí)閃退,無(wú)法正常使用App。這樣的用戶體驗(yàn)十分糟糕,如果沒(méi)有一個(gè)好的解決方案,很容易被用戶刪除App,導(dǎo)致用戶量的流失。因?yàn)闊岣禄静荒苁褂茫蔷椭荒苁茿pp自身修復(fù)能力。目前常用的修復(fù)能力有:

啟動(dòng)Crash的監(jiān)控及修復(fù)

在應(yīng)用起來(lái)的時(shí)候,記錄flag并保存本地,啟動(dòng)一個(gè)定時(shí)器,比如5秒鐘內(nèi),如果沒(méi)有發(fā)生Crash,則認(rèn)為用戶操作正常,清空本地flag。

下次啟動(dòng),發(fā)現(xiàn)有flag,則表明上次啟動(dòng)Crash,如果flag數(shù)組越大,則說(shuō)明Crash的次數(shù)越多,這樣就需要對(duì)整個(gè)App進(jìn)行降級(jí)處理,比如登出賬號(hào),清空Documents/Library/Caches目錄下的文件。

具體業(yè)務(wù)下的Crash及修復(fù)

針對(duì)某些具體業(yè)務(wù)Crash場(chǎng)景,如果是上線的前端頁(yè)面引起的,可以先對(duì)前端功能進(jìn)行回滾,或者隱藏入口,等修復(fù)完畢后再上線,如果是客戶端的某些異常,比如數(shù)據(jù)庫(kù)升遷問(wèn)題,主要是進(jìn)行業(yè)務(wù)數(shù)據(jù)庫(kù)修復(fù),緩存文件的刪除,賬號(hào)退出等操作,盡量只修復(fù)此業(yè)務(wù)的相關(guān)的數(shù)據(jù)。

網(wǎng)絡(luò)降級(jí)

比如點(diǎn)評(píng)App,本身有CIP(公司內(nèi)部自己研發(fā)的)長(zhǎng)連接,接入騰訊云的WNS長(zhǎng)連接,UDP連接,HTTP短連接,如果CIP服務(wù)器發(fā)生問(wèn)題,可以及時(shí)切換到WNS連接,或者降級(jí)到Http連接,保證網(wǎng)絡(luò)連接的成功率。

線上監(jiān)控

Crash監(jiān)控

Crash是對(duì)用戶來(lái)說(shuō)是最糟糕的體驗(yàn),Crash日志能夠記錄用戶閃退的崩潰日志及堆棧,進(jìn)程線程信息,版本號(hào),系統(tǒng)版本號(hào),系統(tǒng)機(jī)型等有用信息,收集的信息越詳細(xì),越能夠幫助解決崩潰,所以各大App都有自己崩潰日志收集系統(tǒng),或者也可以使用開(kāi)源或者付費(fèi)的第三方Crash收集平臺(tái)。

端到端成功率監(jiān)控

端到端監(jiān)控是從客戶端App發(fā)出請(qǐng)求時(shí)計(jì)時(shí),到App收到數(shù)據(jù)數(shù)據(jù)的成功率,統(tǒng)計(jì)對(duì)象是:網(wǎng)絡(luò)接口請(qǐng)求(包括H5頁(yè)面加載)的成敗和端到端延時(shí)情況。端到端監(jiān)控SDK提供了監(jiān)控上傳接口,調(diào)用SDK提供的監(jiān)控API可以將數(shù)據(jù)上報(bào)到監(jiān)控服務(wù)器中。

整個(gè)端到端監(jiān)控的可以在多個(gè)維度上做查詢端到端成功率、響應(yīng)時(shí)間、訪問(wèn)量的查詢,維度包括:返回碼、網(wǎng)絡(luò)、版本、平臺(tái)、地區(qū)、運(yùn)營(yíng)商等。

用戶行為日志

用戶行為日志,主要記錄用戶在使用App過(guò)程中,點(diǎn)擊元素的時(shí)間點(diǎn),瀏覽時(shí)長(zhǎng),跳轉(zhuǎn)流程等,然后基于此進(jìn)行用戶行為分析,大部分應(yīng)用的推薦算法都是基于用戶行為日志來(lái)統(tǒng)計(jì)的。某些情況下,Crash分析需要查詢用戶的行為日志,獲取用戶使用App的流程,幫助解決Crash等其他問(wèn)題。

代碼級(jí)日志

代碼級(jí)別的日志,主要用來(lái)記錄一個(gè)App的性能相關(guān)的數(shù)據(jù),比如頁(yè)面打開(kāi)速度,內(nèi)存使用率,CPU占用率,頁(yè)面的幀率,網(wǎng)絡(luò)流量,請(qǐng)求錯(cuò)誤統(tǒng)計(jì)等,通過(guò)收集相關(guān)的上下文信息,優(yōu)化App性能。

總結(jié)

雖然現(xiàn)在市面上第三方平臺(tái)已經(jīng)很成熟,但是各大互聯(lián)公司都會(huì)自己開(kāi)發(fā)線上監(jiān)控系統(tǒng),這樣保證數(shù)據(jù)安全,同時(shí)更加靈活。因?yàn)橐苿?dòng)用戶的特點(diǎn),在開(kāi)發(fā)測(cè)試過(guò)程中,很難完全覆蓋所有用戶的全部場(chǎng)景,有些問(wèn)題也只會(huì)在特定環(huán)境下才發(fā)生,所以通過(guò)線上監(jiān)控平臺(tái),通過(guò)日志回?fù)频葯C(jī)制,及時(shí)獲取特定場(chǎng)景的上下文環(huán)境,結(jié)合數(shù)據(jù)分析,能夠及時(shí)發(fā)現(xiàn)問(wèn)題,并后續(xù)修復(fù),提高App的穩(wěn)定性。

1

全文總結(jié)

本文主要從開(kāi)發(fā)測(cè)試發(fā)布等流程來(lái)介紹了一個(gè)App穩(wěn)定性指標(biāo)及監(jiān)測(cè)方法,開(kāi)發(fā)階段主要針對(duì)一些比較具體的指標(biāo),靜態(tài)檢查主要是掃描代碼潛在問(wèn)題,然后通過(guò)測(cè)試保證App功能的穩(wěn)定性,線上降級(jí)主要是在盡量不發(fā)版的情況下,進(jìn)行自修復(fù),配合線上監(jiān)控,信息收集,用戶行為記錄,方便后續(xù)問(wèn)題修復(fù)及優(yōu)化。本文觀點(diǎn)是作者從事iOS開(kāi)發(fā)的一些經(jīng)驗(yàn),希望能對(duì)你有所幫助,觀點(diǎn)不同歡迎討論。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,048評(píng)論 6 542
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,414評(píng)論 3 429
  • 文/潘曉璐 我一進(jìn)店門(mén),熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人,你說(shuō)我怎么就攤上這事。” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 178,169評(píng)論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我,道長(zhǎng),這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 63,722評(píng)論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 72,465評(píng)論 6 412
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書(shū)人閱讀 55,823評(píng)論 1 328
  • 那天,我揣著相機(jī)與錄音,去河邊找鬼。 笑死,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,813評(píng)論 3 446
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 43,000評(píng)論 0 290
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,554評(píng)論 1 335
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 41,295評(píng)論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 43,513評(píng)論 1 374
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評(píng)論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 44,722評(píng)論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 35,125評(píng)論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 36,430評(píng)論 1 295
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 52,237評(píng)論 3 398
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 48,482評(píng)論 2 379

推薦閱讀更多精彩內(nèi)容