1 卡頓產(chǎn)生的原因及優(yōu)化
產(chǎn)生卡頓是由于屏幕的成像顯示導(dǎo)致巷嚣,而屏幕畫面的顯示離不開手機的CPU和GPU拾枣;
CPU:(Central Processing Unit 中央處理器)
對象的創(chuàng)建和銷毀,對象屬性的調(diào)整奢入,布局的計算唧取,文本的布局計算和排版,圖片格式的轉(zhuǎn)換和解碼奥邮,圖像的繪制(Core Graphics)
GPU: (Graphics Processing Unit 圖形處理器)
紋理的繪制
iOS是雙幀緩存機制万牺,有前幀緩存,后幀緩存
1.1屏幕成像顯示的過程是:
- CPU先計算出圖像的布局洽腺,大小脚粟,位置等信息;(CPU計算出來的數(shù)據(jù)是不能直接顯示到屏幕上的)
- GPU將CPU計算的數(shù)據(jù)蘸朋,渲染到幀緩存中核无;
- 要顯示圖像的時候,視頻控制器從幀緩存中讀取圖像藕坯,顯示到屏幕上厕宗;
1.2 屏幕成像顯示的原理:
iPhone的刷幀頻率是 60 FPS,也就是每秒顯示60幀數(shù)據(jù)堕担;
每幀圖像顯示的時間間隔是: 1000ms / 60 fps = 16ms已慢;
如圖:
屏幕在顯示一幀數(shù)據(jù)的時候:
- 會先發(fā)送一條垂直同步信號
- 然后會 從上至下 發(fā)送水平同步信號,填充整個屏幕霹购,顯示這一幀的數(shù)據(jù)佑惠;
重點:每隔16ms就會顯示下一幀數(shù)據(jù),接收到 垂直同步信號 代表開始顯示下一幀的內(nèi)容
1.3 顯示和卡頓產(chǎn)生的根本原因:
之前介紹齐疙,屏幕成像在CPU計算和GPU渲染到幀緩存區(qū)之后膜楷,再由視頻控制器讀取并顯示到屏幕上。
如下圖所示:
1贞奋、2赌厅、3、4轿塔、5 代表5幀數(shù)據(jù)的顯示流程特愿,
其中紅色箭頭代表CPU計算所用時間仲墨,藍色箭頭代表GPU渲染所用時間;
- 第一幀 1:CPU和GPU所花的時間 ==16ms揍障,所以在 垂直同步信號到來的時候目养,幀緩存中有完整的數(shù)據(jù),正常展示毒嫡;
- 第二幀 2:CPU和GPU所花的時間 < 16ms, 超前將要顯示的內(nèi)容繪制到幀緩存中癌蚁,正常展示;
-
第三幀 3:CPU和GPU所花的時間 > 16ms, 16ms內(nèi)這一幀的數(shù)據(jù)還沒渲染完成兜畸,垂直同步信號已經(jīng)到來努释,幀緩存中的數(shù)據(jù)不全,這一幀會繼續(xù)顯示上一幀(第二幀)的內(nèi)容咬摇;
所以這就是卡頓的原因洽洁; - 第四幀 4:在這一次的顯示中,第三幀的內(nèi)容CPU和GPU渲染剛完成菲嘴,當垂直信號到來時,去幀緩存中去讀取并直接顯示 第三幀 的內(nèi)容汰翠;
- 第五幀 5:同第二幀龄坪,正常展示;
由上圖直接展示了卡頓產(chǎn)生的 根本原因:
在一幀顯示的頻率16ms中复唤,如果CPU和GPU沒有將要顯示的內(nèi)容渲染到幀緩存中健田,當前垂直同步信號到來的時候,就會顯示上一幀的內(nèi)容佛纫;
這一幀的內(nèi)容妓局,會在下一個周期16ms后,垂直同步信號再次到來的時候呈宇,顯示到屏幕上好爬。
1.4 解決卡頓的方式CPU和GPU:
CPU:
1、使用輕量級的對象:比如不用點擊的地方甥啄,使用CALayer代替UIView存炮;
2、不要頻繁的修改屬性:frame蜈漓,bounds穆桂,transfrom等,這些都需要CPU的計算融虽;
3享完、盡量提前計算好布局:計算好frame,bounds等有额,一次性修改般又,不要多次修改彼绷;
4、使用AutoLayout比直接設(shè)置frame消耗更多的資源倒源;
5苛预、圖片的size最好和UIImageView的size保持一致,這樣就不用耗費CPU資源去進行縮放操作笋熬;
6热某、控制線程的最大并發(fā)數(shù)量:比如說3,不要無限制的開辟新的線程胳螟;
7昔馋、盡量耗時操作放到子線程:
- 文本的計算(高度),繪制(排版)等
// 文字計算
[@"text" boundingRectWithSize:CGSizeMake(100, MAXFLOAT) options:NSStringDrawingUsesLineFragmentOrigin attributes:nil context:nil];
// 文字繪制
[@"text" drawWithRect:CGRectMake(0, 0, 100, 100) options:NSStringDrawingUsesLineFragmentOrigin attributes:nil context:nil];
- 圖片的解碼糖耸、繪制
正常圖片的展示:imageView.image = [UIImage imageNamed:@"test.png"];
UIImageView *imageView = [[UIImageView alloc] init];
imageView.frame = CGRectMake(100, 100, 100, 56);
imageView.image = [UIImage imageNamed:@"test.png"];
[self.view addSubview:imageView];
其實正常圖片的顯示秘遏,不是直接展示到屏幕上的,需要解碼成能夠展示的二進制數(shù)據(jù)嘉竟,而這個解碼的過程邦危,可以異步的放到子線程中去做:
- (void)viewDidLoad {
[super viewDidLoad];
UIImageView *imageView = [[UIImageView alloc] init];
imageView.frame = CGRectMake(100, 100, 100, 56);
[self.view addSubview:imageView];
self.imageView = imageView;
[self image]; //異步解碼圖片,解碼成功后再回主線程展示
}
- (void)image{
dispatch_async(dispatch_get_global_queue(0, 0), ^{
// 獲取CGImage
CGImageRef cgImage = [UIImage imageNamed:@"test.png"].CGImage;
// alphaInfo
CGImageAlphaInfo alphaInfo = CGImageGetAlphaInfo(cgImage) & kCGBitmapAlphaInfoMask;
BOOL hasAlpha = NO;
if (alphaInfo == kCGImageAlphaPremultipliedLast ||
alphaInfo == kCGImageAlphaPremultipliedFirst ||
alphaInfo == kCGImageAlphaLast ||
alphaInfo == kCGImageAlphaFirst) {
hasAlpha = YES;
}
// bitmapInfo
CGBitmapInfo bitmapInfo = kCGBitmapByteOrder32Host;
bitmapInfo |= hasAlpha ? kCGImageAlphaPremultipliedFirst : kCGImageAlphaNoneSkipFirst;
// size
size_t width = CGImageGetWidth(cgImage);
size_t height = CGImageGetHeight(cgImage);
// context
CGContextRef context = CGBitmapContextCreate(NULL, width, height, 8, 0, CGColorSpaceCreateDeviceRGB(), bitmapInfo);
// draw
CGContextDrawImage(context, CGRectMake(0, 0, width, height), cgImage);
// get CGImage
cgImage = CGBitmapContextCreateImage(context);
// into UIImage
UIImage *newImage = [UIImage imageWithCGImage:cgImage];
// release
CGContextRelease(context);
CGImageRelease(cgImage);
// back to the main thread
dispatch_async(dispatch_get_main_queue(), ^{
self.imageView.image = newImage;
});
});
}
其實就是將image轉(zhuǎn)化成CGImage舍扰,然后將CGImage解碼倦蚪,首先創(chuàng)建一個上下文,通過drawImage方法將image畫到上下文context完成解碼操作边苹,然后從上下文獲取解碼后的圖片陵且;
GPU:
1、盡量減少視圖的數(shù)量和層級:多層次的視圖繪制更占用GPU資源个束;
2慕购、盡量避免短時間大量圖片的顯示:可以合成為一張圖片展示;
3茬底、GPU能處理的圖片的最大尺寸是4096x4096,盡量不要超過這個尺寸;
4沪悲、減少透明視圖的使用 alpha < 1,
重疊部分:有透明度:需要混合計算;不透明:計算一次(最上層的顏色)
5阱表、避免離屏渲染:
離屏渲染:
當前屏幕渲染:(On-Screen Rendering)在當前顯示的屏幕緩沖區(qū)進行操作可训;
離屏渲染: (Off-Screen Rendering)在當前屏幕緩沖區(qū)以外,開辟一個新的緩沖區(qū)捶枢;
離屏渲染消耗性能的原因:
需要開辟新的緩沖區(qū)尊剔;
需要多次切換上下文狀態(tài):從當前屏幕(On-Screen)切換到離屏(Off-Screen)吝镣,等離屏渲染結(jié)束以后氮帐,又要從離屏切換到當前屏幕铲咨;
哪些操作會觸發(fā)離屏渲染?
1蒜鸡、光柵化:layer.shouldRasterize = YES胯努;
2牢裳、遮罩:layer.mask;
-
3叶沛、圓角蒲讯,同時設(shè)置layer.masksToBounds = YES、layer.cornerRadius大于0灰署;
解決辦法:考慮通過CoreGraphics繪制裁剪圓角判帮,或者叫美工提供圓角圖片;
4溉箕、陰影晦墙,layer.shadowXXX;
如果設(shè)置了layer.shadowPath就不會產(chǎn)生離屏渲染(不設(shè)置路徑默認是圍繞這個view)
卡頓檢測:
平時所說的“卡頓”主要是因為在主線程執(zhí)行了比較耗時的操作
可以添加Observer到主線程RunLoop中肴茄,通過監(jiān)聽RunLoop狀態(tài)切換的耗時晌畅,以達到監(jiān)控卡頓的目的
耗電優(yōu)化
耗電的主要來源:
- CPU處理計算
- 網(wǎng)絡(luò)請求
- 定位
- 圖形的處理
耗電優(yōu)化的處理:
1、 盡可能減少CPU和GPU的消耗寡痰;
-
2抗楔、 優(yōu)化I/O操作:
- 盡量不要頻繁的讀寫小數(shù)據(jù),可以批量一次性寫入
- 讀寫大量重要數(shù)據(jù)時拦坠,考慮用dispatch_io连躏,其提供了基于GCD的異步操作文件I/O的API。用dispatch_io系統(tǒng)會優(yōu)化磁盤訪問
- 數(shù)據(jù)量比較大的贪婉,建議使用數(shù)據(jù)庫(比如SQLite、CoreData)
-
3卢肃、網(wǎng)絡(luò)優(yōu)化:
- 減少疲迂、壓縮網(wǎng)絡(luò)數(shù)據(jù)
- 如果多次請求的結(jié)果是相同的,盡量使用緩存
- 使用斷點續(xù)傳莫湘,否則網(wǎng)絡(luò)不穩(wěn)定時可能多次傳輸相同的內(nèi)容
- 網(wǎng)絡(luò)不可用時尤蒿,不要嘗試執(zhí)行網(wǎng)絡(luò)請求
- 讓用戶可以取消長時間運行或者速度很慢的網(wǎng)絡(luò)操作,設(shè)置合適的超時時間
- 批量傳輸幅垮,比如腰池,下載視頻流時,不要傳輸很小的數(shù)據(jù)包忙芒,直接下載整個文件或者一大塊一大塊地下載示弓。如果下載廣告,一次性多下載一些呵萨,然后再慢慢展示奏属。如果下載電子郵件,一次下載多封潮峦,不要一封一封地下載
-
4囱皿、定位優(yōu)化
- 如果只是需要快速確定用戶位置勇婴,最好用CLLocationManager的requestLocation方法。
- 定位完成后嘱腥,會自動讓定位硬件斷電
- 如果不是導(dǎo)航應(yīng)用耕渴,盡量不要實時更新位置,定位完畢就關(guān)掉定位服務(wù)
- 盡量降低定位精度齿兔,比如盡量不要使用精度最高的kCLLocationAccuracyBest
- 需要后臺定位時橱脸,盡量設(shè)置pausesLocationUpdatesAutomatically為YES,如果用戶不太可能移動的時候系統(tǒng)會自動暫停位置更新
- 盡量不要使用startMonitoringSignificantLocationChanges愧驱,優(yōu)先考慮startMonitoringForRegion: