libCef基本框架与结构

此篇文章简单介绍了chrome的基本框架和libcef代码结构,主要借鉴了以下四篇文章: http://www.cnblogs.com/duguguiyu/archive/2008/10/04/1303695.html
http://blog.csdn.net/zhuhongshu/article/details/70159672
http://blog.csdn.net/luoshengyang/article/details/47364477
https://github.com/fanfeilong/cefutil

chrome是一个基于多进程框架的浏览器,下图是一个经典的chrome进程结构图(图片来源于: http://www.cnblogs.com/duguguiyu/archive/2008/10/04/1303695.html 【1】)。
libCef基本框架与结构_第1张图片
上图中一共有三个实线框,一个实线框代表一个进程,整体表示有一个browser进程,两个Render进程。其中brower进程是老大,管理着Render进程,两个render进程互不干涉,互不影响。Browser和Render之间通过IPC(inter-process communication)来进行通信,在windows下实质上就是命名管道(Named Pipe)。但实际上browser和render并不只是通过IPC来进行通信,有些时候会配合共享内存一起来使用,比如browser下载的网页数据,或者render渲染好的数据都是放在共享内存中的,ipc传递的只是关键信息,brower或者render根据ipc传过来的信息到共享内存中去存取数据,并执行后续操作。
实线框中的一个虚线框表示一个线程,从图中可以看出,browser进程和render进程都有多个线程,主要线程为UI线程(MainThread),IO线程(其实是指通信线程,包括IPC消息和网络通信),其他的还有File线程,但图中没有画出来。
每一个虚线框中有一些小的实心框框,下面将分别介绍各个实心框框表示的含义。
一个RenderProcessHost对象描述的是它所启动的一个Render进程,一个RenderViewHost对象描述的是运行在Render进程中的一个网页,可以理解为浏览器中的一个TAB页。每一个RenderProcessHost肯定对应一个render进程中RenderProcess,每一个RenderViewHost也肯定对应Render进程中的一个RenderView。按照罗升阳的说法,可以将*Host对象和*对象理解为一对对等体,因为它们是brower进程和render进程进行IPC通信的两个端点。类似于TCP/IP网络堆栈中的层对层通信。RenderViewHost与RenderView之间的通信代表了browser进程请求Render进程加载、更新、渲染一个页面。值得注意的是一个render进程只有一个RenderProcess,但可以有多个RenderView,因此Chrome所宣传的One-Tab-One-Process其实描述并不是十分精准。
灰色框框Channel是命名管道的一个封装,它可以有两种工作模式,一种是Client,一种是Server,分属两个进程,维持一个管道,以完成通信。Channel中两个重要的子类,Message::Sender和MessageListener,其中Sender是消息发送的接口,Listener是消息接收的接口。由于并不是所有的消息都需要进行处理,因此channel中添加了Filter的功能。
图中可以看到RenderView和Channel并不在一个线程中,那么RenderView是如何通过channel来进行通信的呢。Chrome中是通过ChannelProxy来实现的,ChannelProxy将非IO线程想要执行的任务封装成一个Closure并放到IO线程中的消息队列中,等待IO线程的处理。ChannelProxy为Channel的代理类,它们的接口没有多大的区别。
WebKit框框代表网页渲染模块,是一个底层模块。
将上图和官网上的chrome层次结构图一起来看,能够更加清晰的理解chrome的整体框架。
libCef基本框架与结构_第2张图片
以上讨论中我们大致明白了chrome的进程框架,当然我们也知道Chrome中并不只有browser进程和render进程,也有GPU进程和plugin进程,完整的进程关系图可以参考【3】,这里不在贴出。另外也看到了Chrome中使用了多线程结构,那么Chrome中各线程如IO线程,UI线程,File线程之间是如何进行同步的呢。一般而言,多线程访问数据时都会在可疑的地方进行加锁,频繁的加锁和解锁肯定会影响效率。为了尽量少使用锁,Chrome采用了消息循环的机制,每一个chrome线程里面都启动了一个消息循环,等待并执行任务。不同的线程差别在于处理的任务(事物)类别不同。线程之间的通信通过Closure的传递来进行,Closure封装了将要执行的任务和参数。下面这个图(【3】)很好的描述了chrome线程之间的通信过程。从途中可以明显的看出,线程1将任务分发给线程2时,自己也在执行另外一个子任务,说明线程之间的通信是异步的。
libCef基本框架与结构_第3张图片
采用消息循环的方式,锁就只需在Task进入线程的任务队列时使用,其他时候并不需要进行锁操作。这样很大程度上避免了锁竞争带来的资源消耗。
这个机制似乎很完美,但其实对Task划分有很高的要求,为了保证不同task不会同时访问相同的数据结构,需要提前知道每一个任务的执行者,并确保这些数据结构仅会被task的执行者访问。这其实是将很多事情放在了问题建模阶段来完成。
下图(【1】)描述了Task的执行模式,任务可以在任何线程中创建,并投递到IO线程的消息队列中(当然也可以投递到其他任何线程中,只要有需求)。在进入队列和取出执行的时候需要锁操作。
libCef基本框架与结构_第4张图片
在消息循环中根据逻辑的不同,Task可分为及时处理的Task, 延时处理的Task,空闲时处理的Task。通过分类可以满足不同的需求。下图(【1】)描述了线程中消息的循环。
libCef基本框架与结构_第5张图片
关于chrome多线程模型优劣的讨论,可以参见【1】,这里不再复述。

-------------------------------------------------------------------------------
以上简单的过了一遍Chrome中进程和线程的基本结构,下面将根据【2】和【4】简单整理一下开发过程中所需要用到的类的基本结构,即Cef的接口简单说明。
CefApp类接口
CefApp提供了进程级的可定制回调函数,由上面讨论可知,一个cef应该中一般包含两个进程browser 和 render,与之对应的两个基类是CefBrowerProcessHandler, CefRenderProcessHandler,若一个进程即作为browser,也作为render,则需要同时继承这两个基类。如果分开在不同进程完成,则可在两个进程中分开继承两个基类。当前作为cef进程,都需要继承CefApp。
主要接口:
CefApp::OnBeforeCommandLineProcessing 可以附加传给cef的参数,如设置渲染进程名称,是否开启扩展功能,是否开启GPU加速等。参数设置的格式一般如下:command_line->AppendSwitch("disable-gpu");
CefRenderProcessHandler::OnWebKitInitialized 通过这个接口可以在render进程初始化时注册JS扩展代码,实现C++与JS的交互,但由于容易触发崩溃,因此现在这种方法用的较少
CefRenderProcessHandler::OnContextCreated 在这个接口中可以实现窗体绑定,所谓窗体绑定是允许客户端应用程序把值附上一个框架窗口对象,这是实现C++和JS交互的另外一种方法,一般格式如下,Js中可以通过windows.external.passport来调用C++的代码。
libCef基本框架与结构_第6张图片
CefRenderProcessHandler::OnProcessMessageReceived 方法用于接收Browser进程发过来的消息。

CefSettings:
CefSettings结构体定义了Cef的全局配置信息,比如指定单进程模式、指定渲染子进程路径、设置localstorage路径、设置日志等级、Cef资源文件路径。其中对于项目最重要的字段是single_process、multi_threaded_message_loop、windowless_rendering_enabled,分别用于指定单进程模式、多线程渲染模式、离屏渲染模式

CefClient类接口:
每一个browser对象都对应一个CefClient,CefClient可以理解为browser的实体,用于处理浏览器页面的各种回调消息,包括:鼠标,键盘,焦点,下载,对话框以及browser的生命周期。如果对Cef有功能需求,一般可以先看看Client接口中有没有提供相关功能。
CefClient类来定制的回调接口类中常用的有以下一些:
CefDisplayHandler:与显示相关的事件处理。如 OnTitleChange方法
CefLifeSpanHandler:与browser生命周期相关的事件处理。如OnBeforeClose 方法
CefLoadHandler: 与页面加载相关的事件处理。如OnLoadStart方法,OnLoadEnd方法。
CefRenderHandler: 与渲染相关的事件处理。若要实现离屏渲染,就必须实现这个接口类,并在OnPaint方法中做相应操作。
CefRequesthandler: 与资源请求相关的事件处理。若要实现资源的重定向,如加载本地资源,必须实现这个接口类。
CefKeyboardHandler: 与键盘相关的事件处理。如 OnKeyEvent方法
CefDragHandler:与拖拽相关的事件处理。如OnDragEnter方法

另外CefClient::OnProcessMessageReceived方法用于接收render进程发到的消息,C++与JS交互时会用到

关于消息循环
由于Cef是多线程架构,且每个线程都有自定的消息队列,若强制使用单线程(settings.multi_threaded_message_loop = false;)来运行CEF,则需要主动调用Cef的消息循环,调用接口为CefDoMessageLoopWork。但该函数如果调用太频繁则会很消耗CPU,如果调用频率太低,则会导致cef线程中处理任务不及时,cef界面反映变慢。
如果采用多线程设置,则Cef会为自己创建必要的线程(如UI线程,IO线程等),此时外面就无须主动调用CEF的消息循环了。 但需要注意的是,采用多线程后,Browser的UI线程和我们自己的UI线程(如果我们有的话)将不是同一个线程,而cef中大多数对象触发的回调函数都是在CEF的UI线程中,而不是我们自己的UI线程。这点是需要明确的,如果想要在CEF的回调函数(如OnLoadEnd)中操作自己的UI线程,则最好将操作转到到你自己的线程中去。
为了保证CEF消息的正常运行,一般建议采用多线程模式。

你可能感兴趣的:(CEF,LibCef框架,libcef基本结构,常用类说明)