頁(yè)面間跳轉(zhuǎn)的性能優(yōu)化(一)

頁(yè)面間跳轉(zhuǎn)的性能優(yōu)化(一)

來(lái)源:Delpan

鏈接:http://m.itdecent.cn/p/77847c0027c9

前言

現(xiàn)在App的頁(yè)面越來(lái)越復(fù)雜,頁(yè)面初始化的工作越來(lái)越多,加載頁(yè)面所需的時(shí)間也隨之增長(zhǎng),如果頁(yè)面加載的時(shí)間過(guò)長(zhǎng),這將會(huì)影響App的流暢度及用戶體驗(yàn),我們需要解決這一問(wèn)題。觀察過(guò)一些日常使用的App,頁(yè)面間跳轉(zhuǎn)的性能問(wèn)題總結(jié)為以下三種情形:

1).A頁(yè)面跳轉(zhuǎn)到B頁(yè)面,由于B頁(yè)面需要加載大量的數(shù)據(jù),所以導(dǎo)致頁(yè)面跳轉(zhuǎn)延遲。

2).A頁(yè)面跳轉(zhuǎn)到B頁(yè)面,由于B頁(yè)面需要加載大量UI元素,所以導(dǎo)致頁(yè)面跳轉(zhuǎn)延遲。

3).A頁(yè)面跳轉(zhuǎn)到B頁(yè)面,由于A或B頁(yè)面的GPU使用率過(guò)高,所以導(dǎo)致面頁(yè)跳轉(zhuǎn)時(shí)出現(xiàn)過(guò)場(chǎng)動(dòng)畫(huà)不流暢,緩慢等。

情形一比較容易解決,利用輔助線程加數(shù)據(jù)即可;由于圖層樹(shù)的更新(即UI頁(yè)面的更新)需要在主線程上完成,所以情形二的性能優(yōu)化讓很多開(kāi)發(fā)人員頭痛;雖然網(wǎng)上有很多視圖性能優(yōu)化的技術(shù)文,但據(jù)了解,其實(shí)大部份團(tuán)隊(duì)都不會(huì)去做視圖的性能優(yōu)化,情形三也是最普遍存在。本文將會(huì)講述這三種情形的性能優(yōu)化,但并不會(huì)講述頁(yè)面間跳轉(zhuǎn)的過(guò)渡動(dòng)畫(huà),及頁(yè)面間跳轉(zhuǎn)的原理,這部份在網(wǎng)上已經(jīng)有大量技術(shù)文講述。關(guān)于情形三所涉及的像素混合,像素對(duì)齊,離屏渲染等知識(shí)點(diǎn)將不進(jìn)行講述,本文會(huì)講述一種偷懶的方式來(lái)優(yōu)化情形三。

點(diǎn)擊下載Demo,或https://github.com/IOSDelpan/SmoothTransitionDemo。

目錄

基礎(chǔ)知識(shí)

-渲染服務(wù)進(jìn)程

-UIView與CALayer

-圖層樹(shù),呈現(xiàn)樹(shù),渲染樹(shù)

-UI更新過(guò)程

-RunLoop更新UI的工作

情形一

情形二

基礎(chǔ)知識(shí)

想在屏幕上顯示一個(gè)視圖,我們只需要簡(jiǎn)單地實(shí)現(xiàn)以下代碼,并運(yùn)行Application到模擬器或真機(jī)即可。

-渲染服務(wù)進(jìn)程

雖然看到的效果跟Application的代碼是一一對(duì)應(yīng)的,但視圖繪制渲染的工作并不是由Application完成的,而是由一個(gè)名為渲染服務(wù)的進(jìn)程(BackBoard)來(lái)完成的,這個(gè)進(jìn)程的工作便是你在屏幕上看到的一切內(nèi)容。既然做實(shí)際繪制渲染工作的是渲染服務(wù)進(jìn)程,那么渲染服務(wù)進(jìn)程要進(jìn)行繪制渲染的依據(jù)是什么呢?而Application跟渲染服務(wù)進(jìn)程又是怎么交互的呢?

-UIView與CALayer

