webkit之渲染主循环理解

转载 2015年11月19日 01:32:56

# Chromium渲染主循环(mainloop)和requestAnimationFrame

## 概述

曾经写过一段JavaScript代码,因为涉及到需要循环调用某个函数来实现动画的功能,很自然地,我想到了使用setInterval函数(或者setTimeout,大家是否有类似经历呢?),然后心满意足地很快的搞定。结束后,朋友帮忙阅读了一下代码,他提醒我是不是可以考虑使用requestAnimationFrame。之前一直知道这个函数,也知道一些它的一些优点,问题是为什么呢?本着追究到底的精神,决定还是去阅读一下WebKit相关代码和一些相关文档,了解它们背后的故事。好吧,本章我将和大家一起来学习和探讨这背后的故事…

 

## 背景

接触过JavaScript的读者应该有过了解或者使用setTimeout或者setInterval的经历,其功能是在每个时间间隔之后一次性或者重复多次执行一段JavaScript代码(称为回调函数),以完成特定的动画要求。但是,这里面有还有些疑问:

1.      时间间隔应该设置为多少才合适呢?跟屏幕的分辨率有关系吗?

2.      设置的时间间隔会按照预想的执行吗?动画会被平滑地显示出效果吗?

3.      回调函数是复杂的好还是简单的好呢?应该如何编写才能效率高呢?

4.      与平台和浏览器相关吗?如何适应不同平台呢?

这对setTimeout和setInterval来说很重要。如果对mainloop机制和渲染机制有一定了解的读者来说,上面这几条其实是非常难做到地,哪怕是较为接近理想的结果。

幸运地是,总是有聪明的人来帮助大家解决难题。对问题提出一个漂亮解决方案的是mozilla的Robert O’Callahan。他的灵感和依据来源于CSS。CSS是知道动画什么时候发生,所以能够较为准确的知道什么时候刷新UI。对于JavaScript来说,是不是也可以根据类似的机制呢?答案是肯定地。其做法是增加一个新的方法requestAnimationFrame, 该方法告诉浏览器JavaScript想发起一个动画帧,然后在动画帧绘制之前,需要做一些动作,这样浏览器可以根据需要来优化自己的mainloop机制和调用时间点,以达到较好地平衡效果。

好吧,下面来看看mainloop机制及其工作原理。

 

## 渲染mainloop

因为chromium是多进程的结构(参看Chromium多进程架构篇),所以,跟一般浏览器不一样的是,Browser进程UI用户界面的mainloop和Renderer进程的主线程的mainloop不是同一个,分别位于两个不同的进程,所以UI和渲染可以互相不影响,听起来这好像很不错,是的,但是问题依然存在,那就是Renderer进程的渲染工作和JavaScript的执行工作都在其主线程中,由mainloop来负责调度完成,所以竞争依然存在。

大致过程是一个大的循环加上一个事件队列,具体的过程,如下图所示。当队列中有事件时,从队列中取出第一个事件,设置相应的状态信息,处理该事件及其对应的处理函数,直到该函数处理完后,才重新检查队列中是否有事件。如果有,继续处理;如果没有,则继续等待。这其中可以看出,如果队列中事件多的时候,那么很多事件可能来不及处理,从而造成比较大的延时,因而事件的平均等待时间会比较长。同时,如果事件的处理函数需要的时间很长,就会造成后面的事件一直在等待,同样会增加事件的平均等待时间。而当队列比较空闲时或者事件的处理函数需要的时间比较短,则事件的平均等待时间会相对小很多。


##WebKit和Chromium中的实现

理解了mainloop之后,下面来看一看setTimeout和setInterval的实现。

来看一下它们的实现:WebKit中setTimeout和setInterval的实现机制是类似的,区别在于后者是重复性的,见下图所示的类图关系。

WebKit会为DOM中的每个setTimeout和setInterval的调用创建一个DOMTimer,而后该对象会由存储TLS(thread localstorage)中的ThreadTimers负责管理,其内部其实是一个最小堆,每次取timeout时间最小的,同时,时间相同的Timer可以合并。

