嵌入式 linux 智能设备应用中 web 支持的实现

嵌入式 102浏览

http://www.ibm.com/developerworks/cn/linux/l-cn-embeddedweb/index.html

简介: 由两篇文章组成的系列文章主要阐述如何在嵌入式 Linux 智能设备的应用程序中增加 Web 支持。 第 1 部分,我们将会介绍嵌入式 Linux 智能设备开发的现状、Qt 和 WebKit 的概念。并以广告机和手持点菜机等应用为例,叙述在嵌入式 Linux 平台以及 Qt,WebKit 等技术的帮助下,如何在终端应用程序中添加网页浏览的功能,如何用 Web 技术取代传统 UI 技术,以及如何在智能设备上实现从 C/S 到 B/S 的开发模式的转换。
2 部分,将重点介绍如何让在嵌入式设备上运行的 Web 程序能支持设备本身特有的功能。本文分别以四种应用场景为例,介绍如何通过修改浏览器内核代码来实现设备本地应用和 Web 结合的功能。

嵌入式 Linux 智能设备开发的现状

近几年,嵌入式 Linux 在智能设备中的应用发展的非常迅速。可以预见,嵌入式智能设备和我们的生活将会越来越密不可分。

Linux 在嵌入式系统中的应用可以分为两大类:面向服务类和面向应用类。典型的面向服务类系统有交换机、路由器、监控设备等;典型的面向应用类的系统有手机、PDA、机顶盒等。本文主要讨论面向应用类,特别是指带有网络和 UI 的应用系统。

在嵌入式 Linux 上进行带 UI 的应用程序开发是一件非常复杂的事情,主要涉及以下几个方面:

UI 系统

Linux 本身的 UI 系统并不统一,嵌入式版本上的 UI 系统更是五花八门,而且与其 PC 版本相比也有一些适应性的改变。而有些产品的开发甚至不使用 UI 系统,通过直接往 framebuffer 贴图的方式来实现 UI。而且与 PC 相比,嵌入式系统的性能问题,也让嵌入式的 UI 系统在表现上做出很多妥协。这些都使得开发难度增加,带来更多的不兼容问题。这些都使嵌入式 Linux 上的应用开发和移植变得更加困难,另外也使培养一名合格的嵌入式工程师的成本变的比较高。

厂商支持

对于一个嵌入式平台,其主芯片的生产商对这个平台影响巨大。一般来说,生产商会提供对某个或者某些 UI 系统的支持,但是他们不可能支持所有的系统。所以,选定一个嵌入式平台就意味着开发团队需要切换到这个平台所能支持的 UI 平台上。这个团队之前做的应用就需要移植到新的 UI 平台,而这个移植是非常麻烦的。

浏览器

嵌入式 Linux 的网络接口一般都支持 posix 的标准,但是嵌入式设备的浏览器则与 UI 系统一样是五花八门的。在应用的推动下,嵌入式 Linux 平台上的浏览器在近年发展也非常迅速。他们基于不同的 UI 平台进行开发(也有一些直接操作 framebuffer),对 Web 标准的支持和兼容性各不相同。比较简单的浏览器只能支持 HTML 标签,优秀的产品则能在兼容性上做到与 PC 平台上的浏览器几乎同样的水平。

服务器技术

很多嵌入式应用需要与服务器进行连接,点菜机就是一个典型的应用。手持智能设备端需要将用户的操作写入远程的服务器,然后远程的信息管理系统进行进一步的处理。这种类型的应用对于 PC 平台来说就是一个非常简单的数据库应用系统,数据库、远程调用、并发、中间件等技术已经应用了多年,而且还有众多成熟的企业应用的框架,可以灵活而快速的搭建出一个系统。但是这些在嵌入式平台都是不存在的,这意味着开发人员还需要用相当于 PC 平台十几年前的水平来进行开发,为了让嵌入式系统和服务器的数据库进行对接,开发人员还需要在服务器端编写一个专门的程序来充当桥梁的作用。笔者见过在不少项目里还需要直接控制
socket 来和服务器进行数据交换,这些对于 PC 平台都是不可想象的。另外开发一个支持多个客户端并发的稳定服务器程序并不是一件容易的事情,这些问题都会影响整个嵌入式开发过程的成本和质量。

