基于HTML5和Javascript的移动应用架构

如果你认为你能够无视终端用户的移动化需求,那请记住:当个人电脑刚出现时,企业中的IT 部门也曾对它们有抵制情绪。实际情...
       如果你认为你能够无视终端用户的移动化需求,那请记住:当个人电脑刚出现时,企业中的IT 部门也曾对它们有抵制情绪。实际情况会怎么样呢?移动设备的激增正在促使IT部门做出改变,他们必须支持移动设备,并紧接着开发出友好的移动设备应用程序。随着用户对移动设备越来越熟悉,他们对在移动设备浏览器中访问的应用程序的要求也越来越高。

      向用户提供强大的移动应用程序交互体验可以通过开发内建的应用或者基于HTML5和JavaScript的网页应用。内建的应用程序有利于提供更加丰富的用户体验,但你需要为不同类型的操作系统构建相应的应用程序,这是相当耗时和昂贵的。HTML5和JavaScript使开发独立于设备的用户界面成为可能。也就是说可以使用JavaScript组件库来创建用户界面,这些组件库利用HTML5元素呈现交互性的用户界面微件(Widget)。HTML5具有很多支持移动性和富用户界面开发的新特性。


      本文介绍了用以实现基于JavaScript/HTML5的移动设备富客户端应用程序的框架和结构。用户界面和导航元素全都是宿主于浏览器(browser-resident)的组件,而应用程序服务器的唯一角色就是提供让用户界面访问的JSON格式的数据。因为本文的意图是提供一些框架和应用程序结构的参考,所以示例只实现了最基本的特性。


移动设备应用开发考量


