Chrome源碼剖析 【四】

來源:互聯網
上載者:User

【四】Chrome的UI繪製1. Chrome的視窗控制項Chrome提供了自己的一個UI控制項陳列庫,相關文檔可以參見這裡。用Chrome自己的話來說,我覺得市面上的七葷八素的圖形控制項陳列庫都不好用,於是自己倒騰倒騰實現了一套。。。廣告雖如此說,不過,Chrome的圖形控制項結構,我還未發現有啥非常非常特別的地方。Chrome的視窗、按鈕、菜單之類的控制項,都直接或間接派生自 View,這個是控制項基類。 Chrome的View具有樹形結構,其內部有一個子View數組,由此構成一個控制項常用的組合模式。。。有一個比較特殊的View子類,叫做 RootView,顧名思義,它是整個View控制項樹的根,在Chrome中,一個正確的樹形的控制項結構,必須由RootView作為根。之所以要這樣設計,是因為RootView有一個比較特殊的功能,那就是分發訊息。。。我們知道,一般的Windows控制項,都有一個HWND,用與佔據一塊螢幕,捕獲系統訊息。Chrome中的View只是儲存控制項相關資訊和繪製控制項,裡面沒有HWND控制代碼,因此不能夠捕獲系統訊息。在Chrome中,完整的控制項架構是這樣的,首先需要有一個 ViewContainer,它裡麵包含一個RootView。ViewContainer是一個抽象類別,在Window中的一個子類是 HWNDViewContainer,同時,HWNDViewContainer還是MessageLoopForUI::Observer的子類。如果你看過本文第一部分描述的線程通訊的內容的話,你就應該還記得,Observer是用於監聽本線程內系統訊息的東東。。。當有系統訊息進入此線程訊息迴圈後,HWNDViewContainer會監聽到這個情況,如果和View相關的訊息,它就會調用RootView的相關方法,傳遞給控制項。在RootView的內部,會遍曆整個控制項樹上的控制項,將訊息傳遞給各個控制項。當然,有的訊息是可以獨佔的,比如滑鼠移動發送在某個View所管轄的範圍內,它會告知RootView(通過方法的傳回值...),這個訊息我要了,那麼RootView會停止遍曆。。。在設計的時候, View對訊息的處理,採取的是大而全的介面模式。就是說在View內部,提供了所有可能的訊息處理介面,並提供了預設實現,所有子類只需要覆蓋自己需要的訊息處理函數即可。如果對MFC的訊息映射有瞭解的話,可以知道兩者的區別。MFC在設計的時候,覺得無法提供大而全的介面,因為訊息總類實在太多,而且還是可擴充的,於是就有了訊息映射著一套繁瑣的宏。但Chrome的圖形架構,顯然沒有做一個通用的Framework的打算,因此,可以採用這樣的策略,使得子類的派生變得簡單而自然。。。每一個View的子類控制項,比如Button之類的,會儲存一些資料,根據訊息做一些行為,並且繪製出自己。在Chrome中,畫圖的東西是ChromeCanvas這個類,在其內部, 通過Skia和GDI實現繪製。Skia是Android團隊開發的一個跨平台的圖形引擎,在Chrome中負責除了文字之外,所有內容的繪製;而文字繪製的重擔,在Windows中交到了GDI的手上。這樣的設計會給跨平台帶來一些困難,估計是由Skia實現文本繪製會比較繁瑣,才會帶出如此一個設計的模式。。。另外一個曆史遺留產物,就是在Windows下的圖形控制項,還有一些是原生的,就是說帶有HWND那種傳統的控制項,這是Chrome身上不多的趕工期的痕迹,隨著時間的寬裕,這樣的原生控制項會被淘汰進曆史的垃圾箱,而全部變為從View派生的控制項。。。其實,對於Chrome這套控制項架構我還沒算摸得很熟悉,估計等到做一次外掛程式之後會瞭解的更透徹,因此,只說了點皮毛,聊表心意。。。2. Chrome的頁面載入和繪製上面這些UI控制項,都是用在視窗上的(比如瀏覽器的外框,菜單,對話方塊之類的...)。我們在瀏覽器中看到的大部分內容,是網頁頁面。頁面的繪製(繪製,就是把一個HTML檔案變成一個活靈活現的頁面展示的過程...),只有一半輪子是Chrome自己做的,還有一部分來自於WebKit,這個Apple打造的Web渲染器。。。之所以說是一半輪子來源於WebKit,是因為WebKit本身包含兩部分主要內容,一部分是做Html渲染的,另一部分是做JavaScript解析的。在Chrome中,只有Html的渲染採用了WebKit的代碼,而在JavaScript上,重新搭建了一個NB哄哄的V8引擎。目標是,用WebKit + V8的強強聯手,打造一款上網衝浪的法拉利,從效果來看,還著實做的不錯。。。不過,雖說Chrome和WebKit都是開源的,並聯手工作。但是,Chrome還是刻意的和WebKit保持了距離,為其始亂終棄埋下了伏筆。Chrome在WebKit上封裝了一層,稱為WebKit Glue。Glue層中,大部分類型的結構和介面都和WebKit類似,Chrome中依託WebKit的組件,都只是調用WebKit Glue層的介面,而不是直接調用WebKit中的類型。按照Chrome自己文檔中的話來說,就是,雖然我們再用WebKit實現頁面的渲染,但通過這個設計(加一個間接層...)已經從某種程度大大降低了與WebKit的耦合,使得可以很容易將WebKit換成某個未來可能出現的更好的渲染引擎。。。
重用
在《夢斷代碼》中,有一坨調侃重用的文字。他覺著軟體重用的困難一方面來自於情境本身很多變,很難設計出一套包羅永珍的東西;另一方面來自於人,程式員總是瞅著別人寫的代碼不順眼,總喜歡自己寫一套。。。
於是,解決重用這個問題也就只有兩種,寫最NB人見人服無所不能的代碼,或者是有很多很多NB代碼共君任選。Google無疑在這兩個方面做得都不錯,Map/Reduce,Big Table之類的一套東西,強大到可以適合太多的情境,大大簡化了N多上層應用的開發。而對開源的利用使用,使得其可以隨意挑一個巨人站到他肩膀上跳舞,每看到這種情境,MS估計都會氣得拍著胸口吐血。。。
Google本身在服務端的基礎底層,有很深積累,隨著Chrome,Android等等用戶端應用的開發,用戶端的積累也逐步提升,也許,擁抱開源才是MS的正道?。。。