如果一个公司希望在某个嵌入式 Linux 平台上开发一个包含了 Web 浏览的应用,而他自己之前并不拥有一个完善的平台的话,其开发团队首先需要选定一个 UI 系统,然后寻找一家做浏览器的公司,与其合作,再想办法将浏览器移植到自己的平台上。如果是需要在自己的应用程序中嵌入一个浏览网页的窗体,那么问题就会更加复杂,因为还涉及到与选定的浏览器进行代码或者模块级别集成的问题。

Qt 和 WebKit 简介

Qt 是一个跨平台的 C++ 图形用户界面应用程序框架,对 Windows、Linux、Mac OS X、Unix、Free BSD 等主要的操作系统均有支持。Qt 不仅包含了图形界面库,还集成了 Network、File、IO、Database、2D/3D、XML 等模块,基本涵盖了一个应用程序所需要的所有功能,这些让 Qt 成为最有影响力的跨平台框架,Linux 平台上的 KDE 就是基于 Qt 开发的。Qt Embedded 则是 Qt 针对嵌入式平台的版本,在嵌入式平台的 UI 市场占有很大的份额。

Qt 可以解决上一节所讨论的很多问题,因为其优异的跨平台性能,开发人员甚至可以在 PC 上进行开发调试,然后直接将代码放到交叉编译环境中生成嵌入式版本,这样的移植基本不需要改动代码。

WebKit 是一个开源的浏览器引擎,目前 Safari,Chrome 等浏览器均使用了 WebKit 作为核心。Qt 从 4.5 版本开始,集成了 WebKit 作为 Qt 的平台组件,用户可以像使用其他组件一样将 WebKit 引擎集成到自己的应用程序中,以提供 Web 的支持。

 

 

在应用程序中添加网页浏览的支持

通过网址 http://qt.nokia.com/downloads 可以找到 Embedded Linux 版本的最新 Qt 源代码。现在最新版本是 4.5.3。

WebKit 作为第三方资源可以在如下目录中找到:

qt-embedded-Linux-opensource-src-4.5.3src3rdpartyWebKit

Qt 中对 WebKit 做了封装,主要有以下几个类:

QWebView 最常用的类,这是一个窗体控件,可以用来渲染网页 
QWebPage 被 QWebView 包含,表示一个 documentQWebFrame 被 QWebPage 包含,表示一个 frameQWebSettings    Web 渲染的全局设置 QWebHistory 用于浏览的历史记录

QWebView 是最常用的类,接下来我们大概的介绍一下这个类的组成。

根据头文件定义,我们可以得知这个类与 Qt 中按钮、对话框等一样,都是由 QWidget 派生,可以当做一个通用窗体来使用。

class QWebKit_EXPORT QWebView : public Qwidget

这个类有如下的成员函数:

void load ( const QUrl &url );
void setHtml ( const QString &HTML, const QUrl &baseUrl = QUrl() );

这两个函数可以让 QWebView 加载网页或者显示一段 HTML 内容,是 QWebView 最重要的函数。

这个类还有几个重要的 slot:

void stop ();
void back ();void forward ();void reload ();

这几个函数也是我们平时浏览网页时常用的功能。

QwebView 类还有几个重要的 signal:

Q_SIGNALS:
 void loadStarted (); void loadProgress ( int progress ); void loadFinished ( bool );

很明显,这几个 signal 是用来显示网页的加载过程。

接着我们通过代码来看 QWebView 是如何使用的。假设程序有一个主窗体 MainWindow,在 MainWindow 的构造函数中,有如下代码段:

清单 1. MainWindow 构造函数的代码段

				
 QWebView* view = new QWebView ( this ); 
 // 设置窗体左上角的坐标以及长宽
 view -> setGeometry( 50 , 50 , 400 , 300 ); 
 view -> show(); 
 view -> load( QUrl("http://www.google.com") ); 

