嵌入式 linux 智慧型裝置應用中 web 支援的實現(二)

來源:互聯網
上載者:User
由兩篇文章組成的系列文章主要闡述如何在嵌入式 Linux 智慧型裝置的應用程式中增加 Web 支援。第 1 部分介紹了如何裝置上提供常規 Web 功能的支援。本文是第2部分,將重點介紹如何讓在嵌入式裝置上啟動並執行 Web 程式能支援裝置本身特有的功能。本文分別以四種應用情境為例,介紹如何通過修改瀏覽器核心代碼來實現裝置本地應用和 Web 結合的功能。

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<Interpreter> 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 &paramNames, const QStringList &paramValues ); 

我們設計以下的 HTML 來標識我們的對象:

 <object type="application/x-qt-plugin" classid="VideoPlayer" width=800                      height=600 file="kfc.avi" ></object> 

我們設定了在網頁中插入一個 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 &paramNames, const QStringList &paramValues );  ...  ...  };  QObject* QMyWebPage::createPlugin ( const QString &classid, const QUrl &url,  const QStringList &paramNames, const QStringList &paramValues )  {     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 的文檔。

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.