十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

CEF+Qt构建桌面浏览器实战:从Chromium集成到音视频播放

CEF+Qt构建桌面浏览器实战:从Chromium集成到音视频播放 简介这是一套面向高校计算机专业学生及C/Qt初学者的毕业设计与课程设计实战项目基于CEFChromium Embedded Framework深度封装实现跨平台Qt浏览器核心完整支持音视频播放、JS双向交互、离屏渲染、SVG动画及SVGA动态资源加载解决传统Qt WebEngine在多媒体兼容性与定制化能力上的局限。资源包共569个文件包含255个头文件h、106个资源信息文件info、116个Chromium运行时pak资源包、16个动态链接库dll及关键源码文件如QCefWebView.cpp、CefBrowserHandlerImp.cpp等整体体积达193.96MB结构清晰、模块解耦便于二次开发与功能扩展。已有456人学习下载配套详细项目介绍文档与VS2017Qt5.12.11环境配置说明提供可直接编译运行的完整工程含sln解决方案是理解CEF嵌入原理、提升C大型GUI项目架构能力的优质实践样本。 一直有同学问我毕业设计或者课程设计想做一个“带界面的浏览器”但不想用现成的WebView控件糊弄又不想从零写渲染引擎到底选什么方案最合适。我给的答案基本都是一个用CEF封一层Qt外壳底层渲染交给ChromiumUI和业务逻辑用C/Qt自己控制。这篇文章就把我实际做过的一个完整项目拆开揉碎讲清楚包括为什么这么选、CEF和Qt怎么在底层结合、音视频播放怎么处理、编译链接会遇到哪些坑以及最后整个项目结构长什么样。无论你是要做毕设、课设还是想给自己的桌面应用塞一个网页浏览模块这份记录都值得参考。1. 项目整体设计与思路拆解先把这个项目的定位说清楚这是一个基于CEFChromium Embedded Framework封装的桌面浏览器外壳用Qt实现业务逻辑和窗口管理用C核心渲染和音视频解码能力全部交给CEF。最终交付物是一个可运行的桌面浏览器程序支持多标签页、地址栏输入、前进后退刷新、加载进度提示以及关键的音视频在线播放能力。1.1 为什么是CEF而不是Qt WebEngine或者自研内核很多人在选题时会纠结三选一CEF、Qt WebEngine、自己写一个WebView。我先说结论再展开解释。Qt WebEngine本质上是把Chromium的某个旧版本封装成了Qt组件用起来确实方便但有几个问题很致命第一它的内核版本滞后对新型Web标准的支持慢半拍个别H5能力可能直接不支持第二内部线程模型被Qt重新包装过出了问题你很难直接定位到Chromium底层第三如果要深度定制比如拦截网络请求、自定义协议、注入JS它的接口远不如CEF灵活。自研浏览器内核除非你的课题就是“浏览器渲染引擎”否则强烈不建议。一个能用的HTML渲染引擎工作量是天文数字光CSS布局和JavaScript引擎就能让人崩溃毕设别这么折磨自己。CEF的核心优势是它给你一个完整的ChromiumAPI设计是C/C风格封装层级清晰你可以直接拿到进程、线程、请求、渲染、音频输出这些底层控制权。Qt只负责画窗口、接鼠标键盘事件、管理应用生命周期两边各管各的强项。选型依据可以归纳成下面这张表维度CEFQt WebEngine自研内核内核版本Chromium当前版本Chromium旧版本自己写定制能力强可拦截/注入/协议扩展一般接口封闭极强但成本爆炸音视频支持完整Chromium解码能力依赖版本偶有问题几乎不可能开发周期2~4周全功能1周出Demo按年算适合场景毕设/课设/商业项目快速集成轻量Web内容学术研究型课题用一句话概括CEF是“你能用多少它就有多少”这是它成为这个项目核心引擎的根本原因。1.2 浏览器功能范围和交付形态在写代码之前必须先圈定功能范围否则很容易被没尽头的“浏览器功能”拖死。我这个项目的功能清单是这样的多标签页管理支持新建、关闭、切换标签页标题自动同步网页标题地址栏和前进后退刷新按钮地址栏支持URL联想基于历史记录加载状态显示页面加载时地址栏右侧显示进度指示支持音视频在线播放包括流媒体、HTML5播放器和常见视频网站自定义右键菜单提供“刷新”“复制链接”“在新标签页打开”等常用操作下载管理内置下载事件回调弹窗提示保存位置本地网页文件和远端URL统一加载方便演示这个范围对毕设来说已经足够对课设来说属于“超额完成”对商业项目来说算一个可用的MVP。1.3 为什么用Qt做外壳CEF自带窗口创建能力理论上你可以不用Qt直接用纯Win32窗口跑起来。但那样做的后果是界面开发效率极低按钮、地址栏、标签栏全部手绘事件循环要自己调高DPI适配要自己算。这些恰恰是Qt的强项。Qt能给出漂亮的界面布局、信号槽机制天然适合处理浏览器各种异步事件、QSS可以做皮肤样式再加上qmake/CMake构建系统成熟和CEF的生命周期管理可以做到互不干扰。我实际测试下来Qt的UI线程和CEF的消息循环可以高效共存这在后面的章节会细讲。所以这个项目的架构就是UI层全部用Qt Widgets构建业务回调里调CEF接口底层渲染和资源加载全部交给Chromium。分工明确各司其职。2. 核心细节解析CEF和Qt的集成原理这是整个项目最技术性的部分也是网上资料最碎的部分。很多人照着demo抄能跑起来但一旦报错就不知道从哪下手。我先把CEF生命周期和Qt事件循环的关系讲清楚再给出完整的封装方案。2.1 CEF的进程模型和多线程架构CEF是典型的多进程架构主程序启动后会分裂出多个进程Browser进程负责窗口管理、UI交互、加载控制和你的Qt主程序在同一个进程内Renderer进程负责页面渲染、JavaScript执行、DOM操作GPU进程负责合成和硬件加速Utility/Network进程负责网络请求和资源加载这意味着你的程序启动后任务管理器里会看到好几个同名进程这是正常现象不是内存泄漏。Qt主程序运行在Browser进程的UI线程中。CEF要求所有的接口调用必须在特定线程完成尤其是CefBrowser相关的操作必须在UI线程TID_UI执行CefRequestContext的请求调用要在IO线程。这个线程模型稍不注意就会踩坑比如在Renderer进程里尝试更新Qt窗口程序直接闪退。C侧建议在CefApp的OnScheduleMessagePumpWork回调里用Qt的QTimer或QMetaObject::invokeMethod把CEF的任务桥接到Qt事件循环中执行。这样相当于把CEF的工作线程消息泵接到了Qt事件循环上两者不会打架。2.2 事件循环冲突和融合方案CEF默认有自己的消息循环如果你直接在一个线程里同时跑Qt的app.exec()和CEF的CefRunMessageLoop()必然卡死。业界成熟的做法是用CEF的CefSettings.multi_threaded_message_loop参数让CEF跑在自己的消息循环线程Qt跑在主线程两边通过信号槽通信。更稳的方案是用Qt自带的QTimer驱动CEF的CefDoMessageLoopWork()。我在项目里用的是这个思路void BrowserApp::initCef(int argc, char* argv[], const CefSettings settings) { CefMainArgs main_args(argc, argv); void* sandbox_info nullptr; CefExecuteProcess(main_args, this, sandbox_info); CefInitialize(main_args, settings, this, sandbox_info); // 用QTimer驱动CEF消息泵 m_cefTimer new QTimer(this); connect(m_cefTimer, QTimer::timeout, this, [this]() { CefDoMessageLoopWork(); }); m_cefTimer-start(10); }这个方案的核心思想是CEF不需要运行自己的死循环而是每隔10毫秒被Qt事件循环“喂”一次让它处理内部的IPC和渲染任务。实测浏览器页面滚动、JS定时器、XHR请求都会正常工作而Qt的UI线程又不会阻塞这是目前CEFQt最容易维护的一种集成方式。2.3 Chromium沙箱和Qt的兼容性处理Chromium默认启用沙箱机制在Windows上会以受限token运行子进程。但这个沙箱在你用Qt集成时经常出问题尤其是调试模式下子进程无法访问调试器导致启动失败。建议在开发阶段直接关闭沙箱CefSettings settings; settings.no_sandbox true; // 关闭沙箱生产环境下如果对安全性有硬性要求可以保留沙箱但需要额外处理Qt的插件目录、资源文件访问权限等问题。对毕设和课设来说no_sandbox是让你顺利跑通的第一步。2.4 自定义方案封装核心C类整个项目里CEF相关的类我封装成了三个核心C类BrowserApp继承CefApp、BrowserClient继承CefClient、BrowserView继承Qt的QWidget。三层各司其职BrowserApp负责CEF进程创建、命令行参数处理和进程间通信初始化。BrowserClient负责回调事件包括生命周期、加载状态、下载事件、右键菜单请求。BrowserView是C/Qt接口和CEF打交道的关键桥梁它内部持有CefRefPtrCefBrowser对外暴露Qt风格的方法比如loadUrl(QString)、goBack()、goForward()。这样一来Qt层永远不用直接跟CefRefPtr打交道所有CEF的调用被C类封装成了语义清晰的接口哪怕队友不熟悉CEF也能快速上手。3. 实操过程与核心功能实现这一节把我搭建整个项目的实操过程完整列出来每一步都给代码和解释。环境不同可能有差异但思路是通用的。3.1 环境准备和工具链选型我使用的版本组合如下这个组合不是我随手拍的而是反复测试下来对QtCEF兼容性最好的搭配Windows 10/11 64位Visual Studio 2019 或 2022C桌面开发组件Qt 5.15.2 MSVC2019 64位Qt 6.x也可以用但注意CEF对Qt版本无依赖CEF版本109.1.1364位分发版含源码CMake 3.20这里有个关键点CEF官方编译产物区分“开箱即用”和“源码包”。我们要用的是已经预编译好的二进制发行包去官方发布页找到cef_binary_109.1.13ged8d6f4chromium-109.0.5414.120_windows64.tar.bz2这个文件下载后解压目录结构里包含include、libcef_dll、Release、Resources等核心目录。不要一开始就尝试从Chromium源码编译CEF那个过程需要几十GB磁盘空间、代理配置和漫长的编译时间对做自己项目的同学来说得不偿失。用官方预编译包完全够用。3.2 CMake工程配置和CEF依赖链接在CMakeLists.txt中主要需要设置头文件路径、库文件路径、拷贝资源目录。下面是核心配置片断cmake_minimum_required(VERSION 3.20) project(CefQtBrowser) set(CMAKE_CXX_STANDARD 17) set(CEF_ROOT ${CMAKE_SOURCE_DIR}/third_party/cef) set(CEF_LIB ${CEF_ROOT}/Release/libcef.lib) include_directories(${CEF_ROOT}) include_directories(${CEF_ROOT}/include) include_directories(${CEF_ROOT}/include/internal) link_directories(${CEF_ROOT}/Release) add_executable(${PROJECT_NAME} WIN32 src/main.cpp src/BrowserApp.cpp src/BrowserClient.cpp src/BrowserView.cpp src/MainWindow.cpp ) target_link_libraries(${PROJECT_NAME} ${CEF_LIB} libcef_dll_wrapper Qt5::Widgets Qt5::Core Qt5::Gui Qt5::Network ) add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND ${CMAKE_COMMAND} -E copy_directory ${CEF_ROOT}/Resources/icudtl.dat $TARGET_FILE_DIR:${PROJECT_NAME} COMMAND ${CMAKE_COMMAND} -E copy_directory ${CEF_ROOT}/Resources/v8_context_snapshot.bin $TARGET_FILE_DIR:${PROJECT_NAME} )实际编译时还要拷贝libcef.dll、chrome_elf.dll、resources.pak、locales文件夹等数十个文件。偷懒的做法是把CEF的Release和Resources目录直接复制到你的exe输出目录简单又不容易错。有个细节提醒一下CEF的dll和exe必须在同一个目录绝对不能放到子目录后用SetDllDirectory加载否则Chromium子进程会把路径搞乱导致各种奇奇怪怪的崩溃。3.3 主程序入口和CEF初始化main.cpp是整个项目的入口它的难点在于要同时初始化CEF和Qt。顺序不能反必须先初始化CEF再启动Qt应用否则CEF会找不到主窗口句柄。#include QApplication #include BrowserApp.h #include MainWindow.h int main(int argc, char* argv[]) { CefMainArgs main_args(argc, argv); CefSettings settings; settings.multi_threaded_message_loop true; settings.no_sandbox true; settings.log_severity LOGSEVERITY_DISABLE; // CEF子进程Renderer/GPU等直接走这条路径 CefRefPtrBrowserApp app(new BrowserApp); int exit_code CefExecuteProcess(main_args, app.get(), nullptr); if (exit_code 0) { return exit_code; } // 只初始化一遍CEF if (!CefInitialize(main_args, settings, app.get(), nullptr)) { return -1; } QApplication qtApp(argc, argv); qtApp.setApplicationName(CefQtBrowser); qtApp.setOrganizationName(DevStudio); MainWindow window; window.show(); int result qtApp.exec(); CefShutdown(); return result; }这里注意区分两个分支CefExecuteProcess的返回值如果是非负说明当前进程是CEF的子进程不创建Qt窗口直接返回让Chromium自己去干活。主进程才会继续走下去。3.4 BrowserClient回调实现C侧事件处理BrowserClient是CEF和C层之间的桥梁。它的回调方法会被各种线程触发所以在回调里要格外注意线程切换。比如页面标题变化这个事件我们把它从CEF的UI线程切换到Qt的消息循环再绑定到UIclass BrowserClient : public CefClient, public CefLoadHandler, public CefDisplayHandler, public CefLifeSpanHandler { public: void OnTitleChange(CefRefPtrCefBrowser browser, const CefString title) override { QMetaObject::invokeMethod(this, []() { emit titleChanged(QString::fromStdWString(title)); }, Qt::QueuedConnection); } bool OnBeforePopup(CefRefPtrCefBrowser browser, CefRefPtrCefFrame frame, const CefString target_url, const CefString target_frame_name, WindowOpenDisposition target_disposition, bool user_gesture, const CefPopupFeatures popup_features, CefWindowInfo window_info, CefRefPtrCefClient client, CefBrowserSettings settings, CefRefPtrCefDictionaryValue extra_info, bool* no_javascript_access) override { // 屏蔽弹窗改为当前页面导航 browser-GetMainFrame()-LoadURL(target_url); return true; } private: IMPLEMENT_REFCOUNTING(BrowserClient); };在OnBeforePopup里拦截新窗口请求可以让所有target_blank的链接在当前标签页打开这个设计对浏览器体验很重要不然一点外链系统就弹一个空白窗口。3.5 BrowserView的QWidget封装和窗口融合BrowserView是核心中的核心。CEF需要一个句柄来当成浏览器区域在Windows上就是一个HWND。我们把Qt的QWidget的winId获取出来传给CefWindowInfo的SetAsChild方法CEF创建的浏览器窗口就被嵌进Qt窗口内部了。class BrowserView : public QWidget { public: BrowserView(QString url, QWidget* parent nullptr) : QWidget(parent) { setAttribute(Qt::WA_NativeWindow); setFocusPolicy(Qt::StrongFocus); createCefBrowser(url); } private: void createCefBrowser(const QString url) { CefWindowInfo window_info; // 把本Qt窗口的HWND作为CEF窗口的宿主 HWND hostHwnd reinterpret_castHWND(winId()); RECT rect { 0, 0, width(), height() }; window_info.SetAsChild(hostHwnd, rect); CefBrowserSettings browser_settings; browser_settings.plugins STATE_ENABLE; CefRefPtrBrowserClient client(new BrowserClient()); std::string urlStr url.toStdString(); CefRefPtrCefBrowser browser CefBrowserHost::CreateBrowserSync( window_info, client, urlStr, browser_settings, nullptr, nullptr); m_browser browser; } };CreateBrowserSync是同步创建调用完就直接返回可用的CefBrowser对象。如果不想阻塞UI可以用CefBrowserHost::CreateBrowser异步版本但同步版本写起来更直观。有个重要的窗口调整问题当QWidget尺寸变化时必须同步调整CEF窗口大小。在Qt里重写resizeEventvoid BrowserView::resizeEvent(QResizeEvent* event) { QWidget::resizeEvent(event); if (m_browser) { HWND hostHwnd reinterpret_castHWND(winId()); RECT rect { 0, 0, width(), height() }; ::SetWindowPos(hostHwnd, nullptr, rect.left, rect.top, rect.right - rect.left, rect.bottom - rect.top, SWP_NOZORDER); } }不写这个拖动窗口后网页会被拉伸变形或者边缘出现白边。这个坑我一开始没注意调试了很久才发现。3.6 多标签页的实现思路多标签页我用QTabWidget承载每个tab是一个BrowserView实例。切换tab时CEF窗口会闪烁或者切不过来解决办法是切换后调用BrowserView的setFocus()外加CefBrowserHost::SetFocus(true)。创建新标签页的流程通过BrowserClient::OnBeforePopup拦截target_blank在主窗口的槽函数里新建一个BrowserView加载对应URL调用QTabWidget::addTab插入新tab调用setCurrentIndex切换到新tab关闭标签页时要先销毁CEF浏览器再移除tab否则CEF进程会残留在后台。3.7 音视频播放的实现细节音视频播放是浏览器最基本也最复杂的能力。好消息是CEF基于ChromiumH264、H265、AAC、MP3、WebM等格式都原生支持你不用自己写解码器。但有几个关键点必须配置好第一CEF初始化时的CefSettings里要开启enable_media_stream并确保CefBrowserSettings的background_color不干扰视频层。第二Chromium硬解需要GPU进程。如果电脑没有硬件加速或者虚拟机上跑需要把CefSettings里的disable_gpu设为false。默认是false但有些环境实测会闪退这时候要么升级显卡驱动要么在CEF命令行参数里加ignore-gpu-blocklist。第三自动播放策略。新版Chromium默认禁止带声音的视频自动播放如果你要打开页面就自动播放需要在CEF命令行加settings.command_line_args_disabled false; CefString(settings.extra_main_process_args).clear(); // 在初始化前追加参数 CefString(settings.extra_main_process_args) --autoplay-policyno-user-gesture-required;实测加了这个参数后很多视频平台的自动播放模式就可以正常出声了。第四视频全屏。网页里的全屏按钮需要CEF请求进入全屏模式在BrowserClient里实现CefDisplayHandler::OnFullscreenModeChange切换QWidget窗口的全屏状态。还有一个常见问题是音频输出延迟这个多半是驱动问题和CEF本身关系不大。在Qt主窗体内嵌入CEF窗口后音频默认走Windows默认播放设备留意一下系统音量合成器里有没有出现Chromium的进程条目有的话说明音频通道正常工作。4. 常见问题与排查技巧实录这部分挑几个我实际开发中遇到的高频问题每一个都是耗费过时间排查才解决的。列成速查表后续做类似项目的同学可以直接对照。问题现象根本原因解决方案启动白屏/崩溃DLL未复制到exe同目录检查libcef.dll、chrome_elf.dll是否在exe目录打开页面无反应CEF消息循环卡死确认没同时跑CefRunMessageLoop和Qt事件循环右键菜单是英文未加载翻译包复制Resources/locales目录到exe目录视频黑屏有声音GPU进程崩溃命令行加ignore-gpu-blocklist或关GPU加速无法输入中文IME消息未转发CEF在QWidget的keyPressEvent和inputMethodEvent里转发给CEF弹出新窗口拦截失败OnBeforePopup返回值错误确认返回true并且没有调CreateBrowser编译报link错误libcef_dll_wrapper未编译把libcef_dll目录下的源码加入工程编译网页字体模糊DPI感知未设置调用Qt的高DPI属性并设置CEF的device_scale_factor4.1 白屏但进程还在怎么排查白屏是CEF新手最常见的现象。进程在页面也有但就是渲染不出来。第一步打开CEF的日志settings.log_severity LOGSEVERITY_INFO; settings.log_file cef.log;然后跑一遍程序看cef.log里有没有GL初始化失败、gpu_process_launch失败之类的关键日志。80%的情况是GPU沙箱导致的把no_sandbox和disable-gpu都打开问题基本就消失。还有可能是渲染进程崩了在BrowserClient里实现OnRenderProcessTerminated把终止原因打出来void OnRenderProcessTerminated(CefRefPtrCefBrowser browser, TerminationStatus status, int error_code) { qDebug() render process terminated, status status error error_code; }有了日志再去看Render进度的崩溃位置基本不用瞎猜。4.2 尺寸不对网页显示不全这个是CEFQWidget常见的窗口尺寸同步问题。CEF是把自己创建的窗口放在你给的HWND里面你HWND的大小变了CEF不会自动感知必须主动调用NotifyMoveOrResizeStarted()。void BrowserView::resizeEvent(QResizeEvent* e) { QWidget::resizeEvent(e); if (m_browser) { CefRefPtrCefBrowserHost host m_browser-GetHost(); host-NotifyMoveOrResizeStarted(); } }注意在切换标签页的时候也要触发一次因为tab页在不可见状态下大小可能是0切回来之后尺寸可能还是旧的。4.3 中文输入法失效这个问题很微妙。Qt的QWidget在获得焦点后会处理Windows的IME消息而CEF需要直接接收IME消息才能在自己的输入框里正常显示中文候选词。我实测有效的方案是在BrowserView里重写inputMethodEvent和keyPressEvent如果是中文输入的状态直接把未提交的拼音字符串传给CEF的ImeSetComposition接口。但这个代码量不小如果毕设只是演示性质有一个取巧的办法在弹出的输入框里先把中文输入到Qt侧的一个透明输入控件然后通过JS注入把值设置进网页。这个解法不够优雅但应付演示完全够用。如果要求不高也可以试试在winEventFilter里拦截WM_IME_*消息并手动转发实际测试在某些系统上也能生效。4.4 如何正确关闭和清理CEF不要直接调用CefShutdown或者exit更不要在窗口关闭时直接return 0这样CEF的子进程会变成孤儿进程留在系统里。正确的关闭流程是关闭所有标签页调用CefBrowserHost::CloseBrowser(true)强制关闭等待所有浏览器窗口关闭完成再调用CefQuitMessageLoop()最后在主函数末尾调用CefShutdown()我在MainWindow里用一个QAtomicInt计数器判断当前活动的标签页数量当计数器到0时触发退出void BrowserView::closeBrowser() { if (m_browser) { CefRefPtrCefBrowserHost host m_browser-GetHost(); host-CloseBrowser(true); } }确保关干净再退出任务管理器里就不会残留Chromium进程。5. 项目源码结构与二次开发建议最后把项目交付时的目录结构列出来并且给几条二次开发的方向建议。毕竟这个项目不仅是毕设也是将来简历上的作品展示。5.1 最终项目目录结构CefQtBrowser/ ├── CMakeLists.txt ├── src/ │ ├── main.cpp │ ├── BrowserApp.h/.cpp # CEF应用入口和生命周期 │ ├── BrowserClient.h/.cpp # CEF各类回调处理 │ ├── BrowserView.h/.cpp # QWidget封装CEF浏览器窗口 │ ├── MainWindow.h/.cpp # 主窗口含标签栏、地址栏 │ └── DownloadManager.h/.cpp # 下载管理封装 ├── third_party/ │ └── cef/ # CEF预编译二进制 ├── resources/ │ ├── style.qss # Qt界面样式 │ └── icon/ # 应用图标 └── bin/ └── exe输出目录包含全部DLL5.2 几个值得做的扩展方向如果毕设想在功能上再增加亮点我推荐以下方向一是增加书签管理。CEF的Cookie和存储API可以配合Qt的SQLite做一个本地书签库地址栏输入时自动匹配书签和历史记录界面展示上的完成度会高很多。二是自定义协议拦截。通过CefSchemeHandlerFactory注册类似myapp://这样的自定义协议可以让浏览器加载本地加密内容这在“浏览器业务系统”的毕设方向非常加分展示时也容易讲出技术深度。三是增加JS和C的双向调用。通过CefV8Handler注册JS函数网页里可以调C导出的能力比如把网页里的一段文字直接保存到本地文件这会让评委觉得你不只是套壳而是真正理解了CEF的桥接机制。四是多开用户配置目录。用CefSettings.root_cache_path为每个tab分配独立的cache目录实现多会话隔离。这在演示“同时登录两个账号”时效果极佳。5.3 对整个项目的经验总结我做完这个项目最大的感受是CEF和Qt的集成最难的地方不在某个单独的功能点而是要让两个完全不同架构的框架在进程模型、事件循环、窗口系统、线程安全四个维度上和谐共处。一旦把这四件事想明白了后面加功能都是水到渠成。最后再分享一个调试技巧CEF的子进程日志对定位问题特别重要如果遇到诡异崩溃先把settings.log_severity LOGSEVERITY_VERBOSE打开再看cef.log最后几行问题发生在哪个进程、哪个模块里瞬间就清楚了。这个日志开关建议在做项目期间一直开着真的能少掉很多头发。本文还有配套的精品资源点击获取
返回列表