再次附上我的github<a> https://github.com/BudSpore</a>
該文章由全棧工程師,開(kāi)源博主韓大師推薦。
本系列文章都有點(diǎn)長(zhǎng),主要講解AndroidUI顯示的大致流程,并結(jié)合項(xiàng)目對(duì)視圖,流暢度進(jìn)行性能優(yōu)化,最后會(huì)合成一個(gè)大的項(xiàng)目,認(rèn)真讀完,您一定有所收獲。
Android系統(tǒng)在2.3只支持軟件加速,后來(lái)在4.x,5.x就支持了硬件加速,OpenGL加速,Android視圖顯示整體流程是先繪制(CPU的工作)再渲染(GPU的工作),本篇文章講CPU剩下的工作和GPU做的事情,實(shí)現(xiàn)代碼都在native層,代碼極其復(fù)雜,該文章我只聊思想,以后有時(shí)間再去分析~
我們對(duì)整體渲染流程來(lái)了解一下
<h2>一、整體渲染流程</h2>
CPU負(fù)責(zé)把UI組件計(jì)算成Polygons,Texture紋理,然后交給GPU進(jìn)行柵格化渲染。每次從CPU轉(zhuǎn)移到GPU很麻煩,可是OpenGL ES可以把那些需要渲染的紋理保存在GPU Memory里面,在下次需要渲染的時(shí)候直接從中取出進(jìn)行操作。所以如果你更新了GPU保存住的紋理內(nèi)容,那么之前保存的狀態(tài)就會(huì)丟失。
我們先對(duì)相關(guān)概念進(jìn)行講解,然后講解Android渲染Surface的過(guò)程。
<h4>Surface</h4>
Surface位于Native層,我們?cè)谄聊簧峡吹降拿恳粋€(gè)window(如對(duì)話框、全屏的activity、狀態(tài)欄)都有唯一一個(gè)自己的surface,注意TextView,ImageView等視圖控件共用的一個(gè)surface,而surfaceView使用的單獨(dú)的surface,應(yīng)用使用Canvas或OpenGL在上面作畫(huà), 畫(huà)完以后,SurfaceFlinger會(huì)將各個(gè)應(yīng)用窗口的Surface進(jìn)行合成,根據(jù)各個(gè)surface在Z軸上的順序(Z-order)混合,輸出到FrameBuffer中,將它們渲染到最終的顯示屏上。每個(gè)Surface都是雙緩沖,它有一個(gè)backBuffer和一個(gè)frontBuffer,UI先在Back Buffer中繪制,然后再和Front Buffer交換,渲染到屏幕上。(Surface類使用的圖形緩沖區(qū)一般是在匿名共享內(nèi)存中分配)
Surface創(chuàng)建Canvas對(duì)象,View在Canvas繪制完以后,再把內(nèi)容畫(huà)到Surface上。最頂層的View容器(都是DecorView,它包含了View樹(shù)所有節(jié)點(diǎn))的Canvas的數(shù)據(jù)信息會(huì)轉(zhuǎn)換到一個(gè)Surface上。Surface是縱深排序(Z-ordered)的,它總在自己所在窗口的后面實(shí)屏幕上。
SurfaceHolder是Surface的監(jiān)聽(tīng)器,通過(guò)回調(diào)方addCallback(SurfaceHolder.Callback callback )監(jiān)聽(tīng)Surface的創(chuàng)建,通過(guò)獲取Surface中的Canvas對(duì)象,并鎖定之。所得到的Canvas對(duì)象 ,Canvas會(huì)遍歷傳遞給每一個(gè)view,讓每個(gè)view繪制自己的UI部分,傳遞給Surface,Surface中的數(shù)據(jù)完成后,釋放同步鎖,并提交改變Surface的狀態(tài)及圖像,將剛剛繪制好的緩沖區(qū)交換到前臺(tái),然后讓Surface Flinger利用該緩沖區(qū)的數(shù)據(jù)渲染在屏幕上。Callback 中的surfaceCreated 和surfaceDestroyed 就成了繪圖處理代碼的邊界。
(同步鎖機(jī)制是為了在繪制的過(guò)程中,Surface中的數(shù)據(jù)不會(huì)被改變。lockCanvas是為了防止同一時(shí)刻多個(gè)線程對(duì)同一canvas寫(xiě)入)
<h4>SurfaceFlinger</h4>
SurfaceFlinger位于Native層,作者覺(jué)得這個(gè)類的功能極其強(qiáng)大,可以說(shuō)是Android渲染體系的核心,SurfaceFlinger服務(wù)啟動(dòng)時(shí),我們要接觸三大線程,分別是Binder線程,UI渲染線程,控制臺(tái)事件監(jiān)控線程。SurfaceFlinger服務(wù)的作用就是被Android應(yīng)用程序調(diào)用,把繪制(測(cè)量,布局,繪制)后的窗口(Surface)渲染到手機(jī)屏幕上,它管理Android系統(tǒng)的幀緩沖區(qū)(手機(jī)屏幕被抽象成了Frame Buffer),Android把UI繪制在幀緩沖區(qū)上面。SurfaceFlinger服務(wù)在渲染Android應(yīng)用程序窗口時(shí),首先會(huì)將它們的圖形緩沖區(qū)合成到自己的圖形緩沖區(qū)來(lái),然后再渲染到硬件幀緩沖區(qū)上去,這些都在UI渲染線程執(zhí)行。
有人會(huì)有疑問(wèn),UI繞過(guò)SurfaceFlinger直接渲染到屏幕上不可以嗎,這樣確實(shí)效率提高了,可是那么多App,如果都這樣的渲染,可能前一個(gè)App的內(nèi)容會(huì)被其他的App內(nèi)容覆蓋掉。
(實(shí)際上就是兩個(gè)線程,一個(gè)渲染線程,一個(gè)UI更新線程,當(dāng)應(yīng)用正在一個(gè)緩沖區(qū)中繪制自己下一個(gè)UI狀態(tài)時(shí),Surface Flinger可以將另一個(gè)緩沖區(qū)中的數(shù)據(jù)合成顯示到屏幕上,而不用等待應(yīng)用繪制完成。)
Android應(yīng)用程序窗口使用的圖形緩沖區(qū)是在匿名共享內(nèi)存中分配,而SurfaceFlinger服務(wù)使用的圖形緩沖區(qū)是在硬件幀緩沖區(qū)上分配。
<h5>SurfaceFlinger三個(gè)線程的關(guān)系</h5>
Binder線程池用來(lái)讓Android應(yīng)用程序進(jìn)程與SurfaceFlinger服務(wù)進(jìn)行Binder進(jìn)程間通信的,有一部分通信所執(zhí)行的操作就是讓UI渲染線程更新系統(tǒng)的UI??刂婆_(tái)事件監(jiān)控線程是為了輪詢監(jiān)控屏幕(硬件幀緩沖區(qū))的睡眠/喚醒狀態(tài)切換事件的。一旦硬件幀緩沖區(qū)要進(jìn)入睡眠或者喚醒狀態(tài),控制臺(tái)事件監(jiān)控線程都需要通知UI渲染線程,以便UI渲染線程可以執(zhí)行關(guān)閉或者啟動(dòng)顯示屏的操作,下面我們會(huì)接觸到這些。
<h5>幀</h5>
我們看到的動(dòng)畫(huà)效果,其實(shí)是由很多個(gè)圖片快速、連續(xù)顯示造成的,每一幅圖片就是一幀
FPS:每秒渲染了多少幀,Android屏幕刷新列率為60Hz,相應(yīng)的,F(xiàn)PS應(yīng)該也要達(dá)到60, 小了會(huì)卡頓,大了會(huì)畫(huà)面撕裂,SurfaceFlinger把z軸不同順序的Surface合成一張圖,就是一幀。
Hz:屏幕刷新頻率
每秒要加載60幀,每一幀的渲染時(shí)間是 1000/60 = 16.67 (ms)≈16ms
我們看一看Android應(yīng)用程序和SurfaceFlinger服務(wù)的交互框架
Android應(yīng)用程序成功連接到SurfaceFlinger服務(wù)后,就可以獲得一個(gè)對(duì)應(yīng)的Client對(duì)象的Binder代理接口了(單例模式)。應(yīng)用程序使用這些Binder代理接口就可以通知SurfaceFlinger服務(wù)來(lái)繪制自己的UI了(代理模式),SurfaceFlinger服務(wù)的UI渲染線程有一個(gè)消息隊(duì)列。當(dāng)消息隊(duì)列為空時(shí),SurfaceFlinger服務(wù)的UI渲染線程就會(huì)進(jìn)入睡眠等待狀態(tài)。一旦SurfaceFlinger服務(wù)的Binder線程接收到其它進(jìn)程發(fā)送過(guò)來(lái)的渲染UI的請(qǐng)求時(shí),它就會(huì)往SurfaceFlinger服務(wù)的UI渲染線程的消息隊(duì)列中發(fā)送一個(gè)消息,以便可以將SurfaceFlinger服務(wù)的UI渲染線程喚醒起來(lái)執(zhí)行渲染的操作。
AndroidUI元素的數(shù)據(jù)與SurfaceFlinger服務(wù)的傳遞是通過(guò)匿名共享內(nèi)存機(jī)制實(shí)現(xiàn),匿名共享內(nèi)存用來(lái)保存設(shè)備顯示屏的屬性信息,例如,寬度、高度、密度和每秒多少幀等信息,匿名共享內(nèi)存最終是被結(jié)構(gòu)化為一個(gè)SharedClient對(duì)象來(lái)訪問(wèn),(每一個(gè)Android應(yīng)用程序通過(guò)Binder代理對(duì)應(yīng)一個(gè)SharedClient對(duì)象),里面包含多個(gè)SharedBufferStack。
通過(guò)上圖我們就能夠理解了, 為什么每一個(gè)SharedClient里面包含的是多個(gè)SharedBufferStack而不是1個(gè)SharedBufferStack,因?yàn)槊恳粋€(gè)SharedBufferStack都對(duì)應(yīng)1個(gè)Surface(窗口),這也是單例模式,1個(gè)SharedClient對(duì)應(yīng)一個(gè)Android應(yīng)用程序,而一個(gè)Android應(yīng)用程序可能包含有多個(gè)窗口(Surface),一個(gè)Android應(yīng)用程序可以包含多個(gè)Surface。
接下來(lái)我講解一下SharedBufferStack.
<h4>SharedBufferStack</h4>
Android系統(tǒng)分別使用SharedBufferClient和SharedBufferServer來(lái)描述SharedBufferStack,其中,SharedBufferClient用來(lái)管理空閑緩沖區(qū)列表,而SharedBufferServer用來(lái)在SurfaceFlinger服務(wù)這一側(cè)訪問(wèn)SharedBufferStack的排隊(duì)緩沖區(qū)列表(head~queue_head)。
圖中Buffer1,Buffer2區(qū)域?qū)儆谝延镁彌_區(qū),Buffer3,Buffer4屬于空閑緩沖區(qū)列表(Buffer1-2共同組成待渲染隊(duì)列),目前只使到了Buffer1 Buffer2 UI元數(shù)據(jù)緩沖區(qū),因此,只有它們才有對(duì)應(yīng)的圖形緩沖區(qū),而B(niǎo)uffer3,Bffer4I元數(shù)據(jù)緩沖區(qū)沒(méi)有。指針queue_head指向排隊(duì)緩沖區(qū)列表(待渲染隊(duì)列)的尾部,從指針tail到head之間的Buffer即為空閑緩沖區(qū)表,從指針head到queue_head之間的Buffer即為已經(jīng)使用了的緩沖區(qū)列表,已用緩沖區(qū)列表和空閑緩沖區(qū)列表是可以循環(huán)使用的,SurfaceFlinger服務(wù)緩制Buffer-1和Buffer-2的時(shí)候,就會(huì)找到與它們所對(duì)應(yīng)的GraphicBuffer,這樣就可以將對(duì)應(yīng)的UI繪制出來(lái)了。
當(dāng)Android應(yīng)用程序需要更新一個(gè)Surface的時(shí)候,它就會(huì)找到與它所對(duì)應(yīng)的SharedBufferStack,并且從它的空閑緩沖區(qū)列表的尾部取出一個(gè)空閑的Buffer,并為它編號(hào),接下來(lái)Android應(yīng)用程序就請(qǐng)求SurfaceFlinger服務(wù)為這個(gè)Buffer分配一個(gè)圖形緩沖區(qū)GraphicBuffer并為它編號(hào)(比如韓文凱),然后再將這個(gè)圖形緩沖區(qū)GraphicBuffer返回給Android應(yīng)用程序。Android應(yīng)用程序得到了SurfaceFlinger服務(wù)返回的圖形緩沖區(qū)GraphicBuffer之后,就在里面寫(xiě)入U(xiǎn)I數(shù)據(jù)。寫(xiě)完之后,就將與它所對(duì)應(yīng)編號(hào)的空閑緩沖區(qū),插入到對(duì)應(yīng)的SharedBufferStack的已經(jīng)使用了的緩沖區(qū)列表的頭部去。這一步完成了之后,Android應(yīng)用程序就通知SurfaceFlinger服務(wù)去繪制那些保存在已經(jīng)使用了的緩沖區(qū)所描述的圖形緩沖區(qū)GraphicBuffer了,當(dāng)一個(gè)已經(jīng)被使用了的Buffer被繪制了之后,它就重新變成一個(gè)空閑的Buffer了。
SharedBufferStack是在Android應(yīng)用程序和SurfaceFlinger服務(wù)之間共享的
<h4>GraphicBuffer</h4>
GraphicBuffer它是一塊指定大小、像素格式以及用用途的圖形緩沖區(qū)~GraphicBuffer內(nèi)部都包含有一塊用來(lái)保存UI數(shù)據(jù)的緩沖區(qū),而這個(gè)圖形緩沖區(qū)由系統(tǒng)幀緩沖區(qū)(FrameBuffer)或者匿名共享內(nèi)存分配。GraphicBuffer既可以作為一個(gè)Front Buffer,也可以作為一個(gè)Back Buffer,也就是所謂的雙緩沖技術(shù)。
<h5>雙緩沖技術(shù)</h5>
SurfaceFlinger服務(wù)是在Frame Buffer中分配GraphicBuffer的,而Android應(yīng)用程序是從匿名共享內(nèi)存中分配GraphicBuffer的,即SurfaceFlinger服務(wù)使用的是硬件上的雙緩沖技術(shù),而Android應(yīng)用程序使用的是軟件上的雙緩沖技術(shù)。Android應(yīng)用程序最終需要通過(guò)SurfaceFlinger服務(wù)來(lái)將它的GraphicBuffer的內(nèi)容渲染到Frame Buffer中去。并且Android應(yīng)用程序中的每一個(gè)Surface對(duì)應(yīng)的是一系列GraphicBuffer,而不是只有一個(gè)GraphicBuffer。Surface 就是雙緩沖技術(shù)中的第一層,Surface的混合操作是由SurfaceFlinger服務(wù)來(lái)做的。當(dāng)Surface改變,GraphicBuffer也會(huì)改變。
雙緩沖的工作原理是:先把需要呈現(xiàn)的所有元素都畫(huà)在一張圖上(第一層),再把這張圖整個(gè)投放到屏幕上去(第二層)
雙緩沖的優(yōu)點(diǎn)如下:
防止頻閃(記得很久以前寫(xiě)java坦克大戰(zhàn)項(xiàng)目時(shí),如果直接把頁(yè)面元素投放到屏幕上,會(huì)非常的不連貫,造成人眼可識(shí)別的卡頓)
某一具體的幀在投放到屏幕上可見(jiàn)之前,是有一層緩沖的渲染時(shí)間,這個(gè)時(shí)間讓 CPU和GPU能更好的協(xié)調(diào)工作。
<h5>OpenGL</h5>
OpenGL用來(lái)將要繪制的圖形通過(guò)調(diào)用FramebufferNativeWindow類的函數(shù)(包含點(diǎn)密度,刷新頻率等)渲染到幀緩沖區(qū)硬件設(shè)備中去,即顯示在實(shí)際屏幕上.
<h5>DisplayList</h5>
Android需要把XML布局文件轉(zhuǎn)換成GPU能夠識(shí)別并繪制的對(duì)象。這個(gè)操作是在DisplayList的幫助下完成的。DisplayList持有所有將要交給GPU繪制到屏幕上的數(shù)據(jù)信息。
請(qǐng)注意:任何時(shí)候View中的繪制內(nèi)容發(fā)生變化時(shí),都會(huì)需要重新創(chuàng)建DisplayList,渲染DisplayList,更新到屏幕上等一系列操作。這個(gè)流程的表現(xiàn)性能取決于你的View的復(fù)雜程度,View的狀態(tài)變化以及渲染管道的執(zhí)行性能。舉個(gè)例子,假設(shè)某個(gè)Button的大小需要增大到目前的兩倍,在增大Button大小之前,需要通過(guò)父View重新計(jì)算并擺放其他子View的位置。修改View的大小會(huì)觸發(fā)整個(gè)HierarcyView的重新計(jì)算大小的操作。如果是修改View的位置則會(huì)觸發(fā)HierarchView重新計(jì)算其他View的位置。如果布局很復(fù)雜,這就會(huì)很容易導(dǎo)致嚴(yán)重的性能問(wèn)題。
<h5>屏幕旋轉(zhuǎn)原理</h5>
SurfaceFlinger類關(guān)聯(lián)GraphicPlane類,在GraphicPlane里面有兩個(gè)變換矩陣,分別是初始化旋轉(zhuǎn)方向變換矩陣和實(shí)際旋轉(zhuǎn)方向的變換矩陣,各自都由寬度,高度,旋轉(zhuǎn)模式組成,首先二者相等,當(dāng)屏幕旋轉(zhuǎn),實(shí)際旋轉(zhuǎn)方向矩陣的旋轉(zhuǎn)模式也會(huì)變化,實(shí)際旋轉(zhuǎn)方向變換矩陣?yán)锩鎸捄透呋Q,匹配硬件幀緩沖區(qū)旋轉(zhuǎn)方向,最后初始化矩陣和實(shí)際旋轉(zhuǎn)矩陣相乘,得到全局變換矩陣,渲染UI的時(shí)候,任意點(diǎn)向量×全局變換矩陣就得到它的實(shí)際位置。
<h5>屏幕睡眠、喚醒原理</h5>
SurfaceFlinger類包含DisplayHardware成員,DisplayHardware類的父類會(huì)通過(guò)一個(gè)控制臺(tái)事件監(jiān)控線程來(lái)監(jiān)控顯示屏的喚醒/睡眠狀態(tài)切換,線程運(yùn)行起來(lái)輪詢監(jiān)控屏幕狀態(tài),這個(gè)線程有兩種類型,分別對(duì)應(yīng)屏幕的睡眠和喚醒狀態(tài),當(dāng)硬件幀緩沖區(qū)的控制臺(tái)被打開(kāi)和關(guān)閉時(shí),分別對(duì)應(yīng)不同的線程類型,通過(guò)這種方式控制SurfaceFlinger訪問(wèn)屏幕,控制臺(tái)事件監(jiān)控線程發(fā)現(xiàn)硬件幀緩沖區(qū)即將要進(jìn)入睡眠或者喚醒狀態(tài)時(shí),它就會(huì)往SurfaceFlinger服務(wù)的UI渲染線程的消息隊(duì)列中發(fā)送一個(gè)消息,以便SurfaceFlinger服務(wù)的UI渲染線程可以執(zhí)行凍結(jié)或者解凍顯示屏的操作
概念講完了,我們切入正題。
<h3>二、Surface渲染過(guò)程</h3>
Android應(yīng)用程序渲染一個(gè)Surface的過(guò)程大致如下所示:
<h6>
- 在SurfaceFlinger類里有一個(gè)GraphicPlane對(duì)象,GraphicPlane類內(nèi)部聚合一個(gè)DisplayHardware對(duì)象,這個(gè)對(duì)象描述當(dāng)前活動(dòng)的顯示屏,GraphicPlane首先設(shè)置顯示屏的初始大小和方向,詳情請(qǐng)看屏幕旋轉(zhuǎn)原理,然后DisplayHardware初始化FramebufferNativeWindow對(duì)象,F(xiàn)ramebufferNativeWindow類使用的圖形緩沖區(qū)是直接在硬件幀緩沖區(qū)分配的,并且它可以直接將這些圖形緩沖區(qū)渲染到硬件幀緩沖區(qū)中去,這樣可以獲得硬件幀緩沖區(qū)的點(diǎn)密度和刷新頻率等信息,并再加載HAL層中的overlay模塊,接著初始化EGL、OpenGL庫(kù),保存之前獲得的幀緩沖區(qū)信息到EGLConfig里,然后再硬件幀緩沖區(qū)創(chuàng)建系統(tǒng)主繪圖表面,這個(gè)表面用來(lái)合成和渲染所有Application的UI,獲得主繪圖表面的寬,高,點(diǎn)密度,顏色分量的大小等信息,再獲得繪圖contenxt,這樣 DisplayHardware對(duì)象初始化完成,以上都是在SurfaceFlinger服務(wù)的UI渲染線程中創(chuàng)建的,緊接著,SurfaceFlinger類會(huì)把繪圖表面和context設(shè)置為UI渲染線程的繪圖表面和context,并獲得空閑的數(shù)據(jù)緩沖區(qū),硬件幀緩沖區(qū)初始化完畢。
- 從SharedBufferStack中出棧一個(gè)空閑的UI元數(shù)據(jù)緩沖區(qū),(相對(duì)于SharedBufferClient來(lái)講tail棧尾指針往前移一位,對(duì)于SharedBufferServer來(lái)講,指針head向前移一步)減少空閑緩沖區(qū)列表的值;
- 請(qǐng)求SurfaceFlinger服務(wù)為這個(gè)數(shù)據(jù)緩沖區(qū)分配一個(gè)圖形緩沖區(qū)(GraphicBuffer);
- OpenGL通過(guò)FramebufferNativeWindow類得到圖形緩沖區(qū),在圖形緩沖區(qū)上面繪制填充好UI(當(dāng)前正在操作的Surface的裁剪區(qū)域、紋理坐標(biāo),像素格式,旋轉(zhuǎn)方向等信息)之后,OpenGL就會(huì)調(diào)用Surface,將前面得到的空閑UI元數(shù)據(jù)緩沖區(qū)添加到SharedBufferStack中的待渲染隊(duì)列的尾部,來(lái)向一個(gè)UI元數(shù)據(jù)緩沖區(qū)堆棧的待渲染隊(duì)列增加一個(gè)緩沖區(qū),即指針queue_head右移一個(gè)位置(所有需要加入到這個(gè)待渲染隊(duì)列的UI元數(shù)據(jù)緩沖都保存在queue_head的下一個(gè)位置上);
- 應(yīng)用程序請(qǐng)求SurfaceFlinger服務(wù)渲染前面已經(jīng)準(zhǔn)備好了圖形緩沖區(qū)的Surface;
- SurfaceFlinger服務(wù)從即將要渲染的Surface的SharedBufferStack的待渲染隊(duì)列中找到待渲染的UI元數(shù)據(jù)緩沖區(qū);
- SurfaceFlinger服務(wù)得到了待渲染的UI元數(shù)據(jù)緩沖區(qū)之后,接著再找到在前面第2步為它所分配的圖形緩沖區(qū),最后就可以將這些圖形緩沖區(qū)合成在一起渲染到設(shè)備顯示屏上去(幀緩沖區(qū)),最后我們就可以在設(shè)備顯示屏上看到系統(tǒng)的UI了。
</h6>
接下來(lái)我們講解GPU和硬件幀緩沖區(qū)在上述渲染過(guò)程中所做的工作。
<h4>GPU職責(zé)</h4>
GPU的主要功能就是把CPU通過(guò)OpenGL傳遞過(guò)來(lái)的UI數(shù)據(jù)光柵化處理并對(duì)數(shù)據(jù)進(jìn)行緩存。
光柵化將UI矢量數(shù)據(jù)轉(zhuǎn)化為一像素點(diǎn)的像素圖,顯示到屏幕上,XML布局文件需要在CPU中首先轉(zhuǎn)換為多邊形或者紋理,然后再傳遞給GPU進(jìn)行格柵化,這是一個(gè)耗時(shí)的操作,如圖:
<h4>屏幕職責(zé)</h4>
根據(jù)整體渲染流程圖我們對(duì)以下進(jìn)行分析:
垂直同步VSYNC:讓顯卡的運(yùn)算和顯示器刷新率一致以穩(wěn)定輸出的畫(huà)面質(zhì)量。它告知GPU在載入新幀之前,要等待屏幕繪制完成前一幀。下面的三張圖分別是GPU和硬件同步所發(fā)生的情況,Refresh Rate:屏幕一秒內(nèi)刷新屏幕的次數(shù),由硬件決定,例如60Hz.而Frame Rate:GPU一秒繪制操作的幀數(shù),單位是30fps,正常情況過(guò)程圖如下
如果幀渲染時(shí)間太快,可以防止FPS比屏幕刷新率高而導(dǎo)致的畫(huà)面撕裂。
當(dāng)GPU渲染速度過(guò)慢,就會(huì)導(dǎo)致如下情況,某些幀顯示的畫(huà)面內(nèi)容就會(huì)與上一幀的畫(huà)面相同
Android系統(tǒng)每隔16ms發(fā)出VSYNC信號(hào),觸發(fā)對(duì)UI進(jìn)行渲染,如圖:
當(dāng)CPU和GPU處理時(shí)間都很慢,或因?yàn)槠渌脑颍缭谥骶€程中干活太多,那么就會(huì)出現(xiàn)如下圖這樣的狀況:
第2幀的計(jì)算還沒(méi)有完成,所以只能繼續(xù)顯示第一幀,這就相當(dāng)于你盯著一幅圖看了32毫秒,這就是掉幀(jank),jank多了,人眼就能識(shí)別卡頓。
<h4>現(xiàn)在的三緩沖機(jī)制</h4>
正常情況下A顯示時(shí)計(jì)算B,B顯示時(shí)計(jì)算A,如果不正常情況下,會(huì)出現(xiàn)下面情況
在VSync下,在B渲染慢,A就只能在下下個(gè)脈沖開(kāi)始計(jì)算,這樣就導(dǎo)致一幀渲染延遲,每一幀都會(huì)渲染延遲,三緩沖用來(lái)解決這個(gè)問(wèn)題。
在B慢了之后,A在下下次脈沖加載之前,趁著這個(gè)空閑的時(shí)間,計(jì)算C,CPU就一直不會(huì)閑著了,這樣在脈沖到來(lái)時(shí),可能就已經(jīng)完成了B和C的計(jì)算,B,C都待投放到屏幕,多了個(gè)緩沖,解決了一幀慢,幀幀慢的問(wèn)題。
但需要重點(diǎn)說(shuō)明一下的是: 垂直同步機(jī)制是Android一直都有的,除了三緩沖,因?yàn)槿彌_會(huì)導(dǎo)致某一幀(比如C)在計(jì)算完很久之后才被選中投放到屏幕,即幀延后現(xiàn)象。而且選擇C去這個(gè)過(guò)程本身也是一系列計(jì)算,所以三緩沖是選擇性開(kāi)啟,當(dāng)雙緩沖造成的jank現(xiàn)象越來(lái)越嚴(yán)重,就開(kāi)啟去調(diào)節(jié)一下。
渲染的整體流程最好要限制在16ms內(nèi),如果大于16ms,會(huì)造成App的卡頓等性能問(wèn)題,渲染造成的問(wèn)題我們就要想辦法進(jìn)行優(yōu)化,下一篇文章我將詳細(xì)講解Android視圖的性能優(yōu)化。
請(qǐng)UIT和廣大讀者的審閱。
以此篇文章感激即將21歲的我還在努力著。