内容


使用 Google Gears 开发离线应用

Comments

开始之前

您是否设想过,拔掉网线但依然能够访问网页,并在该网页上的操作都有效,这样一个情景?

当网络状况总处于糟糕的状态,你就会为自己在线辛辛苦苦填写了半天的数据将无法提交而苦恼着(数据甚至会丢失)。那有没有一个策略来处理这个糟糕的情况,甚至可以让用户不考虑网络连接与否(网络连接不应该是用户关心的事情)?

这些虽然都是遐想,但 Google 在致力把它们变成现实(从开发者的角度去看,这些情况可以描述为,用户浏览器离线状态下,用户提交的数据将无法与服务器同步)。Google 在 2007 年 5 月发布了 Gears,它是一种为生成离线网络应用软件而开发的开源技术,Google 希望能在广大开发者的帮助下测试完善功能和发现产品的局限性。简单地说,它是 Google 推出的开源的浏览器插件,而开发者围绕着这个插件进行开发。2008 年 3 月,Google 推出了移动版 Gears,应用进程开始蔓延。

关于本教程

本教程首先介绍了 Google Gears 的基本概念和各个模块,采用基于 Java 的 Web 开发与 Google Gears 结合,提供了为解决实际需求而搭建离线应用示例。

目标

通过本教程的学习,您将了解到当今离线应用的各种策略,并且能够学习到基于 Gears 的离线开发技术。您将会意外地发现,Gears 各项离线技术(3 个模块)可以分离使用,例如客户端本地存储,网页捕获机制,Javascript 后台工作池等。

先决条件

要学习本教程,您需要对 Ajax 技术有一定的了解,并且熟悉 JSP。

安装要求

本文示例均在 Windows XP SP2 系统上测试完成。您要确保以下工具在系统上有效(参见 参考资源):

  • 浏览器:
    • Firefox 1.5 或更高版本
    • Internet Explorer 6 或更高版本
  • JDK 1.4 或以上版本;
  • Tomcat 5.5 或以上版本;
  • 开发工具选用 Eclipse(可选);

Google Gears 介绍

Google Gears 是一个开源的浏览器插件,它支持用户浏览器能够使用开发者创建的基于 Gears 的离线应用。它提出了一种离线应用的思想,它的三大核心模块将帮助开发者更好地运用这个思想来开发。Gears 技术是基于客户端语言 JavaScript 技术的扩展。

架构

Gears 的离线思想:参考以下 Gears 的架构图,正常(网络连接正常)的 Web 应用在架构图中走的路线是沿着水平线横着与 Internet 通信。如果网络连接失败,用户依然能够访问,Data Switch 组件则不向 Internet 提交数据(即使提交也会失败),而向本地的客户端提交数据,将数据暂存在客户端本地的轻便型数据库里(Gears 使用的是 sqlite),等待到网络连接恢复时,再选择向 Internet 提交那些暂存的待提交的数据。

图 1. Gears 架构
图 1. Gears 架构
图 1. Gears 架构

此时,您将会产生一些疑问:

  1. Gears 用什么语言和 sqlite 交互?
  2. 网络恢复后再向服务器提交数据,意味着从 sqlite 里读取数据并删除等 IO 操作,浏览器如何承受如此重的负担?
  3. 网络连接失败真的依然能访问网页?

Gears 的三大核心模块将告诉您如何做到这些。

三大核心模块

  • 本地服务器:

    此模块用于存储网络资源文件,以便离线情况下访问网页。

  • 本地数据库:

    此模块用于存储和访问用户离线情况下提交的数据。

  • 工作者池:

    它是一个提高 JavaScript 工作效率和页面性能的模块,使 JavaScript 代码像后台进程一样运行而不影响用户访问网页。

初识 Gears

访问 Google 的 Gears 主题页 下载 Google Gears 的安装文件,安装时确保网络连接正常。

安装完后,就可以使用 Gears 了。对于 Gears 开发,Google 网站 还提供了很多有用的工具和 Sample。

有兴趣的读者可以自行研究一下。在此,我们仅使用 Google 提供的一个本地数据库的操作工具。解压下载好的包,您将发现包内 tools 文件夹里的 dbquery.html 文件。将 tools 文件夹放置 Tomcat 目录下的 webapp 文件夹下,启动 Tomcat 服务器,确保您能够访问 dbquery.html。访问成功,您将看到这样的页面。