设备网络配置好之后,运行程序,我们就可以看到在窗体中有一个区域显示出 google 的主页(编译的时候要注意在项目的 pro 文件中包含 WebKit 的头文件路径和动态库)。可见,Qt 已经将 WebKit 做了很好的封装,子应用程序中加入网页浏览功能是非常方便的。

如果希望能知道网页加载的进度,可以通过如下的代码来实现:

首先实现槽函数的定义:

清单 2. 槽函数的定义

				
 Private slot: 
 void setProgress(int progress) 
 { 
  // progress,即百分比进度
 } 
 void loadFinished() 
 { 
  // 表示网页加载完毕
 } 

第二步连接 Qt 的信号与槽函数:

清单 3. 连接 Qt 信号与槽函数

				
 connect(view , SIGNAL(loadProgress(int)), this, SLOT(setProgress(int))); 
 connect(view , SIGNAL(loadFinished(bool)), this, SLOT(loadFinished())); 

这样,在程序中加一个进度条表示网页加载过程就实现了。

 

 

用 Web 取代本地 UI 的应用

在嵌入式 Linux 智能设备上有一个典型的应用:信息机,或者广告机。这种机器一般都带有一个屏幕,有些会有触摸屏。屏幕上会组合显示文字、图片和视频,或者提供简单的查询功能。这种类型的设备最先是由内置 PC 来实现的,在各种服务大厅供用户使用。现在普遍使用嵌入式系统来取代 PC,以降低成本。

我们在银行、通信运营商服务大厅、医院、电梯房等地方经常能看到各种各样的广告机。一个典型的屏幕显示情况如下:

图 1. 广告机屏幕示例
图 1. 广告机屏幕示例 

这是组合比较复杂的情况,也有整个屏幕就是文字、图片或者视频的。与传统和大型软件开发相比,实现这样的功能看上去并不算很难。开发人员可能需要在嵌入式平台自己去实现字幕、图片和天气的显示组件或者模块(暂时忽略视频播放功能),然后在屏幕上进行显示即可。比较困难的地方在于以下几个方面:

内容显示

图片和字幕需要以某种格式进行存放,天气信息来源于 Internet,开发人员需要编写代码对这些内容进行显示,并根据不同规则对内容进行切换。如有以下的配置文件内容:

清单 4. 屏幕配置文件 1

				
 <screen> 
   <duration>30</duration> 
   <pic>1.jpg</pic> 
   <text>1.txt</text> 
   <video>1.avi</video> 
   <weather>http://xxxxx.HTML</weather> 
 </screen> 
 <screen> 
   <duration>25</duration> 
   <pic>2.jpg</pic> 
   <text>2.txt</text> 
   <video>2.avi</video> 
   <weather>http://xxxxx.HTML</weather> 
 </screen> 
 ... 
 ... 

开发人员需要编写一个解析器,能够解析这个 xml 文件,并且按照其中的规则进行内容的显示。

内容更新

如果需要对广告机的内容进行更换,维护人员则需要将内容按照这个格式进行编排。编排的效果是需要反复调整的,这个时候,要不就是每次都用一台广告机来看实际效果,要不然可能还需要专门设计一个预览程序。

显示布局

屏幕的显示布局很可能也是要变动的,那么意味着布局最好也是可配置的,折旧要求广告机程序能够解析并且实现显示布局的配置。如果说解析上面的配置文件并不是很麻烦的事情,但是如果配置文件变成下面这样,就不一样了。

清单 5. 屏幕配置文件 2

				
 <screen> 
    <duration>30</duration> 
    <pic x=800 y=200 width=320 height=240>1.jpg</pic> 
    <text x=0 y=600 width=1024 height=240>1.txt</text> 
    <video x=0 y=0 width=800 height=600>1.avi</video> 
    <weather>http://xxxxx.HTML</weather> 
 </screen> 
 <screen> 
    <duration>30</duration> 
    <pic x=800 y=200 width=320 height=240>2.jpg</pic> 
    <pic x=800 y=400 width=320 height=240>22.jpg</pic> 
    <text x=0 y=600 width=1024 height=120>2.txt</text> 
    <text x=0 y=800 width=1024 height=120>22.txt</text> 
    <video x=0 y=0 width=800 height=600>2.avi</video> 
    <weather>http://xxxxx.HTML</weather> 
 </screen> 
 ... 
 ... 

