浅析 cef 在 win 和 mac 上的适配
背景 cef 是一种跨平台的框架,属于 chrome 内核,可以用来显示 web 相关页面。目前在咚咚工作台上使用,显示聊天框,历史消息,插件页面等等。之前只是在 win 平台上使用,在今年开发 mac 版本商家咚咚过程中,完成了 mac 版本的适配,积累了一些在两个平台上的使用方法,避免以后再踩坑。
一. 库和资源文件基本的库文件包含了多个文件夹和资源文件,这些是运行 cef 时必须要依赖的,因此需要打包在安装包中。
win mac 说明 include 文件夹 include 文件夹 cef 内部接口对外暴露的头文件 libcef_dll 文件夹 libcef_dll 文件夹 cef 对外的 wrapper 库相关的 cc 文件 Resources 文件夹 Resources 文件夹 cef 依赖的 pak 文件 libcef.dll Chromium Embedded Framework cef 核心库文件其它 dll 库,libEGL.dll 等 其它 dylib 库,libEGL.dylib 等 cef 依赖动态库文件其它 bin 文件,natives_blob.bin 等 无 cef 依赖 bin 文件表 1.1 cef 依赖文件
以上是 cef 运行时所依赖的所有文件,其中 include 和 libcef_dll, 可以组合生成一个 lib 静态库,一般名称为 libcef_dll_wrapper.lib,在其它模块中需要依赖该 cef 头文件时,导入 lib 库即可。
在升级 cef 版本的过程中,只需要替换替换对应的文件夹中的文件,就可以升级到对应的版本,目前最新的 100 以上的版本,基本都是这些固定的文件格式。
如果添加了新的文件或者依赖库,则需要添加对应的库和资源。
二. 文件路径查找资源文件,需要设定路径才能找到对应的文件,在 CefSettings 指定对应的路径。
路径 win macresource 目录(pak 文件) resources_dir_path framework_dir_path/main_bundle_pathlocales 目录 locales_dir_path framework_dir_path/main_bundle_path 子进程路径 browser_subprocess_path browser_subprocess_path 表 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,来获取命令行的参数
win macHINSTANCE hInstance = (HINSTANCE)GetModuleHandle(NULL);CefMainArgs main_args(hInstance);
CefRefPtr<CefCommandLine> command_line = CefCommandLine::CreateCommandLine();
command_line->InitFromString(::GetCommandLineW()); CefMainArgs main_args(argc, argv);
CefRefPtr<CefCommandLine> command_line = CefCommandLine::CreateCommandLine();
command_line->InitFromArgv(argc, argv);使用系统方法,直接获取命令行参数 从 main 方法传递 argc, argv,从中获取命令参数表 3.1 cef 获取启动命令
3.3 设置其它参数
3.4 主进程初始化
四.渲染进程生成渲染进程,可以采用和主进程合并模式,也可以采用单独的进程。一般比较大型或者复杂的项目,都需要单独的进程,并为了不同的页面,还需要启动多个子进程。
win mac 独立的 exe 应用 独立的 app 应用子进程名称.exe 子进程核心应用:xxx Helper.app 子进程 GPU 应用:xxx Helper (GPU)子进程 Plugin 应用:xxx Helper (Plugin)子进程 Render 应用:xxx Helper (Renderer)表 1.1 cef 子进程名称
4.1 子进程的路径,理论上可以设置到专门的目录下面,但也可以让系统去默认路径查找,即和主应用同级别即可。
4.2 在最后初始化时,可以直接调用 cef 的方法:
五. 消息循环不同的系统版本中,采用不同的消息循环模式,需要和主应用的消息循环合作运行。
5.1 在 win 版本中,可以通过设置 multi_threaded_message_loop 为 true 的参数,开启浏览器多线程模式。能够和主应用的主消息循环并行处理运行。
如图所示,win 应用中,在 main 方法中需要开启主界面的消息循环,这里的消息循环就一直在 UI 主线程中运行,直到应用退出,消息循环主线程才会结束退出。
另外在 cef 的浏览器主进程中,也同时开启多线程消息循环,此时 Browser 独享自己的消息循环线程,和主消息循环互不影响,可以实时的收到界面的点击响应和显示 web 界面等。
5.2 在 mac 版本中,有两种 cef 消息循环方式
主循环消息启动后,生命周期跟随主应用,一直到应用退出才会结束。此时定一个时间片分割,时间自定义,可以 50 帧也就是每隔 20ms 处理一次。
当定时周期到的时候,就执行 cef 的消息循环处理,在 CefDoMessageLoopWork()中去处理 cef 的具体事件响应。
另外在 cef 本身的回调中,也可以使用 onScheduleMessageLoopWork()中拿到事件,然后到 cef 事件循环中去处理。
这样主循环和 cef 自己的的循环,就不断运行起来,在用户使用时,感觉不到这个细微的延时差距,主界面和 cef 界面基本都是在同时响应。
六.窗口适配在界面显示过程中,打开网页或者本地页面时,通常需要重新修改窗口尺寸。在不同的版本需要不同的处理方式:
功能 win mac 获取窗口 id (CefWindowHandle)this->winId() (CefWindowHandle)this->winId()改变窗口大小 ::MoveWindow(hwnd, rect.x(), rect.y(), rect.width(), rect.height(), true) [nsview setFrameSize:NSMakeSize(rect.width(), rect.height())]获取窗口句柄 browser->GetHost()->GetWindowHandle() browser->GetHost()->GetWindowHandle()browser->GetHost()->GetOpenerWindowHandle()表 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 版本也可以同时运行,比较方便维护升级。







评论