当Timer超时后,Chromium清除该Timer对象,同时调用相应的回调函数,回调函数通常会更新页面的样式和布局,这会触发relayout,从而触发立即重新绘制一个新帧。


结合上面的描述,我们大致地总结setTimeout和setInterval主要不足就是:

1.      setTimeout和setInterval从不考虑浏览器内部发生了其他什么事,它只要求浏览器在某个时间之后调用它的回调函数,无论浏览器很繁忙或者页面被隐藏(虽然某些浏览器做了这方面的优化,例如chromium);

2.      setTimeout和setInterval只要求浏览器做什么,而不管浏览器能不能做到(例如mainloop有很多事件需要处理),这有点强人所难,而且会带来极大的资源浪费。举个例子,例如屏幕的刷新率是60HZ,但是设置的时间间隔是5ms,其实对用户来说根本看不到这些变化,但是额外需要消耗更多的CPU资源,太不环保了…

3.      setTimeout和setInterval可能是编程风格方面的考虑。如果每一帧可能在不同的代码出需要设置回调函数,一个方法是统一到一个地方,但是这有点勉为其难,另一个方法是分别用setInterval设置它们,这个方法的问题是,浏览器可能需要计算更多次,刷新更多次的屏幕,唉。

现在再来看看requestAnimationFrame的实现,看看其如何解决这些不足之处的。其原理就是其会申请绘制下一帧,至于什么时候不知道,由浏览器决定,只需要浏览器在绘制下一帧前执行其设置的回调函数,完成JavaScript对动画所做的设置和逻辑即可。基本过程是这样的:

1.      JavaScript调用requestAnimationFrame,因而相应的webkit和chromium会调度一个需要绘制下一证的事件,该事件会将requestAnimationFrame的调用上下文和回调函数记录下来;

2.      上面的请求会触发Chromium更新页面内容的事件,该事件被mainloop调度处理后,会检查是否需要调用动画的相关处理,因为有动画需要处理,所以会依次调用那些回调函数,JavaScript引擎会更新相应的CSS属性或者DOM树修改;

3.      Chromium触发重新计算layout(参看layout章节),更新自己的Renderer树(参看webkit渲染基础章节),而后绘制,完成一帧的渲染。

下图是一个上述过程对应的状态转换图,来源于chromium的官方网站,看着的确比较饶人,可以先理解一下其中几个主要的概念:

Floortime:指的是绘制下一帧之前需要等待的事件间隔

Invalidation:触发重新绘制请求的操作;

scheduleAnimation:JavaScript调用requestAnimationFrame所引起的WebKit内部请求调度动画的操作;

这些状态的转换倒是说明了,requestAnimationFrame可以很好地和Chromium内部的绘制过程结合,从而达到比较好的性能。


为了实现更好的性能,chromium中对requestAnimationFrame有三个设计原则

1.      当页面不可见时,其回调函数不会被调用,这可以减少CPU和GPU的使用率,更环保嘛;

2.      其最大调用频率不会超过60hz,无论屏幕的刷新率是多少,因而回调函数也不会每秒调用超过60次,这是因为60FPS已经能够满足UI流畅的要求了,更频繁的刷新效果不明显;

3.      只有当页面真正开始渲染时,回调函数才会被调用。

为了对比二者的性能上的差异,我测试了GuiMark中HTML5Charting Test benchmark,修改里面一些代码(其缺省使用的是setInterval,改为requestAnimationFrame作对比),从实际测试的效果上看,在Google Chrome中,两者相差不是特别大,使用了requestAnimationFrame的benchmark的FPS大概只好了1~2FPS,所以chrome对timer机制的优化做地应该相当不错。如果你遇到了其他差别比较大的例子,欢迎跟我和大家分享。

Google Chrome对其处理的比较好不代表其他浏览器也是,所以各位还是在编程时候多考虑考虑,多思考思考,为了更好的性能,为了环保…

 

## 设计机制带来的编程考虑

最后,结合mainloop和requestAnimationFrame的设计原理和机制,看一看它们带给我们在编写JavaScript代码时有哪些方面的思考和便利:

