UI面是怎么顯示到屏幕上的?
Android視圖顯示流程是先繪制(CPU的工作)在渲染(GPU)的工作
CPU負責把UI組件計算成Polygons,Texture紋理,然后在交給GPU進行柵格化渲染到屏幕上
繪制,畫畫這些都要先有畫布,Android上UI最終的畫布就是Surface
Surfce
Surface位于Native層,全我們在屏幕上看到每一個window(如對話框屏的Activity,狀態欄)都有唯一 一個自己的suface,注意TextView,ImageView等視圖 空間都共用一個surface,而surfaceView使用的surface,應用使用Canvas或OpenGL在上面作畫,畫完SurfaceFinger會將各個應用窗口的Surface進行合成,根據各個Suface在Z軸上的順序混合,輸出到FrameBuffer,UI先在BackBuffer中繪制,然后在和Front Buffer交換,渲染到屏幕上。
Surface創建Canvas對象,View在Canvas繪制完以后,再把內容滑到Surfaces上。最頂層的View容器(DecorView,它包含了View樹所有節點)的Canvas的數據信息會轉換到一個SUrface上,Surface是縱深排序(Z-ordered)的,它總在自己所在窗口的后面實屏幕上。SurfaceHolder是Surface的監聽器,通過獲取Surface中的Canvas對象,并鎖定之,所得到的Canvas對象,Canvas會遍歷傳遞給每一個View,讓每個View繪制自己的UI部分,傳遞給Surface,Surface中的數據完成后,釋放同步鎖,并提交改變Surface的狀態及圖像,將剛剛繪制好的緩沖區交換到前臺,然后Surface Finger利用該緩沖區的數據渲染在屏幕上,Callback中的surfaceCreated和surfaceDestroyed就成了繪圖處理代碼的邊界(同步鎖機制是為了在繪制的過程中,Surface中的數據不會被改變。lockCanvas是為了防止同一時刻多個線程對同一canvas寫入)
SurfaceFlinger
SurfaceFinger位于Native層,這個是Android渲染體系的核心,SurfaceFlinger服務啟動時,我們要接觸三大線程,分別是Binder線程,UI渲染線程,控制臺事件線程,FurfaceFlinger服務作用就是被Android應用程序調用,把繪制(測量,布局,繪制)后的窗口選軟到手機屏幕上,它管理Android系統的幀緩沖去(手機屏幕被抽象成了Frame Buffer),Android把UI繪制在幀緩沖區上面。
SurfaceFinger服務在渲染Android應用程序時,首先會將他們的圖形緩沖區合成到自己的圖形緩沖區來,然后在渲染到硬件幀緩沖區上去,這些都在UI渲染線程執行。
UI繞過SurfaceFinger直接渲染到屏幕上不可以?
這些確實效率提高了,可是那么多APP,如果這樣渲染,可能前一個APP的內容會別其他的APP內容覆蓋掉,(實際上就是兩個線程,一個渲染線程,一個UI更新線程,當應用正在一個緩沖區中繪制自己下一個UI狀態時,Surface Flinger可以將另一個緩沖區中的數據合成顯示到屏幕上,而不用等待應用繪制完成。)Android應用程序窗口使用圖形緩沖區是匿名共享內存分配,而SurfaceFlinger服務使用的圖形緩沖區是在硬件幀緩沖區分配
SurfaceFlinger三個線程的關系
Binder線程池用來讓Android應用程序進程與SurfaceFliger服務進行Binder進程間通訊的,有一部分通信所執行的操作就是讓UI渲染線程更新系統的UI,一旦硬件幀緩沖區要進入睡眠或者喚醒狀態,控制臺事件監控線程都需要通知UI線程渲染,以便UI渲染線程可以執行關閉或者啟動顯示屏的操作
幀
我們看到的動畫效果,其實是由很多個圖片快速,連續顯示造成的,每一副圖片就是一幀FPS:每秒渲染了多少幀,Android屏幕刷新幀率60Hz,相應的,FPS應該也要達到60,小了會卡頓,大了會畫面撕裂,SurfaceFlinger把Z軸不同順序的Surface合成一張圖,就是一幀。
Hz:屏幕刷新頻率,每秒要加載60幀,每一幀的渲染時間是1000/60=16.67約16ms
Android應用程序和SurfaceFlinger服務的交互框架
Android應用程序成功連接到SUrfaceFinger服務后,就可以獲得一個對應的Client的對象的Binder代理接口了(單利模式)。應用程序使用這些Binder代理接口就可以通知SurfaceFlinger服務來繪制自己的UI了(代理模式),SurfaceFlinger服務的UI的UI渲染線程有一個消息隊列,當消息隊列為空時,SurfaceFlinger服務的UI渲染線程就會進入睡眠等待狀態,一旦SurfaceFlinger服務的Binder線程接收到其他進程發送過來的渲染UI的請求時,他就會往SurfaceFlinger服務的UI渲染線程的消息隊列發送一個消息,以便可以將SurfaceFlinger服務的UI渲染線程喚醒起來執行渲染的操作
AndroidUI元素的數據與SufaceFlinger服務的傳遞是通過匿名共享機制實現,匿名共享內存用來保存設備顯示的屬性信息,例如,寬度,高度,秘密和每秒多少幀等信息,匿名共享內存最終是被結構化為一個SHaredClient對象來訪問,(每一個Android應用程序通過Binder代理對應一個SHaredClient對象),里面包含多個SharedBUfferStack.
通過上圖我們就能夠理解了,為什么每一個SharedClient里面包含的是多個SharedBufferStack而不是一個SharedBufferStack,因為每一個SharedBufferStack都對應一個Surface(窗口),這也是單例模式,一個SharedClient對應一個Android應用程序,而一個Android應用程序可能包含有多個窗口(Surface),一個Android應用程序可以包含多個Surface
SharedBufferStack
Android系統分別使用SharedBufferClient和SharedBufferStack,其中,SharedBUfferClient用來管理空閑緩存區列表,而SharedBufferServer用來在SurfaceFlinger服務這一側訪問SharedBufferStack的排隊列表緩沖區列表,(head~queue_head)
圖中Buffer1,Buffer2區域屬于已用緩沖區,Buffer3,Buffer4屬于空閑緩沖區列表(Buffer1-2共同組成的待渲染隊列),目前只使用到了BUffer1,BUffer2 UI元數據緩沖區,因此,只有它們才有對應的圖形緩沖區,而Buffer3,Buffer4元數據緩沖區沒有,指針queue_head指向排隊緩沖區列表(待渲染隊列)的尾部,從指針tail到head之間的Buffer即為空閑緩沖區表,從指針head到queue_head之間的Buffer即為已經使用了緩沖區列表,已用緩沖區類別和空閑緩存區列表是可以循環使用的,SurfaceFlinger服務緩制Buffer-1和Buffer-2的時候,就會找到與它們對應的CraphicBuffer,這樣就可以將他們繪制出來了。當Android應用程序需要更新一個Surface的時候,它就會找到與它對應的SharedBufferStack,并且從它的空閑緩沖區列表的尾部取出一個空閑的Buffer,并為它編號 ,接下來Android應用程序就請求SurfaceFlinger服務為這個Buffer分配一個圖形緩沖區,GraphicBuffer并為它編號(比如1號),然后在將這個圖形緩沖區GraphicBuffer返回給Android應用程序,Android應用程序得到了SurfaceFlinger服務返回的圖形緩沖區GraphicBuffer之后,就在里面寫入UI數據,寫完之后,就將它所對應編號的空閑緩存區,插入到對應的SharedBufferStack的已經使用了的緩沖區列表的頭部去,這一步完成了之后,Android應用程序就通知SurefaceFlinger服務去繪制那些保存在已經使用了緩沖區所描述的圖形GraphicBuffer了,當一個已經被使用了的Buffer被繪制在會后,它就重新變成一個空閑的Buffer,SharedBufferStack是在Android應用程序和SurfaceFlinger服務之間共享的
GraphicBuffer
GraphicBuffer它是一塊指定大小,像素格式以及用途的圖形緩沖區,GraphicBuffer內部包含一塊用來保存UI數據的緩沖區,而這個圖形緩沖區由系統幀緩沖區(FrameBuffer)或者匿名共享內存分配,GraphicBuffer既可以作為一個Font Buffer,也可以作為一個BackBuffer,
雙緩沖技術
SurfaceFlinger服務是在Frame Buffer中分配GraphicBuffer的,而Android應用程序是從匿名共享內存中分配GraphicBuffer的即SurfaceFlinger服務使用的是硬件上的雙緩沖技術,而Android應用程序使用的是軟件上的雙緩沖技術。Android應用程序最終需要通過SurfaceFlinger服務來將它的GraphicBuffer的內容渲染到Frame Buffer中去。并且Android應用程序中的每一個Surface對應的是一系列GraphicBuffer,而不是只有一個GraphicBuffer。Surface 就是雙緩沖技術中的第一層,Surface的混合操作是由SurfaceFlinger服務來做的。當Surface改變,GraphicBuffer也會改變。
雙緩沖的工作原理是:先把需要呈現的所有元素都畫在一張圖上(第一層),再把這張圖整個投放到屏幕上去(第二層)
雙緩沖的優點如下:
防止頻閃(記得很久以前寫java坦克大戰項目時,如果直接把頁面元素投放到屏幕上,會非常的不連貫,造成人眼可識別的卡頓)
某一具體的幀在投放到屏幕上可見之前,是有一層緩沖的渲染時間,這個時間讓 CPU和GPU能更好的協調工作。
OpenGL
OpenGL用來將要繪制的圖形通過調用FramebufferNativeWindow類的函數(包含點密度,刷新頻率等)渲染到幀緩沖區硬件設備中去,即顯示在實際屏幕上.
DisplayList
Android需要把XML布局文件轉換成GPU能夠識別并繪制的對象,這個操作是在DisplayList的幫助下完成的。DisplayList持有所有將要交給GPU繪制到屏幕上的數據信息。請注意:任何時候View中的繪制內容發生變化時,都會需要重新創建DisplayList,渲染DisplayList,更新到屏幕上等一系列操作。個流程的表現性能取決于你的View的復雜程度,View的狀態變化以及渲染管道的執行性能。舉個例子,假設某個Button的大小需要增大到目前的兩倍,在增大Button大小之前,需要通過父View重新計算并擺放其他子View的位置。修改View的大小會觸發整個HierarcyView的重新計算大小的操作。如果是修改View的位置則會觸發HierarchView重新計算其他View的位置。如果布局很復雜,這就會很容易導致嚴重的性能問題。
屏幕旋轉原理
SurfaceFlinger類關聯GraphicPlane類,在GraphicPlane里面有兩個變換矩陣,分別是初始化旋轉方向變換矩陣和實際旋轉方向的變換矩陣,各自都由寬度,高度,旋轉模式組成,首先二者相等,當屏幕旋轉,實際旋轉方向矩陣的旋轉模式也會變化,實際旋轉方向變換矩陣里面寬和高互換,匹配硬件幀緩沖區旋轉方向,最后初始化矩陣和實際旋轉矩陣相乘,得到全局變換矩陣,渲染UI的時候,任意點向量×全局變換矩陣就得到它的實際位置
屏幕睡眠、喚醒原理
SurfaceFlinger類包含DisplayHardware成員,DisplayHardware類的父類會通過一個控制臺事件監控線程來監控顯示屏的喚醒/睡眠狀態切換,線程運行起來輪詢監控屏幕狀態,當硬件幀緩沖區的控制臺被打開和關閉時,分別對應不同的線程類型,通過這種方式控制SurfaceFlinger訪問屏幕,控制臺事件監控線程發現硬件幀緩沖區即將要進入睡眠或者喚醒狀態時,它就會往SurfaceFlinger服務的UI渲染線程的消息隊列中發送一個消息,以便SurfaceFlinger服務的UI渲染線程可以執行凍結或者解凍顯示屏的操作
Surface總結
在SurfaceFlinger類里有一個GraphicPlane對象,GraphicPlane類內部聚合一個DisplayHardware對象,這個對象描述當前活動的顯示屏,GraphicPlane首先設置顯示屏的初始大小和方向,詳情請看屏幕旋轉原理,然后DisplayHardware初始化FramebufferNativeWindow對象,FramebufferNativeWindow類使用的圖形緩沖區是直接在硬件幀緩沖區分配的,并且它可以直接將這些圖形緩沖區渲染到硬件幀緩沖區中去,這樣可以獲得硬件幀緩沖區的點密度和刷新頻率等信息,并再加載HAL層中的overlay模塊,接著初始化EGL、OpenGL庫,保存之前獲得的幀緩沖區信息到EGLConfig里,然后再硬件幀緩沖區創建系統主繪圖表面,這個表面用來合成和渲染所有Application的UI,獲得主繪圖表面的寬,高,點密度,顏色分量的大小等信息,再獲得繪圖contenxt,這樣 DisplayHardware對象初始化完成,以上都是在SurfaceFlinger服務的UI渲染線程中創建的,緊接著,SurfaceFlinger類會把繪圖表面和context設置為UI渲染線程的繪圖表面和context,并獲得空閑的數據緩沖區,硬件幀緩沖區初始化完畢。
從SharedBufferStack中出棧一個空閑的UI元數據緩沖區,(相對于SharedBufferClient來講tail棧尾指針往前移一位,對于SharedBufferServer來講,指針head向前移一步)減少空閑緩沖區列表的值;
請求SurfaceFlinger服務為這個數據緩沖區分配一個圖形緩沖區(GraphicBuffer);
OpenGL通過FramebufferNativeWindow類得到圖形緩沖區,在圖形緩沖區上面繪制填充好UI(當前正在操作的Surface的裁剪區域、紋理坐標,像素格式,旋轉方向等信息)之后,OpenGL就會調用Surface,將前面得到的空閑UI元數據緩沖區添加到SharedBufferStack中的待渲染隊列的尾部,來向一個UI元數據緩沖區堆棧的待渲染隊列增加一個緩沖區,即指針queue_head右移一個位置(所有需要加入到這個待渲染隊列的UI元數據緩沖都保存在queue_head的下一個位置上);
應用程序請求SurfaceFlinger服務渲染前面已經準備好了圖形緩沖區的Surface;
SurfaceFlinger服務從即將要渲染的Surface的SharedBufferStack的待渲染隊列中找到待渲染的UI元數據緩沖區;
SurfaceFlinger服務得到了待渲染的UI元數據緩沖區之后,接著再找到在前面第2步為它所分配的圖形緩沖區,最后就可以將這些圖形緩沖區合成在一起渲染到設備顯示屏上去(幀緩沖區),最后我們就可以在設備顯示屏上看到系統的UI了。
GPU職責
GPU的主要功能就是把CPU通過OpenGL傳遞過來的UI數據光柵化處理并對數據進行緩存,光柵化將UI矢量數據轉化為一像素點的像素圖,顯示到屏幕上,XML布局文件需要在CPU中首先轉換為多邊形或者紋理,然后再傳遞給GPU進行格柵化,這是一個耗時的操作,如圖:
屏幕職責
垂直同步VSYNC:讓顯卡的運算和顯示器刷新率一致以穩定輸出的畫面質量。它告知GPU在載入新幀之前,要等待屏幕繪制完成前一幀。下面的三張圖分別是GPU和硬件同步所發生的情況,Refresh Rate:屏幕一秒內刷新屏幕的次數,由硬件決定,例如60Hz.而Frame Rate:GPU一秒繪制操作的幀數,單位是30fps,正常情況過程圖如下
如果幀渲染時間太快,可以防止FPS比屏幕刷新率高而導致的畫面撕裂。
當GPU渲染速度過慢,就會導致如下情況,某些幀顯示的畫面內容就會與上一幀的畫面相同
Android系統每隔16ms發出VSYNC信號,觸發對UI進行渲染,如圖:
當CPU和GPU處理時間都很慢,或因為其他的原因,如在主線程中干活太多,那么就會出現如下圖這樣的狀況:
正常情況下A顯示時計算B,B顯示時計算A,如果不正常情況下,會出現下面情況
在VSync下,在B渲染慢,A就只能在下下個脈沖開始計算,這樣就導致一幀渲染延遲,每一幀都會渲染延遲,三緩沖用來解決這個問題。
在B慢了之后,A在下下次脈沖加載之前,趁著這個空閑的時間,計算C,CPU就一直不會閑著了,這樣在脈沖到來時,可能就已經完成了B和C的計算,B,C都待投放到屏幕,多了個緩沖,解決了一幀慢,幀幀慢的問題。
但需要重點說明一下的是: 垂直同步機制是Android一直都有的,除了三緩沖,因為三緩沖會導致某一幀(比如C)在計算完很久之后才被選中投放到屏幕,即幀延后現象。而且選擇C去這個過程本身也是一系列計算,所以三緩沖是選擇性開啟,當雙緩沖造成的jank現象越來越嚴重,就開啟去調節一下。
渲染的整體流程最好要限制在16ms內,如果大于16ms,會造成App的卡頓等性能問題,渲染造成的問題我們就要想辦法進行優化