图 2. dbquery.html 页面
图 2. dbquery.html 页面
图 2. dbquery.html 页面

使用这个工具,您将可以访问 sqlite。在 tools 文件夹里您还会发现名为“gears_init.js”的 JavaScript 文件,此文件将是所有 Gears 应用的向导文件。

开发准备

需求

您也许会碰到这样一个情况,在线填写工作报告的时候突然网络断了,此时只能匆忙地将填写的内容复制粘贴到临时文件里,等待下次再重新填写提交。您现在面临的需求就是搭建一个工作报告提交系统,支持离线作业,让用户不再为网络连接状况而头疼。

开发环境

当您确保能够 Gears 的安装和 Tomcat 服务器上流畅地运行 JSP 页面后,我们就可以着手编写一个简单的基于 Ajax 技术的网络应用。在此,我们选用方便快捷的工具 Eclipse 来搭建我们的应用。

一,首先在 Tomcat 的 webapps 目录下新建一个名为“gears”的文件夹。

图 3. 新建一个名为“gears”的文件夹
图 3. 新建一个名为“gears”的文件夹

并在 gears 文件夹内创建 js 和 WEB-INF 两个子文件夹。WEB-INF 文件夹内确保存在 classes、lib 和 src 三个文件夹。最后将 TOMCAT_HOME\webapps\ROOT\WEB-INF下 的 web.xml 复制到 TOMCAT_HOME\webapps\gears\WEB-INF。结构如下:

图 4. 目录结构
图 4. 目录结构
图 4. 目录结构

二,在 Eclipse 里创建一个项目。

打开 Eclipse,左上角 File->New->Project 里选择 Java Project,点击 Next,此例将 Project 命名为“gears”。

图 5. 将 Project 命名为“gears”
图 5. 将 Project 命名为“gears”

三,JavaScript代码。

首先要在 tools 文件夹下的向导文件 gears_init.js 复制到 gears\js 下。

选中 gears 项目里的 js 文件夹单击右键,New->Other 里选择 JavaScript,命名为“common.js”。

图 6. 选择 JavaScript
图 6. 选择 JavaScript
图 6. 选择 JavaScript

修改 common.js,代码如下:

init();
//-------------------------------------------------------------------------
  /**
    * init
    * 检查浏览器是否安装Gears插件,如果没安装,转向Gears安装的网址
    */
function init() {
  var url = window.location.href;
  if (!window.google || !google.gears) {
    var message = "Sorry, you must install Google Gears first!";
    var url = window.location.href+"";
    location.href = 
      "http://gears.google.com/?action=install&message="+ message + "&return="+url;
  }
}

//-------------------------------------------------------------------------
  /**
    * createXMLHttpRequest
    * 创建XMLHttpRequest对象
*/
var request;
function createXMLHttpRequest() {
  
  /*
//方式一:古老的方式创建XMLHttpRequest对象
  if (window.ActiveXObject) {
     request = new ActiveXObject("Microsoft.XMLHTTP");
  }else if (window.XMLHttpRequest) {
     request = new XMLHttpRequest();
  }
  */

//方式二:使用gears api轻松创建XMLHttpRequest对象(推荐)
  try{
    request = google.gears.factory.create('beta.httprequest','1.0');
  }catch (ex) {
    return;
  }
}

  /**
    * doHttpRequest
    * 向action.jsp发送request请求
    */
function doHttpRequest() {
  
  createXMLHttpRequest();
  //组合欲向服务器传递的参数串
  var query = createQuery();
  var url = 'action.jsp?'+query;
  
  request.onreadystatechange = callback;
  request.open('GET',url, true);
  request.send(null);
  
}
  /**
    * createQuery
    * 获得输入框内容,组合成URL请求字符串
    */
function createQuery() {
//获得页面控件的输入数据
  var name = document.getElementById('name').value;
  var title = document.getElementById('title').value;
  var content = document.getElementById('content').value;
  
  var query = "name="+name+"&title="+title+"&content="+content+"&Timestamp=";
  return query;

}

  /**
    * callback
    * 回调函数,当服务器成功相应,则显示反馈消息,并清除输入框内容 
    */