开发桌面浏览器应用程序的许多方法都可以被应用到基于移动设备浏览器的应用程序开发中。然而,开发移动设备应用程序存在一些开发桌面浏览器应用程序时不需要考虑的问题和挑战。将这些困难逐个击破是很重要的。


      看看屏幕分辨率的情况。随着屏幕尺寸越来越小,这就要求进行不同的用户界面设计以优化应用程序的交互体验。HTML5以及相应的移动设备UI微件(比如 jQuery Mobile)提供了使用JavaScript创建特定于移动设备的用户界面的跨设备方法。下面的屏幕截图是使用jQuery Mobile HTML5创建的列表视图:




       这个移动UI(mobile UI)是使用HTML5的role标识符和CSS创建的。下面列出了该列表视图的HTML标签代码片段。请注意data-role属性,其用于让jQuery Mobile呈现友好的移动设备用户界面组件。


           


          连接性是另一个要考虑的问题。即使目前3G网路和WIFI盛行,也并不能保证连接性每时每刻都正常。幸运的是,HTML5具有缓存特性,能让网站资源缓存在本地并在无连接的状态下被使用。可以通过添加下面所示的代码到根级别的HTML元素以启用缓存特性:


            
                      
                      ...
                      
            


           文本形式的Manifest文件定义了要被缓存的资源和不需要缓存的资源路径,以及当请求的资源不存在时应呈现什么。通过相关JavaScript API,还能控制缓存资源的更新和通知机制。下面是一个缓存特性的manifest文件示例:


    CACHE MANIFEST
    # 2012-27-18:v1
    
    # Explicitly cached resources.
    CACHE:
    index.html
    css/stylesheet.css
    Images/logo.png
    scripts/main.js
    
    # White Listed Resources, requires connectivity, will bypass cache
    NETWORK:
    http://api.twitter.com
    
    
    # offline.html will be displayed, if *.HTML are not available.
    FALLBACK:
    *.html /static.html


          另外,HTML5提供了将服务端数据缓存到本地以支持无连接状态下操作和提高性能的机制。一种HTML5的键值对本地存储机制能够存储字符串或者字符串形式的JSON对象。使用JavaScript的localStorage对象可以访问本地存储。下面的示例描述了怎样将Stock对象Backbone.Collection以JSON字符串的形式存储和提取。


    var d = JSON.stringify(data);
    localStorage.setItem('STOCKS', d);
    
    var d = localStorage.getItem('STOCKS');
    data = JSON.parse(d);


           我们还能让应用程序优雅的通知用户“连接不可用”,在启用HTML5的浏览器中,期间的远程数据访问请求将会按键值对的本地存储标准排列于本地。当连接恢复时,本地存储的对象会被更新到服务器。


           网络带宽是另一个要考虑的地方。利用AJAX,以及使用JavaScript对象标记法(JSON)的方式传递服务端数据,可以最小化访问服务器的次数和负载量。相比诸如XML或者基于SOAP的传统的交互协议,JSON格式更加高效和简洁。


    HTML5应用程序架构


          JavaScript从来都没有试图成为一般目的(general purpose)的应用程序开发语言。它的最初意图是通过允许在不用访问远程服务器的情况下动态地呈现和改变HTML来提高浏览器中应用程序的用户体验。利用JavaScript可以极大地提高应用程序的性能和交互体验。移动设备不具备桌面电脑或者笔记本中的浏览器所具有的计算能力和数据传输带宽,所以应该在客户端尽可能地利用JavaScript和HTML5元素来实现富用户界面,并将客户端与服务器之间的双向数据传输量降低到最小。


           这是将原本在服务端Web应用程序代码(JSP/ASP/PHP)中逻辑运算出来的动态HTML元素分离到客户端了。按照这种新的拓扑结构,服务端代码处理权限验证和数据访问请求,而用户交互和大多数应用程序逻辑则在客户端浏览器中执行。以下图表描述了这种拓扑结构:


          值得一提的另外一点是,JavaScript是弱类型的动态语言,具有闭包和将代码块作为数据类型等特性,从而提供了很多编程上的灵活性。但与此同时,也可能导致出现难以维护的代码。因此,用JavaScript编写应用程序时一定要小心谨慎。下面的列表列出了一般需要考虑的机制:


    • 导航(Navigation)
    • 远程数据访问
    • 验证/授权
    • 视图和应用模型间的解耦(MVC模式)
    • 模块化/打包
    • 依赖管理
    • 日志/跟踪
    • 异常处理


           在Java领域,有许多框架帮助我们构建层级的应用程序架构。在JavaScript领域,也存在相应的框架。下面列出的JavaScript框架可以用来构建模块化的、层级的和面向对象的JavaScript应用程序架构,以使应用程序更加可维护和稳定。


    Backbone.js

    Backbone允许以面向对象的方式将模型视图控制器(MVC)模式应用到JavaScript应用程序开发中。Backbone将HTML5用户界面、控制器以及对象模型隔离开来,并提供了用户界面间相互导航的标准机制。


    Require.js

    这是一个用于加载JavaScript文件和模块的框架。可以用该框架来加载和验证JavaScript模块/函数所依赖的其他JavaScript代码。开发者可以断言错误以在JavaScript模块或程序库没有被加载时获得依赖信息。


    _Underscore.js

    Underscore.js提供了在对象集合上的进行操作的公用方法。它还提供了HTML模板特性。


    JQuery

    JQuery库用来访问和操作HTML DOM元素。


    jQuery Mobile

    这是一个HTML5用户界面组件库,提供了基于HTML5的一系列UI控件。其包括事件处理机制、界面样式和界面操控。


    khsSherpa

    这是一个允许通过HTTP请求访问远程Java对象的框架。它提供了基于票据(token)的验证机制,以及自动将Java对象包装为JSON对象的机制。它默认启用了支持跨域的JSONP选项。


    JavaScript目录结构


          JavaScript没有提供组织编程元素的标准方法,其只是纯文本的.js文件。其他编程语言具有相关组织机制,比如Java中的包或者C#中的命名空间。将所有函数和对象定义在一个臃肿的文件中会导致难以维护,特别是当应用程序的大部分逻辑是由JavaScript所编写时。因此,可以在文件系统的根文件夹下定义其他文件夹以将JavaScript源码按其职责存放于相应的区域。



           一种方式是建立文件夹以存放应用程序的MVC模式的JavaScript元素。上图示例描述了这样的目录结构,其假设相关文件夹存在于web服务器/应用服务器的文件根目录下。


    模块化支持


          JavaScript没有用以分离源代码元素的内建机制。其他一些语言则有相关机制,比如Java使用包的概念,而C#使用命名空间的概念。然而,应用程序可以利用目录结构来建立相互依赖的模块。这样,应用程序和代码框架的模块化有利于维护和重用。


          Require.js框架提供了相关机制以有效地分离和模块化JavaScript文件,以及定义、引用和访问所依赖的模块。


           Require框架使用了异步模块定义(Asynchronous Module Definition)框架来定义和加载依赖的功能。下面列出了用以加载模块的Require/AMD函数:


    define([modules], factory function);


           每个模块是一个定义了JavaScript对象的独立JavaScript文件。当以上被调用时,相关模块会被加载,同时一个对象实体被创建并传递给factory函数以让开发者使用。下面的示例使用Require框架的define() 函数来加载所依赖的公用模块。


    define(["util"], function (util) {
              return {
              date: function(){
                     var date = new Date();
                     return util.format(date);
                }
          };
    });


    Require框架还具有用于最小化文件加载次数以提高性能的特性。


    Backbone MVC


          该框架使用JavaScript来实现流行的MVC设计模式。典型的web应用程序使用通用编程语言(general purpose language)在服务器端利用动态HTML生成技术来实现MVC模式,比如JSP/ASP或者某些HTML模板引擎。 该框架提供了相关组件或抽象用以处理用户输入以及将应用程序对象模型应用到动态HTML机制。Backbone框架提供了在JavaScript中应用这些机制的方式,而不是在服务器端生成HTML标签。


    HTML模板


          Backbone视图只是一个应用了HTML5的role属性的静态HTML文件。Backbone提供了模板机制,其在试图/控制器机制生成视图时将模型属性应用到HTML。应用程序示例使用HTML5的role属性定义了一个JQuery Mobile列表视图。下面列出了HTML5视图stock-list.html


    "stockListContainer" data-role='content'>
              
      "listview" id='tcStockList' data-inset="true" data-filter="true">


           列表视图的stock条目定义在stock-list-item.html中。其使用了Backbone模板机制将stock模型(JSON对象)的数据展示出来。具有模板元素的HTML列出如下:


    '#<%=ticker%>'>
              

    <%= ticker %>

              

    <%= name %>

              

    Price: <%= price %>


           请注意,上面的模板表达式和JSP/ASP的相类似,<%= %>用以标识将被替换成JSON格式的模型对象的相关属性值的位置。HTML模板位于模板文件夹中。


    视图/控制器


           Backbone框架通过路由到Backbone控制器实现来呈现HTML视图。控制器将JavaScript对象绑定到HTML视图并告诉框架呈现视图,同时定义和处理事件。用Backbone的术语,视图就是控制器,创建Backbone视图是通过扩展框架所提供的Backone.View对象来达到的。


           下面是stockListPage.js视图控制器示例的部分代码,其使用require.js框架加载了所需要的JavaScipt代码文件(.js文件)。该代码调用了返回Backbone视图控制器函数的JavaScript函数define([modules,...], controller())。请注意该函数是怎样扩展Backbone.View对象的。Require框架的Define函数的优势在于它用于加载视图控制器实现所需要的依赖模块。请注意模型和HTML模板模块是怎样提供给视图/控制器模块对象的:


    define([
                    'jquery',
                    'backbone',
                    'underscore',
                    'model/stockListCollection',
                    'view/stockListView',
                    'text!template/stock-list.html'],
                    function($, Backbone, _, StockListCollection, StockListView, stockListTemplate) {var list = {};
                                return Backbone.View.extend({
                                          id : 'stock-list-page',


          当一个视图/控制器实例被创建时,initialize函数被调用以定义事件和初始化控制器的模型,该模型可以是单独的对象或者对象的集合。


           请注意在stockListPage.js示例的视图/控制器的初始化函数中,有一个StockListCollection对象被创建。集合也是Backbone支持的对象,其提供了为视图管理JavaScript对象模型“集合”的方式。当控制器被调用时,initialize() 方法就被执行。当实例被创建时,它利用jQuery选择符为按钮绑定事件处理函数。下面列出了初始化函数的代码片段:


    var list = {};
    return Backbone.View.extend({
              id : 'stock-list-page',
              initialize : function() {
                        this.list = new StockListCollection();
    
                        $("#about").on("click", function(e){
                                  navigate(e);
                                  e.preventDefault();
                                  e.stopPropagation();
                                  return false;
                        });
    
    
                        $("#add").on("click", function(e){
                                  navigate(e);
                                  e.preventDefault();
                                  e.stopPropagation();
    
                                  return false;
                        });
    
    
              },


           通过将jQuery选择符和表单事件对应到方法名来将事件和视图/控制器方法关联起来。下面的代码描述了和stock列表页面的添加按钮相关的事件和处理函数。请注意代码中的导航命令,我们将在下一节中讨论他们。


    events: {
     "click #about" : "about",
     "click #add" : "add",
    },
    about : function(e) {
    
              window.stock.routers.workspaceRouter.navigate("#about",true);
              return false;
    },
    
    add : function(e) {
              window.stock.routers.workspaceRouter.navigate("#add",true);
              return false;
    },


           render() 方法被传递到一个实例时视图/控制器HTML模板被呈现。stockListPage.js的render函数列在下面。你能够看到它是如何编译模板,接着展现绑定到控制器的el属性的HTML模板。this.el属性就是控制器在DOM中插入HTML代码的地方。接着,请注意另一个视图/控制器是如何被实例化和呈现的。StockListView控制器用于呈现stock的JQueryMobile列表视图。


              render : function(eventName) {
                        var compiled_template = _.template(stockListTemplate);
                        var $el = $(this.el);
                        $el.html(compiled_template());
                        this.listView = new StockListView({
                                  el : $('ul', this.el),
                                  collection : this.list
                        });
                        this.listView.render();
                        return this;
                        },
              });
    });


    导航(Navigation)


          控制器视图间的导航是Backbone提供的另一个机制。Backbone暗含着这种“导航”的意思,其提供了可扩展的Backbone.Router对象来定义导航路由。下面列出了示例应用程序的路由代码:


    更多 backbone 相关内容