不但各种元素的位置可配置,图片、文本等的数量也发生了改变。这个时候,解析程序将变得相当复杂。当更多的需求出现,如要求配置滚动字幕的速度、背景颜色,要求图片和文字等内容可以单独配置刷新时间等,这样的配置文件不会比 HTML 标准简单,而解析程序的规模也将急剧膨胀。

综上,广告机的软件关键并不在于内容如何能显示出来,而是软件需要有复杂的解析能力,能够支持可配置的布局和内容。目前这些广告机的配置文件大多使用 xml 来存储信息,而一个能够同时解决复杂布局和内容显示的程序,实际上已经非常类似浏览器的概念了。那么,让我们从真正的浏览器的角度来看待广告机的这些问题。

如果把屏幕内容当成一个网页的话,屏幕布局、内容显示、更新、维护等都转换成了设计网页的问题,而最关键的解析程序的开发可以忽略,用已有的成熟的浏览器取代。这里的网页设计并不关系到网页服务器端的开发,因为广告机的内容大多是存储在本地,所以只需要直接设计页面。页面内容的更新,或者整个页面的更新都可以通过标准 HTML,或者 JavaScript 之类脚本里实现。对于“天气”播放功能的实现就更加简单了,之前的方式需要编写程序通过网络从远程获取数据然后显示,比较麻烦。转换成网页模式之后,只需要在网页中嵌入一个子网页,指向远程的服务器链接即可。可见,以广告机为例,在架构上使用网页来取代之前的模式,能充分利用已有的资源和工具,极大的减少开发的工作量。

我们再考虑另外一种应用:带交互的信息查询机。带交互功能的信息机主要提供给人们索引和查询信息的功能,信息被分类并且根据索引存储,信息机的解析程序通过与用户的交互,显示所需要的信息。实际上,交互和索引跳转是网页天生的特性,所以,同样可以采用网页和浏览器的模式取代编写本地程序进行交互和解析,可以把几乎所有的工作转换成网页设计,从而将开发工作量减到了最低。

 

 

从 C/S 到 B/S 的转换

在嵌入式智能设备的应用中,有很大一部分是嵌入式智能终端需要与远程的服务器进行连接,通过人机交互和数据采集来实现应用,比较典型的就是点菜机。

无线点菜机是一种很常见的嵌入式智能设备,大多采用 Windows CE 或嵌入式 Linux 作为操作系统。如果把点菜机当成一个 PC 系统,那么这就是一个非常典型的客户端 / 服务器架构的应用。

图 2. 无线点菜机系统示例
图 2. 无线点菜机系统示例 

如图 2 所示,点菜机通过 WIFI 与服务器相连,通过网络与服务器通讯进行点菜操作,厨房端也有客户端与服务器相连,根据点菜情况进行菜品制作的安排。忽略 PC 和嵌入式开发的差异性,这个应用的实现过程和原理与 PC 基本是一致的。不同的是,通过网络实现业务的调用,如我们在前面提到过的,这在 PC 平台上已经有了很多资源甚至成熟企业应用框架,但是在嵌入式 Linux 平台上几乎都没有。所以,开发人员需要自己来实现点菜机和服务器之间的业务调用,进而与服务器端的业务处理部分进行集成。

对于服务器而言,最重要的部分就是数据库,要远程的操作数据库,PC 平台有大量的数据库组件实现对各种数据库的支持,但是在嵌入式 Linux 平台就没那么丰富的组件资源可以利用。所以,开发人员需要在服务器端实现一个“代理”,点菜机通过“代理”对数据库进行操作。这个“代理”不一定很复杂,但问题是,它仍然会占用大量的开发工作量,消耗工作时间。另外一个问题在于,当这个系统功能需要改变时,比如希望在点菜机界面增加显示图片的功能,客户端和“代理”端都需要同步进行开发,因此维护起来也是比较麻烦的。