function callback() {

  if(request.readyState == 4) {
  	if(request.status == 200) {
  		alert(request.responseText);
        clear();
  	}
  }
}
  /**
    * clear
*清除输入框内容 
 */
function clear(){
  document.getElementById('name').value = '';
  document.getElementById('title').value = '';
  document.getElementById('content').value = '';
  
}

四,创建 JSP 网页。选中 gears 项目单击右键,New->Other 里选择 JSP,命名为“index.jsp”。

图 7. 创建 JSP 网页
图 7. 创建 JSP 网页
图 7. 创建 JSP 网页

修改 index.jsp,代码如下:

<%@ page language="java" 
    contentType="text/html; charset=gb2312" 
    import="java.util.*,java.sql.*,java.text.*"%>
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" 
    "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=gb2312">
<title>Send Your Report</title>

<!-- 导入js -->
<script type="text/javascript"  src="js/gears_init.js"></script>
<script type="text/javascript" src="js/common.js"></script>

<!-- CSS样式 -->
<style type="text/css">
.input {
  border:1px solid grey;
  margin-top:2px;
  width:190px;
}
.warn{
	color:red;
}
.frame{
   width: 500px;
   border-bottom-width: thin; border-bottom-style: dotted ;
}
</style>
</head>
<body>
<div id="head" class="frame">
<h1>Send Your Report</h1>
</div>

<div id="body" class="frame">

<!-- Java代码获得当前日期 -->
today: 
<% 
Calendar c = Calendar.getInstance(); 
SimpleDateFormat sdf = new SimpleDateFormat();
sdf.applyPattern("yyyy-MM-dd");
String date=sdf.format(c.getTime());
out.print(date);
%> <br />

<!-- 输入框 -->
name: <input type="text" name="name" id="name" class="input" />
<br />
title:   <input type="text" name="title" id="title" class="input" />
<br />
report:<br />
<textarea cols="70" rows="6" name="content" id="content"
	style="width: 280px; height: 113px;"></textarea> 

<br />
<br />

<!-- 提交按钮-->
<input type="button" value="submit" onclick="doHttpRequest();"  id="submit"/>
<br /><br />
</div>

<div id="buttom" class="frame"> 
</div>

</body>
</html>

选中 gears 项目单击右键,New->Other 里选择 JSP,命名为“action.jsp”。

修改代码如下:

<%@ page import="java.util.Date" %>
<%
String name = request.getParameter("name");
String title = request.getParameter("title");
String content = request.getParameter("content");
String Timestamp = request.getParameter("Timestamp");
//获得时间
if(Timestamp.equals("") || Timestamp==null) {
	Timestamp = new Date().toString();
}

String responseText = "";

//验证
if(name==null||name.equals("")) {//if no name 
	responseText = "Enter your name!";
	out.println(responseText);
			
	//关闭
	out.close();
	return;
}

// 所有事情都解决了,则前台和后台都输出成果
responseText 
  = "The "+name+"'s report(Title:"+title+") is sent successfully at "+Timestamp;
out.println(responseText);
System.out.println(responseText);

%>

五,测试您的网页。

在此,您完成了一个简单的 Ajax 技术的应用。启动 Tomcat,测试一下您的应用是否搭建成功。

首先在客户端浏览器打开 index 页,将看到 Report 应用主页,确保网络连接正常,模拟提交一份工作报告,浏览器将弹出服务器端返回的提示信息,您将看到如下画面:

图 8. 提示信息
图 8. 提示信息
图 8. 提示信息

在服务器端的 Tomcat 应用服务器的后台弹出信息(System.out),代表服务器成功接收到来自客户端提交的数据并进行简单处理。

图 9. Tomcat 后台弹出信息
图 9. Tomcat 后台弹出信息
图 9. Tomcat 后台弹出信息

检测网络状况

要为传统的基于 Ajax 的 Web 应用添加离线应用的功能,首先要让它拥有实时检测网络状况的能力。

一,我们将编写 js 代码用于检测用户的网络状况。

在 common.js 原有代码的基础上添加如下代码:

//-------------------------------------------------------------------------
/*
* 以下方法用于检测网络状况
*/

  /**
    * sendRequestForCheck
    * 定期向服务器上的home.jsp发送请求,用来检测网络状况
    * home.jsp是空代码文件,在应用中不做任何操作
    */