1.      回调函数不能太大,不能占用太长时间,否则会影响页面的响应和绘制的频率;

2.      requestAnimationFrame不需要设置间隔时间,不同刷新率的间隔时间不一样,这完全由浏览器来控制,而不需要JavaScript程序员操心;

3.      回调函数无需合并,程序员可以在任意位置设置回调函数,它们可以被浏览器集中处理,而无需要一个统一的入口。

 

## 源文件目录

third_party/WebKit/Source/WebCore/page/

  支持requestAnimationFrame,setTimeout和setInterval的绝大多数基础设施都在这里,建议在该目录下搜索这些关键字即可

third_party/WebKit/Source/WebCore/platform

  Timer方面的一些支持

 

## 参考文献

1.      http://dev.chromium.org/developers/design-documents/requestAnimationFrame-implementation

2.      http://www.cnblogs.com/rubylouvre/archive/2011/08/22/2148793.html

3.      http://www.nczonline.net/blog/2011/05/03/better-javascript-animations-with-requestAnimationFrame/

4.      https://developer.mozilla.org/en-US/docs/DOM/window.requestAnimationFrame

5.      http://www.w3.org/TR/animation-timing/#requestAnimationFrame

6.      http://creativejs.com/resources/requestAnimationFrame/

7.      http://www.craftymind.com/factory/guimark2/HTML5ChartingTest.html

相关文章推荐

理解WebKit和Chromium: 渲染主循环(main loop)和requestAnimationFrame

## 概述 曾经写过一段JavaScript代码,因为涉及到需要循环调用某个函数来实现动画的功能,很自然地,我想到了使用setInterval函数(或者setTimeout,大家是否有类似经历呢...

理解WebKit和Chromium: Chromium软件渲染

转载请注明原文地址:http://blog.csdn.net/milado_nju/article/details/7455348 # Chromium的软件渲染基础 ## 概述 本章将介绍ch...

Android WebKit HTML主资源加载过程

  • 2016年04月06日 16:58
  • 33KB
  • 下载

WebKit渲染基础之Render树的建立

WebKit是一个渲染引擎,而不是一个浏览器,它专注于网页内容展示,其中渲染是其中核心的部分之一。...

WebKit渲染基础之Render树的建立

WebKit是一个渲染引擎,而不是一个浏览器,它专注于网页内容展示,其中渲染是其中核心的部分之一。这里着重于对渲染部分的基础进行一定程度的了解和认识,主要理解基于DOM树来介绍Render树和Rend...

PhantomJS(webkit)/SlimerJS (Gecko) + CasperJS获取JavaScript渲染后的网页内容

前言: 使用PhantomJS(webkit)或者SlimerJS (Gecko) 配合 CasperJS获取JavaScript渲染后的网页内容以及内嵌子网页渲染后的内容, 那么这样做到底能为我们做...
  • xlxxcc
  • xlxxcc
  • 2016年07月28日 14:07
  • 4288

webkit渲染过程

webkit一般性渲染过程               在浏览器中,有一个最重要的模块,它主要作用是将页面转变成可视化的图形结果,这就是浏览器内核。通常,它也被称为渲染引擎。所...

使用统一模式的WebKit浏览器渲染引擎的利弊

在本周前几天,欧朋浏览器(Opera)宣布正在逐步关闭其独立浏览器渲染引擎(brower rendering engine)的相关开发工作,继而转向使用开源的WebKit引擎,该消息很快引起了不小的轰...

WebKit/Blink 渲染网页的主要步骤

1.  发送网络资源请求。      WebKit在渲染网页之前,首先需要从网络上下载资源文件,这里把资源文件分为两类,一类称为主资源文件,比如Frame,另外一类成为子资源文件,比如图片,JavaS...
  • yl02520
  • yl02520
  • 2014年02月19日 09:49
  • 1980
内容举报
返回顶部
收藏助手
不良信息举报
您举报文章:webkit之渲染主循环理解
举报原因:
原因补充:

(最多只允许输入30个字)