為了方便往后的講述,首先簡(jiǎn)單講述一下UIView與CALayer的關(guān)系(不講述兩者的區(qū)別)。簡(jiǎn)單來(lái)說(shuō),UIView就是CALayer的管理器,CALayer的主要工作是為屏幕的繪制渲染提供所需的數(shù)據(jù)源,也就是說(shuō),你在屏幕上看到的內(nèi)容,都是來(lái)源于CALayer。每一個(gè)UIView都有一個(gè)Backing Layer,UIView的UI屬性跟CALayer的屬性是一一對(duì)應(yīng)的,設(shè)置UIView的UI屬性實(shí)際上是設(shè)置CALayer對(duì)應(yīng)的屬性,即UIView的繪制渲染工作是由CALayer完成。UIView對(duì)象之間存在著一定的層級(jí)關(guān)系,那么所以UIView的Backing Layer也相應(yīng)的存在著一定的層級(jí)關(guān)系,這個(gè)層級(jí)關(guān)系叫做圖層樹(shù)(模型樹(shù))。接下來(lái)的知識(shí)點(diǎn)直接用圖層來(lái)講述。

-圖層樹(shù),呈現(xiàn)樹(shù),渲染樹(shù)

使用Core Animation的Application(iOS默認(rèn)使用),除了圖層樹(shù),還有呈現(xiàn)樹(shù)和渲染樹(shù),每個(gè)圖層對(duì)象集合都扮演著不同的角色。圖層樹(shù)中的圖層對(duì)象負(fù)責(zé)存儲(chǔ)在屏幕上顯示的目標(biāo)值,呈現(xiàn)樹(shù)中的圖層對(duì)象負(fù)責(zé)存儲(chǔ)在屏幕上顯示的瞬時(shí)值,而渲染樹(shù)的圖層對(duì)象是渲染服務(wù)進(jìn)程用來(lái)繪制渲染所使用的。Application使用到的是圖層樹(shù)與呈現(xiàn)樹(shù),上圖中的代碼,使用的則是圖層樹(shù)中的圖層對(duì)象。既然渲染服務(wù)進(jìn)程使用的是渲染樹(shù),那么圖層樹(shù)中的圖層對(duì)象所存儲(chǔ)的目標(biāo)值又是如何顯示在屏幕上呢?

-UI更新過(guò)程

在Application的主線程中設(shè)置圖層樹(shù)中的圖層對(duì)象時(shí),被設(shè)置的圖層對(duì)象會(huì)被標(biāo)記為待處理狀態(tài)(在輔助線程設(shè)置圖層對(duì)象,圖層對(duì)象不會(huì)被標(biāo)記),當(dāng)Application的主線程即將進(jìn)入休眠時(shí),Core Animation會(huì)打包圖層樹(shù)中待處理的圖層對(duì)象,并通過(guò)IPC發(fā)送到渲染服務(wù)進(jìn)程,IPC是通過(guò)端口交互的,消息在兩個(gè)端口間傳遞,而渲染服務(wù)進(jìn)程的端口是不公開(kāi)的(更多關(guān)于內(nèi)核方面的資料可以閱讀《OS X與iOS內(nèi)核編程》),當(dāng)打包的圖層發(fā)送到渲染服務(wù)進(jìn)程時(shí),這些圖層會(huì)被反序列化成渲染樹(shù),渲染服務(wù)進(jìn)程便可以開(kāi)始繪制渲染的工作。

-RunLoop更新UI的工作

Application的主線程為了保持存活狀態(tài),啟動(dòng)了運(yùn)行循環(huán)(RunLoop),RunLoop是一個(gè)事件處理循環(huán),使用RunLoop的目的是讓你的線程在有工作的時(shí)候忙于工作,而沒(méi)工作的時(shí)候處于休眠狀態(tài)。下圖為RunLoop調(diào)度的順序。