function sendRequestForCheck() {
  
  createXMLHttpRequest();
  
  var url = "home.jsp";
  request.open("GET", url, true);
  request.onreadystatechange = processCurrentResponse;
  request.send(null); 
  
}

  /**
    * processCurrentResponse
    * 
    * 根据请求的状态参数来判断网络状况
    */
function processCurrentResponse() {
  if(request.readyState == 4) {
    try{
      if(request.status == 200) {
         setPageStatus("online");
      } else{
         setPageStatus("offline");
      }
    }catch(ex) {
       setPageStatus("offline");
    }
  }
}

  /**
    * setPageStatus
    * 页面上显示出网络状况(离线状态或者在线状态)
    */
function setPageStatus(sta){
  if(sta=="online"){
    addStatus("online");
    document.getElementById('sync').disabled = false;
    setTimeout("sendRequestForCheck()", 1000);
  }else{
    addStatus("offline");
    document.getElementById('sync').disabled = true;
    setTimeout("sendRequestForCheck()", 1000);
  }
}
  /**
    * addStatus
    * 显示出数据
    */
function addStatus(message){
  var status = document.getElementById("status");
  status.innerHTML = message;
}

二,修改 index.jsp 代码,在 <div id="buttom" class="frame"> </div> 内添加代码如下:

<div id="buttom" class="frame"> 
status:<span id="status"></span>
<input type="button" value="sync" onclick="asyncExc()" id="sync"/>
</div>

在 <body> 标签里加上:

<body onLoad="sendRequestForCheck();">

三,创建相应文件。

选中 gears 项目单击右键,New->Other 里选择 JSP,命名为“home.jsp”。在 home.jsp 里把所有的源代码都删除掉,即 home.jsp 是空代码文件。

图 10. 创建 JSP 文件
图 10. 创建 JSP 文件
图 10. 创建 JSP 文件

四,测试。启动 Tomcat,打开 index.jsp。网页的底部将出现网络状态的显示。暂时先不理睬 sync 按钮的用途。

图 11. 网络状态显示
图 11. 网络状态的显示
图 11. 网络状态的显示

如果是单机版的测试运行(服务器和客户端是同一台机器),我们可以关闭 Tomcat 服务器来模拟客户端断线的情景(服务器与客户端失去连接,即网线离线)。关闭 Tomcat 后,您将看到网页显示的状态变为 offline。

图 12. 关闭 Tomcat 后的网络状态
图 12. 关闭 Tomcat 后的网络状态
图 12. 关闭 Tomcat 后的网络状态

在解释一下 JS 代码的用途:在服务器端有一个 home.jsp(空代码文件),是为了让客户端的 js 代码发送请求检测而用,如果客户端与服务器失去连接,则客户端无法访问到服务器的 home.jsp,根据请求的返回参数可以判断客户端是离线状态。而 home.jsp 在应用中没有任何用途。

离线访问网页

马上要进入精彩的 Gears 世界了。这一节涉及到 Gears 技术的核心模块之一,讲述如何在线捕获网页资源,离线状态可以随意访问已捕获的资源。

一,首先创建 JSON 文件。

选中 gears 项目单击右键,选择 New->File。将文件命名为“my_menifest.json”。

图 13. 命名文件
图 13. 命名文件
图 13. 命名文件

打开 my_menifest.json,然后编辑 JSON 文件:

{
  "betaManifestVersion": 1,
  "version": "version 1.4",
  "entries": [
      { "url": "."},
      { "url": "index.jsp"},
      { "url": "js/common.js"},
      { "url": "js/gears_init.js"}
    ]
}

观察 JSON 文件,我们可以看到“entries”里指定了很多 url,这些 url 就是我们离线访问时需要的资源。接着我们将使用 Gears API 来读取该文件。

二,在 common.js 原有代码的基础上添加如下代码:

//-------------------------------------------------------------------------
/**
*local Store module
* 以下是local Server模块的应用函数
*/
var localServer;
var STORE_NAME = 'test_store';
var store;
var MANIFEST_FILENAME = 'my_manifest.json';

  /**
    * setOffline
    * 此方法用于页面显示,当用户点击时被调用
    * 
    */
