浅析 cef 在 win 和 mac 上的适配
背景
cef 是一种跨平台的框架,属于 chrome 内核,可以用来显示 web 相关页面。目前在咚咚工作台上使用,显示聊天框,历史消息,插件页面等等。之前只是在 win 平台上使用,在今年开发 mac 版本商家咚咚过程中,完成了 mac 版本的适配,积累了一些在两个平台上的使用方法,避免以后再踩坑。
一. 库和资源文件
基本的库文件包含了多个文件夹和资源文件,这些是运行 cef 时必须要依赖的,因此需要打包在安装包中。
表 1.1 cef 依赖文件
以上是 cef 运行时所依赖的所有文件,其中 include 和 libcef_dll, 可以组合生成一个 lib 静态库,一般名称为 libcef_dll_wrapper.lib,在其它模块中需要依赖该 cef 头文件时,导入 lib 库即可。
在升级 cef 版本的过程中,只需要替换替换对应的文件夹中的文件,就可以升级到对应的版本,目前最新的 100 以上的版本,基本都是这些固定的文件格式。
如果添加了新的文件或者依赖库,则需要添加对应的库和资源。
二. 文件路径查找
资源文件,需要设定路径才能找到对应的文件,在 CefSettings 指定对应的路径。
表 2.1 cef 资源路径设置
2.1 对于资源路径,win 版本可以放到同一目录下面,例如在安装目录下,新建一个 CEF 目录,专门用来放置资源文件:
2.2 dll 库则在主安装目录下面:
2.3 mac 版本的目录文件基本是固定的:
Chromium Embedded Framework.framework
一般可以放置到 app 包的顶层或者 Frameworks 目录下面
以上的路径,都需要明确指定绝对路径,即完整的访问路径资源,不能使用相对路径,比如../../../之类的,因此在程序内部,需要专门封装查找资源路径的方法,这样在应用安装到用户电脑上,才能准确找到对应的资源。
若资源文件无法找到,则在后续的初始化,第一步就会报错,只有找到文件,才能进行后续的步骤。
三. 主进程初始化
主进程初始化分为多个步骤:
3.1 加载 cef 动态库
如果将 libcef 库放置在标准目录下面,即 win 是 exe 同级目录,则无需查找动态库,系统可以自行查找到,如果放到其它目录,则需要采用动态加载库的方式。
mac 可以使用 cef 提供的标准方法:
CefScopedLibraryLoader library_loader;
library_loader.LoadInMain()
来直接加载主进程库。
若采用显示路径来加载的方式,可以指定
Chromium Embedded Framework.framework 下面的 Chromium Embedded Framework,整个的完整路径
然后使用 cef 提供的方法:cef_load_library,加载指定路径下的主进程文件。
加载主库必须成功,失败则直接返回,后续一切步骤都依赖主进程的加载完成。
3.2 获取启动参数
启动的时候,都需要使用 CefMainArgs,来获取命令行的参数
表 3.1 cef 获取启动命令
接着生成 CefRefPtr<ClientApp> app,这个类继承自 cef 的基本 app 类:CefApp
继承 ClientApp,来生成主进程方法:
app = new ClientAppBrowser();
3.3 设置其它参数
设置日志级别:settings.log_severity
日志目录:settings.log_file
浏览器版本和说明:settings.user_agent
调试时的端口:settings.remote_debugging_port
3.4 主进程初始化
直接调用 CefInitialize(main_args, settings, app.get(), sandbox_info)
传入命令,设置选项,app 实例,sandbox 的内容,通常来说 sandbox 为空,不启用即可
返回成功可以进行后续步骤,失败表示主进程启动异常,后续也无法显示页面。
四.渲染进程生成
渲染进程,可以采用和主进程合并模式,也可以采用单独的进程。一般比较大型或者复杂的项目,都需要单独的进程,并为了不同的页面,还需要启动多个子进程。
启动流程和主进程类似,先加载子进程路径,再获取命令行参数,继承 CefApp 启动实例,最后再初始化。
进程名称,win 可以自己定义,mac 有严格限制,必须以 xxx Helper 结尾,否则找不到对应的渲染子进程。
表 1.1 cef 子进程名称
4.1 子进程的路径,理论上可以设置到专门的目录下面,但也可以让系统去默认路径查找,即和主应用同级别即可。
加载 cef 资源的时候,win 版本在主应用同级别的 exe,不用专门的方法去加载。
mac 版本需要使用 cef 的专用方法:
CefScopedLibraryLoader library_loader;
library_loader.LoadInHelper()
若是需要专门的路径,则仍然使用 cef_load_library 去加载库的绝对路径,加载成功后,才能正常初始化子进程。
4.2 在最后初始化时,可以直接调用 cef 的方法:
CefExecuteProcess(main_args, app, nullptr)
返回值大于 0,表示启动子进程已经成功,可以开启渲染页面等操作。
返回值小于 0,表示启动子进程失败,页面将无法进行渲染,但是不影响主进程的正常运行。
子进程实现的最好方式,是将所有的依赖文件,都集成在同一个模块工程中,不依赖其它的模块,这样在编译运行的时候,就不会由于依赖过多导致各种错误。
五. 消息循环
不同的系统版本中,采用不同的消息循环模式,需要和主应用的消息循环合作运行。
5.1 在 win 版本中,可以通过设置 multi_threaded_message_loop 为 true 的参数,开启浏览器多线程模式。能够和主应用的主消息循环并行处理运行。
如图所示,win 应用中,在 main 方法中需要开启主界面的消息循环,这里的消息循环就一直在 UI 主线程中运行,直到应用退出,消息循环主线程才会结束退出。
另外在 cef 的浏览器主进程中,也同时开启多线程消息循环,此时 Browser 独享自己的消息循环线程,和主消息循环互不影响,可以实时的收到界面的点击响应和显示 web 界面等。
5.2 在 mac 版本中,有两种 cef 消息循环方式
第一种:直接将主应用的消息循环设置为 cef 的消息循环,即所有的消息接收和传递,都在 CefRunMessageLoop()中进行,此时 cef 的消息循环占据了主导地位,主界面的按钮点击等操作,都需要 cef 抛出并提交给主线程。
第二种:当 mac 中采用了 Qt 等框架时,它本身有自己的消息主循环,而 cef 在 mac 中无法再开启自己的消息主循环,也不支持采用多线程机制单独运行。因此采用了将 cef 消息循环嵌入到 Qt 的消息循环中的方式,相当于时间片分割方式,到了定时时间,去专门处理 cef 的事件响应。
主循环消息启动后,生命周期跟随主应用,一直到应用退出才会结束。此时定一个时间片分割,时间自定义,可以 50 帧也就是每隔 20ms 处理一次。
当定时周期到的时候,就执行 cef 的消息循环处理,在 CefDoMessageLoopWork()中去处理 cef 的具体事件响应。
另外在 cef 本身的回调中,也可以使用 onScheduleMessageLoopWork()中拿到事件,然后到 cef 事件循环中去处理。
这样主循环和 cef 自己的的循环,就不断运行起来,在用户使用时,感觉不到这个细微的延时差距,主界面和 cef 界面基本都是在同时响应。
六.窗口适配
在界面显示过程中,打开网页或者本地页面时,通常需要重新修改窗口尺寸。在不同的版本需要不同的处理方式:
表 6.1 cef 窗口适配
在创建一个浏览器窗口时,需要调用 SetAsChild 设置浏览器的子窗口,这里首先获取当前生成的窗口 id,可以直接用 CefWindowHandle 来进行转换。
在重绘窗口大小的时候,需要移动窗口,调整高和宽,这里 cef 没有提供通用的方法,只能使用不同平台的原生方法,因此调用了操作系统的系统接口。
获取窗口可以直接使用 cef 内部封装好的 GetWindowHandle,这个是通用的接口,需要在 UI 线程中调用,但是在一些特殊的 web 窗口中,需要使用 GetOpenerWindowHandle 来获取非 pop 窗口的句柄,从而操作窗口界面正常显示。
七.版本区分
7.1 操作系统版本区分可以直接使用开源库的方法:
win 系统:Q_OS_WIN
mac 系统:Q_OS_MAC
当需要区分使用不同操作系统的接口,或者调用对应系统的原生方法时,需要使用该宏定义进行区分处理。
7.2 cef 版本区分,可以使用 cef_version 中的定义:
#define CEF_VERSION_MAJOR 106
#define CEF_VERSION_MINOR 1
#define CEF_VERSION_PATCH 0
#define CEF_COMMIT_NUMBER 2678
通常来说,若应用程序中,同时存在多个 cef 版本,比如 89,106 等,主要是主版本号不同,则直接判断 CEF_VERSION_MAJOR 的数值即可,另外的三个小版本可以不做过多关注,若大版本相同,则需要进行小版本的详细区分。
不同的 cef 大版本,接口基本是一样的,但是当相差几十个版本的 cef 库时,则会出现接口参数变更,或者名称变化,新增部分接口等差异点,则需要在不同版本进行对应的处理和适配。
结尾
使用 cef 在 win 和 mac 上用的同一套文件,利用 Qt 的宏定义对平台进行了区分,利用 cef 版本对不同版本进行兼容,因此同样的源代码文件,在两个平台都可以运行,不同的 cef 版本也可以同时运行,比较方便维护升级。
后续可以继续完善,形成更多不同的功能,并进一步拆分成独立的模块,让大家少走一些弯路。
版权声明: 本文为 InfoQ 作者【京东科技开发者】的原创文章。
原文链接:【http://xie.infoq.cn/article/64f73c9e451dc788dc8113004】。文章转载请联系作者。







评论