实际上,这一类的应用在 PC 上现在大多已经由 C/S 模式转向了 B/S 模式。既然现在嵌入式 Linux 平台已经有了 Web 的支持,那么在嵌入式 Linux 平台 B/S 是否也同样适用呢。对于点菜系统这样的应用,假如我们在 pc 平台已经用 B/S 模式进行了实现(与很多企业应用相比,这是比较简单的)。只要嵌入式平台支持标准 Web,那么直接用嵌入式客户端的浏览器,这套系统的迁移很可能只需要考虑嵌入式设备的屏幕尺寸问题,把网页显示调整一下而已。

除了点菜机之外,物流行业中广泛使用的条码扫描机也是典型的应用之一。当货物入仓之时,工作人员用手持智能终端扫描条形码,数据会上传到服务器进行处理。如果采用 Web 方式,嵌入式 Linux 端开发工作量就会大大降低,只需要读取条码,然后通过 Web 输入即可。

 

 

小结

通过上面的方法,我们已经解决了在嵌入式 Linux 智能设备里提供标准 Web 支持,以及利用 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 的结构图
图 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 在目录 src3rdpartyWebKitWebCorebridge 中提供了一个 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 两个文件在目录 src3rdpartyWebKitWebKitqtApi 下。重新编译 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 src="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 的文档。

 

 

展望

PC 平台的软件技术在这些年有了很大的发展,体现在设计思想、开发工具、开发规模、模块服用和集成等方面。与之相比,嵌入式 Linux 上的开发总有些不尽如意,存在着应用简单、开发环境简陋、复用性低等问题。当 SaaS、Web2.0 等概念兴起,PC 与嵌入式 Linux 的软件开发水平的差距进一步的增加。

PC 平台的软件开发在经历了长久的发展之后,现在有向瘦客户端的方向发展的趋势。云计算等概念兴起,大量应用的负荷转移到了服务器端,很多应用都降低了对 客户端平台性能的要求。嵌入式平台性能近几年也有了较大的发展,频率上 G 的 CPU 已经得到普遍应用,2D/3D 和视频的加速在嵌入式 SoC 中也有了支持。

一方面是对性能要求的降低,另一方面是嵌入式平台性能的增长,两者不断的接近;并且当嵌入式 Linux 平台得到了 Web 的充分支持之后,PC 平台在 Web Server 方面的积累就可以被嵌入式 Linux 平台继承。以点菜机和条码扫描机为例,编写一个同时能被数十个客户端查询的服务器程序并不容易,但是,一个比较流行的 Web 服务器却能轻松的支持数千个并发。充分利用 Web 平台和技术,能让嵌入式的开发迅速发展,直接和 PC 平台开发站到同一起跑线上。

消费类电子领域的快速增长是近年来嵌入式发展的亮点。在手机上,浏览器应用已经成为最热点的领域,移动消费领域的客户也成为了各大 IT 巨头的争夺目标。在 Web 技术的继续发展下,如果应用服务都是通过网络提供,就可以由浏览器来解决嵌入式和 PC 平台的差异问题。当应用的问题被解决之后,由于嵌入式设备的可定制性和针对性,在很多特别应用模式下,PC 和嵌入式之间的界限正在变得越来越模糊,嵌入式 Linux 平台很可能会取代现今的 PC 平台,比如专门用来无线上网,以便随身处理邮件等事务的商务便携式设备;小巧精致,专用于欣赏在线影片和音乐的娱乐设备。而这正是各
CPU 巨头,如 Arm,Intel 等正在推动的移动平台发展的方向。

 

 

小结

本文先描述了当前嵌入式 Linux 开发中存在的问题,然后通过若干实际应用场景,阐述了用 Web 技术来改进嵌入式 Linux 设备开发的方式。文章随后着重描述了如何解决在嵌入式 Linux 中实现 Web 和本地代码配合的问题,以解决用 Web 取代传统开发方式中存在的障碍。

嵌入式 Linux 近年快速发展,在 IT 应用 Web 化这个发展方向已趋于明朗的背景下,在嵌入式 Linux 设备中提供 Web 支持,必然是将来嵌入式 Linux 开发平台中的一个非常重要的方面。