引言
QWebEngineView使得Qt软件具有内置浏览器的功能,但相对基础,实际开发中需要在此之上进行封装,方便混合开发,本文是基于实际项目进行的封装,增加如下功能:
- 集成QWebChannel以及通用配置,避免子类重复实现
- 快捷键(ctrl + alt + b)呼出调试弹窗,用于调试js代码
- 命令缓存,用于解决网页未加载完成,runJavaScript不生效问题
- 加载计时
WebViewBase中内置两个View,一个用来展示网页,另一个用来展示调试界面,通过快捷键呼出,不设置父窗体,方便调试时切换界面。
命令缓存,则是通过类内标志位m_isLoaded判断当前的js命令是否进入队列,未加载完成则放入队列中,等待加载完成后一并runJavaScript。防止出现c++代码在网页未加载完成调用js命令不生效的情况。
判断是否加载完成:
void WebViewBase::runJs(QString jsStr)
{
if (m_isLoaded) {
m_webView->page()->runJavaScript(jsStr);
}
else {
m_cmdList << jsStr;
}
}
加载完成后,将之前缓存的命令一并发送:
void WebViewBase::slot_loadFinished(bool isOK)
{
if (!m_cmdList.isEmpty()) {
auto tmpStr = m_cmdList.join(";\n");
m_webView->page()->runJavaScript(tmpStr);
m_cmdList.clear();
}
m_isLoaded = true;
emit sig_loadFinish(isOK);
}
实现效果如下:
代码实现
#include <QWidget>
#include <QWebEngineView>
#include <QWebChannel>
class WebViewBase : public QWidget
{
Q_OBJECT
public:
WebViewBase(QWidget* parent);
virtual ~WebViewBase();
QWebEngineView* webView();
void runJs(QString jsStr);
signals:
void sig_loadFinish(bool isOK);
private slots:
void slot_loadStarted();
void slot_loadFinished(bool isOK);
protected:
QWebEngineView* m_webView;
QWebEngineView* m_webDebugView;
QWebChannel* m_webChannel;
private:
QList<QString> m_cmdList;// 未加载完成则先放入容器中
bool m_isLoaded;
QTime m_loadTime;
};
#include <QWebEngineSettings>
#include <QShortcut>
#include <QWebEngineProfile>
#include <QHBoxLayout>
#include <QDebug>
//
WebViewBase::WebViewBase(QWidget *parent)
: QWidget(parent)
, m_isLoaded(false)
{
m_webView = new QWebEngineView(this);
m_webChannel = new QWebChannel(m_webView->page());// 定义一个channel作为和JS或HTML交互
m_webView->page()->setWebChannel(m_webChannel);// 把channel配置到page上,让channel作为其信使
m_webChannel->registerObject("webObject", this);
auto webSetting = m_webView->page()->settings();
webSetting->setAttribute(QWebEngineSettings::FullScreenSupportEnabled, true);//全屏支持
webSetting->setAttribute(QWebEngineSettings::DnsPrefetchEnabled, true);//预取dns,加快浏览速度
webSetting->setAttribute(QWebEngineSettings::ShowScrollBars, false);// 隐藏滚动条
webSetting->setAttribute(QWebEngineSettings::DnsPrefetchEnabled, true);// 预取DNS条目以加快浏览速度
QWebEngineProfile * engineProfile = m_webView->page()->profile();
engineProfile->clearHttpCache();// 删除缓存
//QWebEngineCookieStore *cookie = engineProfile->cookieStore();
//cookie->deleteAllCookies();// 删除cookie
// 关联调试快捷键
QShortcut* shortcut = new QShortcut(QKeySequence(Qt::CTRL + Qt::ALT + Qt::Key_B), this);
QObject::connect(shortcut, &QShortcut::activated, this, [this]() {
m_webDebugView = new QWebEngineView();
m_webDebugView->setWindowFlags(m_webDebugView->windowFlags() | Qt::Window);
m_webDebugView->resize(800, 600);
m_webDebugView->setAttribute(Qt::WA_DeleteOnClose, true);
connect(m_webDebugView, &QWidget::destroyed, m_webDebugView, [this] { m_webDebugView = nullptr; });
m_webView->page()->setDevToolsPage(m_webDebugView->page());
m_webDebugView->page()->triggerAction(QWebEnginePage::InspectElement);
m_webDebugView->raise();
m_webDebugView->show();
});
connect(m_webView, &QWebEngineView::loadStarted, this, &WebViewBase::slot_loadStarted);
connect(m_webView, &QWebEngineView::loadFinished, this, &WebViewBase::slot_loadFinished);
// 布局
auto mainLayout = new QHBoxLayout(this);
mainLayout->addWidget(m_webView);
}
WebViewBase::~WebViewBase()
{
}
QWebEngineView* WebViewBase::webView()
{
return m_webView;
}
void WebViewBase::runJs(QString jsStr)
{
if (m_isLoaded) {
m_webView->page()->runJavaScript(jsStr);
}
else {
m_cmdList << jsStr;
}
}
void WebViewBase::slot_loadStarted()
{
m_isLoaded = false;
m_loadTime.start();
}
void WebViewBase::slot_loadFinished(bool isOK)
{
if (!m_cmdList.isEmpty()) {
auto tmpStr = m_cmdList.join(";\n");
m_webView->page()->runJavaScript(tmpStr);
m_cmdList.clear();
}
m_isLoaded = true;
// 耗时打印
qDebug() << "Web load time:" << m_loadTime.elapsed() / 1000.0 << "s";
emit sig_loadFinish(isOK);
}
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
auto tmpWeb = new WebViewBase(this);
tmpWeb->webView()->load(QString("http://127.0.0.1:8088"));
tmpWeb->runJs("setCenter({latitude: 22.5336, longitude: 113.98}); setZoom(13)");
setCentralWidget(tmpWeb);
}
性能优化
使用QWebEngineView的过程中,有个令人困扰的问题,就是资源占用较高,因为本身是浏览器,打开一个地图界面的资源占用甚至高达500M,且隐藏后并不会释放资源,如果存在四五个页签占用很容易高达3G,严重影响其他功能的使用。
为解决这个问题,采取了隐藏页面则卸载网页的方法,重新显示再将原来的网页加载出来,为保证隐藏再显示用户的看到的内容与之前一致,除了重新加载网页之外,还需要将c++调用的js命令重新再调用一遍,需要用到上述代码中的成员m_cmdList,对所有runJs命令进行保存,用于重新加载后的命令调用,代码修改如下:
void WebViewBase::slot_loadFinished(bool isOK)
{
// 加载完成,发送命令,解决如下问题:
// 1.未加载完成runJavaScript不成功
// 2.隐藏卸载网页后,重新显示需要复原界面
if (!m_cmdList.isEmpty() && m_oriUrl.isEmpty()) {
auto tmpStr = m_cmdList.join(";\n");
m_webView->page()->runJavaScript(tmpStr);
}
m_isLoaded = true;
m_waitProgress->stop(isOK);
emit sig_loadFinish(isOK);
}
增加对隐藏、显示事件处理:
void WebViewBase::showEvent(QShowEvent *)
{
if (!m_oriUrl.isEmpty()) {
m_webView->setUrl(m_oriUrl);
m_oriUrl.clear();
}
}
void WebViewBase::hideEvent(QHideEvent *)
{
if (!m_webView->url().isEmpty()) {
m_oriUrl = m_webView->url();
m_webView->setUrl(QUrl(""));
}
}