简介
在此之前,前端开发框架Chromium Embedded Framework (CEF)入门介绍了如何在Windows平台上搭建CEF的开发环境,并就官方的sample源码进行了简单的剖析,但是还不够,所以笔者继续对CEF进行了一番探索
线程模型
CEF中每个进程都运行多个线程,其中常用的线程有:
TID_UI
:UI线程是浏览器进程的主线程,如果在调用CefInitialize()
时CefSettings.multi_threaded_message_loop
设置为false,那么该线程也是主应用程序线程TID_IO
:IO线程在浏览器进程中用于处理IPC和网络消息TID_FILE_*
:这会是一系列类型的线程,在浏览器进程中用于与文件系统进行交互工作。阻塞的操作只能在此线程或者客户端创建的CefThread
线程中执行TID_RENDERER
:该线程是渲染器进程中的主线程,所有Blink
和V8
的交互都必须在该线程中进行
开发时需要验证当前线程为哪一类型可以使用CEF定义的宏:
#define CEF_REQUIRE_UI_THREAD() DCHECK(CefCurrentlyOn(TID_UI));
#define CEF_REQUIRE_IO_THREAD() DCHECK(CefCurrentlyOn(TID_IO));
#define CEF_REQUIRE_FILE_BACKGROUND_THREAD() \
DCHECK(CefCurrentlyOn(TID_FILE_BACKGROUND));
#define CEF_REQUIRE_FILE_USER_VISIBLE_THREAD() \
DCHECK(CefCurrentlyOn(TID_FILE_USER_VISIBLE));
#define CEF_REQUIRE_FILE_USER_BLOCKING_THREAD() \
DCHECK(CefCurrentlyOn(TID_FILE_USER_BLOCKING));
#define CEF_REQUIRE_RENDERER_THREAD() DCHECK(CefCurrentlyOn(TID_RENDERER));
CEF应用程序代码结构
每个CEF程序都有大致相同的结构,需要完成以下几点工作:
- 初始化CEF并运行子进程逻辑或者进行CEF消息循环
- 提供
CefAPP
子类,实现特定于每种类型的进程的回调函数 - 提供
CefClient
子类,实现处理与之绑定的浏览器实例特定的回调函数 - 通过
CefBrowserView::CreateBrowserView()
或者CefBrowserHost::CreateBrowser()
创建浏览器实例,并使用CefLifeSpanHandler
对该浏览器实例的生命周期进行管理。CEF框架通过你提供并重写CefClient
的GetLifeSpanHandler()
方法获取CefLifeSpanHandler
初始化时命令行配置信息
初始化首先需要指定命令行的配置信息,CEF在启动子进程时,这些配置信息将会作用于子进程。这些信息必须通过CefMainArgs
结构传递给CefExecuteProcess()
。CefMainArgs
的获取是特定于平台的
在Linux和MacOS上,通过入口函数的argc
和argv
值获取
CefMainArgs main_args(argc, argv);
在Windows上,通过入口函数的hInstance
获取
CefMainArgs main_args(hInstance);
在 前端开发框架Chromium Embedded Framework (CEF)入门文章中有介绍Windows上CEF程序是以单可执行文件运行的,即子进程和主进程都运行同一个可执行程序,在入口函数处区分不同的进程类型,执行不同的逻辑,这是Windows和Linux所支持的,但是这在MacOS上不好使
这是因为在MacOS平台上一个APP bundle会涉及一些安全问题,如果采用和MacOS和Linux的程序架构,会导致主进程和子进程拥有相同的安全限制,但是像渲染进程这样的子进程往往会运行js这样的代码,容易受到攻击,让一个容易受到攻击的进程拥有和主进程相同的安全限制不是一个很好的主意
所以在MacOS上,主进程和子进程不共用一个可执行程序,主进程拥有较高的权限,子进程则只有满足其正常运行的最低权限
CefAPP和CefClient
CefAPP和CefClient在 前端开发框架Chromium Embedded Framework (CEF)入门中已经有过介绍
CefAPP提供对某个进程特定回调的访问,你需要实现CefAPP的子类,主进程可以通过CefInitialize()
绑定一个CefApp,子进程则可以通过CefExecuteProcess()
绑定一个CefApp,其重要回调包括:
OnBeforeCommandLineProcessing()
:CEF框架在启动子进程前,通过这个回调提供修改子进程命令行参数的机会。这里需要留意的是不要去引用该函数的入参GetBrowserProcessHandler()
:CEF用于获取特定于浏览器进程的处理程序——CefBrowserProcessHandler
GetRenderProcessHandler()
:CEF用于获取特定于渲染进程的处理程序——GetRenderProcessHandler
。JavaScript相关的回调和进程消息都和该处理程序相关
CefClient提供对浏览器实例特定回调的访问,你需要实现CefClient
的子类,并在创建浏览器实例时,将你实现的子类与创建的浏览器实例进行绑定。CefBrowserView::CreateBrowserView()
或CefBrowserHost::CreateBrowser()
返回的对象就是一个浏览器实例,在调用这两个API时就需要传入实现的CefClient
子类与之绑定
CefClient重要的回调包括:
Get*Handler()
:众多返回Handler的方法,返回的每个Handler用于对与之绑定的浏览器实例的控制。例如GetDisplayHandler()
返回的CefDisplayHandler
则可以控制改变浏览器实例的titleOnProcessMessageReceived()
:当收到渲染进程的IPC消息时,回调该函数
浏览器的生命周期
一个浏览器实例的生命周期从调用CefBrowserHost::CreateBrowser()
或CefBrowserHost::CreateBrowserSync()
开始,该逻辑一般在回调函数CefBrowserProcessHandler::OnContextInitialized()
中进行。在Windows平台,也有可能在WM_CREATE
中进行
浏览器实例的生命周期通过CefLifeSpanHandler
进行管理,CEF框架如何获取CefLifeSpanHandler呢?正如我们前面所说,通过和该浏览器实例绑定的CefClient获取。CefClient提供GetLifeSpanHandler()
返回一个CefLifeSpanHandler。当然,返回的CefLifeSpanHandler是你实现的CefLifeSpanHandler子类的实例
CefLifeSpanHandler提供的重要回调包括:
OnAfterCreated()
:CEF框架将在创建浏览器后回调。在这里,你可以保存对该浏览器对象CefBrowser
的引用OnBeforeClose()
:CEF将在浏览器销毁之前回调。在此回调之后,你不能尝试在该浏览器对象CefBrowser
上调用任何方法(IsValid
、GetIdentifier
和IsSame
除外,这些方法还可以调用)
公众号名称:zl.rs