Updates from 四月, 2014 Toggle Comment Threads | 键盘快捷键

  • Roger 7:08 pm on April 16, 2014 固定链接 | 回复
    Tags: , , , , webgl,   

    How Rendering Work (in WebKit and Blink) 

    How Rendering Work (in WebKit and Blink)

    自从开始从事浏览器内核开发工作以来,已经写过不少跟渲染相关的文章。但是一直想写一篇像 How Browsers Work 类似,能够系统,完整地阐述浏览器的渲染引擎是如何工作的,它是如何对网页渲染性能进行优化的文章,却一直因为畏惧所需要花费的时间和精力,迟迟无法动笔。不管如何,现在终于鼓起勇气来写了,希望自己能够完成吧…

    文章包括的主要内容如下 —

    • 渲染基础 - DOM & RenderObject & RenderLayer
    • WebView,绘制与混合,多线程渲染
    • 硬件加速
    • 分块渲染
    • 图层混合加速
    • 网页游戏渲染 - Canvas & WebGL

    首先明确文中关于渲染的定义,浏览器内核引擎通常又被称为网页渲染引擎,但是这里的渲染实际上是一个泛指,广义的渲染,它包括了浏览器内核所有的主要工作 - 加载,解析,排版,绘制等等。而在本文里面的渲染,指的是跟绘制相关的部分,也就是浏览器是如何将排版后的结果最终显示在屏幕上的这一过程。如果读者希望先对浏览器内核引擎,特别是 WebKit 有一个大概的了解,How Browsers WorkHow WebKit WorkWebKit for Developers 可以提供不错的入门指引。

    其次,本文主要描述 WebKit 引擎的实现,不过因为 Blink 实际上从 WebKit 分支出来的时间并不长,两者在渲染整体架构上还是基本一致的,所以文中不会明确区分这两者。

    最后,希望这篇文章能够给从事浏览器内核开发,特别是渲染引擎开发的开发者一个能够快速入门的指引,并给前端开发者优化网页渲染性能提供足够的知识和帮助。

    因为文章后续还有可能会持续修订和补充,如果要查看最新的内容,请访问个人博客的原文:http://blog.csdn.net/rogeryi/article/details/23686609,如果有什么疏漏和错误,也欢迎读者来信指正(roger2yi@gmail.com)。

    渲染基础 - DOM & RenderObject & RenderLayer

    DOM,RenderObject,RenderLayer

    图片来自 GPU Accelerated Compositing in Chrome

    当浏览器通过网络或者本地文件系统加载一个 HTML 文件,并对它进行解析完毕后,内核就会生成它最重要的数据结构 - DOM 树。DOM 树上每一个节点都对应着网页里面的每一个元素,并且网页也可以通过 JavaScript 操作这棵 DOM 树,动态改变它的结构。但是 DOM 树本身并不能直接用于排版和渲染,内核还会生成另外一棵树 - Render 树,Render 树上的每一个节点 - RenderObject,跟 DOM 树上的节点几乎是一一对应的,当一个可见的 DOM 节点被添加到 DOM 树上时,内核就会为它生成对应的 RenderOject 添加到 Render 树上。

    Render Tree

    图片来自 How WebKit Work

    Render 树是浏览器排版引擎的主要作业对象,排版引擎根据 DOM 树和 CSS 样式表的样式定义,按照预定的排版规则确定了 Render 树最后的结构,包括其中每一个 RenderObject 的大小和位置,而一棵经过排版的 Render 树,则是浏览器渲染引擎的主要输入,读者可以认为,Render 树是衔接浏览器排版引擎和渲染引擎之间的桥梁,它是排版引擎的输出,渲染引擎的输入。

    Layer Tree

    图片来自 How WebKit Work

    不过浏览器渲染引擎并不是直接使用 Render 树进行绘制,为了方便处理 Positioning(定位),Clipping(裁剪),Overflow-scroll(页內滚动),CSS Transform/Opacity/Animation/Filter,Mask or Reflection,Z-indexing(Z排序)等,浏览器需要生成另外一棵树 – Layer 树。渲染引擎会为一些特定的 RenderObject 生成对应的 RenderLayer,而这些特定的 RenderObject 跟对应的 RenderLayer 就是直属的关系,相应的,它们的子节点如果没有对应的 RenderLayer,就从属于父节点的 RenderLayer。最终,每一个 RenderObject 都会直接或者间接地从属于一个 RenderLayer。

    RenderObject 生成 RenderLayer 的条件,来自 GPU Accelerated Compositing in Chrome

    • It’s the root object for the page
    • It has explicit CSS position properties (relative, absolute or a transform)
    • It is transparent
    • Has overflow, an alpha mask or reflection
    • Has a CSS filter
    • Corresponds to canvas element that has a 3D (WebGL) context or an accelerated 2D context
    • Corresponds to a video element

    浏览器渲染引擎遍历 Layer 树,访问每一个 RenderLayer,再遍历从属于这个 RenderLayer 的 RenderObject,将每一个 RenderObject 绘制出来。读者可以认为,Layer 树决定了网页绘制的层次顺序,而从属于 RenderLayer 的 RenderObject 决定了这个 Layer 的内容,所有的 RenderLayer 和 RenderObject 一起就决定了网页在屏幕上最终呈现出来的内容。

    软件渲染模式下,浏览器绘制 RenderLayer 和 RenderObject 的顺序,来自 GPU Accelerated Compositing in Chrome

     

    In the software path, the page is rendered by sequentially painting all the RenderLayers, from back to front. The RenderLayer hierarchy is traversed recursively starting from the root and the bulk of the work is done in RenderLayer::paintLayer() which performs the following basic steps (the list of steps is simplified here for clarity):

    1. Determines whether the layer intersects the damage rect for an early out.
    2. Recursively paints the layers below this one by calling paintLayer() for the layers in the negZOrderList.
    3. Asks RenderObjects associated with this RenderLayer to paint themselves.
    4. This is done by recursing down the RenderObject tree starting with the RenderObject which created the layer. Traversal stops whenever a RenderObject associated with a different RenderLayer is found.
    5. Recursively paints the layers above this one by calling paintLayer() for the layers in the posZOrderList.

    In this mode RenderObjects paint themselves into the destination bitmap by issuing draw calls into a single shared GraphicsContext (implemented in Chrome via Skia).

    WebView,绘制与混合,多线程渲染

    WebView

    图片来自 [UC 浏览器 9.7 Android版],中间是一个 WebView,上方是标题栏和工具栏

    浏览器本身并不能直接改变屏幕的像素输出,它需要通过系统本身的 GUI Toolkit。所以,一般来说浏览器会将一个要显示的网页包装成一个 UI 组件,通常叫做 WebView,然后通过将 WebView 放置于应用的 UI 界面上,从而将网页显示在屏幕上。

    一些 GUI Toolkit,比如 Android,默认的情况下 UI 组件没有自己独立的位图缓存,构成 UI 界面的所有 UI 组件都直接绘制在当前的窗口缓存上,所以 WebView 每次绘制,就相当于将它在可见区域内的 RenderLayer/RenderObject 逐个绘制到窗口缓存上。上述的渲染方式有一个很严重的问题,用户拖动网页或者触发一个惯性滚动时,网页滑动的渲染性能会十分糟糕。这是因为即使网页只移动一个像素,整个 WebView 都需要重新绘制,而要绘制一个 WebView 大小的区域的 RenderLayer/RenderObject,耗时通常都比较长,对于一些复杂的桌面版网页,在移动设备上绘制一次的耗时有可能需要上百毫秒,而要达到60帧/每秒的流畅度,每一帧绘制的时间就不能超过16.7毫秒,所以在这种渲染模式下,要获得流畅的网页滑屏效果,显然是不可能的,而网页滑屏的流畅程度,又是用户对浏览器渲染性能的最直观和最重要的感受。

    要提升网页滑屏的性能,一个简单的做法就是让 WebView 本身持有一块独立的缓存,而 WebView 的绘制就分成了两步 1) 根据需要更新内部缓存,将网页内容绘制到内部缓存里面 2) 将内部缓存拷贝到窗口缓存上。第一步我们通常称为绘制(Paint)或者光栅化(Rasterization),它将一些绘图指令转换成真正的像素颜色值,而第二步我们一般称为混合(Composite),它负责缓存的拷贝,同时还可能包括位移(Translation),缩放(Scale),旋转(Rotation),Alpha 混合等操作。咋一看,渲染变得比原来更复杂,还多了一步操作,但实际上,混合的耗时通常远远小于网页内容绘制的耗时,后者即使在移动设备上一般也就在几个毫秒以内,而大部分时候,在第一步里面,我们只需要绘制一块很小的区域而不需要绘制一个完整 WebView 大小的区域,这样就有效地减少了绘制这一步的开销。以网页滚动为例子,每次滚动实际上只需要绘制新进入 WebView 可见区域的部分,如果向上滚动了10个像素,我们需要绘制的区域大小就是10 x Width of WebView,比起原来需要绘制整个 WebView 大小区域的网页内容当然要快的多了。

    进一步来说,浏览器还可以使用多线程的渲染架构,将网页内容绘制到缓存的操作放到另外一个独立的线程(绘制线程),而原来线程对 WebView 的绘制就只剩下缓存的拷贝(混合线程),绘制线程跟混合线程之间可以使用同步,部分同步,完全异步等作业模式,让浏览器可以在性能与效果之间根据需要进行选择,比如说异步模式下,当浏览器需要将 WebView 缓存拷贝到窗口缓存,但是需要更新的部分还没有来得及绘制时,浏览器可以在还未及时更新的部分绘制一个背景色或者空白,这样虽然渲染效果有所下降,但是保证了每一帧窗口更新的间隔都在理想的范围内。并且浏览器还可以为 WebView 创建一个更大的缓存,超过 WebView本身的大小,让我们可以缓存更多的网页内容,可以预先绘制不可见的区域,这样就可以有效减少异步模式下出现空白的状况,在性能和效果之间取得更好的平衡。

    硬件加速

    上述的渲染模式,无论是绘制还是混合,都是由 CPU 完成的,而没有使用到 GPU。绘制任务比较复杂,较难使用 GPU 来完成,并且对于各种复杂的图形/文本的绘制来说,使用 GPU 效率有时反而更低(并且系统资源的开销也较大),但是混合就不一样了,GPU 最擅长的就是并行处理多个像素的计算,所以 GPU 相对于 CPU,执行混合的速度要快的多,特别是存在缩放,旋转,Alpha 混合的时候,而且混合相对来说也比较简单,改成使用 GPU 来完成并不困难。

    并且在多线程渲染模式下,因为绘制和混合分别处于不同的线程,绘制使用 CPU,混合使用 GPU,这样可以通过 CPU/GPU 之间的并发运行有效地提升浏览器整体的渲染性能。更何况,窗口的更新是由混合线程来负责的,混合的效率越高,窗口更新的间隔就越短,用户感受到 UI 界面变化的流畅度就越高,只要窗口更新的间隔能够始终保持在16.7毫秒以内,UI 界面就能够一直保持60帧/每秒的极致流畅度(因为一般来说,显示屏幕的刷新频率是60hz,所以60帧/秒已经是极限帧率,超过这个数值意义不大,而且 OS 的图形子系统本身就会强制限制 UI 界面的更新跟屏幕的刷新保持同步)。

    所以对于现代浏览器来说,所谓硬件加速,就是使用 GPU 来进行混合,绘制仍然使用 CPU 来完成。

    分块渲染

    Tile Rendering

    图片来自 [UC 浏览器 9.7 Android版],使用256×256大小的分块

    网页的缓存通常都不是一大块,而是划分成一格一格的小块,通常为256×256或者512×512大小,这种渲染方式称为分块渲染(Tile Rendering)。使用分块渲染的主要原因是因为 –

    1. 所谓 GPU 混合,通常是使用 Open GL/ES 贴图来实现的,而这时的缓存其实就是纹理(GL Texture),而很多 GPU 对纹理的大小有限制,比如长/宽必须是2的幂次方,最大不能超过2048或者4096等,所以无法支持任意大小的缓存;
    2. 使用小块缓存,方便浏览器使用一个统一的缓存池来管理分配的缓存,这个缓存池一般会分配成百上千个缓存块供所有的 WebView 共用。所有打开的网页,需要缓存时都可以以缓存块为单位向缓存池申请,而当网页关闭或者不可见时,这些不需要的缓存块就可以被回收供其它网页使用;

    总之固定大小的小块缓存,通过一个统一缓存池来管理的方式,比起每个 WebView 自己持有一大块缓存有很多优势。特别是更适合多线程 CPU/GPU 并发的渲染模型,所以基本上支持硬件加速的浏览器都会使用分块渲染的方式。

    图层混合加速

    Layer Accelerated Compositing

    图片来自 [UC 浏览器 9.7 Android版],可见区域内有4个 Layer 有自己的缓存 – 最底层的 Base Layer,上方的 Fixed 标题栏,中间的热点新闻栏,右下方的 Fixed 跳转按钮

    图层混合加速(Accelerated Compositing)的渲染架构是 Apple 引入 WebKit 的,并在 Safari 上率先实现,而 Chrome/Android/Qt/GTK+ 等都陆续完成了自己的实现。如果熟悉 iOS 或者 Mac OS GUI 编程的读者对其应该不会感到陌生,它跟 iOS CoreAnimation 的 Layer Rendering 渲染架构基本类似,主要都是为了解决当 Layer 的内容频繁发生变化,或者当 Layer 触发一个2D/3D变换(2D/3D Transform )或者渐隐渐入动画,它的位移,缩放,旋转,透明度等属性不断发生变化时,在原有的渲染架构下,渲染性能低下的问题。

    非混合加速的渲染架构,所有的 RenderLayer 都没有自己独立的缓存,它们都被绘制到同一个缓存里面(按照它们的先后顺序),所以只要这个 Layer 的内容发生变化,或者它的一些 CSS 样式属性比如 Transform/Opacity 发生变化,变化区域的缓存就需要重新生成,此时不但需要绘制变化的 Layer,跟变化区域(Damage Region)相交的其它 Layer 都需要被绘制,而前面已经说过,网页的绘制是十分耗时的。如果 Layer 偶尔发生变化,那还不要紧,但如果是一个 JavaScript 或者 CSS 动画在不断地驱使 Layer 发生变化,这个动画要达到60帧/每秒的流畅效果就基本不可能了。

    而在混合加速的渲染架构下,一些 RenderLayer 会拥有自己独立的缓存,它们被称为混合图层(Compositing Layer),WebKit 会为这些 RenderLayer 创建对应的 GraphicsLayer,不同的浏览器需要提供自己的 GrphicsLayer 实现用于管理缓存的分配,释放,更新等等。拥有 GrphicsLayer 的 RenderLayer 会被绘制到自己的缓存里面,而没有 GrphicsLayer 的 RenderLayer 它们会向上追溯有 GrphicsLayer 的父/祖先 RenderLayer,直到 Root RenderLayer 为止,然后绘制在有 GrphicsLayer 的父/祖先 RenderLayer 的缓存上,而 Root RenderLayer 总是会创建一个 GrphicsLayer 并拥有自己独立的缓存。最终,GraphicsLayer 又构成了一棵与 RenderLayer 并行的树,而 RenderLayer 与 GraphicsLayer 的关系有些类似于 RenderObject 与 RenderLayer 之间的关系。

    混合加速渲染架构下的网页混合,也变得比以前复杂,不再是简单的将一个缓存拷贝到窗口缓存上,而是需要完成源自不同 Layer 的多个缓存的拷贝,再加上可能的2D/3D变换,再加上缓存之间的Alpha混合等操作,当然,对于支持硬件加速,使用 GPU 来完成混合的浏览器来说,速度还是很快的。

    RenderLayer 生成 GraphicsLayer 的条件,来自 GPU Accelerated Compositing in Chrome

    1. Layer has 3D or perspective transform CSS properties
    2. Layer is used by < video> element using accelerated video decoding
    3. Layer is used by a < canvas> element with a 3D context or accelerated 2D context
    4. Layer is used for a composited plugin
    5. Layer uses a CSS animation for its opacity or uses an animated webkit transform
    6. Layer uses accelerated CSS filters
    7. Layer with a composited descendant has information that needs to be in the composited layer tree, such as a clip or reflection
    8. Layer has a sibling with a lower z-index which has a compositing layer (in other words the layer is rendered on top of a composited layer)

    混合加速的渲染架构下,Layer 的内容变化,只需要更新所属的 GraphicsLayer 的缓存即可,而缓存的更新,也只需要绘制直接或者间接属于这个 GraphicsLayer 的 RenderLayer 而不是所有的 RenderLayer。特别是一些特定的 CSS 样式属性的变化,实际上并不引起内容的变化,只需要改变一些 GraphicsLayer 的混合参数,然后重新混合即可,而混合相对绘制而言是很快的,这些特定的 CSS 样式属性我们一般称之为是被加速的,不同的浏览器支持的状况不太一样,但基本上CSS Transform & Opacity 在所有支持混合加速的浏览器上都是被加速的。被加速的CSS 样式属性的动画,就比较容易达到60帧/每秒的流畅效果了。

    Falling Leaves

    图片来自 Understanding Hardware Acceleration on Mobile Browsers,展现了经典的 CSS 动画 Demo – Falling Leaves 的图层混合的示意图,它所使用的 Transform 和 Opacity 动画在所有支持混合加速的浏览器上都是被加速的

    不过并不是拥有独立缓存的 RenderLayer 越多越好,太多拥有独立缓存的 Layer 会带来一些严重的副作用 – 首先它大大增加了内存的开销,这点在移动设备上的影响更大,甚至导致浏览器在一些内存较少的移动设备上无法很好地支持图层混合加速;其次,它加大了混合的时间开销,导致混合性能的下降,而混合性能跟网页滚动/缩放操作的流畅度又息息相关,最终导致网页滚动/缩放的流畅度下降,让用户觉得操作不够流畅。

    在 Chrome://flags 里面开启“合成渲染层边框”就可以看到哪些 Layer 是一个 Compositing Layer,也就是拥有自己的独立缓存。前端开发者可以用此帮助自己控制 Compositing Layer 的创建。总的的说, Compositing Layer 可以提升绘制性能,但是会降低混合性能,网页只有合理地使用 Compositing Layer,才能在绘制和混合之间取得一个良好的平衡,实现整体渲染性能的提升。

     

    Chrome Flags

    图片来自 Chrome,展现了经典的 CSS 动画 Demo – Falling Leaves 的合成渲染层边框

    网页游戏渲染 - Canvas & WebGL

    2D Canvas

    图片来自 [UC 浏览器 9.7 Android版],基于2D Canvas 的游戏不江湖,在主流配置手机上可以达到60帧/每秒的流畅度

    以前网页游戏一般都是使用 Flash 来实现,但是随着 Flash 从移动设备被淘汰,越来越多的网页游戏会改用 Canvas 和 WebGL 来开发,浏览器关于 Canvas 的基本绘制流程可以参考我以前的文章Introduce My Work。虽然一般网页元素都是使用 CPU 来绘制,但是对于加速的2D Canvas 和 WebGL 来说,它们的绘制是直接使用 GPU 的,所以它们一般会拥有一个GL FBO(FrameBufferObject)作为自己的缓存,Canvas/WebGL 的内容被绘制到这个 FBO 上面,而这个 FBO 所关联的纹理再在混合操作里面被拷贝到窗口缓存上。简单的来说,对于加速的2D Canvas 和 WebGL,它们的绘制和混合都是使用 GPU。

    WebGL

    图片来自 [UC 浏览器 9.7 Android版],一个演示 WebGL 的网页 Demo

    关于如何优化 Canvas 游戏的性能,请参考我以前的文章 – High Performance Canvas Game for Android(高性能Android Canvas游戏开发)

    参考索引

    How Browsers Work: Behind the scenes of modern web browsers
    How WebKit Work
    WebKit for Developers
    GPU Accelerated Compositing in Chrome
    Understanding Hardware Acceleration on Mobile Browsers
    Web Page Rendering and Accelerated Compositing
    我的2013 – 年终总结 + 浏览器渲染发展的一些思考
    Introduce My Work
    High Performance Canvas Game for Android(高性能Android Canvas游戏开发)
    OpenGL Frame Buffer Object (FBO)

    Advertisements
     
  • Roger 9:24 pm on August 12, 2013 固定链接 | 回复
    Tags: , , , , ios, ,   

    [译文]关于移动Web性能的5个神话 

    译者前言

     

    这篇文章(http://www.sencha.com/blog/5-myths-about-mobile-web-performance/)由Sencha的CEO  Michael Mullany所写,主要是回应早前的一篇引起较多关于移动Web性能讨论的文章“Why mobile web apps are slow”(原文译文),作者的主要观点是“Why mobile web apps are slow”文中给出的数据虽然基本正确,但是对数据的解读却存在误导的成分,并且只考量了JavaScript的性能,而对移动应用来说更关键的Graphics性能并没有被考量在内。并且移动应用性能的提升不仅仅会得益于浏览器提升JavaScript的性能,还会得益于更高程度的GPU加速渲染,多线程并行化处理等等。

     

    译文

     

    我们最近听到了一些在不断重复的关于移动HTML性能的神话,但是这并不完全准确。就像那些流传已久的都市传说一样,它们听起来似乎有理有据,让人信服。但是这些神话是基于不正确的前提,对于原生应用和Web应用的软件堆栈之间的关系的概念是错误的,只是一些因为曲解了数据而推导出的漫无目的的观点。我们认为根据我们多年收集的一些性能数据和对移动Web应用进行性能优化的丰富经验,对这些神话进行澄清是十分重要的。

     

    神话#1:移动Web性能主要是由CPU主导的JavaScript性能所决定的
    真相:大部分Web性能是由浏览器对渲染流水线的优化,DOM操作的速度和使用GPU加速的程度来决定的。更快的JavaScript性能的确不错,但它很少会成为性能的瓶颈。

     

    神话#2:依赖CPU的JavaScript性能仅仅因为硬件的提升才变得更快(aka 摩尔定律)
    真相:在过去4年里面50%以上的JavsScript性能的提升是得益于软件上的优化,而不是硬件上的提升。甚至单线程的JavaScript性能还在不断提升,更不用说现在很多应用开发者通过使用Web Workers来利用多线程提升性能。

     

    神话 #3:移动浏览器最近的性能优化已经基本停滞,未来也没有更多上升的空间
    真相:每一个移动浏览器都在一些特定的领域比起其它浏览器在性能上有10倍-40倍的提高。Surface的SVG性能比iPhone高30倍。而iPhone的DOM操作性能是Surface的10倍。所以对每个浏览器来说,仅仅做到向其它浏览器表现优秀的领域看齐,就有很多性能提升的空间。

     

    神话 #4:移动应用很难再从未来的硬件性能提升中受益
    真相:在过去三年里,每一次硬件更新换代都会带来JavaScript性能的飞跃。不但浏览器的单线程性能仍然在不断提升,并且还会因为更快的GPU速度,更快的内存带宽,和通过多线程并行化处理更充分地利用多核CPU来不断提升性能。越来越多的浏览器已经开始更多地利用并行化来减轻主线程的负担,比如说:Firefox使用了单独的混合线程;Chrome并行化处理HTML解析;还有IE把JavaScript的JIT编译放到了其它线程

     

    神话 #5:JavaScript的垃圾收集对移动应用来说是性能杀手
    真相:这种说法曾经是正确的,不过现在的状况已经不太一样。Chrome在2011年的Chrome 17引入了增量垃圾收集机制Firefox去年也实现了同样的特性。这个特性将每次GC暂停的时间从200ms降到了10ms —— 相当于丢掉一帧 vs 一个可以明显感知的停顿。避免垃圾收集事件的确可以显著提升性能,但是只有你还在使用桌面Web开发模式或者在一个老旧的浏览器上运行,垃圾收集才会成为性能杀手。以我们在Fastbook(一个Facebook HTML5应用的克隆)使用的关键技术为例,通过建立一个DOM节点的对象池来回收不再使用的DOM对象并循环使用,我们成功避免了创建新对象的开销,同时也避免了因为删除旧对象而导致GC。浏览器的确很有可能提供了一个性能非常糟糕的垃圾收集器(比如说旧的IE),但这并不是支持垃圾收集的语言比如JavaScript(或者Java)的天生的无法克服的缺陷。

     

    OK:关键要素

     

    首先,让我们回顾一下最基本的概念。从5万英尺的高度往下看,浏览器本身是构建于操作系统之上的一个丰富和复杂的抽象层。而Web应用混合运用标记语言,JavaScript脚本语言和样式表,使用这个抽象层来创建应用的体验。这个抽象层本身需要额外的性能开销,而开销的多少很大程度决定于你使用抽象层的哪一部分。抽象层的某些部分更快是因为它们离操作系统的系统调用或者某些系统库的调用更近(比如说Canvas2D on MacOS)。另外一些部分会比较慢是因为它们无法直接映射到底层的操作系统,并且它们天生就十分复杂(DOM树操作,JavaScript对象属性访问的原型链遍历)。

     

    很少有移动应用是计算密集型的:没有谁会试图在iPhone上计算DNA序列。大多数应用有着一个合理的响应模型。用户执行一个操作,然后应用会马上回应一个30fps或者更高帧率的视觉动画,然后在几百毫秒之内完成任务。只要应用满足上面的要求,没有人会在乎应用基于的抽象层距离最底层的硅晶圆之间到底有多远。从这点来说,单纯地比较原生应用的抽象层和Web应用的抽象层意义并不大。

     

    对于Sencha来说,我们知道一个优秀的开发者使用移动Web技术和基于一个现代的Web框架比如Sencha Touch,是可以创建出优秀的应用体验的,它运行的足够快,满足用户的期待。并且过去3年的移动性能飞速提升的趋势也使我们深受鼓舞。我们很乐意跟您在接下来的文章里分享一些相关的数据。

     

    但是我们的本意并不是说移动Web应用能够运行的和原生应用一样快,或者它们能够在性能上能够与桌面Web应用相媲美。这不是真实的。移动设备的硬件性能比起桌面设备要慢5倍到10倍:CPU性能比较低,缓存层次结构过于扁平,缺少多级缓存的支持,网络链接的延迟也很高。并且任何软件抽象层本身(比如浏览器)都需要付出额外的开销。其实这不仅仅是Web应用开发者的问题。iOS原生应用的开发者一样可以告诉你,当iOS CoreGraphics在第一款视网膜iPad上性能很低的时候,这使得他们相当一部分人不得不抛弃CoreGraphics而直接使用OpenGL。

     

    进一步追溯神话的真相

     

    通过过去三年持续对Sencha Touch在数据驱动应用中的使用进行性能优化的经历,我们可以很自信的说,我们很少会被最原始的JavaScript性能所困扰。仅仅是在构建Sencha Touch布局系统时,因为Android 2.x的JavaScript性能太差,使得我们改用了Flexbox。

     

    更多时候,我们碰到的问题是DOM操作太慢,浏览器渲染引擎性能比较差和垃圾事件堆积过多无法及时处理。而这些局限基本上都是因为浏览器的架构设计者和开发者造成的,跟JavaScript语言和JavaScript引擎本身并没有本质的联系。举个例子来说,有一次我们和浏览器开发者一起优化浏览器性能,我们最终在某个特定操作上(颜色属性的改变)获得了40倍的性能提高,而这之前是我们的滚动列表实现的性能瓶颈,而类似的例子还有很多。

     

    在iOS和Android上的JavaScript性能

     

    虽然我们说过JavaScript性能其实对于移动设备来说并不是那么重要,但是我们还是希望可以推翻它并没有得到改善的神话。下图是SunSpider测试(越低越好)在iOS上过去4年的得分(按照硬件版本和OS版本划分)。(很幸运,SunSpider是一个广泛被应用的测试,所以我们很容易就在网上找到旧的iOS设备的测试成绩)。2009年的时候,当时运行iOS3的iPhone 3GS得分是15,000 —— 非常的糟糕,跟当时的桌面浏览器的300-600的得分相差30倍左右。

     

     

    然而,如果你把iPhone 3GS升级到iOS 4,5和6,你就可以在同样的硬件上面体验到4倍的性能提升。(在iOS4到iOS5之间性能的巨大的飞跃主要得益于新的Nitro引擎。)实际上SunSpider成绩在iOS7上仍然会有所提高,只是基于保密协议我们这里就不再多说了。跟今天的桌面浏览器相比,最先进的移动浏览器大概还有5倍左右的性能差距 —— 比起09年的30倍已经是相当大的改进。

     

    如果需要了解更多关于iOS硬件和软件性能改进的信息,可以参考Anandtech去年10月份的评测

     

    在Android平台上也差不多有相当等级的改进。从我们的测试实验室,我们找到了一些可以认为是过去3年里面在当时比较有代表性的高性能机器。我们测试了下面4款手机:
    • 三星Captivate Android 2.2(2010年7月发布)
    • Droid Bionic Android 2.3.4(2011年9月发布)
    • 三星Galaxy Note 2 Android 4.1.2(2012年9月发布)
    • 三星Galaxy S4 Android 4.2.2(2013年4月发布)

     

    如下图所示,SunSpider的成绩在过去4年的提升非常明显。从Android 2.x到Android4.x就带来了接近3倍的提升。

     

    无论是iOS还是Android,这些性能提升都不仅仅是由于摩尔定律本身带来的。过去3年,如果按照摩尔定律,我们期望获得的性能提升大概是4倍左右(18个月提升一倍),但实际上却远远不止,所以软件上的优化的确起了相当大的作用。

     

    更多有意义的测试

     

    如之前我们已经提过的,SunSpider的测试成绩其实越来越不重要,因为它跟应用本身的性能要求关系其实并不大。相反,DOM操作的测试,还有Canvas和SVG的测试成绩对应用的用户体验关系更密切。(理想状态下,我们应该还要去测量改变CSS属性的速度,还有CSS动画,过渡动画和几何变换动画的帧率 —— 因为它们在Web应用中使用的更频繁 —— 不过由于缺少浏览器的支持,仍然无法准确地获取这些测试数据)

     

    第一个DOM操作测试:我们使用了Dromaeo Core DOM测试。下图是之前的4台Android设备上得到的测试成绩,我们把Captivates上的4项Core DOM测试成绩(Attributes,Modifications,Query,Traversal)作为1分,其它设备的测试成绩就是相对于Captivates的得分,然后取4项得分的平均值作为最终结果。

     

     

    如你所见,Android从2.x到4.x带来了3.5倍的性能提升,虽然S4比起Note2的提升幅度比较小。我们在iOS设备上也进行了Dromaeo测试。不幸的是,由于iOS无法降级,我们没法得到老的iOS版本的测试成绩,不过我们仍然可以看到随着硬件的升级,Dromaeo测试性能一样是稳步上升。并且有趣的是,不同的iOS6设备之间的Dromaeo性能提升幅度要大于它们的CPU速度提升幅度,这说明了内存带宽或者缓存的速度提升肯定带来了更大的帮助,所以才能比单纯依靠摩尔定律所能获得的结果更好。

     

     

    为了显示浏览器还有多少潜在的性能提升空间(仅仅是为了赶上其它浏览器表现优异的领域),我们还测试了Surface RT。IE槽糕的DOM操作性能一直困扰着我们,但这说明了Surface RT上的IE10在DOM操作上还有很大的改善空间。这也是我们之前打破的一个神话 —— “移动设备的软件堆栈本身已经足够好,未来没有太多的优化空间”。起码对于Windows RT来说,在DOM操作上还有10倍的差距需要去填补。(我们后面还会展现在哪些测试上,iOS表现不佳)

     

    图形性能

     

    除了展现JavaScript和DOM操作性能的巨大提升外,我们还想为您展现浏览器在Canvas和SVG上的性能提升。我们之前就发现了Canvas2D性能在同样硬件上iOS5比iOS4提升了5-8倍。甚至当iPad 2升级到iOS5后,一些局部测试提升了80倍。并且因为Canvas实际上是对iPhone上的CoreGraphics API的直接调用,所以当原生图形性能获得提升时,Canvas性能也获得了同样的提升。在下面的测试中,我们使用了mindcat Canvas2D benchmark来测试性能。这里,我们看到了随着iPhone硬件的提升(都运行iOS6),Canvas性能也在不断提升。

     

    请牢记,上图的显示的数据已经计入了iOS4到iOS5的性能飞跃。如你所见,上图显示出历代iPhone在Canvas2D上的性能提升达到了7倍之多,远比它们的CPU速度提升幅度要大(按照摩尔定律CPU最多也就提升了4倍),这也反映了iPhone的软件堆栈充分利用了CPU/GPU来提升自身的性能。移动Web性能的提升实际上有很大一部分是受益于GPU性能的提升和浏览器更多使用GPU进行图形加速。

     

     

    我们再来看看在Android上运行同样的测试,我们看到一些有趣的数据显示Android上Canvas性能跟CPU性能之间并没有必然的联系。从Android 2.x到Android 4.x上的性能飞跃主要是因为2.x的Canvas完全没有使用GPU加速。这再次说明了,仅仅是浏览器充分利用GPU加速就能够带来巨大的性能提升。

     

    SVG测试

     

    SVG是另外一个可以展现Web性能的广阔的领域。虽然不如Canvas流行(很大程度是因为Canvas已经足够快),SVG的性能随着硬件提升仍然在稳步提升。下图是来自Stephen Bannasch的一个测试,测试了绘制10,000线段构成的一个SVG路径所需的时间。再一次,测试结果显示了各代iPhone CPU/GPU性能提升带来的稳定的SVG性能提升(所有的iPhone都运行iOS6)。

     

     

    但是最大的差距还是源于软件本身:Surface RT比iPhone 5快了30倍(对比iPad4也是如此,虽然这里我们没有列出来)。实际上,Surface RT比起运行在我一年前买的Macbook上的Safari仍然快了10倍!这个差距是是否使用了GPU加速造成的,Window 8/IE10在SVG上充分利用了GPU进行加速,才获得了如此惊人的成果。只要浏览器开发者把更多原来由CPU完成的工作转移到GPU上面去,我们就有可能在iOS和Android上也看到同样的性能提升。

     

    除了上面的长路径绘制外,我们还运行了另外一个来自Cameron Adams的SVG测试,测试了500个不断反弹的小球的动画帧率。再一次,我们可以看到由硬件提升所带来的SVG性能的稳步提升。

     

     

    比起单纯的性能数据,最终的fps值更让人感兴趣。一旦动画超过了30帧,你就可以得到一个跟电影动画(24fps)相似的结果,这样的流畅度已经基本上可以让观看者满意。如果能够达到60帧,那你就会获得由GPU加速带来的极致流畅的体验。

     

    真实世界的性能:垃圾收集,动态语言和更多

     

    我们希望之前的移动性能探索之旅已经说明了一些事情,也打破了一些神话。我们希望为您展现下列真相:
    • JavaScript的性能持续地快速提升
    • 性能的提升由硬件提升和软件优化同时驱动
    • 虽然高性能的JavaScript是一件好事,但实际上大部分Web应用的性能跟JavaScript性能的关系甚少
    • 幸运的是,其它影响Web应用性能的领域,像DOM操作,Canvas,SVG的性能也在飞速提升

     

    虽然我们可以展现一些高速摄影机下的动画测试,不过实际上所有移动Web应用的开发者都清楚,CSS动画,过渡动画和属性修改的性能从Android 2.1开始已经得到极大的提高,并且它们还在不断提高。

     

    之前我们已经澄清了一些不真实的论断,现在再让我们做一个最终的说明。我们不断听到的各种传言汇总而成的最终结论是“移动Web应用总是很慢,这是因为JavaScript是一种低性能的动态语言,并且垃圾收集机制对性能是一个极大的伤害”。应该说这个结论本身并不是完全错误的。不过如果你的Web应用使用类似Sencha Touch这样的框架来动态产生DOM内容,一个很大的优势在于,我们会在浏览器之上,在特定的应用上下文下,合理地去管理对象的创建和销毁,包括事件对象。这样即使你的应用需要展现无穷尽的数据内容(通过表格,列表或者转盘),我们通过回收DOM对象,过滤多余的事件,对要执行的动作进行优先级排序等优化,可以帮助您的应用获得60fps的视觉动画体验。

     

    如果没有一个中间层进行类似的间接处理,的确很容易得到非常糟糕的移动Web应用体验 —— 就像Facebook移动Web应用的第一个版本一样。我们相信如果应用直接使用类似jQuery Mobile这样直接操作底层DOM模型的UI框架时,在可见的未来的确会持续受到性能相关问题的困扰。

     

    汇总

     

    文中包含了大量的数据和覆盖了不同的主题,最后在这里让我们再总结一下。如果您是一位开发者,您应该可以从这篇文章了解到:

    • 移动平台的速度不及桌面平台的1/5 — 较慢的CPU,还有受限的内存大小和速度和较慢的GPU等等。这些都是无法改变的事实。
    • 移动平台的JavaScript + DOM的访问速度越来越快,但是你始终应该把iPhone 5看作跟2008年在桌面电脑上运行的Chrome 1.0一样 (即比桌面版的IE8快5-10倍)。
    • 移动Web应用的图形性能随着浏览器更多使用GPU进行图形加速和其它通用软件优化,已经基本可以实现30帧每秒的动画。
    • 垃圾收集和平台渲染性能有限的问题仍然会使你困扰,所以使用一个类似Sencha Touch这样的抽象框架来获得更佳性能是十分有必要的。
    • 充分利用移动Web平台提供的远程调试器和性能监控能力:像Chrome for Android现在已经提供了一个不错的fps计数器,还可以显示需要混合的图层边界,这可以告诉你哪些网页内容实际上已经生成了贴图并由GPU负责绘制,还有贴图被加载的次数。

     
    我们希望对这些性能数据的回顾能够帮助我们打破一些虚假的神话。我需要感谢在Sencha的所有人对这篇文章的贡献,包括Ariya Hidayat 的审阅和提供了大量关于浏览器性能优化的文章链接,还有 Jacky Nguyen关于Sencha Touch的抽象层如何进行性能优化的一些实现细节。

     

    翻译后记

     

    喔,终于翻译完了,以前还没有翻译过这么长的文章,没想到还真是一件累死人的事情。每一句话都需要斟字酌句细细体会字面下的意思,再用较为通顺的中文表述出来,无论是脑力还是体力都是相当大的摧残,说多了都是泪啊 =_=

     

    应该说要翻译这篇文章,甚至要读懂这篇文章,译者和读者都需要对浏览器内核的一些工作原理有所了解。
    • 比如文中多处强调JavaScript RAW performance和DOM Interaction的区别,这是因为虽然DOM Interaction虽然也是由JS去调用,但是对浏览器来说,实际上JS只是调用浏览器内核提供的JS Binding API,整个Interaction是由浏览器本身去执行的,所以不应该当作JavaScript本身的性能来考量。
    • 又如浏览器所构建的抽象层的不同部分直接或者间接映射到OS的系统调用或者系统库调用对性能的不同影响的说法要怎么理解?举个例子来说,Web应用开发者可以用DOM+CSS或者用Canvas实现同样的动画效果,下面分别是基于QuarkJS实现的两个同样的动画,一个基于DOM,一个基于Canvas,对于Canvas绘制直接使用OS本身的2D绘图库去实现,并且支持GPU加速的浏览器来说,Canvas动画的效率会比使用DOM要高的多,这是因为基于DOM和CSS的动画,浏览器通常需要进行重新计算样式,重新排版,重新光栅化,重新上传贴图,重新混合等这样一个复杂的流程,效率自然高不起来。再举一个例子,对于支持图层混合加速(Accelerated Compositing)和硬件加速的浏览器来说,对付CSS Transform这样的动画就是小菜一碟,因为对它来说这个动画就是不断改变元素所属图层的Transform属性,然后使用GPU重新混合的过程。而支持硬件加速的浏览器所谓的图层混合其实就是通过OpenGL进行贴图的这样一个过程。

     

    最后要说的是,文中的一些观点还是需要在一定的条件下才能成立的,并不是放之四海而皆准,这是读者需要留意的地方:
    • 大部分Web应用性能跟JavaScript性能关系不大,对它的要求不高

     
    是的,大部分是这样的,但不见得你的Web应用就是这大部分之一。实际上,对于有一定复杂程度的基于Canvas的Web Game来说,JavaScript性能很有可能成为它的性能瓶颈。这些Web Game的场景通常比较复杂,包含成百甚至上千的绘图对象(比如实现一个绚丽的粒子效果),需要在JavaScript里面构建一个成百上千个节点的Scene Graph。每绘制一帧,都意味着需要对这个Scene Graph进行遍历,访问每一个节点,更新它的状态,然后再调用Canvas API将它绘制出来。如果要达到30fps的速度,这意味着最多只有30ms左右的时间来完成每一帧(实际上应该没有那么多),即使不算Canvas API本身的绘制开销,单单是遍历和状态更新的操作就很有可能达到几十毫秒的量级了,特别是状态更新中包含大量的碰撞检测和物理运动计算的时候。

    • 通过并行化处理是未来浏览器有效提升性能的一个有效手段

     
    应该说,当前通过并行化处理充分利用多核CPU/GPU提升性能是浏览器内核技术研究发展的一个热点。但是并行化并不是银弹,指望它能够短期内戏剧性地大幅度提升浏览器的整体性能并不现实。

    1. 首先对于移动设备来说,iOS还好,但是Android由于自身的开放性,硬件水平参差不齐,低端硬件还有相当大的保有量,它们缺乏足够的资源去支持并行化,并行化对它们来说反而更糟糕。不过得益于像MTK这样的芯片厂商大力提升中低端设备的性能,现在的千元机性能已经跟几年前不可同日而言,大概再过多一两年这个问题应该就不再成为问题了。
    2. 其次并行化处理并不是想象中的那么容易,因为浏览器的大部分作业实际上都有某种程度的顺序依赖和上下文依赖,需要很多额外的处理才有可能实现部分并行化(毕竟不是数据处理,要做到完全并行化可能性极低)。这样的并行化需要额外的开销,并且只适应于部分场景,有一定的局限性。目前浏览器除了网络链接的部分,并行化程度最高的应该就是渲染了,除了图层混合会运行在独立线程并且主要使用GPU外,像Chrome,Android Browser都把光栅化从主线程剥出来,渲染性能的确从并行化中获益极大,不过也付出了额外的CPU/内存开销的代价。其它领域的并行化进展还是很慢,并且也难见有可能使得性能大幅度提升,比如Chrome在做的HTML解析和样式计算的并行化,最多也就能够减少网页从开始加载到第一次完整呈现的时间,对于整体性能提升意义不大。至于JavaScript,除了IE所实现的JIT并行化外,垃圾收集也是一个有可能剥离出主线程的领域,只是我个人对JavaScript引擎了解不多,不知道具体的技术难点在哪里。
    3. 最后还需要前端开发者有意识地去使用并行化,或者为了更好地支持浏览器的并发作业对自己的应用进行专门优化,比如说Web Workers可以让部分JavaScript代码运行在独立的线程,但在实际的网页里面使用的应该很少。

     

    最后的话

     

    作为读者,如果您能够一直看到这里,说明您应该对Web App/Game开发是有着真爱的^_^ ,所以不妨再看完这最后一节。从我个人的开发经验来看,一个经过充分优化的应用比起没有经过优化的应用通常会有非常明显的性能差别,如果您的Web App/Game对性能要求很高,并且主要运行在移动平台,那么性能优化对您来说那就更加重要了(移动平台可没有那么多可以挥霍性能的空间)。而为了帮助前端开发者更好地做好性能优化,Chrome提供了可称为逆天的神器Dev Tools,学会使用这套工具(推荐Code School上面的视频教程),然后使用它来对您的应用进行性能分析和优化,您会发现这才是真正能够获得戏剧性的性能飞跃的最大可能,这也是所谓的“求诸人不如求诸己”。

     
  • Roger 1:20 pm on April 20, 2013 固定链接 | 回复
    Tags: , , , ,   

    国内Android手机浏览器内核未来竞争势态分析 

    前几天跟主管谈到这个话题,讨论得出一些结论,记录下来以备将来验证。国内手机浏览器在所谓“自有内核”的竞争上将会分为两个不同阶段,目前处于第一阶段。

    第一阶段是基于WebKit开发的“自有内核”,大概有两种方式,一种是WebKit内核代码(WebCore部分)直接跟主干保持同步,定期更新,平台适配层架构基本跟Android系统浏览器的适配层架构保持一致,将大部分系统浏览器适配层代码移植过来;另外一种就是直接全部采用Android系统浏览器的代码,包括适配层代码和WebKit内核代码,然后WebKit内核代码再逐步向主干靠拢(Android系统浏览器包含的WebKit内核代码版本比较旧);当然这两种方式发展到一定程度,其实是殊途同归的,相对来说,第一种方式对团队的整体水平要求更高一些。

    这一阶段的主要玩家就是UC和腾讯,百度也有参与,但是它的移动战略实在让人看不懂,感觉十分摇摆不定,按理说百度人和钱都不缺,但是始终没有下定决心大幅度投入手机浏览器领域,不知道在第二阶段是否会改变策略。

    其它国内桌面浏览器的主要玩家如360,搜狗,金山基本不参与这一阶段的竞争,360只在Android系统提供的WebView组件之上做了一层壳,搜狗和金山压根就没有什么大动作,360去年上半年还通过猎头在挖做手机浏览器内核的人,据说金山本来也准备投入,但是后来都改变了策略,更多把人力投入到桌面浏览器的竞争上面,在手机浏览器上,估计会直接参与第二阶段的竞争(听猎头说,传言金山本来已经成立手机浏览器团队,但是后来又合并回去全力做桌面浏览器)。

    第二阶段的“自有内核”会是基于Chrome&Blink,Chrome从WebKit主干分离出Blink,开始自己独自主导浏览器内核的发展,并且围绕Blink构建了一套Content API,用于将Chrome核心模块如多进程沙盒模型,渲染架构,网络堆栈,V8&Dart虚拟机和Blink内核封装在一起,用于支持Chrome本身和第三方基于Chrome&Blink内核的浏览器的开发(如新版的Opera)。

    从目前的状况来看,Chrome&Blink内核在Android平台大范围普及的时机还不成熟,主要在于Chrome&Blink内核本身还处于一个不太稳定的状态,Content API还在剧烈变化中,代码的性能优化和系统稳定性还有很多不足,并且Chrome本身的多进程架构对手机的性能有比较高的要求,包括CPU/GPU和内存大小,一些性能较差的手机连基本操作的流畅度都无法保证,另外默认的渲染架构强制要求4.0以上的系统版本。这些原因会导致第二阶段的竞争最早也会在明年才开始,开始贴身肉搏式的剧烈争斗预估也要到2015年。

    估计360,搜狗,金山会直接投入第二阶段的竞争,可能现在已经开始筹建团队和进行初期的技术准备。对他们来说,第一阶段已经基本尘埃落定,参与的意义不大,有限的人力还不如早点开始准备第二阶段,并且他们在桌面浏览器上本来就是基于Chrome在开发,所以第二阶段的竞争对他们来说反而在技术储备上会更有利。

    最终,从明年到2015年上半年这段时间,国内Android手机浏览器在“自有内核”上竞争的主要玩家会逐步增加到5~6家,因为各方都是基于Chrome&Blink进行二次开发,要获得相对于其它竞争者的技术优势会变得殊为不易。技术上的领先更多会体现在谁能更快跟进HTML5标准的发展,跟进由Google主导的Chrome&Blink内核本身的演进,一些局部的性能优化和更好地解决设备兼容性问题和系统稳定性问题。
     
  • Roger 5:03 pm on April 4, 2013 固定链接 | 回复
    Tags: , ,   

    Why Blink and Why not Blink 

    清明放假的第一天,Mozilla 和 Google同时宣布了他们新的浏览器引擎的开发计划 —— Servo 和 Blink。Servo 早前其实就一直有消息了,而 Blink 的发布则是相当突然,因为工作的原因,我自然是对 Blink 更感兴趣(放个假都不得安生,苦逼的程序员),更希望了解 Google 为什么要从 WebKit fork 出一个新的浏览器引擎(Why Blink),这样的做法会给 Chrome 后续的发展带来什么样正面变化(Why not Blink)。

    在Blink的官方主页里,Google 认为 Blink 的使命是通过技术创新和良好的社区合作来推动开放网络的发展(Blink’s Mission : To improve the open web through technical innovation and good citizenship)。根据官方 Developer FAQ 的说法,Google 之所以选择不再继续跟随 WebKit 而是独自开发新的引擎的主要原因是:

    There are two main reasons why we’re making this change.

    The main reason is that Chromium uses a different multi-process architecture than other WebKit-based browsers. So, over the years, supporting multiple architectures has led to increasing complexity for both the WebKit and Chromium communities, slowing down the collective pace of innovation.
    In addition, this gives us an opportunity to do open-ended investigations into other performance improvement strategies. We want web applications to be as fast as possible. So for example, we want to make as many of the browser’s duties run in parallel, so we can keep the main thread free for your application code. We’ve already made significant progress here–for example by reducing the impact JavaScript and layout has on page scrolling, and making it so an increasing number of CSS animations can run at 60fps even while JavaScript is doing some heavy-lifting–but this is just the start.

    We want to do for networking, rendering and layout what V8 did for JavaScript. Remember JS engines before V8? We want the same sort of healthy innovation that benefits all users of the web, on all browsers.

    在多进程架构上,Google一开始就独自开发了一套沙盒多进程架构,它和后来由Apple主导的WebKit2多进程架构差异很大,为了支持WebKit2架构而加入WebCore的大量代码,对Google不但一点用也没有,还不得不花时间去处理兼容性的问题,而Google需要修改WebCore来支持自己架构的代码又很难进入WebKit主干,必须很小心处理避免影响其它的Port,大量的代码不得不通过迂回的方式放在外部处理,一些没方法在外部处理而需要对WebCore进行大改的特性不得不暂时放弃。

    并且,因为历史原因,WebCore本身一开始就没有多线程或者多进程的概念,现有的架构对并行处理的支持非常困难,Google也认为必须对WebCore进行整体架构上的大改才能更好的支持并行处理,更充分利用多核CPU的能力,避免主线程过度拥挤(虽然现在大部分的WebKit Port都把主要的渲染工作分离到其它线程,但是主线程仍然需要负担HTML解析,CSS样式计算和匹配,排版,JS执行等繁重的任务,为了避免单项任务长时间阻塞主线程,WebCore目前是用延时Timer的方式将一个复杂任务分解成多段来顺序执行,这种方式即不优雅,更无法充分利用多核的能力)。

    另外,WebCore现在的模块化比较混乱;一些历史遗留的代码和仅仅用于支持某些特定平台的代码导致WebCore代码臃肿不堪;平台相关的处理也没有一个统一的标准和方式,没有一个很好的抽象层去隔离平台相关和平台无关的部分;WebCore为了可以同时支持不同的JS虚拟机(如JSC和V8)导致了额外的性能开销和妨碍了对JS性能更多的改进;除此以外,更安全的隔离机制;对现有的网络层进行更大的结构优化等等这些原因也是Google需要自己发展Blink的主要原因。

    总之,Chrome有太多激进的改进需要对WebCore进行大改,而原来那种在外围做文章,曲线救国的方式再也行不通,为了能够自行主导架构的演进方向,避免跟其它Port相互干扰,相互扯皮给双方带来的困扰和痛苦,加快开发的速度,从WebKit主干分离,自己发展新的浏览器引擎就成了必然的选择。

    从Chrome自身的开发者来看,对此无一不表示欢欣鼓舞之情 ^_^,纷纷表示 ——

    “这实在太令人兴奋啦!我们早就该这么做啦!”
    “以后老子想怎么改就怎么改,想蘸糖吃就蘸糖吃,想蘸醋吃就蘸醋吃,再也不用跟那帮斯扯来扯去纠缠不清”
    “自从用了Blink以后,内个不痛,月月轻松… so easy!妈妈再也不用担心我的学习啦…”

    http://infrequently.org/2013/04/probably-wrong/

     
  • Roger 11:47 am on March 25, 2013 固定链接 | 回复
    Tags: , ,   

    Introduce My Work 2 

    早前写了一篇文章“Introduce My Work”,用一个例子 —— HTML5 Canvas的一个简单实现,介绍自己工作的内容,而这篇文章试图去描绘自己工作内容所需的一个完整的知识体系的层次结构(见下图),并对其进行简单介绍。

    android_graphics

    Browser App

    基于系统的GUI框架开发完整的浏览器应用。

    Android GUI Framework

    1. 了解Android的View System机制;
    2. 从3.0开始Android支持使用Layer作为View的Backing Store,用于对动画进行加速;
    3. 了解Android Window Surface相关的Java封装和实现细节;
    4. Canvas是Android Java层用于2D绘图的主要对象;

    WebKit

    WebKit通常可以分为WebCore和WebKit Port,在WebCore中,跟渲染相关的对象主要有RenderLayer, RenderObject, GraphicsContext, Image, ImageBuffer等,而WebKit Port则负责整个渲染架构的设计和实现,它需要考虑:

    1. 使用什么作为Backing Store,是Vector(矢量图),还是Bitmap(位图),还是Texture(贴图),还是Surface(Window Surface);
    2. 支不支持Layer Rendering(分层渲染,需要WebCore开启Accelerated Compositing的支持),支不支持Tile Rendering(分块渲染);
    3. 采用多线程(单进程)的渲染架构还是像WebKit2或者Chrome那样的多进程渲染架构;

    Android Graphics Stack (Client)

    Android在客户端的绘图堆栈通常包括:

    1. Skia,一个使用CPU进行2D绘图的绘图库;
    2. OpenGL ES,使用GPU进行3D和2D的绘图的API(比如Android 3.0后出现的hwui模块就使用GLES来取代Skia用于2D绘图,实现GPU加速的2D绘图);
    3. EGL,衔接GLES和系统的Native Window系统的适配层;
    4. Surface(Native Window),可以通过Surface接口的一个Stub实现操控远程的Native Window,另外Android 4.0后,应用还可以创建自己的Native Window并且自己负责它的混合;

    Android Graphics Stack(Server)

    SurfaceFlinger是Android用于管理Display和负责Window Composite(窗口混合),把应用的显示窗口输出到Display的系统服务。

    Android Drivers(HAL)

    Android的驱动层,通过Android本身的HAL(硬件抽象层)机制,运行于User Space,跟渲染相关的包括:

    1. hwcomposer,如果硬件支持,SurfaceFlinger可以请求hwcomposer去做窗口混合而不需要自己来做,这样的效率也会更高,减少对GPU资源的占用;
    2. gralloc,用来管理Graphics Buffer的分配和管理系统的framebuffer;
    3. copybit,早期Android在GPU不支持完整的GL ES1的情况下,内部的软件版本的GL ES1实现可以使用copybit来加速贴图操作(其实也就是加速窗口混合),hwcomposer出现后就已经移除了;
    4. gles/egl驱动;

    Linux Kernel and Drivers

    除了标准的Linux内核和驱动(如fb是framebuffer驱动),硬件厂商自己的驱动外,Android自己的一些Patches:

    1. ashmem,异步共享内存,用于在进程间共享一块内存区域,并允许系统在资源紧张时回收不加锁的内存块;
    2. pmem,用于分配物理地址连续的内存块,一般从已经预留的内存区域中分配,主要用来作为Graphics Buffer,不过4.0以后,逐步被更完整的内存管理器ION替代;
    3. binder,高效的进程间通信机制;
    4. sync,用来将呼叫者阻塞,并强制跟显示器的刷新保持同步;

    Hardware

    CPU,GPU,VPU(Video Process Unit),和内存等等。

     

    虽然目前的工作主要集中在WebKit这一层,但是由上至下打通每一个层次,对更好的拓展自己的工作范围,具备挑战更高难度的任务的能力,产出更高品质的成果,的的确确是非常重要的。应该说,自己目前仅仅只是把上面的一个知识体系的架子搭起来,描绘出一个较为完整的轮廓而已,很多部分也仅仅是了解一些简单的概念,特别是底层的层次,无论是其中的原理还是实现的细节了解的非常少。而未来的两到三年的时间,要做的最重要的事情就是不断的拓展,补充和完善上述的知识体系,以期能够早日达成“恢恢乎其于游刃必有余地矣”的境界。

     
  • Roger 11:11 am on February 2, 2013 固定链接 | 回复
    Tags: , ,   

    我的2012 

    去年放假时为我的2011一整年写了一篇回顾,今年也打算继续,希望以后每年坚持下来形成习惯。

    2012年的最大变化就是工作内容从外壳的GUI框架层转向内核的网页渲染:
    • 年初第一季度的研究重点是在Android 4.0系统浏览器的硬件加速AC渲染架构和整个适配层的架构;
    • 接下第二季度的工作是参考系统浏览器的渲染架构,在我们的浏览器上也实现多线程渲染架构,当时考虑4.0的渲染架构太过复杂,而且很多版本兼容性问题无法很好解决,所以还是以2.3的系统浏览器为主要参考对象;
    • 完成初始的预研工作,实现演示Demo后,项目这边决定第三季度在8.6上多线程渲染架构改造,不过后续的实际开发工作中碰到了很多困难,主要是因为程序从原来的单线程架构一下子改成多线程,需要重写的模块和修改的地方非常多,一些原来没有很好处理多线程的地方引发了大量的崩溃问题,并且多线程在低端机上的性能表现一开始不是很好,也花费了大量的时间去做性能分析跟优化;
    • 接下来的8.7版本我的主要工作是在8.6的基础上进一步优化一些基础渲染性能,如滑屏,缩放等,最终的表现也还算不错,渲染性能比起以前的版本改进很大,虽然还无法跟支持硬件加速的4.x系统浏览器相比,但是在还未支持硬件加速的情况下也基本上做到极致了;
    • 做完8.7后,8月下旬开始准备我们浏览器整个适配层的改造工作,在8.6,8.7多线程渲染架构改造的基础上,我们决定完全抛弃掉以前自己写的C++适配层和一套从塞班时代延续过来的自己开发的C++ GUI Toolkit,完全转向跟系统浏览器一样的Java和C++结合的适配层架构,跟Android SDK一样,内核为外壳提供一个标准的Android UI组件WebView,而外壳完全使用Android原生的UI组件进行开发。不过跟8.6,8.7时不太一样的地方在于,这次我主要负责前期技术研究和框架搭建的工作,在完成Demo,技术文档和培训与宣讲后,后续的主要工作就是协助其它同学对各自负责的模块进行设计与实现,只负责一些设计和代码Review的工作而不再直接参与8.8的项目工作;
    • 10月开始了硬件加速方面的研究,在之前工作的基础上,开始移植4.1系统浏览器的渲染架构代码(这时4.1已经发布了,并且4.1比起4.0又复杂了很多),初始的移植工作完成后,已经可以运行一个硬件加速AC的Demo,接下来的主要工作就是解决版本兼容性问题,我们不但需要解决2.x和4.x之间的版本兼容性问题,而且还要解决4.x不同版本之间的兼容性问题,版本兼容性问题的解决使得我们可以一个程序同时运行在2.x和4.x上面,支持非硬件加速和硬件加速两种不同的渲染路径,可以在运行时根据系统决定选择哪种渲染方式,最后因为4.2的发布,我们又把4.2的代码也同步了过来;
    • 前面的工作完成后就差不多到年底了,马不停蹄地又开始了硬件加速2D Canvas的研究,为此也学习了Android的OpenGL ES编程,加深对Android Native Window System,EGL,GLES的理解,另外还研究了Chrome for Android的2D Canvas实现,到目前为止,硬件加速2D Canvas已经基本实现,Demo初始的测试结果还不错,跑GUIMark 3的帧数比Chrome正式版(Chrome 18)要高,不过比Chrome Beta版(Chrome 25)要低,但是考虑到目前我们所使用的Skia的代码比较旧(来自Android 4.2 AOSP),而Chrome这几个版本还在不断优化Skia的性能,后续升级Skia后,相信性能还会再有所提高;
    2013年,最重要的工作就是在之前的硬件加速相关的研究工作的基础上,协助硬件加速AC,硬件加速2D Canvas顺利进入产品,从过往的经验也可以知道,从Demo到产品,后续要做的工作还很多。另外下半年也有可能会去研究WebGL(3D Canvas)。

    而对自己的职业发展的规划,主要的技术研究方向还是会集中在以下几个领域:
    1. 网页渲染相关的研究(CSS动画,Filter/Shader,2D/3D Canvas,Chrome的多进程渲染架构等等);
    2. Android图形子系统
    3. 2D/3D绘图(Skia,OpenGL ES等等);
     
  • Roger 10:32 am on January 18, 2013 固定链接 | 回复
    Tags: Accelerated 2D Canvas, Accelerated Compositing, , ,   

    Introduce My Work 

    图像

    我在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的浏览器上的实现:

    1. 如果网页包含Canvas标签,或者由JavaScript动态生成,都会导致WebKit内核生成一个HTMLCanvasElement Node对象;
    2. 当JS从Canvas元素获得一个2D Context对象时(var ctx = canvas.getContext(“2d”)),实际上这个Context JS对象会跟一个C++的CanvasRenderingContext2D对象绑定,JS在这个Context上的全部调用都会被虚拟机转发给对应的CanvasRenderingContext2D对象;
    3. WebKit内部的2D绘图是基于GraphicsContext的,所以CanvasRenderingContext2D对象需要一个GraphicsContext来执行真正的2D绘图操作,它会向HTMLCanvasElement对象发出请求,通过它的drawingContext()方法返回一个GraphicsContext;
    4. 而HTMLCanvasElement则把这个请求转发给内部的ImageBuffer对象,所以真正用于绘图的GraphicsContext是由ImageBuffer提供的;
    5. ImageBuffer顾名思义,是用来管理一块位图缓存,默认情况下,它会分配一块内存,把这块内存封装成一个位图(在Android平台是SkBitmap),然后通过这个位图创建一个平台相关的绘图上下文PlatformGraphicsContext(在Android平台是SkCanvas和PlatformGraphicsContextSkia),最后把这个PlatformGraphicsContext封装成一个平台无关的GraphicsContext返回;
    6. 从上面可知,当JS通过Canvas元素获得的Context对象绘图时,最终是绘制到HTMLCanvasElement对象内部的ImageBuffer对象管理的一块内存里面;
    上面仅仅说明了JS调用本身的绘制路径,要最终在屏幕上看到结果,我们还需要把ImageBuffer里面的内容绘制到窗口上:

    1. 当JS执行Context对象的每一个绘图操作时,对应的HTMLCanvasElement对象都会对外广播一个内容发生变化的通知(canvasChanged);
    2. 这个通知会被转换成网页内容的某个区域的重绘请求;
    3. 这个网页内容的重绘请求会被WebKit内核的适配层拦截,在Android平台上,它最终会被转换成用于显示这个网页的View对象的某个区域的重绘请求(View.invalidate),重绘区域需要经过从网页的内容坐标系到View的显示坐标系的坐标转换;
    4. 当Android当前Activity的View Hierachy的某个View需要重绘时,Android会在稍后的某个一个时间点发起一个View Hierachy的遍历操作(ViewRootImpl.doTraversal);
    5. 在这个View Hierachy的遍历操作中,Android会生成一个Canvas对象,然后逐个调用View的onDraw方法,让View把自身的内容通过这个Canvas绘制到当前的窗口上,这个Canvas对象实际上从当前窗口对应的Surface对象中获得,而Surface对象需要从当前窗口的Graphics Buffer队列中取出一块空闲的Buffer(这个Buffer由系统的窗口混合器SurfaceFlinger分配),然后把这块Buffer封装成一个SkBitmap,再根据SkBitmap生成SkCanvas,再根据SkCanvas生成Java Canvas…
    6. 当用于显示网页的View对象的onDraw方法被Android系统调用到时,它需要重新把C++的SkCanvas对象从Java的Canvas对象中取出来,然后把这个SkCanvas包装成WebKit所需的GraphicsContext对象,传给WebKit让它去绘制网页,当WebKit绘制到对应这个Canvas元素的Render Object(RenderHTMLCanvas)时,RenderHTMLCanvas实际上会把这个绘制请求转发给它对应的HTMLCanvasElement对象(就是前面说的那一个),而HTMLCanvasElement则把自己的ImageBuffer对象通过传入的GraphicsContext最终绘制到当前窗口上;
    7. 当Android遍历完整个View Hierachy,所有View都通过传入的Canvas绘制到了当前的窗口上,但这时我们还未能在屏幕上看到更新的内容,因为实际上我们是绘制到了当前窗口的back buffer,Android还需要把绘制完的back buffer解锁,放回窗口的Graphics Buffer队列,通知SurfaceFlinger窗口的内容发生了变化(Surface.unlockAndPost);
    8. 当SurfaceFlinger收到窗口更新的通知时,它会切换窗口的前后台缓存(Page Flip),然后逐个把当前可见的窗口的front buffer拷贝到系统的framebuffer里面去,这样我们最终才在屏幕上看到Canvas绘制的结果(Android 4.0开始,如果硬件支持,可以省略掉将窗口front buffer拷贝到系统framebuffer这一步,由display hardware自己负责);
    上面的例子足够复杂吗,实际上已经省略了很多不太重要的部分,也简化了一些比较复杂的概念比如Android的Native Window和Window Compositing机制,并且这仅仅是一个最最最简单的实现!!!

    如果我们需要考虑支持:
    1. 支持像系统浏览器一样的多线程渲染架构或者像Chrome一样的多进程渲染架构;
    2. 支持Android的GPU加速绘图(从Android3.0 开始支持);
    3. 支持WebKit的图层混合加速(Accelerated Compositing);
    4. 支持硬件加速的2D Canvas实现(Accelerated 2D Canvas);
    上面的每一个选项都会让本来的流程再加倍的复杂,而这些就是我的主要工作内容 ^_^
     
    • unbug 10:38 上午 on 一月 18, 2013 固定链接 | 回复

      强文,营养丰富,收藏备读!

    • 一丝 10:55 上午 on 一月 18, 2013 固定链接 | 回复

      虽不明,但觉厉(PS,楼上的你好讨厌捏,老是抢在我前面。)

  • Roger 2:24 pm on November 19, 2012 固定链接 | 回复  

    Android DisplayList机制浅析 

    Android3.0后可以在UI线程使用OpenGL达成支持硬件加速的Canvas的目的,当GUI框架接收到View重绘的请求后,框架跟之前一样,回调View的onDraw方法,并传入一个Canvas,但是跟之前不同的是,这个Canvas只是用于记录View的绘图指令到View本身的DisplayList中,并没有真正执行。
    1. 每个View拥有自己的DisplayList,DisplayList本身也构成一个树状的结构,跟View Hierachy保持一致;
    2. 当整个View Hierachy的DisplayList树都更新完毕后,框架就会执行该DisplayList树(status_t DisplayList::replay(OpenGLRenderer& renderer, Rect& dirty, int32_t flags, uint32_t level)),所谓执行,实际上就是把缓存的2D Canvas绘图指令转换成OpenGL的方法,最后调用glSwap执行OpenGL命令更新当前缓存,并切换前后缓存;
    3. Android 4.1后,DisplayList支持属性,如果View的一些属性发生变化(比如Scale),框架只是简单改变View的DisplayList的属性,而不需要调用View的onDraw方法重新生成新的DisplayList;
    4. DisplayList中可以嵌入Functor,Functor是一个用于回调的函数子,DisplayList把嵌入的Functor也当作一条绘图指令缓存起来(DisplayListRenderer::callDrawGLFunction(Functor *functor, Rect& dirty)),在执行时直接调用Functor(status_t OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty));
    5. DisplayList的执行可以有不同的返回结果,在4.1上,Done表示不再需要重新执行DisplayList,而Draw表示需要重新执行DisplayList(重现执行一次窗口重绘,但是不需要更新DisplayList,因为没有View被设置为dirty),Invoke是专门针对Functor而言的(4.1新增),如果Functor的执行返回Invoke,该Functor会被加入一个队列中,框架在执行完DisplayList后,马上会发起一个延迟调用,把队列里面的Functor再执行一遍(private void HardwareRenderer::handleFunctorStatus(View.AttachInfo attachInfo, int status)),执行的结果如果是Draw或者Invoke则继续处理直到Done为止;
    6. DisplayList的执行在4.0返回结果只有true和false,false等于Done,true相当于Draw;

    status_t DisplayListRenderer::callDrawGLFunction(Functor *functor, Rect& dirty) {
    // Ignore dirty during recording, it matters only when we replay
    addOp(DisplayList::DrawGLFunction);
    addInt((int) functor);
    return DrawGlInfo::kStatusDone; // No invalidate needed at record-time

    }
    status_t OpenGLRenderer::callDrawGLFunction(Functor* functor, Rect& dirty) {
    interrupt();
    detachFunctor(functor);

    if (mDirtyClip) {
    setScissorFromClip();
    }

    Rect clip(*mSnapshot->clipRect);
    clip.snapToPixelBoundaries();

    #if RENDER_LAYERS_AS_REGIONS
    // Since we don’t know what the functor will draw, let’s dirty
    // tne entire clip region
    if (hasLayer()) {
    dirtyLayerUnchecked(clip, getRegion());
    }
    #endif

    DrawGlInfo info;
    info.clipLeft = clip.left;
    info.clipTop = clip.top;
    info.clipRight = clip.right;
    info.clipBottom = clip.bottom;
    info.isLayer = hasLayer();
    info.width = getSnapshot()->viewport.getWidth();
    info.height = getSnapshot()->height;
    getSnapshot()->transform->copyTo(&info.transform[0]);

    status_t result = (*functor)(DrawGlInfo::kModeDraw, &info) | DrawGlInfo::kStatusDrew;

    if (result != DrawGlInfo::kStatusDone) {
    Rect localDirty(info.dirtyLeft, info.dirtyTop, info.dirtyRight, info.dirtyBottom);
    dirty.unionWith(localDirty);

    if (result & DrawGlInfo::kStatusInvoke) {
    mFunctors.add(functor);
    }
    }

    resume();
    return result;
    }

            private void handleFunctorStatus(View.AttachInfo attachInfo, int status) {
    // If the draw flag is set, functors will be invoked while executing
    // the tree of display lists
    if ((status & DisplayList.STATUS_DRAW) != 0) {
    if (mRedrawClip.isEmpty()) {
    attachInfo.mViewRootImpl.invalidate();
    } else {
    attachInfo.mViewRootImpl.invalidateChildInParent(null, mRedrawClip);
    mRedrawClip.setEmpty();
    }
    }

    if ((status & DisplayList.STATUS_INVOKE) != 0) {
    scheduleFunctors(attachInfo, true);
    }
    }

            private void scheduleFunctors(View.AttachInfo attachInfo, boolean delayed) {
    mFunctorsRunnable.attachInfo = attachInfo;
    if (!attachInfo.mHandler.hasCallbacks(mFunctorsRunnable)) {
    // delay the functor callback by a few ms so it isn’t polled constantly
    attachInfo.mHandler.postDelayed(mFunctorsRunnable,
    delayed ? FUNCTOR_PROCESS_DELAY : 0);
    }
    }
            class FunctorsRunnable implements Runnable {
    View.AttachInfo attachInfo;

    @Override
    public void run() {
    final HardwareRenderer renderer = attachInfo.mHardwareRenderer;
    if (renderer == null || !renderer.isEnabled() || renderer != GlRenderer.this) {
    return;
    }

    final int surfaceState = checkCurrent();
    if (surfaceState != SURFACE_STATE_ERROR) {
    int status = mCanvas.invokeFunctors(mRedrawClip);
    handleFunctorStatus(attachInfo, status);
    }
    }
    }

    status_t OpenGLRenderer::invokeFunctors(Rect& dirty) {
    status_t result = DrawGlInfo::kStatusDone;
    size_t count = mFunctors.size();

    if (count > 0) {
    SortedVector<Functor*> functors(mFunctors);
    mFunctors.clear();

    DrawGlInfo info;
    info.clipLeft = 0;
    info.clipTop = 0;
    info.clipRight = 0;
    info.clipBottom = 0;
    info.isLayer = false;
    info.width = 0;
    info.height = 0;
    memset(info.transform, 0, sizeof(float) * 16);

    for (size_t i = 0; i < count; i++) {
    Functor* f = functors.itemAt(i);
    result |= (*f)(DrawGlInfo::kModeProcess, &info);

    if (result & DrawGlInfo::kStatusDraw) {
    Rect localDirty(info.dirtyLeft, info.dirtyTop, info.dirtyRight, info.dirtyBottom);
    dirty.unionWith(localDirty);
    }

    if (result & DrawGlInfo::kStatusInvoke) {
    mFunctors.add(f);
    }
    }
    }

    mCaches.activeTexture(0);

    return result;
    }

     
    • Chao Chen 4:38 下午 on 十二月 20, 2013 固定链接 | 回复

      hi 我对这篇文章很感兴趣,我想知道是什么情况下是会返回draw和invoke呢?我这里有几个利用到webview和hardwareacceleration的例子,基本上第一帧都会返回draw,有时候还会返回invoke去画第二帧。当返回值是draw或者invoke的时候是否表示当前帧有些东西没有画好吗?

  • Roger 10:30 am on October 13, 2012 固定链接 | 回复
    Tags: android 4.1, rom,   

    Android 4.1 – 将系统浏览器编译成独立应用 

    为了方便在手机上(Galaxy Note with CM10),调试Android4.1 系统浏览器的代码,进行代码研究,我把系统浏览器编译成了一个独立的应用,不会跟ROM原来的系统浏览器产生冲突,可以很方便地在Eclipse自己建立的工程里面对Java部分的代码进行跟踪调试,理论上C++的部分也可以通过GDB进行调试。
    自己编译的库,显示Layer边界和信息
    首先系统浏览器可以认为分为3部分:

    1,Browser.Apk 一个全功能浏览器应用
    2,android.webkit 平台适配层的Java部分代码,对外提供了封装好的WebView
    3,libwebcore.so 包括WebKit的代码和平台适配层C++部分的代码,libchromium_net.so Chrome的网络堆栈
    我们实际只需要后面两部分(2和3),然后加上自己的一个简单的测试用外壳就可以了。

    首先参考官方的文档,建立ROM编译环境,编译ROM(http://source.android.com/source/index.html)。

    开始建立独立的应用(home/roger/a41是我的ROM的目录,需要替换成自己ROM的目录):
    1. 在Eclipse创建一个Android工程,把android.webkit目录下的Java代码拷贝过来;
    2. 将/home/roger/a41/out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/src/core/java/android/webkit下面的EventLogTags.java也拷贝到自己的工程;
    3. 因为android.webkit下的类会使用SDK中非公开的API,我们需要解决编译错误:
      1. 创建一个User Library,并且勾选System Library的选项;
      2. 加入以下Jar包:
        1. /home/roger/a41/out/target/common/obj/JAVA_LIBRARIES/framework_intermediates/classes.jar
        2. /home/roger/a41/out/target/common/obj/JAVA_LIBRARIES/core_intermediates/classes.jar
        3. /home/roger/a41/out/target/common/obj/JAVA_LIBRARIES/bouncycastle_intermediates/classes.jar
      3. 在Java Build Path/Order and Export把创建的库放在最前面;
    4. 因为在我们应用中的android.webkit包跟SDK中的重名,所以我们需要更改包名,可以改成android.webkit2;
    5. 我们需要重新编译libchromium_net.so和libwebcore.so,并且使用另外的名字,并且把其代码中使用的android/webkit/ JNI路径改成android/webkit2/保证JNI的正确性:
      1. 在/home/roger/a41/external/chromium下面,把所有源文件的android/webkit/路径改成android/webkit2/;
      2. 打开/home/roger/a41/external/chromium/Android.mk,修改库名为libchromium_net2,并且加多一行“LOCAL_MODULE_TAGS := optional“,具体内容见后;
      3. 重新编译chromium_net,得到libchromium_net2.so;
      4. 在/home/roger/a41/external/webkit/Source/WebKit/android下面,把所有源文件的android/webkit/路径改成android/webkit2/;
      5. 打开/home/roger/a41/external/webkit/Android.mk,将库名改成libwebcore2.so,并且加多一行“LOCAL_MODULE_TAGS := optional“(需要修改两个地方,静态库编译和动态库编译),另外还需要把导入库libchromium_net改成libchromium_net2,具体内容见后;
      6. 重新编译webcore,得到libwebcore2.so;
    6. 接下来我们可以把修改后的libwebcore2.so和libchromium_net2.so push到手机的rom里面,假设路径是/data/local(如果没有写权限,用Root Explorer修改);
    7. 然后我们需要修改Java的代码,让它去加载我们自己的库,修改的地方位于JniUtil.java和WebViewCore.java,具体内容见后(加载顺序需要改变,先加载libchromium_net2.so再加载libwebcore2.so);
    8. 最后加上我们自己的Test Shell的代码,运行就OK了,如果只修改了C++的代码,重编译后再Push到手机,然后重新运行Test Shell就可以马上生效,Java的代码可以在Eclipse里面很方便的调试,C++的代码理论上也可以通过GDB进行调试;

    LOCAL_MODULE := libchromium_net2
    LOCAL_MODULE_CLASS := SHARED_LIBRARIES
    LOCAL_MODULE_TAGS := optional
    INTERMEDIATES := $(call local-intermediates-dir)


    # Define our module and find the intermediates directory
    LOCAL_MODULE := libwebcore2
    LOCAL_MODULE_CLASS := STATIC_LIBRARIES
    LOCAL_MODULE_TAGS := optional
    base_intermediates := $(call local-intermediates-dir)


    # Do not attempt prelink this library. Needed to keep master-gpl happy, no

    1. effect in master.
    2. TODO: remove this when master-gpl is updated.

    LOCAL_PRELINK_MODULE := false
    LOCAL_MODULE := libwebcore2
    LOCAL_MODULE_TAGS := optional
    LOCAL_LDLIBS := $(WEBKIT_LDLIBS)


    1. Build the list of shared libraries
    2. We have to use the android version of libdl

    LOCAL_SHARED_LIBRARIES := \
    libEGL \
    libGLESv2 \
    libandroid \
    libandroidfw \
    libandroid_runtime \
    libchromium_net2 \
    libcrypto \
    libcutils \
    libdl \
    libgui \
    libicuuc \
    libicui18n \
    libmedia \
    libmedia_native \
    libnativehelper \
    libskia \
    libsqlite \
    libssl \
    libstlport \
    libutils \
    libui \
    libz


    static {
    System.load(“/data/local/libchromium_net2.so”);

             System.load(“/data/local/libwebcore2.so”);
    }
     
    • ddy123 6:47 上午 on 十月 29, 2012 固定链接 | 回复

      不知道楼主对sony给出的webkit,使其支持webgl的代有研究没,想交流下。

      • Roger 9:59 上午 on 十月 29, 2012 固定链接 | 回复

        同事有研究过,貌似是使用离屏的Pixmap进行绘制,效率不怎么样。

    • 匿名 7:26 下午 on 三月 4, 2013 固定链接 | 回复

      博主,这里从第5步开始就不怎么明白了,我在源码external/chromium下面,没找到android/webkit目录啊。

  • Roger 7:55 pm on April 24, 2012 固定链接 | 回复
    Tags: Acclerated Compositing, , , Web Page Rendering   

    Web Page Rendering and Accelerated Compositing 

    这是最近在公司做的一次技术分享,介绍了WebKit里面的Accelerated Compositing机制,Accelerated Compositing(图层混合加速)主要用于CSS3动画的加速,由Apple首先引入WebKit并在Safari上实现,而目前,包括Qt,Chrome,Android等其它主要的WebKit Port也都提供了各自的实现。

    讲义内容包括:

    1,网页的基本渲染流程

    2,AC基本概念解释

    3,Qt基于QGraphicsView架构的AC实现介绍

    4,Qt基于TextureMapper架构的AC实现介绍

    5,Android 4.0系统浏览器硬件加速和非硬件加速的AC实现介绍

     
    • unbug 11:41 上午 on 十一月 19, 2012 固定链接 | 回复

      能在国内同仁找到这样的资源不容易啊!谢过!一直想理清composited layer的来龙去脉!

      • Roger 1:07 下午 on 十一月 19, 2012 固定链接 | 回复

        兄台也是在国内做浏览器开发?

        • unbug 5:27 下午 on 十一月 19, 2012 固定链接

          我是前端开发的,在研究webkit页面绘制渲染的过程,期望能对优化页面性能的有所帮助

    • Roger 9:57 上午 on 十一月 20, 2012 固定链接 | 回复

      做前端做到要研究浏览器内核这么有突破性,您也未免太敬业了吧 ^_^

      • unbug 10:59 上午 on 十一月 20, 2012 固定链接 | 回复

        确实非常有帮助,兄台要是方便不如加QQ346742896,有问题能向你请教请教,毕竟你才是专业级的

        • Roger 11:11 上午 on 十一月 20, 2012 固定链接

          我很少上Q了,你加我微信(roger2yi)或者邮件联系吧

    • yisibl 7:25 下午 on 一月 17, 2013 固定链接 | 回复

      不远万里,翻得墙来,只为一睹君颜,膜拜之!

    • Geri 2:22 上午 on 一月 7, 2015 固定链接 | 回复

      Created the greatest aritlces, you have.

c
Compose new post
j
Next post/Next comment
k
Previous post/Previous comment
r
回复
e
编辑
o
Show/Hide comments
t
返回顶部
l
Go to login
h
Show/Hide help
shift + esc
取消