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