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