當你鍵入一個Url並敲下斷行符號後,Chrome會在Browser進程中下載Url對應的頁面資源(包括Web頁面和Cookie),而不是直接將Url發送給Render進程讓它們自行下載(你會越來越發現,Render進程絕對是100%的名符其實,除了繪製,幾乎啥多餘的事情都不會乾的...)。與各個Render進程各自為站,各自管好自己所需的資源相比,這種策略彷彿會增加大量的處理序間通訊。之所以採用,按照這篇文檔的解釋,主要有三個優點,一個是避免子進程與網路通訊,從而將網路通訊的許可權牢牢握在主進程手中,Render進程能力弱了,想造反幹壞事的可能性就降低了(可以更好控制各個Render進程的許可權...);另一個是有利於Cookie等持久化資源在不同頁面中的共用,否則在不同Render進程中傳遞Cookie這樣的事情,做起來更麻煩;還有一點很重要的,是可以控制與網路建立HTTP串連的數量,以Browser為代表與網路各方進行通訊,各種最佳化策略都比較好開展(比如池化)。。。當然,在Browser進程中進行統一的資源管理,也就意味著不再方便用WebKit進行資源下載(WebKit當然有此能力,不過再次被Chrome拋棄了...),而是依託WinHTTP來做的。WinHTTP在接受資料的過程中,會不停的把資料和相關的訊息通過IPC,發送給負責繪製此頁面的Render進程中對應的RenderView。在這裡,路由訊息中的那個ID值起了關鍵的作用,系統依照此ID,能夠準確的將相關的訊息發送到相關的View頭上,這玩意發錯了地方還真不是和有人把錢錯到你賬戶上一樣,因為錯收的進程基本上無福消受這個意外來客,輕者頁面顯示混亂,重者消化不良直接噎死。。。RenderView接收到頁面資訊,會一邊繪製一邊等待更多的資源到來,在使用者看來,所請求的頁面正在一點一點顯示出來。當然,如果是一個通知傳輸開始、傳輸結束這樣的訊息,通過序列化到訊息參數裡面,經由IPC發過來,代價還是可以承受的,但是,想資源內容這樣大段大段的位元組流,如果通過訊息發過來,浪費兩邊進程大量空間和時間,就不合適了。於是這裡用到了共用記憶體。Browser進程將下載到的資源寫到共用記憶體中,並將共用記憶體的控制代碼和共用地區的大小序列化在訊息中發送給Render進程。Render進程拿到這個控制代碼,就可以通過它訪問到共用記憶體相關的地區,讀取資訊並進行繪製。通過這樣的方式,即享用到了統一資源管理的優點,由避免了很高的進程通訊開銷,左右逢源,好不快活。。。3. Chrome頁面的訊息響應Render進程是一個嬌生慣養的進程,這一點從上面一段已經可以看出來了。它自己的資源它自己都不下載,而是由Browser進程來幫忙。不過Render進程也許比你想象的還要懶惰一些,它不但不自己下載資源,甚至,連自己的系統訊息都不接收。。。Render進程中不包含HWND,當你滑鼠在頁面上划來划去,點上點下,這些訊息其實都發到了Browser進程,它們擁有頁面呈現部分的HWND。Browser會將這些訊息轉手通過IPC發送給對應的Render進程中的RenderView,很多時候WebKit會處理此類訊息,當它發現出現了某種值得告訴Browser進程的事情,它會組個報回贈給Browser進程。舉個例子,你開啟一個頁面,然後拿滑鼠在頁面上亂晃。Browser這時候就像一個碎嘴大嬸,不厭其煩的告訴Render進程,“滑鼠動了,滑鼠動了”。如果Render對這個資訊無所謂,就會很無聊的應答著:“哦,哦”(發送一個回包...)。但是,當滑鼠划過連結的時候,矜持的Render進程坐不住了,會大聲告訴Browser進程:“換滑鼠,換滑鼠~~”,Browser聽到後,會將滑鼠從箭頭狀換成手指狀,然後繼續以上過程。。。比較麻煩的是Paint訊息,重新繪製頁面是一個太頻繁發生的事情,不可能重繪一次就序列化一坨位元組流過去。於是策略也很清楚了,就是依然用共用記憶體讀寫,用訊息發控制代碼。在Render進程中,會有一個共用記憶體池(預設值為2...),以size為key,以共用記憶體為值,簡單的先入先出淘汰演算法,利用局部性的特徵,避免反覆的建立和銷毀共用記憶體(這和資源傳遞不一樣,因為資源傳遞可以開一塊固定大小的共用記憶體...)。Render進程從共用記憶體池中拿起一塊(二維位元組數組...),就好像拿著一塊螢幕似的,拼了命往上繪製,為了讓Render安心覺著有成就感,Browser會偷偷幫Render把這些內容繪製到螢幕上,造成Render進程直接繪製螢幕的假象。這可就苦了螢幕取詞的工具們,因為在HWND上壓根就沒啥字元資訊,全部就是一坨映像而已,啥也取不著。於是Google金山詞霸,網易有道詞霸各自發揮智慧,另闢蹊徑,也算是都利用Chrome做了一把廣告。。。為什麼不讓Render進程自己擁有HWND,自己管理自己的訊息,既快捷又便利。在Chrome的官方Blog上,有一篇解釋的文章,基本上是這個意思,速度是必須快的髮指的,但是為了使用者響應,放棄一些速度是必要的,畢竟,沒有人喜歡總假死的瀏覽器。在Browser進程中,基本上是杜絕任何同步Render進程的工作,所有操作都是非同步完成。因為Render進程是不靠譜的,隨時可能犧牲掉,同步它們往往導致主進程停止回應,從而導致整個瀏覽器停下來甚至掛掉,這個代價是不可以容忍的。但是,Windows有一個惡習,喜歡往整個HWND繼承體系中發送同步訊息(我不是很清楚這個狀況,有人能解釋嗎?...),這時候,如果HWND在Render進程中,就務必會導致主進程與Render進程的同步,Chrome無法控制Windows,於是,它們只能夠控制Render,把它們的HWND搬到主進程中,避免同步操作,換取使用者響應的速度。。。4. 結論整個Chrome的UI架構,就是一個權責分配的問題。可以把Browser進程看成是一個類似於朱元璋般的勤勞皇帝(詳見《明朝那些事 一》...),把大多數的權利都牢牢把握在手中,這樣,雖然Browser很操勞,但是整體上的協調和同步,都進行的非常順暢。Render進程就是皇帝手下的傀儡宰相們,只負責自己的一畝三分地,聽從皇帝的調配即可。這這樣的環境下,Render進程的生死變得無足輕重,Render的死亡,只是少了一個繪製頁面的工具而已,其他一切如故。通過控制權力,換取天下太平,這招在coding界,同樣是一個不錯的策略,但是,唯一的意外來自於Plugin。按照規範,Chrome的Plugin是可以創立視窗的(HWND),這必然導致同步問題,Chrome沒有辦法通過控制權力的方式解決這個問題,只能想些別的亡羊補牢的招來搞定。。。

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.