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