Qt嵌入瀏覽器(五)——CEF入口與QCefView控件的使用

本篇簡介

上一節(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;
}

至此给僵,我們的瀏覽器應用初版終于完成了毫捣。運行一下看看效果:


cef_browser.png

流程總結

本節(jié)所涉及到的組件及其流程可以總結為下面的時序圖:


QCefBrowser應用時序圖.png

有關基于CEF的瀏覽器基本功能的實現(xiàn)详拙,就講解到這里了帝际。下一節(jié)我們將介紹如何基于CEF實現(xiàn)瀏覽器與頁面的互相通信。
>>返回系列索引

參考鏈接

[1] Chromium Embedded Framework官網(wǎng)
[2] Chromium Embedded Framework官方教程

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末饶辙,一起剝皮案震驚了整個濱河市蹲诀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌弃揽,老刑警劉巖脯爪,帶你破解...
    沈念sama閱讀 216,651評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異矿微,居然都是意外死亡痕慢,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,468評論 3 392
  • 文/潘曉璐 我一進店門涌矢,熙熙樓的掌柜王于貴愁眉苦臉地迎上來掖举,“玉大人,你說我怎么就攤上這事娜庇∷危” “怎么了?”我有些...
    開封第一講書人閱讀 162,931評論 0 353
  • 文/不壞的土叔 我叫張陵名秀,是天一觀的道長励负。 經(jīng)常有香客問我,道長匕得,這世上最難降的妖魔是什么继榆? 我笑而不...
    開封第一講書人閱讀 58,218評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上略吨,老公的妹妹穿的比我還像新娘攒发。我一直安慰自己,他們只是感情好晋南,可當我...
    茶點故事閱讀 67,234評論 6 388
  • 文/花漫 我一把揭開白布惠猿。 她就那樣靜靜地躺著,像睡著了一般负间。 火紅的嫁衣襯著肌膚如雪偶妖。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,198評論 1 299
  • 那天政溃,我揣著相機與錄音趾访,去河邊找鬼。 笑死董虱,一個胖子當著我的面吹牛扼鞋,可吹牛的內容都是我干的。 我是一名探鬼主播愤诱,決...
    沈念sama閱讀 40,084評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼云头,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了淫半?” 一聲冷哼從身側響起溃槐,我...
    開封第一講書人閱讀 38,926評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎科吭,沒想到半個月后昏滴,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,341評論 1 311
  • 正文 獨居荒郊野嶺守林人離奇死亡对人,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,563評論 2 333
  • 正文 我和宋清朗相戀三年谣殊,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片牺弄。...
    茶點故事閱讀 39,731評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡姻几,死狀恐怖,靈堂內的尸體忽然破棺而出猖闪,到底是詐尸還是另有隱情鲜棠,我是刑警寧澤,帶...
    沈念sama閱讀 35,430評論 5 343
  • 正文 年R本政府宣布培慌,位于F島的核電站豁陆,受9級特大地震影響,放射性物質發(fā)生泄漏吵护。R本人自食惡果不足惜盒音,卻給世界環(huán)境...
    茶點故事閱讀 41,036評論 3 326
  • 文/蒙蒙 一表鳍、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧祥诽,春花似錦譬圣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,676評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至维哈,卻和暖如春绳姨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背阔挠。 一陣腳步聲響...
    開封第一講書人閱讀 32,829評論 1 269
  • 我被黑心中介騙來泰國打工飘庄, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人购撼。 一個月前我還...
    沈念sama閱讀 47,743評論 2 368
  • 正文 我出身青樓跪削,卻偏偏與公主長得像,于是被迫代替她去往敵國和親迂求。 傳聞我的和親對象是個殘疾皇子碾盐,可洞房花燭夜當晚...
    茶點故事閱讀 44,629評論 2 354

推薦閱讀更多精彩內容

  • Spring Cloud為開發(fā)人員提供了快速構建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務發(fā)現(xiàn)锁摔,斷路器廓旬,智...
    卡卡羅2017閱讀 134,652評論 18 139
  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,079評論 25 707
  • 失望這種情緒好像有一種微妙的感覺哼审,像是你生氣了谐腰,但是又沒有憤怒的情緒,但還是覺得對方做錯了涩盾,說到底是對方?jīng)]有達...
    p葭閱讀 229評論 0 0
  • 假如我是一個畫家 我一定要 畫出媽媽頭上的一根根白發(fā) 畫出歲月從頭發(fā)上掠過時 快速的 淡淡的身影 假如我是一個畫家...
    錦瑟年華h閱讀 563評論 1 6
  • 非要他媽的禮拜六來檢查工作春霍,你們是來檢查工作還是來杭州游玩啊砸西,麻痹的,害得老子沒法休息V啡濉G奂稀!神經(jīng)病啊莲趣,一個個都給我...
    車尾靠窗閱讀 234評論 0 0