我在LinkedIn上面的簡歷是:
Roger at UC Mobile Ltd. (www.uc.cn), focus on graphics stack (rendering architecture) research of WebKit based Browser in Android platform, include the graphics stack of WebKit itself along with the display subsystem of
Android platform, and design how to combine the best of both them to achieve butter graphics performance of page rendering.
簡單的說就是專註於Android平台基於WebKit的瀏覽器繪圖堆棧的研究,可是,到底什麼是所謂的”Android平台基於WebKit的瀏覽器繪圖堆棧“,下面我用一個例子來說明。
我們來考究一個最簡單的HTML5 Canvas在Android平台基於WebKit的瀏覽器上的實現:
- 如果網頁包含Canvas標籤,或者由JavaScript動態產生,都會導致WebKit核心產生一個HTMLCanvasElement Node對象;
- 當JS從Canvas元素獲得一個2D Context對象時(var ctx = canvas.getContext(“2d”)),實際上這個Context JS對象會跟一個C++的CanvasRenderingContext2D對象綁定,JS在這個Context上的全部調用都會被虛擬機器轉寄給對應的CanvasRenderingContext2D對象;
- WebKit內部的2D繪圖是基於GraphicsContext的,所以CanvasRenderingContext2D對象需要一個GraphicsContext來執行真正的2D繪圖操作,它會向HTMLCanvasElement對象發出請求,通過它的drawingContext()方法返回一個GraphicsContext;
- 而HTMLCanvasElement則把這個請求轉寄給內部的ImageBuffer對象,所以真正用於繪圖的GraphicsContext是由ImageBuffer提供的;
- ImageBuffer顧名思義,是用來管理一塊位元影像緩衝,預設情況下,它會分配一塊記憶體,把這塊記憶體封裝成一個位元影像(在Android平台是SkBitmap),然後通過這個位元影像建立一個平台相關的繪圖上下文PlatformGraphicsContext(在Android平台是SkCanvas和PlatformGraphicsContextSkia),最後把這個PlatformGraphicsContext封裝成一個平台無關的GraphicsContext返回;
- 從上面可知,當JS通過Canvas元素獲得的Context對象繪圖時,最終是繪製到HTMLCanvasElement對象內部的ImageBuffer對象管理的一塊記憶體裡面;
上面僅僅說明了JS調用本身的繪製路徑,要最終在螢幕上看到結果,我們還需要把ImageBuffer裡面的內容繪製到視窗上:
- 當JS執行Context對象的每一個繪圖操作時,對應的HTMLCanvasElement對象都會對外廣播一個內容發生變化的通知(canvasChanged);
- 這個通知會被轉換成網頁內容的某個地區的重繪請求;
- 這個網頁內容的重繪請求會被WebKit核心的適配層攔截,在Android平台上,它最終會被轉換成用於顯示這個網頁的View對象的某個地區的重繪請求(View.invalidate),重繪地區需要經過從網頁的內容座標繫到View的顯示座標系的座標轉換;
- 當Android當前Activity的View Hierachy的某個View需要重繪時,Android會在稍後的某個一個時間點發起一個View Hierachy的遍曆操作(ViewRootImpl.doTraversal);
- 在這個View Hierachy的遍曆操作中,Android會產生一個Canvas對象,然後逐個調用View的onDraw方法,讓View把自身的內容通過這個Canvas繪製到當前的視窗上,這個Canvas對象實際上從當前視窗對應的Surface對象中獲得,而Surface對象需要從當前視窗的Graphics Buffer隊列中取出一塊閒置Buffer(這個Buffer由系統的視窗混合器SurfaceFlinger分配),然後把這塊Buffer封裝成一個SkBitmap,再根據SkBitmap產生SkCanvas,再根據SkCanvas產生Java
Canvas…
- 當用於顯示網頁的View對象的onDraw方法被Android系統調用到時,它需要重新把C++的SkCanvas對象從Java的Canvas對象中取出來,然後把這個SkCanvas封裝成WebKit所需的GraphicsContext對象,傳給WebKit讓它去繪製網頁,當WebKit繪製到對應這個Canvas元素的Render Object(RenderHTMLCanvas)時,RenderHTMLCanvas實際上會把這個繪製請求轉寄給它對應的HTMLCanvasElement對象(就是前面說的那一個),而HTMLCanvasElement則把自己的ImageBuffer對象通過傳入的GraphicsContext最終繪製到當前視窗上;
- 當Android遍曆完整個View Hierachy,所有View都通過傳入的Canvas繪製到了當前的視窗上,但這時我們還未能在螢幕上看到更新的內容,因為實際上我們是繪製到了當前視窗的back buffer,Android還需要把繪製完的back buffer解鎖,放回視窗的Graphics Buffer隊列,通知SurfaceFlinger視窗的內容發生了變化(Surface.unlockAndPost);
- 當SurfaceFlinger收到視窗更新的通知時,它會切換視窗的前後台緩衝(Page Flip),然後逐個把當前可見的視窗的front buffer拷貝到系統的framebuffer裡面去,這樣我們最終才在螢幕上看到Canvas繪製的結果(Android 4.0開始,如果硬體支援,可以省略掉將視窗front buffer拷貝到系統framebuffer這一步,由display
hardware自己負責);
上面的例子足夠複雜嗎,實際上已經省略了很多不太重要的部分,也簡化了一些比較複雜的概念比如Android的Native Window和Window Compositing機制,
並且這僅僅是一個最最最簡單的實現!!!
如果我們需要考慮支援:
- 支援像系統瀏覽器一樣的多線程渲染架構或者像Chrome一樣的多進程渲染架構;
- 支援Android的GPU加速繪圖(從Android3.0 開始支援);
- 支援WebKit的圖層混合加速(Accelerated Compositing);
- 支援硬體加速的2D Canvas實現(Accelerated 2D Canvas);
上面的每一個選項都會讓本來的流程再加倍的複雜,而這些就是我的主要工作內容 ^_^