Web 與本地應(yīng)用的關(guān)聯(lián)
雖然在嵌入式 Linux 智能設(shè)備中采用 Web 支持已經(jīng)解決了很多問題,但是還有一些和設(shè)備相關(guān)的特殊功能是 Web 支持不能提供的。比如廣告機(jī)中的音視頻播放功能,條碼掃描機(jī)的模式識(shí)別功能,還有與某種外設(shè)的通信等。這些并不是 HTML 和瀏覽器的標(biāo)準(zhǔn)所包含的,而是需要本地應(yīng)用的支持。既然我們希望使用 Web 和 B/S 等技術(shù)來實(shí)現(xiàn)我們的應(yīng)用,那么這些本地應(yīng)用功能也應(yīng)該由 Web 來控制。比如說廣告機(jī)的視頻播放,實(shí)際的播放是由本地應(yīng)用實(shí)現(xiàn)的,但是什么時(shí)候在什么位置播放什么視頻應(yīng)該由 Web 來決定。并且廣告頁面內(nèi)容的編輯也應(yīng)該在網(wǎng)頁的 HTML 中體現(xiàn),而不需要另外一套播放控制機(jī)制。
但是想要由 Web 來控制本地應(yīng)用存在一個(gè)問題,這些本地應(yīng)用的調(diào)用沒有一種統(tǒng)一的機(jī)制。有的可能通過驅(qū)動(dòng),有的可能是通過 I2C、串口的通訊口,有的可能是第三方提供的庫,還有的可能是與其他進(jìn)程的通信。可以說,除了他們大多用 C/C++ 語言進(jìn)行開發(fā)之外,幾乎沒有什么共同點(diǎn)。
那么現(xiàn)在我們要解決的問題就是,當(dāng) QWebView 渲染一個(gè)網(wǎng)頁的時(shí)候,如何讓我們?cè)诰W(wǎng)頁里編寫的一些特定的 HTML 能和我們的 C/C++ 代碼關(guān)聯(lián)起來。幸運(yùn)的是,Qt 封裝的 WebKit 提供了多種方法使我們可以很好實(shí)現(xiàn)這個(gè)關(guān)聯(lián)。接下來,我們會(huì)以幾種應(yīng)用場(chǎng)景為例來討論 Web 和本地應(yīng)用關(guān)聯(lián)的幾種實(shí)現(xiàn)方法。
截取 request 的方法
首先我們介紹第一種應(yīng)用場(chǎng)景:某嵌入式智能設(shè)備需要實(shí)現(xiàn)下面的功能,用戶點(diǎn)擊網(wǎng)頁上“更新”的鏈接,設(shè)備就會(huì)下載指定的 Firmware 并且進(jìn)行更新。
為了實(shí)現(xiàn)這個(gè)功能,客戶端的瀏覽器需要在用戶點(diǎn)擊了某個(gè)特定的 Link 之后,啟動(dòng)系統(tǒng)的更新過程。包括獲取最新 Firmware 的地址,進(jìn)行下載,最后更新設(shè)備。Firmware 的更新過程和設(shè)備硬件相關(guān),標(biāo)準(zhǔn)瀏覽器不能實(shí)現(xiàn)這個(gè)功能,因此我們必須“截獲”用戶的這個(gè)請(qǐng)求,然后使用本地代碼來完成整個(gè)更新過程。
為了實(shí)現(xiàn)截獲用戶的這個(gè) HTML request,我們先分析一下 QWebView 的結(jié)構(gòu)。
圖 1. QWebView 的結(jié)構(gòu)圖
QWebView 使用 QWebPage 來實(shí)現(xiàn)頁面,QWebPage 使用 QWebFrame 來實(shí)現(xiàn)頁面元素。當(dāng)頁面發(fā)出一個(gè) Navigation 的 request 時(shí),QWebPage 會(huì)來進(jìn)行處理。這個(gè)時(shí)候有一個(gè)函數(shù)會(huì)被調(diào)用:
bool QWebPage::acceptNavigationRequest ( QWebFrame *frame, const QWebNetworkRequest &request, QWebPage::NavigationType type )
這個(gè)函數(shù)會(huì)在發(fā)生 Navigation Request 的時(shí)候獲取到觸發(fā)事件的頁面元素、request 內(nèi)容和類型。如果函數(shù)如果返回 false,瀏覽器將忽略這個(gè) request。
我們可以從 QWebPage 派生一個(gè)子類,重寫 acceptNavigationRequest,在發(fā)現(xiàn)特定 request 內(nèi)容的時(shí)候,做出自己的處理。假設(shè)目標(biāo)地址是?http://xxxx.com/update/Firmware.bin,實(shí)現(xiàn)如下:
清單 6. acceptNavigationRequest 函數(shù)的定義和實(shí)現(xiàn)
class QMyWebPage : public QwebPage { protected: bool acceptNavigationRequest ( QWebFrame *frame, const QWebNetworkRequest &request, QWebPage::NavigationType type ); ... ... }; QMyWebPage::acceptNavigationRequest ( QWebFrame *frame, const QWebNetworkRequest &request, QWebPage::NavigationType type ) { if( type == QWebPage::NavigationTypeFormSubmitted ) { QString str = url = request.url().path(); // 如果是特定的目標(biāo) if( str == “http://xxxx.com/update/Firmware.bin” ) { // 從 link 中獲取 Firmware 地址 get Firmware addr from path // 下載 Firmware download Firmware // 更新設(shè)備 Firmware update Firmware // 返回 false 讓瀏覽器不再處理這個(gè) request return false; } } return QWebPage::acceptNavigationRequest ( frame, request , type ); }
上面實(shí)現(xiàn)部分中獲取、下載和更新 Firmware 部分用說明性文字來表示,不是真實(shí)的實(shí)現(xiàn)代碼,用戶可以根據(jù)自身的需求改寫這部分本地代碼。
除了實(shí)現(xiàn)具體功能之外,我們還需要讓 QMyWebPage 被 QWebView 使用。這是通過 QWebView 的 setPage 調(diào)用實(shí)現(xiàn)的,可以在構(gòu)造 QWebView 實(shí)例的時(shí)候加入:
QWebView* Webview = new QWebView ( this );
QMyWebPage* page = new QMyWebPage ();Webview -> setPage ( page ); ?// 讓 WebView 使用我們的 QwebPage
至此,我們實(shí)現(xiàn)了當(dāng)頁面發(fā)生了點(diǎn)擊 http://xxxx.com/update/Firmware.bin 的時(shí)候,截取了這個(gè) request,并讓我們的本地代碼能被適時(shí)的調(diào)用運(yùn)行。
acceptNavigationRequest 還可以被用在另外一種場(chǎng)合,某些網(wǎng)站會(huì)根據(jù)設(shè)備的 mac 地址決定是否提供下載服務(wù),讓設(shè)備在請(qǐng)求下載鏈接的時(shí)候,要求其在頭信息里提供 mac 地址。我們注意到 acceptNavigationRequest 的參數(shù)里有 QWebNetworkRequest 的變量,這個(gè)類實(shí)際上就包含了頭信息,雖然在這個(gè)變量在這里是一個(gè)不可更改的引用,但是我們可以保留這個(gè)信息,復(fù)制一份,在頭信息里加入 mac 信息,然后讓 QWebView 主動(dòng)進(jìn)行一次下載請(qǐng)求,從而實(shí)現(xiàn)在頭信息里添加自定義內(nèi)容的功能。
在頁面中執(zhí)行自定義的 JavaScript 的方法
接著我們介紹另外一種應(yīng)用場(chǎng)景:手持條碼機(jī)對(duì)準(zhǔn)貨物的條碼,按鍵掃描之后,該貨物的信息立刻在條碼機(jī)上顯示出來。
這個(gè)功能是一個(gè)很典型的網(wǎng)頁查詢應(yīng)用,我們可以假設(shè)條碼是被手工輸入到網(wǎng)頁上的編輯框,然后 submit 一個(gè)請(qǐng)求,服務(wù)器返回該條碼表示的貨物信息。所以,如果在按鍵掃描之后,條碼號(hào)能被填入網(wǎng)頁上的編輯框并且觸發(fā)一個(gè) submit,這個(gè)功能就可以實(shí)現(xiàn)。
Qt 封裝的 WebKit 可以在已加載的頁面中插入執(zhí)行用戶自定的 JavaScript,這是通過 QWebFrame 的 evaluateJavaScript 接口來實(shí)現(xiàn)。
QVariant QWebFrame::evaluateJavaScript ( const QString& scriptSource );
下面我們通過幾個(gè)例子來演示如何執(zhí)行 JavaScript。
假設(shè)我們的頁面中有一個(gè)編輯框,名稱為“code”,它的旁邊還有一個(gè)按鈕名稱為“query”。掃描機(jī)對(duì)準(zhǔn)條形碼之后,用戶按下一個(gè)按鍵,觸發(fā)了 Qt 程序窗體 form 中的一個(gè)消息響應(yīng)函數(shù),在消息響應(yīng)函數(shù)中通過如下的語句可以設(shè)置編輯框中的內(nèi)容:
清單 7. 設(shè)置編輯框內(nèi)容的代碼實(shí)現(xiàn)
QWebFrame *frame = form.WebView->page()->mainFrame(); QString code = getScanCode (); // 調(diào)用掃描條形碼的功能,需要自己實(shí)現(xiàn) QString js = QString ("document.getElementById('code').value ="%1";" ).arg(code) ); frame->evaluateJavaScript ( js );
接下來可以用下面語句來實(shí)現(xiàn)觸發(fā) query 按鈕:
清單 8. 觸發(fā) query 按鈕的代碼實(shí)現(xiàn)
QWebFrame *frame = form.WebView->page()->mainFrame(); QString js = QString ( "document.getElementById('query').submit();" ); frame -> evaluateJavaScript ( js );
除了可以設(shè)置網(wǎng)頁上編輯框內(nèi)容外,我們還可以通過下面的語句獲取編輯框中的內(nèi)容:
清單 9. 獲取編輯框內(nèi)容的代碼實(shí)現(xiàn)
QWebFrame *frame = form.WebView->page()->mainFrame(); QString s1 = frame->evaluateJavaScript ("document.getElementById ('code').name" );
這樣就解決了條碼機(jī)的貨物查詢功能所碰到的問題。我們可以讓頁面隨時(shí)運(yùn)行我們自定義的 JavaScript,這個(gè)功能將發(fā)揮非常大的作用。它實(shí)際上解決了在由設(shè)備進(jìn)行主動(dòng)觸發(fā)的的應(yīng)用模式下,本地代碼和網(wǎng)頁進(jìn)行配合的問題。但是這個(gè)方法只能用于特定的網(wǎng)頁,因?yàn)槲覀冏约翰迦氲?JavaScript 必須與網(wǎng)頁上運(yùn)行環(huán)境匹配。
自定義 JavaScript 擴(kuò)展的方法
接著我們介紹第三種應(yīng)用場(chǎng)景:我們先考慮這樣一個(gè)問題,如果頁面的 JavaScript 代碼中需要得到本地應(yīng)用的支持怎么辦?比如一個(gè)機(jī)頂盒軟件需要配置本地網(wǎng)絡(luò)(這種應(yīng)用原本都是編寫本地應(yīng)用程序?qū)崿F(xiàn)的,但是既然我們討論采用 Web 方法來代替原有開發(fā)模式,就需要考慮如何在 Web 上實(shí)現(xiàn))。首先,頁面需要獲取當(dāng)前網(wǎng)絡(luò)設(shè)置方式,IP 地址、子網(wǎng)掩碼、DNS 等。在網(wǎng)頁上的編輯框、下拉框等控件內(nèi)顯示,用戶做了一些配置之后,點(diǎn)擊網(wǎng)頁上的“確定”按鈕,這些配置信息就生效了。
從頁面的角度來說,這些都需要用 JavaScript 代碼來實(shí)現(xiàn),那么我們就需要讓 JavaScript 代碼能和本地代碼關(guān)聯(lián)起來。Qt 支持自定義的 JavaScript 擴(kuò)展,也就是說用戶可以自己在 Qt 中定義一個(gè)對(duì)象,編譯到 WebKit 中。頁面中的 JavaScript 腳本可以直接生成這個(gè)對(duì)象并且調(diào)用其方法。Qt 在目錄 \src\3rdparty\WebKit\WebCore\bridge\ 中提供了一個(gè) demo。測(cè)試代碼在文件 testqtbindings.cpp 中。我們可以參考他的方法的編寫自定義的類:
清單 10. 自定義類的實(shí)現(xiàn)代碼
class MyObject : public QObject { Q_OBJECT // 定義屬性和函數(shù)的關(guān)聯(lián) Q_PROPERTY ( QString ip READ ip WRITE setIp ) public: MyObject (){} QString ip () { // 以字符串方式返回 IP 地址的實(shí)現(xiàn) }; void setIp( QString ) { // 設(shè)置 IP 地址的實(shí)現(xiàn) }; }; // 通過如下的代碼來生成對(duì)象實(shí)例: MyObject* myObject = new MyObject;
然后用下面的方法實(shí)現(xiàn)對(duì)象 myObject 和 JavaScript 中的對(duì)象 myInterface 的關(guān)聯(lián):
清單 11. C++ 對(duì)象和 JavaScript 對(duì)象的關(guān)聯(lián)代碼
Global* global = new Global (); RefPtr interp = new Interpreter ( global ); ExecState* exec = interp->globalExec (); // 實(shí)現(xiàn) C++ 對(duì)象和 JavaScript 對(duì)象的關(guān)聯(lián) global->put ( exec, Identifier("myInterface" ), Instance::createRuntimeObject ( Instance::QtLanguage, (void*)myObject) );
將 MyObject 的定義在 QWebFrame.h 中聲明,并且將清單 11 中的代碼加入到 QWebFrame 的構(gòu)造函數(shù)中(QWebFrame.cpp)。QWebFrame.h 和 QWebFrame.cpp 兩個(gè)文件在目錄 src\3rdparty\WebKit\WebKit\qt\Api 下。重新編譯 WebKit 模塊之后,在網(wǎng)頁中就可以使用 myInterface 來調(diào)用對(duì)象 myObject 的方法了。調(diào)用的 JavaScript 代碼如下:
需要獲取 ip 的時(shí)候:
str = myInterface.ip;
需要設(shè)置 ip 的時(shí)候:
myInterface.Ip = str;
上面的代碼只是 ip 地址獲取和設(shè)置示例,其他類似掩碼、dns 之類可以使用類似的方法。
任何嵌入式智能設(shè)備的用 C/C++ 實(shí)現(xiàn)的本地功能,都可以通過上述方法讓 JavaScript 來進(jìn)行調(diào)用。這種擴(kuò)展能讓瀏覽器來解釋執(zhí)行任何我們想要的功能,幾乎讓 Web 和本地代碼的結(jié)合完全掃除了障礙。將實(shí)現(xiàn)具體功能的本地代碼封裝成為庫和模塊,然后由 Web 來進(jìn)行上層架構(gòu)和耦合,這將大大降低嵌入式 Linux 智能設(shè)備的軟件開發(fā)難度。特別是對(duì)于經(jīng)常要更新功能的應(yīng)用,以前需要刷新 Firmware,而現(xiàn)在只要更新遠(yuǎn)程服務(wù)器上的網(wǎng)頁就可以了。
編寫 WebKit plugin 的方法
除了自定義 JavaScript 對(duì)象之外,有時(shí)候我們還會(huì)用到自定義的網(wǎng)頁元素。在 PC 上,最典型的網(wǎng)頁元素就是 FLASH。FLASH 并不是 HTML 標(biāo)準(zhǔn),但是可以用插件的方式讓瀏覽器對(duì)這些特定的標(biāo)簽進(jìn)行解釋。
最后一種應(yīng)用場(chǎng)景:以廣告機(jī)為例,之前我們提到過廣告機(jī)的視頻播放功能。不同平臺(tái)播放視頻的方式都是不同的,而網(wǎng)頁也沒有像定義圖片一樣定義視頻播放的標(biāo)準(zhǔn),因此廣告機(jī)作為區(qū)別于 PC 的嵌入式設(shè)備,其視頻播放功能必須用本地代碼來實(shí)現(xiàn)。當(dāng)我們用網(wǎng)頁方式來組織廣告機(jī)的屏幕,視頻部分就應(yīng)該像圖片、文字一樣,成為網(wǎng)頁中的一個(gè)部分,可以通過 HTML 的定義來控制。HTML 提供了標(biāo)簽“object”,來方便實(shí)現(xiàn)一些特別的對(duì)象。比如:
< object data="yahtzee.gif" type="image/gif" title="A Yahtzee animation" width=200 height=100 >
如果我們的瀏覽器支持我們用類似方法在網(wǎng)頁中插入一個(gè)自定義對(duì)象,那么這個(gè)問題就可以得到解決。
Qt 支持在 WebKit 中添加自定義的插件。在文件 FrameLoaderClientQt.cpp 中的函數(shù) FrameLoaderClientQt::createPlugin 中,可以找到如下的代碼片段:
清單 12. FrameLoaderClientQt.cpp 代碼片段
if ( mimeType == "application/x-qt-plugin" || mimeType == "application/x-qt-styled-widget" ) { object = m_WebFrame->page()->createPlugin( classid, qurl, params, values );
通過上面的代碼,可以看出如果 HTML 中一個(gè) object 將自己的 type 設(shè)置為 application/x-qt-plugin 或者是 application/x-qt-styled-widget,Qt 則會(huì)識(shí)別并要求 QWebPage 來創(chuàng)建插件,其方式就是調(diào)用 QWebPage 的 createPlugin 函數(shù),函數(shù)定義如下:
QObject *QWebPage::createPlugin ( const QString &classid, const QUrl &url, const QStringList ?mNames, const QStringList ?mValues );
我們?cè)O(shè)計(jì)以下的 HTML 來標(biāo)識(shí)我們的對(duì)象:
我們?cè)O(shè)定了在網(wǎng)頁中插入一個(gè) VideoPlaye 的對(duì)象,并且設(shè)定了寬高、要播放的文件等參數(shù)。因?yàn)槲覀冊(cè)O(shè)定了這個(gè)對(duì)象的 type 為 application/x-qt-plugin,所以當(dāng)瀏覽器碰到這段 HTML 代碼時(shí),會(huì)調(diào)用到 QWebPage 的 createPlugin 功能。這個(gè)函數(shù)被要求返回一個(gè)窗體。而這個(gè)窗體會(huì)被當(dāng)成一個(gè)標(biāo)準(zhǔn)網(wǎng)頁對(duì)象,和編輯框、下拉框等一樣被嵌入到 Web 頁面中。
我們先從 QWebPage 中派生自己的對(duì)象,實(shí)現(xiàn) createPlugin:
清單 13. createPlugin 函數(shù)的實(shí)現(xiàn)
class QMyWebPage : public QWebPage { protected: QObject *createPlugin ( const QString &classid, const QUrl &url, const QStringList ?mNames, const QStringList ?mValues ); ... ... }; QObject* QMyWebPage::createPlugin ( const QString &classid, const QUrl &url, const QStringList ?mNames, const QStringList ?mValues ) { if ( classid == "VideoPlayer" ) { // 在這里創(chuàng)建一個(gè)自定義的帶視頻播放功能的窗體, VideoWindow* window = new VideoWindow(); // 配置參數(shù)如 width=800 等,會(huì)在參數(shù) paramNames 和 paramValues 中傳過來 window->setGeometry( ........ ) ; window->setSourceFile( ...... ) ; return window; // 返回創(chuàng)建的窗體 } ... }
與截取 request 的方法一樣,我們要讓自己 QMyWebPage 被使用:
QWebView* Webview = new QWebView ( this ); QMyWebPage* page = new QMyWebPage (); Webview->setPage ( page ); ?// 讓 WebView 使用我們的 QMyWebPage
注意加載頁面之前要打開插件使能的選項(xiàng),方法如下:
QWebSettings* setting = Webview->settings (); setting->setAttribute ( QWebSettings::PluginsEnabled, true );
至此,我們創(chuàng)建了自己的網(wǎng)頁元素:類型為 VideoPlayer 的 object。網(wǎng)頁可以像使用標(biāo)準(zhǔn)網(wǎng)頁元素一樣,靈活的使用嵌入式平臺(tái)自己特有的功能。當(dāng)然,不一定非要把這個(gè)網(wǎng)頁元素用 application/x-qt-plugin 或者是 application/x-qt-styled-widget 來定義,Qt 也支持 type 不是這兩者或者以動(dòng)態(tài)鏈接庫的方式來使用插件,這樣就可以支持類似 FLASH 之類非 Qt 自定義的 object,關(guān)于這方面更多信息可以參考 Qt 的文檔。
?
評(píng)論
查看更多