您好,登錄后才能下訂單哦!
作者:個推安卓開發工程師 一七
隨著科技的發展,各種移動端早已成為人們日常生活中不可或缺的部分,人們使用移動端產品工作、社交、娛樂……移動端界面的流暢性已經成為影響用戶體驗的重要因素之一。那么你是否思考過移動端所展現的流暢畫面是如何實現的呢?
本文通過對移動端View顯示過程的簡略分析,幫助開發者了解View渲染的邏輯,更好地優化自己的APP。
上圖展示的是一個完整的頁面渲染過程。通過上圖,我們可以初步了解每一幀頁面從代碼布局的編寫到展示給使用者,其背后的邏輯是如何一步一步執行的。
在電子屏幕中顯示的圖片,其實都是由一個個“小點”所組成的,這些“小點”被稱為“像素點”。每一個像素點都有自己的顏色,每一張完整的圖片都是由它們相連拼接形成的。
每個像素點一般都有 3 個子像素:紅、綠、藍,根據這三種原色,我們能夠調制出各種各樣的顏色。
與現在的平板電視不同的是,以前的黑白電視機或者大背投彩電,總是帶著大大的“后背”。“大后背”電視其實就是陰極射線管電視機,俗稱顯像管電視。其成像原理是電子槍發射出的電子束(陰極射線)通過聚焦系統和偏轉系統,射向屏幕上涂有熒光層的指定位置。被電子束轟擊的每個位置,熒光層都會產生一個小亮點,最終小亮點們將會組成一幅幅影像,顯示在電視屏幕上。
這也是以前大電視機的屏幕都呈圓弧形的原因。因為越接近圓形,邊長到中心的距離越相近,呈像越均勻。那為什么當磁鐵貼近電視機時,會讓電視機的成像出現問題呢?那是因為磁鐵會干擾電子束的正常軌跡,并且在貼近屏幕的時候,也可能使得屏幕的熒光層磁化,出現一個個不正常的光斑。
下圖展示的是攝像機慢放后,電子束的繪制過程。
隨著科技的不斷進步,電視、手機、電腦的體積越來越薄,射線管顯像方式也逐漸被淘汰。目前在手機市場上占據主流地位的是 LCD 和 OLED 兩種屏幕。
LCD 全稱為 Liquid Crystal Display ,即液晶顯示器。OLED 全稱為 Organic Light-Emitting Diode ,即有機發光二極管。這兩者之間存在顯著的差別:
1. 兩者成像原理不同
LCD 是靠白色的背光穿透彩色薄膜顯色的,而 OLED 則是靠每個像素點自行發光。
2. 在耗電量方面
LCD的耗電量較高,即使只顯示一個亮點,LCD 的背光源也需要一直發光,而且容易出現漏光現象。而OLED的每個像素都能獨立工作,而且 可以自行發光,因此采用OLED的設備可以制作得更薄,甚至可以彎曲。
3.在制作方面
LCD使用的是無機材料, OLED 則需要使用有機材料,因此 OLED的制作費用更高,并且使用壽命不如 LCD 。
與CPU相對比,GPU的計算單元更多,更擅長大規模并發計算,例如密碼破解、圖像處理等。CPU 則是遵循馮諾依曼架構存儲程序順序執行,在大規模并行計算能力上,受到的限制更大,因此更擅長邏輯控制。
在沒有統一的 API 之前,開發者需要在各式各樣的圖形硬件上編寫各種自定義接口和驅動程序,工作量極大。
1990 年 SGI(硅谷圖形公司)成為了工作站 3D 圖形領域的領導者,并將其 API 轉變為一項開放標準,即 OpenGL。后來,SGI還促成了 OpenGL 架構審查委員會(OpenGL ARB)的創建。
當我們在使用手機 APP 的過程中,發現頁面出現卡頓現象,那么極有可能是頁面沒有在 16ms 內更新導致的。實際上,人眼與大腦之間的協作無法感知超過 60fps 的畫面更新。60fps 相當于是每秒 60 幀,那么每個頁面需要在 1000/60 = 16ms 內更新為其他頁面,才不會讓我們感受到頁面的卡頓。
而在沒有 VSync 的情況下可能會出現以下情況:
如上圖所示,在沒有 VSync 的情況下,會出現需要顯示第二幀時,其尚未處理完成的情況,因此Display 中顯示的仍是第一幀。這會造成該幀顯示時長超過16ms,從而導致頁面卡頓的現象。
為了使 CPU、GPU 生成幀的速度與 Display 保持一致,Android 系統每 16ms 就會發出一次 VSYNC 信號,觸發 UI 渲染更新。
從上圖中我們可以看出,每隔 16ms ,安卓會發出一個 VSync 信號,收到信號后 CPU 開始處理下一幀的的內容,GPU 在 CPU 處理結束之后,將會進行光柵化,此時屏幕上顯示的是上一幀已經處理完成的頁面。如此反復,就可以在頁面中展示一幅幅的指定畫面。而確保畫面流暢的前提是CPU 和 GPU 處理一幀所花費的時間不能超過 16 ms,否則就會出現以下情況:
當CPU 和 GPU 處理一幀的時間超過了16 ms時,在第一個 Display 中,由于 GPU 處理 B 畫面的時間過長,導致系統發出 VSync 信號時, Display不能及時地顯示出 B 畫面,而重復顯示A頁面,造成卡頓。
此外,在第二個 Display 中,由于 A Buffer 還在被 Display 所使用,不能在收到 VSync 信號后開始處理下一幀的頁面,導致該時間段內 CPU 的閑置。為了避免這種時間的浪費,三緩存機制由此出現:
如上圖所示,在三緩存機制中,當 A 緩存被 Display 使用、B 緩存被 GPU 處理時,系統會發出 Vsync 信號,并加入新的緩存 C ,用來緩存下一幀的內容。這種方式雖然不能完全避免 A頁面的重復顯示,但是能夠讓后面頁面的顯示更加平滑。
View 的繪制是從 ViewRootImpl 的 performTraversals() 方法開始的,其整體流程大致分為三步,如下圖所示:
控件測量過程從 performMeasure() 方法開始。在該方法中childWidthMeasureSpec 和 childHeightMeasureSpec,分別是用來確定寬度和高度的。
MeasureSpec 是一個 int 值,它存儲著兩個信息:低 30 位是 View 的 specSize,高 2 位是 View 的 specMode。
1.UNSPECIFIED
父視圖對子視圖沒有任何限制,可以將視圖按照開發者的意愿設置成任意的大小,在一般開發過程中不會用到。
2.EXACTLY
父視圖為子視圖指定一個確切的尺寸,該尺寸由 specSize 的值來決定。
3.AT_MOST
父視圖為子視圖指定一個最大的尺寸,該尺寸的最大值是 specSize。
觀察 View 的 measure() 方法,可以發現該方法是被 final 修飾的,因此 View 的子類只能夠通過重載 onMeasure() 方法來完成自己的測量邏輯。
在 onMeasure() 方法中:
調用 getDefaultSize() 方法來獲取視圖的大小:
該方法中的第二個參數 measureSpec 是從 measure() 方法中傳遞過來的:通過 getMode() 和 getSize() 解析獲取其中對應的值,再根據 specMode 給最終的 size 賦值。
不過以上只是一個簡單控件的一次 measure 過程,在真正測量的過程中,由于一個頁面往往包含多個子 View ,所以需要循環遍歷測量,在 ViewGroup 中有一個 measureChildren() 方法,就是用來測量子視圖的:
measure 整體流程的方法調用鏈如下:
在performTraversals() 方法的測量過程結束后,進入 layout 布局過程:
performLayout(lp,desiredWindowWidth,desiredWindowHeight);
該過程的主要作用即根據子視圖的大小以及布局參數,將相應的 View 放到合適的位置上。
host.layout(0,0,host.getMeasuredWidth(),host.getMeasuredHeight());
如上,layout() 方法接收了四個參數,按照順時針,分別是左上右下。該坐標針對的是父視圖,以左上為起始點,傳入了之前測量出的寬度和高度。之后,讓我們進入到 layout() 方法中觀察:
我們通過 setFrame() 方法給四個變量賦值,判斷 View 的位置是否變化以及是否需要重新進行 layout,而且其中還調用了 onLayout() 方法。
在進入該方法后,我們可以發現里面是空的,這是因為子視圖的具體位置是相對于父視圖而言的,所以 View 的 onLayout 為空實現。
再進入 ViewGroup 類中查看,我們可以發現,這其實是一個抽象的方法,在這樣的情況下, ViewGroup 的子類便需要重寫該方法:
繪制的流程主要如下圖所示,該流程也是存在遍歷子 View 繪制的過程:
需要注意的是,View 的 onDraw() 方法是空的,這是因為每個視圖的內容都不相同,這個部分交由子類根據自身的需要來處理,才更加合理:
1.APP 在 UI 線程構建 OpenGL 渲染需要的命令及數據;
2.CPU 將數據上傳(共享或者拷貝)給 GPU 。(PC 上一般有顯存,但是 ARM 這種嵌入式設備內存一般是 GPU 、 CPU 共享內存);
3.通知 GPU 渲染。一般而言,真機不會阻塞等待 GPU 渲染結束,通知結束后就返回執行其他任務;
4.通知 SurfaceFlinger 圖層合成;
5.SurfaceFlinger 開始合成圖層。
移動端技術發展很快,而畫面顯示優化是一個持續發展的實踐課題,貫穿于每個開發者的日常工作中。未來,個推技術團隊將繼續關注移動端的性能優化,為大家分享相關的技術干貨。
免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。