function setOffline() {

  //使页面显示离线应用有效
  setShowStatus(1);

  //打开本地资源存储并捕获网络资源
  openLocalStore();
  capture();

}
  /**
    * openLocalStore
    * 该方法用于内部
    * 创建local server,并打开local store
    */
function openLocalStore() {
  
  if (window.google && google.gears) {
    try {
      //创建local server
      localServer =
          google.gears.factory.create('beta.localserver', '1.0');
          
    } catch (ex) {
      //使得submit按钮无效
      var buttons = document.getElementById('submit');
      buttons.disabled = true;
      
      //显示错误信息
      var offline = document.getElementById("offline");
      offline.style.color = "red";
      offline.innerHTML = "<strong><u>Could not create local server</u></strong> "; 
      
      return;
    }
  }
  //创建 local store
  store = localServer.createManagedStore(STORE_NAME);
  
}

  /**
    * capture
    * 该方法用于内部
    * 捕获JSON文件里指定的网络资源 
    * 
    */
function capture() {
  
  //确保local store已经创建
  if (!store) {
    addStatus('Please create a store for the captured resources');
    return;
  }
  
  //显示信息
  addStatus('Capturing...');

  // JSON文件里指定了我们需要的资源文件,读取它并捕获这些文件
  store.manifestUrl = MANIFEST_FILENAME;
  store.checkForUpdate();
  
  //显示信息
  addStatus('Capturing is done!');
}

  /**
    * removeStore
    * 该方法用于页面,当用户点击时调用
    * 删除local store,用户只能在线使用,并显示离线应用无效信息。
    */
function removeStore() {
  if(localServer){
    if (localServer.openManagedStore(STORE_NAME)) {
    
      //删除local store
      localServer.removeManagedStore(STORE_NAME);
      store = null;
      
      //显示信息
      addStatus('Removed the store');
    } else {
    
      //显示信息
      addStatus('The store does not exist');
    }
  }else{
    //显示信息
    addStatus('The localServer does not be created');
  }
  
  //显示离线应用无效
  setShowStatus(0);
}

  /**
    * setShowStatus
    * 
    * 在页面上显示离线应用有效或无效.
    */
function setShowStatus(flag) {
  var offline = document.getElementById("offline");

  if(flag==0){
    offline.style.color = "red";
    offline.innerHTML = "<strong>Work online only</strong> ";  
    status = 0;
    
  }else if(flag==1){
    offline.style.cursor = "auto";
    offline.style.color = "green";
    offline.innerHTML = "<strong>Offline Access effective</strong>";
    status = 1;
  }
  
}

三,在 index.jsp 代码中 <div id="body" class="frame"> 里添加代码如下:

... ...
<div id="body" class="frame">
<!-- 添加部分 -->
<span id="offline" class="warn"><strong>Work online only</strong> </span>
<br />
<input type="button" value="Work offline available" onclick="setOffline()"/>
<input type="button" value="Work just online" onclick="removeStore()"/>
<br />

<!-- Java代码获得当前日期 -->
today:
... ...

四,测试。

启动 Tomcat,再次访问您修改好后的应用主页。

图 14. 应用主页
图 14. 应用主页
图 14. 应用主页

点击“Work offline available”按钮,使得 Report 系统能够离线工作。然后让网络连接断开(如果单机版操作,即服务器和客户是同一台机器,可以将 Tomcat 服务器关闭来模拟网络连接断开的场景),如图:

图 15. 网络连接断开
图 15. 网络连接断开
图 15. 网络连接断开

此时,您处于 offline 的状态(与服务器通讯失败),接下来我们再试着与服务器通讯访问这个页面,为了确保不是由于浏览器缓存而依然能够访问的页面,我们先把页面缓存数据全部清除。

图 16. 清除页面数据缓存
图 16. 清除页面数据缓存
图 16. 清除页面数据缓存

重新打开一个新的浏览器,输入该网页的 URL,访问它。访问是成功的!因为 Gears 已经把 my_menifest.json 里指定要捕获的网络资源给存储到本地的存储源里。浏览器的“工具”选项里有一个 Google Gears Setting 的选项,您可以将 Gears 存储的那些访问过的网址资源给清除。清除后,您将无法离线访问该网址。

图 17. Google Gears Setting 选项
图 17. Google Gears Setting 选项
图 17. Google Gears Setting 选项

