本篇簡介
上一節(jié)中我們講解了基于CEF瀏覽器開發(fā)的基本方法,并實現(xiàn)了QCefView控件和其核心組件QCefClient。>>點這里回顧上節(jié)內容
先來回顧一下上一節(jié)中提到的CEF3應用整體結構:
- 提供一個入口方法初始化CEF弓候,并執(zhí)行CEF的消息循環(huán);
- 提供CefApp的實現(xiàn),以處理進程相關(process-specific)的回調瑟捣;
- 提供CefClient的實現(xiàn),以處理瀏覽器實例相關(browser-instance-specific)的回調栅干。
其中第三條瀏覽器實例相關的實現(xiàn)在上一節(jié)中已經(jīng)完成了蝶柿,本篇我們將繼續(xù)完成另一個核心組件QCefApp的開發(fā),并通過實際使用QCefView非驮,展示如何提供CEF初始化入口交汤,最終完成瀏覽器核心功能和基本UI的開發(fā)。
本篇的小目標:
- 實現(xiàn)QCefApp組件
- 實現(xiàn)CEF程序入口
- 使用封裝好的QCefView,完成瀏覽器開發(fā)
實現(xiàn)QCefApp組件
和CefClient類似芙扎,我們的應用程序需要提供一個CefApp的封裝星岗,來處理進程相關的回調——這里進程相關的回調對于我們要實現(xiàn)的簡單瀏覽器而言,就是對瀏覽器進程本身的管理戒洼。因此俏橘,我們的QCefApp組件頭文件聲明如下:
class QCefApp: public CefApp,
public CefBrowserProcessHandler
{
public:
QCefApp();
virtual ~QCefApp();
// CefApp接口
virtual CefRefPtr<CefBrowserProcessHandler> GetBrowserProcessHandler()
OVERRIDE { return this; }
// CefBrowserProcessHandler接口:
virtual void OnContextInitialized() OVERRIDE;
// 創(chuàng)建瀏覽器進程的工廠方法
CefRefPtr<QCefClient> addBrowser(QList<QSslCertificate> caCerts = QList<QSslCertificate>());
// 關閉所有瀏覽器進程
void closeAllBrowser();
private:
bool m_contextReady;
QQueue<CefRefPtr<QCefClient> > m_clients;
// Include the default reference counting implementation.
IMPLEMENT_REFCOUNTING(QCefApp)
};
和CefClient類似,CefApp也可以通過繼承多個接口的方式實現(xiàn)進程級的各類管理圈浇。因為我們要實現(xiàn)的簡單瀏覽器暫時不涉及太多復雜的管理寥掐,所以這里只簡單實現(xiàn)了瀏覽器進程處理和上下文初始化的接口。同樣和CefClient類似磷蜀,對于CefXXXHandler接口召耘,只需要將引用設為本實例,即可重載對應接口所提供的方法了褐隆。
額外說明一點:這里的創(chuàng)建瀏覽器進程方法里有一個添加ca證書的方法污它,目前先作為預留,有關ca證書和https的話題在之后的小節(jié)中會有專門的講解庶弃。
瀏覽器上下文初始化衫贬、添加和關閉瀏覽器接口的具體實現(xiàn)如下:
void QCefApp::OnContextInitialized()
{
CEF_REQUIRE_UI_THREAD();
m_contextReady = true;
}
CefRefPtr<QCefClient> QCefApp::addBrowser(QList<QSslCertificate> caCerts)
{
if (m_contextReady)
{
// 創(chuàng)建本地窗口所需的信息
CefWindowInfo windowInfo;
#if defined(OS_WIN)
// 針對Windows系統(tǒng),需要指定特殊的標識歇攻,
// 這個標識會被傳遞給CreateWindowEx()方法
windowInfo.SetAsPopup(NULL, "QCefView");
#endif
// 初始化cef client方法
CefRefPtr<QCefClient> client(new QCefClient());
client->setCaCerts(caCerts);
// 指定CEF瀏覽器設置
CefBrowserSettings browserSettings;
std::string url = "data:text/html,chromewebdata";
// 創(chuàng)建瀏覽器窗口
CefBrowserHost::CreateBrowser(windowInfo, client.get(), url, browserSettings, NULL);
// 將瀏覽器引用添加到瀏覽器隊列
m_clients.enqueue(client);
return client;
}
return NULL;
}
void QCefApp::closeAllBrowser()
{
while (!m_clients.empty())
{
m_clients.dequeue()->browser()->GetHost()->CloseBrowser(true);
}
}
通過上面的實現(xiàn)可以看出固惯,添加瀏覽器實例進程實際上就是創(chuàng)建了一個QCefClient的引用,并將這個引用和瀏覽器相關的一些設置傳入到靜態(tài)方法CefBrowserHost::CreateBrowser中缴守。而OnContextInitialized方法通過設置m_contextReady標志確保在創(chuàng)建瀏覽器實例時CEF上下文已初始化完成葬毫。
CEF程序入口
在完成CefApp組件的實現(xiàn)后,我們已經(jīng)基本湊齊了啟動CEF所需的零件斧散。最后讓我們來看看如何把這些零件借助CEF程序入口組裝起來供常。
首先,聲明一個QCefContext類鸡捐,來封裝CEF程序入口所需的基本設置和初始化方法:
class QCefContext
{
public:
QCefContext(CefSettings* settings);
~QCefContext();
//初始化 Cef
int initCef(int argc, char *argv[]);
public:
CefRefPtr<QCefApp> cefApp() const;
private:
int initCef(CefMainArgs& mainArgs);
private:
CefSettings* m_settings;
CefRefPtr<QCefApp> m_cefApp;
CefRefPtr<CefCommandLine> m_cmdLine;
};
其中栈暇,負責初始化CEF的initCef方法實現(xiàn)如下:
int QCefContext::initCef(int argc, char *argv[])
{
// 創(chuàng)建CEF默認命令行參數(shù).
m_cmdLine = CefCommandLine::CreateCommandLine();
#ifdef CEF_LINUX
CefMainArgs mainArgs(argc, argv);
m_cmdLine->InitFromArgv(argc, argv);
#else
// 兼容WINDOWS系統(tǒng)
HINSTANCE hInstance = (HINSTANCE)GetModuleHandle(NULL);
CefMainArgs mainArgs(hInstance);
m_cmdLine->InitFromString(::GetCommandLineW());
#endif
return initCef(mainArgs);
}
int QCefContext::initCef(CefMainArgs& mainArgs)
{
CefRefPtr<CefApp> app;
// 創(chuàng)建一個正確類型的App Client
if (!m_cmdLine->HasSwitch("type"))
{
app = new QCefApp();
m_cefApp = CefRefPtr<QCefApp>((QCefApp*)app.get());
}
int result = CefExecuteProcess(mainArgs, app, NULL);
if (result >= 0)
{
return result;
}
// 初始化CEF
CefInitialize(mainArgs, *m_settings, app.get(), NULL);
return -1;
}
這個初始化方法包含了下面流程:
- 創(chuàng)建CEF默認命令行參數(shù),并將應用程序本身的參數(shù)也提供給CEF上下文
- 根據(jù)命令行參數(shù)類型箍镜,實例化對應類型的CefApp
這里需要特別說明的是源祈,CEF應用在默認情況下包含很多子進程(渲染進程、插件色迂、GPU進程等等)香缺,這些進程會共享同一個執(zhí)行入口。這里我們簡單起見歇僧,僅就主進程進行處理——從上面的實現(xiàn)可以看到图张,當檢測到當前進程為主進程時锋拖,創(chuàng)建一個CefApp的實例即可。這個實例的引用會通過cefApp()方法提供給需要獲取CefApp的其他組件使用祸轮。
QCefView控件的使用
接下來我們來看看如何實際使用上面封裝好的程序入口兽埃。
首先聲明一個繼承了QDialog的主窗口MainDlg:
namespace Ui {
class MainDlg;
}
class MainDlg : public QDialog
{
Q_OBJECT
public:
explicit MainDlg(CefRefPtr<QCefApp> cefApp, QWidget *parent = 0);
~MainDlg();
private:
void initWebview(CefRefPtr<QCefApp> cefApp);
private:
Ui::MainDlg *ui;
QCefView* m_webview;
};
在這個主窗口的構造方法中,會調用初始化QCefView的方法initWebview:
MainDlg::MainDlg(CefRefPtr<QCefApp> cefApp, QWidget* parent) : QDialog(parent), ui(new Ui::MainDlg)
{
ui->setupUi(this);
initWebview(cefApp);
}
initWebview方法包含了QCefView界面布局相關的一些設置适袜,這里我們略過這些實現(xiàn)柄错,只專注于QCefView本身初始化的流程:
void MainDlg::initWebview(CefRefPtr<QCefApp> cefApp)
{
// 省略界面布局的設置
...
// 初始化QCefView
m_webview = new QCefView(cefApp->addBrowser(), upperFrame);
connect(m_webview, SIGNAL(cefEmbedded()), this, [this]() {
show();
});
connect(ui->btnGo, &QPushButton::clicked, this, [this]() {
m_webview->load(QUrl(ui->editAddress->text()));
});
// 省略界面布局的設置
...
}
從上面的實現(xiàn)可以看出,這里我們只需要通過CefApp的添加瀏覽器方法獲取QCefClient的引用苦酱,并將其提供給QCefView售貌,就能簡單完成QCefView控件的創(chuàng)建。
回到整個應用程序的入口疫萤,也就是main函數(shù)颂跨,除了傳統(tǒng)Qt應用的實現(xiàn)之外,還需要添加一下CEF入口相關(也就是我們上一小節(jié)封裝好的QCefContext)的實現(xiàn):
int main(int argc, char *argv[])
{
int result = 0;
CefSettings settings;
// 禁用日志
settings.log_severity = LOGSEVERITY_DISABLE;
// 設置CEF資源路徑(cef.pak和/或devtools_resources.pak)
CefString(&settings.resources_dir_path) = CefString();
// 本地化資源路徑
CefString(&settings.locales_dir_path) = CefString();
QCefContext* cef = new QCefContext(&settings);
result = cef->initCef(argc, argv);
if (result >= 0)
return result;
QApplication a(argc, argv);
QApplication::addLibraryPath(".");
MainDlg* browser = new MainDlg(cef->cefApp());
result = a.exec();
delete browser;
delete cef;
CefShutdown();
return result;
}
至此给僵,我們的瀏覽器應用初版終于完成了毫捣。運行一下看看效果:
流程總結
本節(jié)所涉及到的組件及其流程可以總結為下面的時序圖:
有關基于CEF的瀏覽器基本功能的實現(xiàn)详拙,就講解到這里了帝际。下一節(jié)我們將介紹如何基于CEF實現(xiàn)瀏覽器與頁面的互相通信。
>>返回系列索引
參考鏈接
[1] Chromium Embedded Framework官網(wǎng)
[2] Chromium Embedded Framework官方教程