在上文《使用CEF(2)— 基于VS2019编写一个简单CEF样例》中,我们介绍了如何编写一个CEF的样例,在文章中提供了一些代码清单,在这些代码清单中提到了一些CEF的定义的类,例如CefApp
、CefClient
等等。它们具体有什么作用,和CEF的进程架构有什么关系呢?本文将逐一进行介绍。
CEF的进程架构
CEF3使用多个进程运行。处理窗口创建、绘制和网络访问的主要进程称为浏览器进程。这通常与宿主应用程序的进程相同,大多数应用程序的逻辑将在浏览器进程中运行。使用Blink引擎渲染HTML和JavaScript执行在单独的渲染进程中发生。一些应用程序逻辑(如JavaScript绑定和DOM访问)也将在渲染进程中运行。默认进程模型将为每个唯一源地址(scheme+domain)运行一个新的渲染进程。其他进程将根据需要生成,例如处理Flash等插件的插件进程和处理加速合成的GPU进程。综合上述文档,我们整理一下:
浏览器进程(Browser Process):
- 窗口创建、绘制
- 网络访问
- ......
渲染进程(Renderer Process):
-
通过Blink引擎渲染HTML
-
JavaScript执行(V8引擎)
-
......
需要注意的是,浏览器进程中会进行窗口绘制,并不是指绘制HTML内容,而是承载网页内容的那个窗体壳,同样渲染进程也不是用来创建窗体的进程。接下来,本人将以官方CefSimple Demo源码入手,逐步介绍Cef的概念。
本来本人想要使用上一文中的编写的simple-cef进行源码解析,但是为了让本文相对的独立,所以还是决定使用官方的Demo:cefsimple进行源码解析。尽管和simple-cef项目的内容差别不大。需要注意的是一下的源码在解析的时候,会进行适当的删改,读者最好对照源码进行阅读更佳。PS:源码中显示......
表明示例代码有所删除。
cefsimple_win.cc
// ......
// Entry point function for all processes.
int APIENTRY wWinMain(HINSTANCE hInstance,
HINSTANCE hPrevInstance,
LPTSTR lpCmdLine,
int nCmdShow) {
// ......
// CEF applications have multiple sub-processes (render, plugin, GPU, etc)
// that share the same executable. This function checks the command-line and,
// if this is a sub-process, executes the appropriate logic.
int exit_code = CefExecuteProcess(main_args, nullptr, sandbox_info);
if (exit_code >= 0) {
// The sub-process has completed so return here.
return exit_code;
}
// ......
// SimpleApp implements application-level callbacks for the browser process.
// It will create the first browser instance in OnContextInitialized() after
// CEF has initialized.
CefRefPtr<SimpleApp> app(new SimpleApp);
// Initialize CEF.
CefInitialize(main_args, settings, app.get(), sandbox_info);
// Run the CEF message loop. This will block until CefQuitMessageLoop() is
// called.
CefRunMessageLoop();
// Shut down CEF.
CefShutdown();
return 0;
}
首先第一个重要点是:
// CEF applications have multiple sub-processes (render, plugin, GPU, etc)
// that share the same executable. This function checks the command-line and,
// if this is a sub-process, executes the appropriate logic.
int exit_code = CefExecuteProcess(main_args, nullptr, sandbox_info);
if (exit_code >= 0) {
// The sub-process has completed so return here.
return exit_code;
}
这段代码看起来有点奇怪,对于英文的翻译如下:
然后,我们查看该函数:CefExecuteProcess
:
///
// This function should be called from the application entry point function to
// execute a secondary process. It can be used to run secondary processes from
// the browser client executable (default behavior) or from a separate
// executable specified by the CefSettings.browser_subprocess_path value. If
// called for the browser process (identified by no "type" command-line value)
// it will return immediately with a value of -1. If called for a recognized
// secondary process it will block until the process should exit and then return
// the process exit code. The |application| parameter may be empty. The
// |windows_sandbox_info| parameter is only used on Windows and may be NULL (see
// cef_sandbox_win.h for details).
///
/*--cef(api_hash_check,optional_param=application,
optional_param=windows_sandbox_info)--*/
int CefExecuteProcess(const CefMainArgs& args,
CefRefPtr<CefApp> application,
void* windows_sandbox_info);
翻译:
从这段话我们不难推断出,CEF在以多进程架构下启动的时候,会多次启动自身可执行程序。启动的时候,会通过命令行参数传入某些标识,由CefExecuteProcess
内部进行判断。如果是主进程,则该函数立刻返回-1,程序会继续执行下去,那么后续继续运行的代码全部都运行在主进程中;如果是子进程(渲染进程等),那么该函数会阻塞住,直到子进程结束后,该函数会返回一个大于等于0的值,并在main函数直接返回,进而退出。
对CefExecuteProcess分析就到这里,细节可以阅读官方文档,我们继续后续的代码分析:
// SimpleApp implements application-level callbacks for the browser process.
// It will create the first browser instance in OnContextInitialized() after
// CEF has initialized.
CefRefPtr<SimpleApp> app(new SimpleApp);
注释翻译如下
查看SimpleApp的声明,发现该类继承了CefApp:
class SimpleApp : public CefApp, public CefBrowserProcessHandler {
public:
SimpleApp();
......
}
于是,我们迎来了第一个重要的类:CefApp。
CefApp
CefApp在官方文档中,就写了一句话介绍:
本人一开始看到CefApp时,想到上面提到的CEF的多进程架构,结合后文还会提到的CefClient,以为所谓CefApp就是指浏览器进程,CefClient就对应其他的进程(一个App对应多个Client,多么的自然的理解),然而这样错误的理解,让本人在阅读代码的时候走了很大的弯路。
首先,我们看一下CefApp的头文件声明:
class CefApp : public virtual CefBaseRefCounted {
public:
virtual void OnBeforeCommandLineProcessing(
const CefString& process_type,
CefRefPtr<CefCommandLine> command_line) {}
virtual void OnRegisterCustomSchemes(
CefRawPtr<CefSchemeRegistrar> registrar) {}
virtual CefRefPtr<CefResourceBundleHandler> GetResourceBundleHandler() {
return nullptr;
}
virtual CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler() {
return nullptr;
}
virtual CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler() {
return nullptr;
}
};
先看其中有两个本文讨论的重点方法:GetBrowserProcessHandler
、GetRenderProcessHandler
。它们的文档注释如下:
///
// Return the handler for functionality specific to the browser process. This
// method is called on multiple threads in the browser process.
// 返回浏览器进程特定功能的处理程序。在浏览器进程中的多个线程上调用此方法。
///
virtual CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler()
///
// Return the handler for functionality specific to the render process. This
// method is called on the render process main thread.
// 返回渲染进程特定功能的处理程序。在渲染进程中的主线程上调用此方法。
///
virtual CefRefPtr<CefRenderProcessHandler> GetRenderProcessHandler()
读者看到这些注释可能会疑问:为什么注释中一会儿说在浏览器进程中一会儿又说在渲染进程中?难道这个类的实例还会在多个进程中使用吗?对也不对。这个类的实例确实会在浏览器进程和渲染进程中使用,但是我们又知道,两个进程之间的资源是不共享的,包括类实例,所以在浏览器进程运行的过程中,会使用到CefApp的某个实例化对象,而在渲染进程的运行过程中,又会使用到CefApp另一个实例化对象,它们都是CefApp子类的实例,但一定不是同一个实例对象。
我们可以这样理解:一个CefApp对应了一个进程,而一个进程可以是浏览器进程(Browser Process),可以是渲染进程(Renderer Process)。因此,CefApp提供了GetBrowserProcessHandler和GetRendererProcessHandler来分别在相关进程中获取对应的handler。
这两个方法的实现由我们来决定,即我们可以通过编程方式来返回handler,但这两个方法不会由我们客户端代码进行调用,而是CEF在运行过程中,由CEF在某个时刻来回调这两个方法。所以,这里虽然写了两个GetXXXProcessHandler,但在浏览器进程和渲染进程中只会分别调用GetBrowserProcessHandler和GetRendererProcessHandler。
按照程序运行的角度讲,当浏览器进程运行的时候,CEF框架就会在某个时候调用CefApp::GetBrowserProcessHandler获得由我们定义的BrowserProcessHandler实例,这个实例会在适当的时候调用它提供的一些方法(后文介绍有哪些方法);当渲染进程运行的时候,CEF框架就会在某个时候调用CefApp::GetRendererProcessHandler得到我们定义的RendererProcessHandler实例,然后在适当的时候调用RenererProcessHandler中的一些方法(后文介绍有哪些方法)。
在cefsimple的示例代码中只有一个SimpleApp是继承的CefApp,这个类还继承了CefBrowserHandler,表明自身是同时也是CefBrowserHandler,这样实现的GetBrowserProcessHandler
就返回自身。那么CEF是如何将我们的CefApp实例关联到CEF运行中的呢?
// SimpleApp implements application-level callbacks for the browser process.
// It will create the first browser instance in OnContextInitialized() after
// CEF has initialized.
CefRefPtr<SimpleApp> app(new SimpleApp);
// Initialize CEF.
CefInitialize(main_args, settings, app.get(), sandbox_info);
注意CefInitialize中的app.get()
参数,就是将我们的CefApp关联到CEF的运行中的。那么,有些读者会有疑问,在示例代码中,只看到我们创建的SimpleApp类继承了CefApp,并通过GetBrowserProcessHandler
返回自身来表明是一个浏览器进程的回调实例,并没有看到体现渲染进程的代码呢?确实,cefsimple作为helloworld级别的代码,没有体现这一点。在cefclient示例代码中(更高阶的CEF示例,也更复杂),你会看到:
上图是浏览器进程CefApp子类ClientAppBrowser(这里的”Client“是cefclient示例代码的“client”,请勿和下文的CefClient类混淆)。
同时你还能找到一个CefApp子类ClientAppRenderer:
你甚至还能找到一个名为ClientAppOther的CefApp子类:
那么它们在哪儿被使用到呢?
看到这里,我相信绝大多数的读者应该能够理解我所说的CefApp代表的是一个进程的抽象了。这块的大体流程是,通过一个工具函数GetProcessType
从命令行中解析--type=xxx
(浏览器进程没有这个命令参数)来判断进程的类型,然后实例化对应的CefApp子类,最后通过CefExecuteProcess
来运行进程。
在介绍了CefApp的基本概念以后,我们可以继续分析SimpleApp
。
通过上文,我们知道SimpleApp是CefApp子类,并且通过只会在浏览器进程中,会使用到该类的实例,因为实现了接口CefBrowserProcessHandler
,并且有如下代码:
// CefApp methods:
virtual CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler()
OVERRIDE {
return this;
}
那么在CEF中,作为CefBrowserProcessHandler
,有哪些回调可以供我们定制呢?下面是头文件声明,并且我也写了下概要注释:
class CefBrowserProcessHandler : public virtual CefBaseRefCounted {
public:
// Cookie处理定制化
virtual void GetCookieableSchemes(std::vector<CefString>& schemes,
bool& include_defaults) {}
// 在CEF上下文初始化后,在浏览器进程UI线程中进行调用。
virtual void OnContextInitialized() {}
// 可定制化处理子进程启动时的命令行参数
virtual void OnBeforeChildProcessLaunch(
CefRefPtr<CefCommandLine> command_line) {}
// 打印处理
virtual CefRefPtr<CefPrintHandler> GetPrintHandler() { return nullptr; }
// 自定义消息循环的时候,消息循环的频率
virtual void OnScheduleMessagePumpWork(int64 delay_ms) {}
// 获取默认的CefClient
virtual CefRefPtr<CefClient> GetDefaultClient() { return nullptr; }
};
通过阅读该Handler的头文件以及每个函数的调用说明,我们继续阅读在SimpleApp::OnContextInitialized
这个函数:
void SimpleApp::OnContextInitialized() {
CEF_REQUIRE_UI_THREAD();
CefRefPtr<CefCommandLine> command_line =
CefCommandLine::GetGlobalCommandLine();
const bool enable_chrome_runtime =
command_line->HasSwitch("enable-chrome-runtime"); // 是否启用chrome运行时
#if defined(OS_WIN) || defined(OS_LINUX)
// Create the browser using the Views framework if "--use-views" is specified
// via the command-line. Otherwise, create the browser using the native
// platform framework. The Views framework is currently only supported on
// Windows and Linux.
// 如果命令行中指定了"--use-views",那么使用CEF自己的视图框架(Views framework)
// 否则使用操作系统原生API。视图框架目前只支持Windows和Linux。
const bool use_views = command_line->HasSwitch("use-views");
#else
const bool use_views = false;
#endif
// SimpleHandler implements browser-level callbacks.
CefRefPtr<SimpleHandler> handler(new SimpleHandler(use_views));
// Specify CEF browser settings here.
CefBrowserSettings browser_settings;
std::string url;
// Check if a "--url=" value was provided via the command-line. If so, use
// that instead of the default URL.
url = command_line->GetSwitchValue("url");
if (url.empty())
url = "http://www.google.com";
if (use_views && !enable_chrome_runtime) {
// Create the BrowserView.
CefRefPtr<CefBrowserView> browser_view = CefBrowserView::CreateBrowserView(
handler, url, browser_settings, nullptr, nullptr,
new SimpleBrowserViewDelegate());
// Create the Window. It will show itself after creation.
CefWindow::CreateTopLevelWindow(new SimpleWindowDelegate(browser_view));
} else {
// Information used when creating the native window.
CefWindowInfo window_info;
#if defined(OS_WIN)
// On Windows we need to specify certain flags that will be passed to
// CreateWindowEx().
window_info.SetAsPopup(NULL, "cefsimple");
#endif
// Create the first browser window.
CefBrowserHost::CreateBrowser(window_info, handler, url, browser_settings,
nullptr, nullptr);
}
}
对于这段代码,我整理了如下流程,方便读者对照阅读:
在这个流程中,最关键的3个部分被我用红色标记出来:
- SimpleHandler(CefClient子类);
- 使用CEF的窗体视图框架创建CefBrowserView和CefWindow;
- 使用操作系统原生API构建窗体。
整个过程中会创建CefClient的子类实例,然后通过CEF提供的API来将CefClient和窗体结合在一起。
对于使用CEF自己的视图框架,有如下的步骤:
- 首先是调用CefBrowserView::CreateBrowserView得到CefBrowserView实例,这个过程会把CefClient实例和View对象通过API绑定。
- 调用CefWindow::CreateTopLevelWindow,传入CefBrowserView实例来创建窗体。
对于使用操作系统原生API创建浏览器窗体,主要是如下步骤:
- 使用CefWindowInfo设置窗体句柄
- 调用CefBrowserHost::CreateBrowser将对应窗体句柄的窗体和CefClient绑定起来
当然,上述两个窗体的创建过程涉及到CEF的窗体模块,我们不在这里细说,但是两个流程都离不开一个重要的类:CefClient,它具体是什么呢?接下来,我们将对CefClient进行介绍,并对SimpleHandler这个类(CefClient子类)进行一定的源码分析。
CefClient
在官方的文档,描述了CefClien的概念:
首先需要解释一下什么什么是特定浏览器实例,实际上,指的是以下过程产生的浏览器实例:
CefRefPtr<CefBrowserView> browser_view = CefBrowserView::CreateBrowserView(
handler, url, browser_settings, nullptr, nullptr,
new SimpleBrowserViewDelegate());
// 或
CefBrowserHost::CreateBrowser(window_info, handler, url, browser_settings,
nullptr, nullptr);
通过上述两种方式创建的浏览器实例,是一个概念上的实例,并不是指你能看得到的浏览器的窗口,窗口只是浏览器实例的宿主而已。而浏览器中发生的事件,例如:生命周期的变化,对话框等,都只会通过CefClient中返回的各种类型Handler以及这些Handler接口实例提供的方法回调。
下面时CefClient的声明:
class CefClient : public virtual CefBaseRefCounted {
public:
virtual CefRefPtr<CefAudioHandler> GetAudioHandler() { return nullptr; }
virtual CefRefPtr<CefContextMenuHandler> GetContextMenuHandler() {
return nullptr;
}
virtual CefRefPtr<CefDialogHandler> GetDialogHandler() { return nullptr; }
virtual CefRefPtr<CefDisplayHandler> GetDisplayHandler() { return nullptr; }
virtual CefRefPtr<CefDownloadHandler> GetDownloadHandler() { return nullptr; }
virtual CefRefPtr<CefDragHandler> GetDragHandler() { return nullptr; }
// ...... 还有很多的Handler
}
在这个CefClient提供了很多GetXXXHandler
方法,这些方法会在合适的时候,被CEF调用以得到对应的Handler,然后再调用返回的Handler中的方法。例如,HTML页面中的Title发生变化的时候,就会调用CefClient::CefDisplayHandler()
得到一个CefDisplayHandler实例,然后再调用其中的CefDisplayHandler::OnTitleChange
,而这些过程不是我们调用的,而是CEF框架完成的。只是具体的实现有我们客户端代码编写。
那么现在思考一下,为什么会有这个CefClient呢?在本人看来主要是如下的理由:
在CefClient中各种回调的事件,本质上发生的地方是渲染进程。因为每当一个浏览器实例(不是浏览器进程)创建的时候,会有一个对应的渲染进程创建(也可能由于配置,而共用一个,这里先认为默认多个一对一)。渲染进程中发生的各种V8事件、下载事件,显示事件等触发后,会通过进程间通讯给到浏览器进程,然后在浏览器进程中找到与之相关的CefClient,然后从CefClient中找到对应的Handler,回调Handler对应的方法。
也就是说,将在渲染进程发生的事件,用在浏览器进程中的CefClient一定的抽象映射,而不是直接在浏览器进程处理器中进行,因为一个浏览器进程可能会创建多个渲染进程,让CefClient作为中间层避免耦合。
当然,文档也为我们指出,CefClient实例与浏览器实例可以不是一一对应的,多个浏览器实例可以共享一个CefClient,如此一来我们也可以总结关于CefClient的一点:非必要情况,不要编写具有状态的CefClient。
至此,我们通过对Demo源码入手,对CefApp和CefClient已经有了一个整体的认识,读者可以阅读官方文档来更加深入的了解:官方文档。
常见问题FAQ
- 免费下载或者VIP会员专享资源能否直接商用?
- 本站所有资源版权均属于原作者所有,这里所提供资源均只能用于参考学习用,请勿直接商用。若由于商用引起版权纠纷,一切责任均由使用者承担。更多说明请参考 VIP介绍。
- 提示下载完但解压或打开不了?
- 找不到素材资源介绍文章里的示例图片?
- 模板不会安装或需要功能定制以及二次开发?
发表评论
还没有评论,快来抢沙发吧!