從RunLoop調(diào)度的順序得知,當(dāng)沒(méi)有未處理事件時(shí),線程就會(huì)進(jìn)入休眠狀態(tài)。在RunLoop中注冊(cè)了一個(gè)觀察者,這個(gè)觀察者用于監(jiān)聽(tīng)線程即將進(jìn)入休眠的狀態(tài),當(dāng)線程即將進(jìn)入休眠時(shí),觀察者會(huì)執(zhí)行監(jiān)聽(tīng)回調(diào)_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv(),這個(gè)函數(shù)實(shí)現(xiàn)了Core Animation打包圖層樹(shù)中待處理的圖層對(duì)象,并通過(guò)IPC發(fā)送到渲染服務(wù)進(jìn)程的工作。本文不會(huì)提及深入的RunLoop原理,深入部份會(huì)在RunLoop篇講述。

情形一

絕大多數(shù)的App頁(yè)面都是用來(lái)展示各式各樣的數(shù)據(jù),如果跳轉(zhuǎn)頁(yè)面的同時(shí),在主線程加載大量的數(shù)據(jù),便會(huì)出現(xiàn)以下情況。

如Gif圖所示,屏幕卡頓了一會(huì)才出現(xiàn)頁(yè)面跳轉(zhuǎn)的過(guò)場(chǎng)動(dòng)畫(huà),即出現(xiàn)了頁(yè)面跳轉(zhuǎn)延遲的情況。從基礎(chǔ)知識(shí)的UI更新過(guò)程,RunLoop更新UI的工作中得知,Application的UI更新在于主線程即將進(jìn)入休眠時(shí),RunLoop觀察者的回調(diào)函數(shù)_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv(),只要該函數(shù)執(zhí)行完,我們就可以在屏幕上看到UI更新的結(jié)果。既然知道這是由于在主線程加載大量數(shù)據(jù)所致,那么我們來(lái)解決這一情形,首先需要知道是那個(gè)函數(shù)占用了CPU,使用Instruments的Time Profiler測(cè)試一下。

從測(cè)試的結(jié)果可以看到,是setUpData這個(gè)方法占用了主線程,而setUpData方法是在viewDidLoad里被調(diào)用的,那么viewDidLoad又是在何時(shí)被調(diào)用的呢?

從主線程活動(dòng)的狀態(tài)以及執(zhí)行堆??梢钥闯觯瑅iewDidLoad是在_ZN2CA11Transaction17observer_callbackEP19__CFRunLoopObservermPv()里被調(diào)用的,大致過(guò)程如下圖。

知道了問(wèn)題函數(shù)和主線程的執(zhí)行堆棧,那么解決這一問(wèn)題就變得很簡(jiǎn)單。只需要把加載數(shù)據(jù)的setUpData方法放到輔助線程中執(zhí)行并返回結(jié)果到主線程顯示即可。

當(dāng)我們使用多線程去加載數(shù)據(jù)時(shí),由于主線程沒(méi)有被阻塞,所以沒(méi)有出現(xiàn)頁(yè)面跳轉(zhuǎn)延遲的情況,具體代碼請(qǐng)看Demo。

情形二

在頁(yè)面跳轉(zhuǎn)時(shí),除了加載數(shù)據(jù),還需要加載UI元素,而加載UI元素的工作一般會(huì)在viewDidLoad中完成,如果需要加載的UI元素過(guò)多,同樣會(huì)出現(xiàn)頁(yè)面跳轉(zhuǎn)延遲的情況。

如Gif圖所示,出現(xiàn)了頁(yè)面跳轉(zhuǎn)延遲的情況,這是由于在viewDidLoad中生成大量的UI元素所致。在情形一中,我們用輔助線程加載數(shù)據(jù)解決了頁(yè)面跳轉(zhuǎn)延遲的情況,那么我們可以以同樣的方式來(lái)加載UI元素。

雖然我們可以把生成UI元素的工作放到輔助線程中完成,且看到的效果相同,但這種處理方式的效率非常低,這種方式生成大量UI元素所需要的時(shí)間比直接在主線程中生成要多數(shù)倍,增加加載頁(yè)面所需要的時(shí)間,這顯然不是我們想要的結(jié)果,我們想要的是既可以在主線程生成UI,又可以不出現(xiàn)頁(yè)面跳轉(zhuǎn)延遲的情況。