离线存储数据

离线访问操作依赖于 Gears 的资源捕获,即 Local Store。而离线存储数据,与 Gears 的本地数据库操作有关。Gears 使用的轻便型数据库是 sqlite,最幸运的是,我们只需要会使用 Gears 提供的方法就能轻松访问 sqlite。

一,首先修改 common.js

在 common.js 原有代码的基础上添加如下代码:

//-------------------------------------------------------------------------
/**
*local Database module
* 以下方法是关于本地数据库的操作
*/
var db;
var success = false;
  /**
    * openDB
    * 
    * 打开本地数据库
    */
function openDB() {
  
  if (window.google && google.gears) {
    try {
      db = google.gears.factory.create('beta.database', '1.0');

      if (db) {
        // 打开database-demo数据库
        db.open('database-demo');
                   
        db.execute('create table if not exists report'  +
                   ' (name varchar(255), '
                   +' title varchar(255), '
                   +' content varchar(255), '
                   +' Timestamp int);');

        success = true;
      }

    } catch (ex) {
      addStatus('Could not create database: ' + ex.message);
    }
  }
// 使所有控件都失效
  var inputs = document.getElementsByTagName('input');
  for (var i = 0, el; el = inputs[i]; i++) {
    el.disabled = !success;
  }
}

  /**
    * insert
    * 
    * 向本地数据库插入信息
    */
function insert(name, title, content, currTime) {
  if (!google.gears.factory || !db) {
    return;
  }
  // Insert the new item.
  // The Gears database automatically escapes/unescapes inserted values.
  db.execute('insert into report values(?, ?, ?, ?)', [name, title, content, currTime]);
 
}

  /**
    * LocalSubmit
    * This method is used internally
    * 
    * 从页面获取数据并插入本地数据库中
    */
function LocalSubmit() {
  
  //获得页面数据
  var name = document.getElementById('name').value;
  var title = document.getElementById('title').value;
  var content = document.getElementById('content').value;
  
  //获得当前时间
  var currTime = "";
  var d =new Date();
  currTime +=  d.getFullYear()+ "-";
  currTime += (d.getMonth() + 1) + "-";
  currTime += d.getDate();
  currTime += " "+d.getHours();
  currTime += ":"+d.getMinutes();
  currTime += ":"+d.getSeconds();

  //插入数据
  insert(name, title, content, currTime);
  
  //清除输入框 
  clear();

}

在 common.js 中的 init 函数里添加:

