背景:
CEF:chromium embedded Framework是由Marshall Greenblatt在08年成立的一个开源项目,目的是开发一个基于Google Chromium项目的Web browser控制器。CEF目前支持大多数编程语言和操作系统,并可以轻松的整合新的和已存在的应用。设计的目的也是为了效率/性能和方便使用。基础的框架包括了借助原生库的C/C++接口,这样将主机的应用与chromium和WebKit隔离开来。它提供了浏览器控制和主机应用程序(包括支持自定义插件、协议、JavaScript对象和JavaScript扩展)之间紧密的集成。主机应用程序可以有选择地控制资源加载、导航、菜单、打印等,当利用了相同的性能和Google Chrome浏览器具备的HTML5技术。
依赖/相关:
CEF项目依靠一堆其它由第三方维护的项目,主要又以下:
Chromium:基础,网络堆栈,线程,消息机制,log,进程控制,生成Web browser。 WebKit:提供DOM解析,布局,事件处理,渲染,HTML5JS的API。 V8:JS引擎。 Skia:2D图形库。
Angle:3D图形转换,和DirectX有关。
版本: 3个版本:
CEF1,单进程工具调用chromium Webkit API CEF2,多进程工具建立在Chromium browser CEF3,多进程工具调用Chromium Content API
通用API用法:
所有版本的CEF都公开一个简单好用的API(让用户从Chromium和Webkit复杂的代码中分隔开来),详细如下:
1. 调用CefInitialize()对CEF初始化。
2. 调用CefRunMessageLoop() or CefDoMessageLoopWork()来在UI消息上处理事务。
3. 调用CreateBrowser() or CreateBrowserSync()并传递一个CefClient事例来创造一个browser
窗口。
4. 在程序退出前调用CefShutdown()来关闭CEF。 CEF1
CEF1使用单进程架构,并直接将Chromium和Webkit整合到客户端应用程序中。单进程的优点包括了建设内存的使用和进一步与客户端应用的耦合。缺点有某些类型的加载内容性能低,和由于同进程中运行Flash插件的崩溃问题。
CEF1 API用法:
CefApp,此接口用来传递到CefInitialize(),和允许应用程序定制全局,如资源加载,代
理。
CefClient,此接口用来传递到CefCreateBrowser() or CefCreateBrowserSync(),和充当单独
CEF Browser事例和客户端应用程序的连接,也负责请求和显示处理的接口
CefBrowser,公开由浏览器提供的功能。包括前进后退导航,来源检索,加载请求等,
一个CefBrowser可能有一到多个子类的CefFrame对象
线程注意事项
CEF1包括UI,IO和FILE线程。 UI线程创建Browser窗口,用来所有与WebKit和V8的交互,IO线程用来处理模式和网络请求。 FILE线程用于应用程序缓存和其他各种活动。
当使用CEF1时,你应该记住以下线程考虑:
不要在UI线程上执行会造成阻塞的操作。这将导致严重的性能问题。
如果CefInitialize()通过值为false的CefSettings.multi_threaded_message_loop被调用,那
么UI线程将和主线程一样。
所有WebKit和V8互为作用必须在UI线程上进行。因此,有些CEF API函数只能在UI线程上
调用。有相同限制的功能会被相应地记录并关联到CEF的头文件里。 CefPostTask方法可以用来动态同步地进行不同线程里的任务。
实施细则
CEF1主要有以下实现类:
CefContext 代表全局CEF内容。单个CefContext对象是由CefInitialize()创建,由
CefShutdown()销毁
CefProcess 由CefContext创建和管理的CEF线程。
BrowserWebKitInit 管理全局WebKit环境作为Chromium WebKit API暴露。
WebViewHost 提供本地窗口“wrapper”工具给WebView。这个类扩展了,提供和在某
些平台上弹出部件(如选择菜单)一样的功能,WebWidgetHost。
CefBrowserImpl 实现CefBrowser的接口,创建WebViewHost,并提供了但个Browser实例
的粘合代码。
BrowserWebViewDelegate 实现WebKit接口,提供CefBrowserImpl和底层的WebView之间
的通信。
CEF2已经被取消了 CEF3
CEF3借助Chromium Content API使用和Chromium Web 浏览器一样的多进程架构,相比于单进程的CEF1,他具有更多的优势。 同时支持单进程和多进程模式。
更多的代码分析通过Chromium浏览器
改进的性能和由于代码路径的支持而减少的损坏 新特性的快速获取
大多数情况下,CEF3和Chromium浏览器拥有相同的性能和稳定特征。
API用法
CEF3的初始化需要考虑多进程。在Windows和Linux上的所有进程享有相同的可执行文件,或选择使用单独的可执行文件的子进程(借助CefSettings.browser_subprocess_path的值)。在Mac OS-X的所有的子进程都必须绑推一个分开的app使用。这是必要的因为在Mac OS-X的子
进程必须有一个不同的Info.plist文件。
CefExecuteProcess()函数是负责执行子过程的逻辑。它检查“类型”的命令行标志来决定目前正在执行的进程类型。如果被browser进程调用(识别为没有“类型”的命令行值)它会立即返回一个为-1值。如果被调用且认为是次级进程,它会阻塞直到该进程应该退出,然后返回进程的退出代码。如果执行browser进程,应用程序会继续在标准进程初始化,被描述为“通用API的用法”部分。将cef_app.h理解为一个完成的的功能初始化和用法的描述。将Wcefclient_win.cpp wWinMain功能视作一个窗口例程。
“单进程”命令行标志或者CefSettings.single_process的值可用在一个单一的进程中运行CEF3。这是有用于调试目的,但不建议用于生产。
下面是主要CEF3接口和它们的用途的概述:
CefApp,此接口用来传递到CefInitialize(),和允许应用程序定制全局,如资源加载,代
理。一些功能是由所有进程共享的,有些必须实现浏览器的过程中,必须在渲染过程中执行。见详情头文件的意见。
CefClient,此接口用来传递到CefCreateBrowser() or CefCreateBrowserSync(),和充当单独
CEF Browser事例和客户端应用程序的连接,也负责请求和显示处理的接口。请求处理,显示处理等额外的接口,通过这个接口暴露。
CefBrowser,公开由浏览器提供的功能。包括前进后退导航,来源检索,加载请求等,
一个CefBrowser可能有一到多个子类的CefFrame对象。在一个特定的过程或一个特定的线程必须调用一些方法,所以仔细阅读文档。
CefBrowserHost - 公开有关运行browser进程中唯一可用的browser窗口的功能。例如,
检索本地父窗口句柄,或销毁browser窗口。
CefRenderProcessHandler - 公开WebKit和V8对渲染进程中应用程序的集成能力。通过
CefApp返回此对象的一个实例。
实施细则CEF3主要有以下实现类:
CefMainDelegate 实现通用进程的引导逻辑。
CefContentClient 实现所有进程中共同的Content API回调。
CefContext 代表全局CEF内容在browser进程中。单个CefContext对象由CefInitialize()创
建并由CefShutdown()销毁。
CefBrowserMainParts 实现browser进程中的引导逻辑。
CefContentBrowserClient 实现browser进程的ContentAPI回调。
CefBrowserHostImpl 实现在browser进程中CefBrowser和CefBrowserHost的接口。提供粘
合代码和工具的借口来和RenderViewHost通信。
CefContentRendererClient 实现渲染进程中的ContentAPI回调。
CefBrowserImpl 实现渲染进程中的CefBrowser接口。提供粘合代码和工具的借口来和
RenderView通信。
cef 是一个基于google chromiun的简单的框架。 它主要是作为一个内嵌浏览器嵌入到客户端应用程序中。
可以再 http://cefbuilds.com 下载最新的编译版本。
总体框架预览
- CEF 使用了多进程。主进程是“browser”进程。 而子进程是由rederes, plugins, GPU, 等组件创建。
- 在ECF的所有进程中,都可以有多线程。CEF提供了函数和接口在不同的线程中来传递任务。
- 一些回调方法和函数只能在特定线程和进程中使用。在使用API之前请确保仔细阅读注释。
源代码
cefsimple 工程初始化CEF并创建了一个简单的浏览器窗口。
- 系统在入口点函数中(man或者wWinMain)函数中开启browser进程
-
入口点函数:
- 创建SimpleApp的实例,在这个类中保存process-level callbacks.
- 初始化CEF并开启消息循环。
- 创建SimpleApp的实例,在这个类中保存process-level callbacks.
-
当CEF初始化完毕以后, SimpleApp::OnContextInitialized()会被调用。在这个方法中:
- 创建一个单例的SimpleHandler
- 由CefBrowserHost::CreateBrowserSync()创建一个浏览器窗口
- 创建一个单例的SimpleHandler
- 所有的浏览器共享同一个在SimpleHandler。SimpleHandler负责定制浏览器的行为并保存browser-related callbacks(loading状态,标题行为等)
-
当浏览器窗口被关闭的时候, SimpleHandler::OnBeforeClose() 被调用。当所有浏览器窗口被关闭,CEF消息循环退出。
可以看下如下代码:
int APIENTRY wWinMain(HINSTANCE hInstance
, HINSTANCE hPrevInstance
, LPTSTR lpCmdLine
, int nCmdShow)
{
CefMainArgs args(hInstance);
CefRefPtr<MySimpleApp> app(new MySimpleApp);
int exitCode = CefExecuteProcess(args, app, NULL);
if(exitCode >= 0) return exitCode;
CefSettings settings;
CefInitialize(args, settings, app, NULL);
CefRunMessageLoop();
CefShutdown();
return 0;
}
此时进程开启,但是没有任何窗口, 如果我们需要建立窗口需要如下
CefWindowInfo winInfo;
winInfo.SetAsPopup(NULL, "myCefSimple");
CefBrowserSettings browser_settings;
CefRefPtr<CefClient> client(new MySimpleClient());
std::string url = "file:///D:/project/github/cptf/resource/binding.html";
CefBrowserHost::CreateBrowser(winInfo
, client.get()
, url
, browser_settings
, NULL);
这样就显示出来窗口了。
但是我们发现在关闭的时候进程没有关掉, 所以我们要做如下动作。
class MySimpleClient : public CefClient
, public CefLifeSpanHandler
void MySimpleClient::OnBeforeClose(CefRefPtr<CefBrowser> browser) {
CEF_REQUIRE_UI_THREAD();
CefQuitMessageLoop();
}
总结
App和Client是CEF中最重要的两个类。CefExecuteProcess 和 CefInitialize 是建立app的两个最重要方法。
而CreateBrowser 是创建浏览器窗口的最重要方法。
Cef支持各种语言和多种操作系统。在设计的时候充分考虑了性能和易用性。cef核心功能提供了c和c++的接口。cef提供了和主程序之间的通信能力(利用 custom plugins, protocols,javascrpit object 和 javascript extensions)。主应用程序可以选择性的使用控制 资源的加载,切换, context menus, printing等。
依赖
- chromium
- webkit
- v8
- skia
- angle
线程注意事项
CEF线程有如下几种
typedef enum {
TID_UI,
TID_DB,
TID_FILE,
TID_FILE_USER_BLOCKING,
TID_PROCESS_LAUNCHER,
TID_CACHE,
TID_IO,
TID_RENDERER,
} cef_thread_id_t;
在使用线程的时候需要注意如下几点:
- 千万不要阻塞UI线程
- UI线程会任务是主线程,当 CefSettings.multi_threaded_message_loop = false的时候。
- 所有的webkit和V8的交互必须用 TID_RENDERER线程
- CefPostTask 方法可以再不同线程中进行异步调用
接口
- CefApp,此接口用来传递到CefInitialize(),和允许应用程序定制全局,如资源加载,代理。这些功能是由所有进程共享的,有些必须实现浏览器的过程中,必须在渲染过程中执行。见详情头文件的注释。
- CefClient,此接口用来传递到CefCreateBrowser() or CefCreateBrowserSync(),和充当单独CEF Browser事例和客户端应用程序的连接,也负责请求和显示处理的接口。请求处理,显示处理等额外的接口,通过这个接口暴露。
- CefBrowser,公开由浏览器提供的功能。包括前进后退导航,来源检索,加载请求等,一个CefBrowser可能有一到多个子类的CefFrame对象。在一个特定的过程或一个特定的线程必须调用一些方法,所以仔细阅读文档。
- CefBrowserHost - 公开有关运行browser进程中唯一可用的browser窗口的功能。例如,检索本地父窗口句柄,或销毁browser窗口。
CefRenderProcessHandler - 公开WebKit和V8对渲染进程中应用程序的集成能力。通过CefApp返回此对象的一个实例。
进程注意事项
CEF3使用了很多不同的进程:
- Broser process- 这个进程可以认为是应该程序的主进程,当调用CefInitialize()的时候建立
- Render process- web容器(webkit和v8)在此进程中执行
- plugin process- 插件(如 Flash)
- GPU process-GPU渲染进程
- Utility process- 各种其他任务在这个进程中跑。
CefBrowser 和 CefFrame 在browser和render进程中都存在,并且传递一系列callbacks。 CefProcessMessage能够在browser进中利用CefBrowser::SendProcessMessage 函数发出, 并且在CefClient::OnProcessMessageRecieved 和CefRenderProcessHandler::OnProcessMessageRecieved 接收。
重要的细节
CEF3 有如下几个比较重要的类:
- CefMainDelegate - 用于普通进程的逻辑过程
- CefContentClient- 在普通进程 展现Content Api的回调。
- CefContext-在browser进程中,展现全局的CEF上下文。 一个单独的CefContext对象时由CefInitialize()建立,有CefShutdown销毁。
- CefBrowserMainParts- browser 进程的逻辑
- CefContentBrowserClient-在browser 进程展现Content Api的回调。
- CefBrowserHostImpl-是CefBrowserHost 的实现
- CefContentRendererClient- 在render 进程中展现Content Api的回调
- CefBrowserImpl- CefBrowser的实现者