我們知道當(dāng)Application的主線程即將進(jìn)入休眠時(shí),Core Animation會(huì)打包圖層樹(shù)中待處理的圖層對(duì)象,除了打包圖層對(duì)象,Core Animation還會(huì)打包基礎(chǔ)動(dòng)畫(huà)對(duì)象,一并發(fā)送到渲染服務(wù)進(jìn)程,渲染服務(wù)進(jìn)程接收到圖層對(duì)象和動(dòng)畫(huà)對(duì)象后,會(huì)根據(jù)動(dòng)畫(huà)對(duì)象來(lái)不斷計(jì)算和繪制圖層對(duì)象,形成屏幕上看到的動(dòng)畫(huà)效果,所以動(dòng)畫(huà)對(duì)象能否及時(shí)發(fā)送到渲染服務(wù)進(jìn)程就顯得非常重要,這關(guān)系到你App的用戶體驗(yàn)。頁(yè)面跳轉(zhuǎn)時(shí)的過(guò)場(chǎng)動(dòng)畫(huà)的打包工作,跟viewDidLoad是在同一次RunLoop中,所以viewDidLoad的執(zhí)行時(shí)間就顯得很關(guān)鍵。除了viewDidLoad以外,在UIViewController的生命周期里還有另外幾個(gè)方法,我們來(lái)看一下這幾個(gè)方法的被調(diào)度的情況。

從打印信息中得知,viewWillAppear,viewWillLayoutSubviews,viewDidLayoutSubviews是緊跟viewDidLoad之后執(zhí)行的,所以這幾個(gè)方法的執(zhí)行時(shí)間同樣很重要,但我們發(fā)現(xiàn)viewDidAppear方法并沒(méi)有被調(diào)度,即viewDidAppear跟前面幾個(gè)方法并在不同一次RunLoop中,既然如此,我們可以便使用viewDidAppear來(lái)解決頁(yè)面跳轉(zhuǎn)延遲的情況。

Gif圖顯示的效果和根據(jù)基礎(chǔ)知識(shí)猜想的結(jié)果一樣,解決了頁(yè)面跳轉(zhuǎn)延遲的情況,那么viewDidAppear何時(shí)被調(diào)用?

從主線程的執(zhí)行堆??傻弥瑅iewDidAppear是在過(guò)場(chǎng)動(dòng)畫(huà)結(jié)束后被調(diào)用的,而過(guò)場(chǎng)動(dòng)畫(huà)的持續(xù)時(shí)間是0.35秒。

我們來(lái)算一下整個(gè)過(guò)程所需要的時(shí)間,假設(shè)生成頁(yè)面需要0.5秒,那么優(yōu)化前后所需要的時(shí)間都是0.85秒(經(jīng)測(cè)試,其實(shí)時(shí)間有減少,只是少到可以忽略,時(shí)間減少的部份應(yīng)該是GPU計(jì)算量的問(wèn)題),雖然問(wèn)題解決了,但效果并不理想,因?yàn)橥瓿烧麄€(gè)過(guò)程所需要的時(shí)間并沒(méi)有減少,所以我們需要進(jìn)一步優(yōu)化。嘗試過(guò)很多種方式,但似乎沒(méi)有什么方式可以很好地減少生成UI元素所需要的時(shí)間,那么我們只能把優(yōu)化的方向放在過(guò)場(chǎng)動(dòng)畫(huà)的持續(xù)時(shí)間上了。

從Gif圖顯示的效果可以看到,完成整個(gè)過(guò)程所需要的時(shí)間明顯減少了,實(shí)現(xiàn)原理請(qǐng)看下圖。