function init() {
  var url = window.location.href;
  if (!window.google || !google.gears) {
    var message = "Sorry, you must install Google Gears first!";
    var url = window.location.href+"";
    location.href = "http://gears.google.com/?action=install&message="+ message +
        "&return="+url;
  }
  //添加部分
  openDB();
}
修改common.js中的callback函数
function callback() {

  if(request.readyState == 4) {
    try{
      if(request.status == 200) {
//在线,返回response信息
         alert(request.responseText);
         clear();
      }else{
//离线状态 
         if (!store) {
//离线状态下没有设置本地存储源 
            alert("Your network is ineffective now! Please set 
              offline next time you come!");
            sendRequestForCheck();  
            return;
         }
//离线状态下设置了本地存储源,则向本地数据库提交用户数据 
         alert("Your network is ineffective now! But you can still work offline!");
         LocalSubmit(); 
         sendRequestForCheck();     
      }
}catch(e){
//网络参数出错情况下,没有设置本地存储源
      if (!store) {
          alert("Your network is ineffective now! Please set offline!");
sendRequestForCheck();
          return;
       }
//网络参数出错情况下,设置了本地存储源,则向本地数据库提交用户数据 
       alert("Your network is ineffective now! But you can still work offline!");
       LocalSubmit();
       sendRequestForCheck();
    }
  }
}

二,测试。

首先,为了方便测试,我们把 Google Gears 提供的 tools 文件夹复制到 gears 项目下。

图 18. tools文件夹
图 18. tools文件夹

打开浏览器,清除页面缓存和 Google Gears 的本地存储资源。

图 19. 清除页面缓存和 Google Gears 的本地存储资源
图 19. 清除页面缓存和 Google Gears 的本地存储资源

接下来,访问网页。

图 20. 访问网页
图 20. 访问网页

然后让网络连接断开(如果单机版操作,即服务器和客户是同一台机器,可以将 Tomcat 服务器关闭来模拟网络连接断开的场景),如图:

图 21. 断开网络连接
图 21. 断开网络连接

输入测试数据,点击 submit 按钮。因为用户没有设置离线工作有效(没有点击 Work offline available 按钮),数据提交失败,将看到以下提示:

图 22. 数据提交失败
图 22. 数据提交失败
图 22. 数据提交失败

点击 work offline avaliable 按钮,如图:

图 23. 点击 work offline avaliable 按钮
图 23. 点击 work offline avaliable 按钮
图 23. 点击 work offline avaliable 按钮

此时,您已经捕获了网络资源,可以离线访问该网站,并且可以成功提交数据,而数据则会提交给客户端上的本地数据库 sqlite。提示如图:

图 24. 提交数据成功
图 24. 提交数据成功
图 24. 提交数据成功

此时,用户已经把数据提交成功了,数据存到了客户端本地的 sqlite 里。如何获知数据已经成功到达本地数据库中?将网络连接恢复,我们访问一下 tools 文件夹里的 dbquery.html,我们将看到我们的测试数据。输入数据库名:database-demo。

图 25. 输入数据库名
图 25. 输入数据库名
图 25. 输入数据库名

点击 Execute 按钮:

图 26. 击 Execute
图 26. 击 Execute
图 26. 击 Execute

此时您将看到 js 语句里建立的名为 report 的表。输入查询语句:Select * from report;,您将看到以下:

图 27. 查看名为 report 的表
图 27. 查看名为 report 的表
图 27. 查看名为 report 的表

同步数据

好了,到了这一步我们已经能够将离线状态用户提交的数据暂存,当网络状况好转的时候,如何把数据同步到服务器上?这里将涉及到 Gears 的工作者池模块。

访问 report 主页我们观察到 sync 按钮,它的用途是将本地 sqlite 的数据上传到服务器上。

一,首先,在 common.js 原有代码的基础上添加如下代码:

//-------------------------------------------------------------------------
/**
*WorkerPool module
*/
var workerPool;
var childID;
  /**
    * WorkerPoolInit
    * 
    * 创建工作者池
    */
function WorkerPoolInit() {
  if (!window.google || !google.gears) {
    return;
  }

  try {
    workerPool =
        google.gears.factory.create('beta.workerpool', '1.0');
  } catch (ex) {
    document.getElementById('sync').disabled = true;
    alert('Could not create worker pool: ' + ex.message);
    return;
  }
  //设置父worker对子worker返回信息的处理
  workerPool.onmessage = parentHandler;


  // 创建子worker
  try {
   childId = workerPool.createWorkerFromUrl('js/worker.js');
  } catch (e) {
   alert('Could not create worker: ' + e.message);
  }

}

  /**
    * parentHandler
    *  处理子worker返回的信息
    */
function parentHandler(msg, sender) {
  alert('Asynchronous result: ' + msg);
}

  /**
    * asyncExc
    * 用户点击sync按钮将触发子worker的工作
    */
function asyncExc() {
  if (workerPool) {
    var message = 'sync';
    workerPool.sendMessage(message, childId);
  }
}

这里提到的父 worker 和子 worker 有点类似于主进程与子进程,父 worker 将调用子 worker 的后台操作。

二,在 common.js 中的 init 函数里添加:

function init() {
  var url = window.location.href;
  if (!window.google || !google.gears) {
    var message = "Sorry, you must install Google Gears first!";
    var url = window.location.href+"";
    location.href = "http://gears.google.com/?action=install&message="+ message +
        "&return="+url;
  }
  openDB();
//添加部分
  WorkerPoolInit();
}

三,创建一个子 worker 的工作代码,在 gears/js 里创建 worker.js,添加代码如下:

var db;
var request;
var count=0;
  /**
    * createXMLHttpRequest
    * 创建httpRequest对象
    */
function createXMLHttpRequest() {
  try{
    request = google.gears.factory.create('beta.httprequest','1.0');
    
  }catch (ex) {
    return;
  }
}
  /**
    * openDB
    * 打开本地数据库
    */
function openDB() {
    try {
      db = google.gears.factory.create('beta.database', '1.0');
      if (db) {
        db.open('database-demo');
        db.execute('create table if not exists report'  +
                   ' (name varchar(255), '
                   +' title varchar(255), '
                   +' content varchar(255), '
                   +' Timestamp int);');
      }
    } catch (ex) {
       return;
    }
}
  /**
    * childInit
    * 
    * 子worker初始化
    */
function childInit() {
  openDB();
  google.gears.workerPool.onmessage = childHandler;
}

  /**
    * childHandler
    * 子worker跟据父worker发来的信息判断是否同步操作
    */
function childHandler(msg, sender) {

  if(msg=='sync') {//if the message recieved from parent is 'sync'
    sync();
  }
   
  //最后给父worker发送消息
  google.gears.workerPool.sendMessage(''+ count + ' done', sender);
  
  //clear variable
  count=0;
}
  /**
    * sync
    * 从本地数据库查询出数据,上传到服务器,并删除这些数据
    */
function sync() {
  if(!db) {
    return;
  }
  rs = db.execute('select name,title,content,Timestamp 
    from report order by Timestamp desc');
  var name;
  var title;
  var content;
  var Timestamp;
  
  while (rs.isValidRow()) {
    name = rs.field(0);
    title = rs.field(1);
    content = rs.field(2);
    Timestamp = rs.field(3);
    //同步操作
    upload(name,title,content,Timestamp);
    deleteBy(name,title,content,Timestamp);
    count++;
    rs.next();
  }
  //最后
  rs.close();
}
  /**
    * upload
    * 将数据上传到服务器
    */
function upload(name,title,content,Timestamp) {

  createXMLHttpRequest();
  var url = '../action.jsp?name='+name+'&title='+title+'&content='+content
+'&Timestamp='+Timestamp;
  
  request.onreadystatechange = uploadCallback;
  request.open('GET',url, true);
  request.send(null);

}

  /**
    * uploadCallback
    * 回调函数,什么也不做
    */
function uploadCallback() {

  if(request.readyState == 4) {

    if(request.status == 200) {
       //if you want to do sth else
    }
  }
}
  /**
    * deleteBy
    * 从本地数据库中删除数据
    */
function deleteBy(name,title,content,Timestamp) {
  if(!db) {
    return;
  }
  db.execute('delete from report where name=? 
    and title=? and content=? 
    and Timestamp=?', [name,title,content,Timestamp]);
  
}
childInit();

四,测试

首先把页面缓存和 Gears 本地存储资源完全清除,访问页面,并点击“work offline available”。

图 28. 访问页面
图 27. 访问页面
图 27. 访问页面

然后让网络连接断开(如果单机版操作,即服务器和客户是同一台机器,可以将 Tomcat 服务器关闭来模拟网络连接断开的场景),输入测试数据,并提交,如图:

图 29. 断开网络连接
图 28. 断开网络连接
图 28. 断开网络连接

恢复网络连接,点击 sync 按钮,则弹出提示,代表本地数据库有一条记录上传成功,Tomcat 服务器上弹出信息,代表服务器端接受到子 worker 上传的数据。

图 30. 恢复网络连接,同步成功
图 29. 恢复网络连接,同步成功
图 29. 恢复网络连接,同步成功

到此您已经完成了一个 Gears 的 DEMO 应用,这个 report 系统采用的同步策略是用户驱动式的,即在线时用户自行选择将本地暂存的数据上传到远端服务器。开发者也可以选择另外一种同步策略,状态驱动式,即当检测到网络连接成功时立即将数据上传到远端服务器上。

结束语

文章提供的示例仅为了让读者明白如何使用 Gears 代码编写离线应用程序,尚存在着许多欠考虑的问题,可由读者自行完善。JavaSrcipt 框架 Dojo 也在关注着 Gears 的发展,推出了 Dojo offline,是对 Google Gears 的包装应用,让开发者们可以更轻松地使用 Gears 进行离线开发。

致谢

在本文完成之际,特别感谢 IBM 中国软件开发中心 DB2 z/OS 开发团队我的同事们和我的 Mentor 陈威在工作和学习中给我的巨大帮助!


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development, Java technology, Open source
ArticleID=308811
ArticleTitle=使用 Google Gears 开发离线应用
publish-date=05162008