如圖所示,把生成UI元素的任務(wù)從本次RunLoop中抽出,提交到下一次的RunLoop當(dāng)中,因?yàn)楸敬蜶unLoop沒(méi)有被阻塞,所以能及時(shí)把圖層對(duì)象和動(dòng)畫(huà)對(duì)象發(fā)送到渲染服務(wù)進(jìn)程,渲染服務(wù)進(jìn)程便開(kāi)始進(jìn)行過(guò)場(chǎng)動(dòng)畫(huà)的繪制與渲染,與此同時(shí),Application的主線程RunLoop進(jìn)入下一次Loop,開(kāi)始執(zhí)行生成UI元素的任務(wù),即,可以理解為渲染服務(wù)進(jìn)程繪制渲染過(guò)場(chǎng)動(dòng)畫(huà),和Application生成UI元素的任務(wù)同時(shí)進(jìn)行,這樣我們便把動(dòng)畫(huà)的時(shí)間也利用上,從而大大減小了整個(gè)過(guò)程所需的時(shí)間。

在Demo中,是使用GCD的方式來(lái)實(shí)現(xiàn),也可以使用performSelector: withObject: afterDelay:方法來(lái)實(shí)現(xiàn)同樣的效果,但不建議,因?yàn)檫@樣會(huì)增加主線程RunLoop的執(zhí)行時(shí)間。

我們還可以把這個(gè)耗時(shí)的任務(wù)分解成若干個(gè)小的任務(wù)來(lái)實(shí)現(xiàn)。

如Gif圖所示,沒(méi)有出現(xiàn)頁(yè)面跳轉(zhuǎn)延遲的情況。使用定器時(shí)把任務(wù)分解,可以得到同樣的結(jié)果,若是加上一些動(dòng)畫(huà),效果會(huì)更棒。在Demo中,用到的定時(shí)器是CADisplayLink,用NSTimer可以達(dá)得到樣的效果,關(guān)于CADisplayLink,建議能不用就不用,因?yàn)樗鼤?huì)使目標(biāo)線程長(zhǎng)期處于活躍狀態(tài)。

情形三將會(huì)在頁(yè)面間跳轉(zhuǎn)的性能優(yōu)化(二)中講述。如果文中有講錯(cuò)的地方,還望指出。

Tips:雖然黑科技很強(qiáng)大,但也很危險(xiǎn),在你沒(méi)有足夠了解它的情況下,不能輕易去使用,更不能濫用。本文的講述旨在如何利用基礎(chǔ)知識(shí)來(lái)解決日常開(kāi)發(fā)中遇到的問(wèn)題,并不是硬式化地講解使用方式。

閱讀 281712 投訴

精選留言

寫(xiě)留言

5

wh

放在 view did appear 每次調(diào)用那個(gè)頁(yè)面都會(huì)執(zhí)行一次吧 比如 返回這個(gè)頁(yè)面 也會(huì)調(diào)用一次

2天前

3

DENG

這個(gè)好,mark

3天前

3

003

漲知識(shí)

3天前

以上留言由公眾號(hào)篩選后顯示

了解留言功能詳情

?

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 前言 現(xiàn)在App的頁(yè)面越來(lái)越復(fù)雜,頁(yè)面初始化的工作越來(lái)越多,加載頁(yè)面所需的時(shí)間也隨之增長(zhǎng),如果頁(yè)面加載的時(shí)間...
    Delpan閱讀 22,750評(píng)論 92 464
  • 續(xù)言 在頁(yè)面間跳轉(zhuǎn)的性能優(yōu)化(一)中介紹了一些基礎(chǔ)知識(shí),講述了情形一與情形二的優(yōu)化方式及原理,但有許多人對(duì)情...
    Delpan閱讀 8,252評(píng)論 32 95
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 179,366評(píng)論 25 708
  • 寒降南山,霜滿前庭。東風(fēng)惡、尤勝刀兵。 三春桃李,一夜霜清。見(jiàn)天殘?jiān)?,人殘影,?shù)殘形。 匹年窮歲,長(zhǎng)情篤信。笑書(shū)生...
    十萬(wàn)字的夢(mèng)想閱讀 407評(píng)論 2 6
  • 1、復(fù)制字符串到剪切板 2、打電話 創(chuàng)建一個(gè)成員變量UIWebView來(lái)加載URL,撥完后能自動(dòng)回到原應(yīng)用 3、發(fā)...
    univer2012閱讀 210評(píng)論 0 0

友情鏈接更多精彩內(nèi)容