Android Web 应用高级编程


移动与嵌入式开发技术 Android Web 应用 高级编程 (美) Damon Oehlman Sébastien Blanc 著 张 龙 译 北 京 Damon Oehlman, Sébastien Blanc Pro Android Web Apps: Develop for Android Using HTML5, CSS3 & JavaScript EISBN: 978-1-4302-3276-6 Original English language edition published by Apress, 2855 Telegraph Avenue, #600, Berkeley, CA 94705 USA. Copyright © 2011 by Damon Oehlman, Sébastien Blanc.Simplified Chinese-language edition copyright © 2012 by Tsinghua University Press. All rights reserved. 本书中文简体字版由 Apress 出版公司授权清华大学出版社出版。未经出版者书面许可,不得以任何方式复制 或抄袭本书内容。 北京市版权局著作权合同登记号 图字:01-2011-2257 本书封面贴有清华大学出版社防伪标签,无标签者不得销售。 版权所有,侵权必究。侵权举报电话:010-62782989 13701121933 图书在版编目(CIP)数据 Android Web 应用高级编程/(美)奥尔蒙(Oehlman, D.),(美)布兰克(Blanc , S.) 著;张龙 译. —北京:清华大学出版社,2012.3 (移动与嵌入式开发技术) 书名原文:Pro Android Web Apps: Develop for Android Using HTML5, CSS3 & JavaScript ISBN 978-7-302-28050-7 Ⅰ.①A… Ⅱ.①奥… ②布…③张… Ⅲ.①移动终端—应用程序—程序设计 Ⅳ.①TN929.53 中国版本图书馆 CIP 数据核字(2012)第 024160 号 责任编辑:王 军 于平 装帧设计:牛艳敏 责任校对:成凤进 责任印制: 出版发行:清华大学出版社 网 址:http://www.tup.com.cn,http://www.wqbook.com. 地 址:北京清华大学学研大厦 A 座 邮 编:100084 社 总 机:010-62770175 邮 购:010-62786544 投稿与读者服务:010-62776969,c-service@tup.tsinghua.edu.cn 质 量 反 馈:010-62772015,zhiliang@tup.tsinghua.edu.cn 印 刷 者: 装 订 者: 经 销:全国新华书店 开 本:185mm×260mm 印 张:19.75 字 数:481 千字 版 次:2012 年 3 月第 1 版 印 次:2012 年 3 月第 1 次印刷 印 数:1~3500 定 价:48.00 元 ———————————————————————————————————————————— 产品编号: 前 言 我们现在正步入新的世界,移动设备逐渐成为人们与 Internet 互联的主要方式,开发 移动设备应用正成为人们新的追求。供应商之间的竞争也日趋白热化,这导致市场中充满 了各种各样的设备。 我们看到各个供应商都在为自己的设备提供开发工具与市场,并希望围绕着自己的 产品创建软件生态圈。在很大程度上,一些供应商的战略也是这样的。开发者使用这些 工具并为特定的设备创建“原生”应用,然后需要重新构建应用的大部分内容以面向各 种不同的设备。 对于构建移动应用的公司来说,这是可接受的方式。然而,从长远来看,这种方式是 行不通的。考虑到拥有 Web 产品的每个公司在未来几年间都需要为多种设备提供桌面版的 Web 应用与适合的移动客户端。再考虑软件开发者的数量——就像你我,还有其他开发者。 我们拥有必要的资源满足这种需求吗?我没有。一定会有更好的方式。确实是有的。 构建移动 Web 应用是更好的方式。这是一种移动应用开发的方式,你只需要重写很少 的代码就可以满足市场上存在的众多设备的需求。本书关注于为 Android 编写移动 Web 应 用,但实际上,很多概念也可以轻松移植到其他移动设备上(这是重点)。 何谓移动 Web 应用 移动 Web 应用就是使用核心客户端 Web 技术 HTML、CSS 与 JavaScript 构建,并且专 门针对移动设备的应用。吸引人们关注移动 Web 应用是 HTML5 与 CSS3 的趋势——这两 个技术的“最新版”。本书会详细介绍 HTML5、CSS3 和 JavaScript。 JavaScript 是令很多开发者又爱又恨的语言。有些人甚至都不把它看做是一门编程语 言。然而,JavaScript 很可能会成为未来 5 年内最需要的技能之一。 本书使用了哪些技术 本书将会使用大量 JavaScript 代码。显然还会使用很多 HTML 与 CSS,但 JavaScript 确实是适合于移动 Web 应用开发的语言。 如果不熟悉 JavaScript,那么我们并不会将你置于纷繁复杂的 JavaScript 代码中,我们 会推荐一些学习资料,因为这并非一本 JavaScript 基础书。我们还大量使用了优秀的 jQuery JavaScript 库以简化开发。如果不熟悉 jQuery,那么我们建议你看看 jQuery 教程。如果熟 悉 Prototype、MooTools 或是其他的 jQuery“竞争者”,那么你可以很轻松地修改书中的示 例代码。 对于移动 Web 应用来说(以及其他的富 JavaScript Web 应用),掌握如何组织应用以保 证可读性与可维护性是很重要的。这正是本书中使用了很多小应用项目而非代码片段来展 Android Web 应用高级编程 VIII 示特定功能的原因所在。这样你就可以熟悉移动 Web 应用开发的方方面面,还能理解如何 高效地整合真实世界中的移动 Web 应用。 如果熟悉 Web 应用开发,那么通过本书的学习,你就可以轻松转换到移动 Web 应用 开发上来。但如果你熟悉移动应用开发,想要学习 Web 应用开发方式,那么学习额外的资 料将是非常重要的。 本书内容 本书围绕着两个应用示例展开,你会从中学习到移动 Web 应用开发的方方面面。第 2 章~第 6 章介绍了首个迷你应用,它是一个简单的“任务清单列表”应用,第 8 章~第 12 章则从头开始介绍如何构建一个简单的位置感知游戏。 在这两顿大餐之间还有 3 章作为“点心”。第 1 章主要介绍了编写 Android Web 应 用的基本概念。第 7 章简要介绍了交互性与 HTML5 canvas。第 13 章介绍了移动应用世 界的未来。 作 者 简 介 Damon Oehlman 是一位经验丰富的软件开发人员与技术经理,目前居住在澳大利亚 布里斯班。他曾在多种平台上进行过开发,从 Windows 到 Web 开发,现在则转向了移动 开发,Damon 对移动 Web 应用开发“一次编写,到处运行”的理念有着独到的见解,这也 促使他将精力转向了这里。 由于看到了移动开发日益增长的势头,Damon 放弃了稳定的工作,与人联合创立了移 动开发公司 Sidelab(www.sidelab.com)。Sidelab 为移动 Web 应用开发提供专业的开发服务,特 别是在地图、基于位置的服务以及数据可视化等领域。Damon 还维护着一个技术博客 Distractable(www.distractable.net)并创建了 HTML5 移动地图 JavaScript 库 Tile5(www.tile5.org)。 在编码或写作的闲暇时间,Damon 喜欢与妻子和孩子们共度时光,他们让 Damon 知 道除了编写软件外,生活也是五彩缤纷的。 Sébastien Blanc 是一位资深 JEE 软件工程师。他就职于荷兰公司 E-id(www.e-id.nl)。 此外,Sébastien 还将很多精力放在了移动 Web 应用开发上。他是各种会议演讲的常客,深 信基于 Web 的移动开发拥有美好的明天。与 Damon 一样,在编码的闲暇之余,他喜欢与 家人共度。 技术审校者简介 Kunal Mittal 是 Sony Pictures Entertainment 的技术执行理事,负责 SOA、身份管理和 内容管理程序。他为不同的业务线提供了集中的工程服务,并且为 Sony Pictures Enterprise IT 环境引入了新的平台与技术。 Kunal 是一位企业家,帮助初创公司制定技术战略、产品路线图和开发计划。由于他 与全球几家开发伙伴保持着紧密的关系,因此可以帮助初创公司甚至是大型公司构建适当 的开发伙伴关系。通常他的工作职责是顾问或咨询 CTO,但也在项目管理与技术架构领域 表现活跃。他曾创作和编辑过几本关于 J2EE、云计算和移动技术方面的书籍和文章。 译 者 序 随着科技的日益进步,现在我们已经逐渐步入了移动世界。移动开发浪潮席卷全球, 智能手机与平板电脑的销量日益增加,这不仅为最终用户的生活和工作带来了极大的便利, 同时也为众多的开发者创造了展示自己的舞台。 自 Google 推出 Android 系统以来,Android 家族一直呈现出蓬勃发展的态势。世界上 已经有多家厂商在开发、研制基于 Android 系统的手机与平板电脑,如三星、摩托罗拉、 联想、HTC、华为等,Android 生态圈也表现出了欣欣向荣的发展势头。无数的开发者涌 入 Android 开发的世界中,本书也在这个时机下应运而生。 本书与其他讲述 Android 开发的书籍最大的区别在于:目前绝大多数 Android 开发书 籍依然在介绍如何构建原生应用,而本书的重点则在于如何通过最先进的 HTML5、CSS3 与 JavaScript 等标准和技术构建基于 Android 的 Web 应用。Web 应用是应用未来的发展趋 势,而本书正是契合了这种趋势,讲述了 Android Web 应用开发所涉及的方方面面的知识 与技术。 本书通过深入浅出的讲解帮助读者迅速进入到 Android Web 应用开发的世界中。即便 读者不具备 Web 应用开发相关的技能也能通过本书快速掌握移动 Web 开发所涉及的各方 面技能与技巧。本书通过大量示例帮助读者深刻理解原生应用与 Web 应用之间的差异,如 何构建基于 Android 的 Web 应用,同时还详细讲解了如何通过桥接框架将 Android Web 应 用转换为原生应用以在 Google 市场上发布;此外,本书还对目前移动世界中的流行 UI 框 架 Jo、jQTouch、jQuery Mobile 与 Sencha Touch 进行了分析与比较,这样读者就可以根据 书中的介绍为自己的应用选择合适的框架。 除了 Android Web 应用开发本身所涉及的内容外,本书还通过大量篇幅详细介绍了如 何实现基于位置的服务与移动地图、如何集成 Social API 并探讨了移动计算的未来,这些 都是移动 Web 开发者的兴趣所在。相信通过本书的学习,读者会对 Android Web 应用开发 拥有更加深刻和清晰的认识。 翻译技术书籍是一项艰苦的劳动,在这里我要将我最真挚的谢意送给我的妻子张明 辉。本书翻译期间,我们在经历了 8 年的相识后终于步入了婚姻的殿堂,本书的出版也是 赐予我们最好的结婚礼物。将最真诚的感谢送给我的父母,没有你们的哺育和教诲就不会 有今天的我。 感谢清华大学出版社的李阳老师,认识你是我的荣幸,你的专业与认真都给我留下了 深刻的印象。 本人从事 Android 相关开发工作已经有两年多,在这期间积累了不少经验,深谙移动 Android Web 应用高级编程 II 开发的精髓。曾翻译过《Spring 高级程序设计》、《iPhone 游戏开发》、《iPhone SDK 编程入 门经典:使用 Objective-C》、《设计原本:计算机科学巨匠 Frederick P. Brooks 的思考》等 著作。本人还是国内高端技术站点 InfoQ 中文站编辑、满江红开放技术研究组织成员,参 与了 Spring 2.5 官方文档的翻译工作,同时拥有 5 年以上的 Java EE 培训讲师经历。 对于译者来说,能将英文转换为中文并给读者带来切实的帮助是我最大的荣幸。因此, 您在阅读过程中所发现的问题都将是我的责任,也真诚希望各位读者不吝赐教。由于译者 水平有限,失误和遗漏之处在所难免,恳请读者批评指正。敬请广大读者提供反馈意见, 读者可以将意见反馈到 wkservice@vip.163.com,我会仔细查阅读者发来的每一封邮件,以 求进一步提高今后译著的质量。 译者 目 录 第 1 章 入门 ........................................... 1 1.1 了解 Android 平台功能 ............... 1 1.1.1 设备连接 ................................... 2 1.1.2 触摸 ........................................... 2 1.1.3 地理位置 ................................... 3 1.1.4 硬件传感器 ............................... 3 1.1.5 本地数据库与存储 ................... 4 1.1.6 相机支持 ................................... 4 1.1.7 消息与推送通知 ....................... 4 1.1.8 WebKit Web 浏览器 .................. 5 1.1.9 进程管理 ................................... 5 1.1.10 Android OS 特性小结 ............. 6 1.2 准备开发环境 ............................... 6 1.2.1 文本编辑器与工作目录 ........... 7 1.2.2 Web 服务器 ............................... 7 1.2.3 模拟器 ....................................... 9 1.3 Hello World ................................. 13 1.4 小结 ............................................. 15 第 2 章 构建移动 HTML 输入表单 ....... 17 2.1 面向移动 Web 的 HTML ........... 17 2.1.1 面向移动的网页 ..................... 17 2.1.2 添加表单元素 ......................... 21 2.2 添加样式 ..................................... 22 2.2.1 使用 CSS3 的表单样式 .......... 24 2.2.2 改进页面标题的外观 ............. 26 2.2.3 针对不同的屏幕尺寸编写代码 ... 27 2.2.4 处理设备方向的变化 ............. 28 2.3 添加表单验证 ............................. 32 2.4 小结 ............................................. 38 第 3 章 HTML5 Storage API ................ 39 3.1 Web Storage API ......................... 39 3.1.1 使用 JSON 将对象保存到 Web Storage 中 ........................ 41 3.1.2 Local Storage 与 Session Storage ..................................... 45 3.2 Web SQL Database...................... 45 3.2.1 使用客户端数据库保存任务清 单条目 ...................................... 46 3.2.2 数据库的版本化与升级.......... 51 3.3 小结 ............................................. 53 第 4 章 构建多页面应用 ...................... 55 4.1 单个 HTML 文件、多个应用 页面 ............................................. 55 4.1.1 创建视图管理器 ..................... 58 4.1.2 实现视图动作 ......................... 60 4.2 构建应用的主界面 ..................... 62 4.2.1 修改 ViewManager 功能 ......... 65 4.2.2 主界面的存储需求 ................. 66 4.2.3 装配主界面 ............................. 70 4.3 构建所有任务界面 ..................... 72 4.4 实现视图栈 ................................. 78 4.5 小结 ............................................. 81 第 5 章 与云进行同步 .......................... 83 5.1 探索在线存储的选择 ................. 83 5.1.1 在线同步存储的需求.............. 83 5.1.2 避免 3 层架构 ......................... 84 5.1.3 用户认证 ................................. 84 5.1.4 JavaScript 同步库 .................... 84 Android Web 应用高级编程 X 5.1.5 可能的同步解决方案 ............. 85 5.2 Google App Engine 入门 ............ 85 5.2.1 在本地部署 jsonengine ........... 86 5.2.2 选择合适的同步模式 ............. 87 5.2.3 向 jsonengine 发送离线数据 ... 87 5.3 针对在线同步更新用户界面 ..... 89 5.4 开发桌面界面 ............................. 92 5.4.1 查询 jsonengine 实例 .............. 92 5.4.2 将应用部署到云中 ................. 95 5.5 小结 ............................................. 95 第 6 章 与原生应用竞争 ...................... 97 6.1 添加轻量级动画与类似于原生 应用的布局 ................................. 97 6.1.1 添加简单的旋转加载器 ......... 97 6.1.2 添加可滚动的内容 .............. 100 6.1.3 修改动作栏 .......................... 102 6.2 为应用添加位置感知特性 ....... 103 6.3 离线运行应用 ........................... 108 6.3.1 离线缓存清单文件 .............. 108 6.3.2 探索隐蔽的离线缓存特性 .. 109 6.3.3 检测连接状态 ....................... 111 6.4 小结 ........................................... 112 第 7 章 探索交互性 ............................ 113 7.1 HTML5 Canvas 简介 ................ 113 7.2 为 Canvas 绘制增加交互性 ..... 115 7.2.1 交互:使用鼠标 ................... 115 7.2.2 交互:使用触摸 ................... 117 7.3 实现 Canvas 动画 ..................... 119 7.3.1 创建动画循环 ...................... 120 7.3.2 绘制一帧动画 ...................... 121 7.3.3 绘制图像:考虑设备 DPI ... 124 7.4 高级动画技术 ........................... 128 7.4.1 在动画中实现逼真的移动 .. 128 7.4.2 Canvas 变换与动画.............. 132 7.4.3 小汽车动画的变换 .............. 134 7.5 小结 ........................................... 137 第 8 章 基于位置的服务与移动地图 ... 139 8.1 基于位置的服务 ....................... 139 8.2 地理社交网络 ........................... 140 8.3 移动地图 ................................... 141 8.3.1 使用 Google Maps 显示地图... 141 8.3.2 Tile5:另一个 HTML5 Mapping API ......................... 143 8.3.3 向 Google Map 添加标记 .... 146 8.3.4 显示标记的详细信息........... 147 8.4 针对移动优化的地图 UI .......... 149 8.4.1 地图 UI 模型 ........................ 149 8.4.2 编写移动地图 UI 样板 ........ 150 8.4.3 在样板代码中实现 UI 导航 ... 154 8.4.4 使用导航栏选择标记........... 159 8.5 小结 ........................................... 166 第 9 章 使用 PhoneGap 桥接原生应用 ... 167 9.1 桥接框架简介 ........................... 167 9.1.1 何时使用 PhoneGap ............. 168 9.1.2 下载 PhoneGap ..................... 168 9.2 示例 PhoneGap 应用 ................ 168 9.2.1 构建示例应用 ...................... 170 9.2.2 探索示例应用 ...................... 175 9.3 一个简单的 PhoneGap 地图 应用 ........................................... 179 9.3.1 修改示例 PhoneGap 项目 .... 179 9.3.2 将现有代码转换为 PhoneGap 应用 ..................... 184 9.4 小结 ........................................... 187 第 10 章 集成 Social API ................... 189 10.1 连接到 Web API ..................... 189 10.1.1 何谓 JSONP ...................... 189 10.1.2 处理缺少 JSONP 支持的 API ....................... 194 10.1.3 Geominer API 简介 .......... 196 10.2 在 Moundz 中定位资源 ......... 198 10.2.1 使用 Geominer API 找到 附近资源 ........................... 200 10.2.2 使用 Geolocation 追踪位置 ..... 203 目 录 XI 10.3 实现用户登录 ......................... 206 10.3.1 构建欢迎与登录界面 ...... 206 10.3.2 Twitter Anywhere 与登录 过程 .................................. 208 10.3.3 另一种通过 Geominer 实现的 Twitter 认证 ......... 212 10.4 小结 ......................................... 215 第 11 章 移动 UI 框架比较 .................217 11.1 移动 UI 框架概览 ................... 217 11.1.1 框架之间的异同点........... 217 11.1.2 为框架比较做好准备 ....... 219 11.2 Jo ............................................. 222 11.2.1 Jo 入门 .............................. 222 11.2.2 当 Moundz 遇到 Jo ........... 224 11.3 jQTouch ................................... 229 11.3.1 jQTouch 入门.................... 229 11.3.2 将 jQTouch 应用到 Moundz 上 ........................ 231 11.4 jQuery Mobile ......................... 235 11.4.1 jQuery Mobile 入门 .......... 236 11.4.2 Moundz 与 jQuery Mobile ..238 11.5 Sencha Touch .......................... 243 11.5.1 Sencha Touch 入门 ........... 243 11.5.2 Moundz 与 Sencha Touch . 245 11.6 小结 ......................................... 252 第 12 章 完善与打包应用以进行发布 .... 255 12.1 继续使用 jQuery Mobile ........ 255 12.1.1 恢复登录界面 .................. 255 12.1.2 改进导航布局 .................. 259 12.2 搜集资源 ................................. 261 12.2.1 构建资源详细信息界面 ... 261 12.2.2 使用 Geominer 实现资源 追踪 ................................... 267 12.3 将 Moundz 打包为原生应用 .... 269 12.3.1 针对 PhoneGap 打包 ........ 269 12.3.2 处理应用权限 .................. 272 12.4 PhoneGap、认证与 Intent ...... 273 12.4.1 之前的 Web 认证流程 ..... 273 12.4.2 Android Intent 概览 .......... 274 12.4.3 使用 PhoneGap 插件处理 Intent ................................. 276 12.5 打包应用以进行发布 ............. 281 12.6 小结 ......................................... 284 第 13 章 移动计算的未来 .................. 285 13.1 移动计算时代 ......................... 285 13.1.1 全球范围的现象............... 285 13.1.2 桌面已死 .......................... 286 13.1.3 拥抱渐进增强 .................. 287 13.2 移动技术预测 ......................... 288 13.2.1 工具与库的改进............... 289 13.2.2 设备架构的变化............... 290 13.2.3 面向未来架构的编码 ....... 291 13.3 物联网 ..................................... 291 13.3.1 硬件传感器网络............... 292 13.3.2 人体传感器 ...................... 293 13.4 小结 ......................................... 294 附录 A 调试 Android Web 应用 ....... 295 入 门 欢迎来到 Android Web 应用开发的精彩世界。本书将带你领略移动 Web 应用构建的过 程。虽说本书内容主要面向 Android,但大部分代码都可以用在 Chrome OS 上。事实上, 应用代码的重用并不局限于 Chrome OS——本书的代码可以运行在提供了基于 WebKit 浏 览器的任何设备上。如果你现在不熟悉 WebKit 或 Chrome OS,那也没关系——学完本书 后就会熟悉了。 本章将会介绍一些高层次主题,这样你就能尽快开始构建应用了: ● Android 平台功能概览 ● 通过 Web 浏览器可以使用哪些功能(默认情况下以及使用诸如 PhoneGap 之类的桥 接框架时) ● 为编写本书示例及创建自己的应用配置开发环境 ● Android 开发包自带的工具以及能够帮助你构建 Web 应用的一些支持工具概览 1.1 了解 Android 平台功能 Android 操作系统(OS)是一种面向移动设备(包括智能手机与平板电脑)的通用 OS。 Android 的计划是成为多个设备厂商自己的设备 OS,接下来各个厂商就可以定制它并在其 上构建应用。这个愿景在很大程度上已经实现了,众多厂商已经开始发布预装 Android 的 设备并成为开放手机联盟(http://openhandsetalliance.com)的成员。 但 Android 并非唯一的移动 OS,这意味着必须重写 Android 原生应用来支持其他(非 Android)移动设备。这样就需要针对希望支持的每个平台管理移动应用的开发过程。虽然 大型公司能够承担得起这些成本,但小型组织或创业公司就很难。开发移动 Web 应用的吸 引力正在于此——编写一次应用代码就可以运行在多种设备上。 本节将会简要介绍 Android OS 的现有特性,以及在构建 Web 应用时是否能够使用这 些功能。 第 章 1 0 Android Web 应用高级编程 2 若更想了解系统功能概要以及通过浏览器或桥接框架可以访问哪些功能,则可以直接 翻阅本节最后的表 1-2。 桥接框架 桥接框架向开发者提供了这样一种技术:可以构建部署到移动设备上的 Web 应用。你 还可以通过框架对原生 API 的包装(通常使用 JavaScript)访问部分原生设备功能(比如加速 计与照相机)。 本书将会介绍一些使用 PhoneGap(http://phonegap.com)来桥接部分原生功能的示例。 PhoneGap 是出现较早的桥接框架,此外还有不少桥接框架也可以使用。但本书将专注于 PhoneGap,因为它提供了一种简单、轻量级的方式来包装移动 Web 应用而实现原生部署。 要想了解关于各种移动 Web 应用框架的更多信息,请参考我撰写的关于该主题的几篇 博文。其中,下面这篇文章有来自于项目贡献者的很好的评论,介绍了这些框架擅长的领 域,地址是 http://distractable.net/coding/iphone-android-web-application-frameworks。 虽然我很乐意在本书中对每个框架都进行详尽的介绍,但本书的关注点是构建移动 Web 应用。依我看来,它们是可以部署到 Web 并通过设备浏览器访问的应用。桥接框架应 该是一种可选的手段而非必要条件。在这个特定的情况下,PhoneGap 毫无疑问是胜者。 1.1.1 设备连接 作为消费者,我们可能认为将自己的移动设备连接起来是理所当然的事情,但对于移 动开发者(Web 应用或原生应用)来说,最好不要这么想。如果在构建移动应用时就假设应 用时刻与 Web 相连,那么当连接受限时就会限制应用的可用性——这种情况的发生概率远 远超出你的想象。 理解应用在不同时间具有不同的连接级别对于创建拥有良好用户体验的应用来说至 关重要。 简单来说,从 Web 角度来看移动设备具有以下 3 种连接级别: ● 高带宽连接(比如 WiFi) ● 低带宽连接(比如 3G) 第 1 章 入 门 3 ● 限制或无连接(离线) 目前,在构建纯 Web 应用时,只能测试是否具备连接(无法通过下载等手段测试连接 速度)。这与构建原生 Android 应用不同,因为这些应用可以访问原生 API,而原生 API 提 供了关于设备当前的连接类型与质量等信息。 第 5 章将会介绍 HTML5 API 中的一些特性,应用可以通过这些特性实现脱机使用; 第 9 章将会介绍一些示例,这些示例使用桥接框架来访问某些原生的连接检测功能。 1.1.2 触摸 令目前的移动设备有别于老式设备的一个特性就是触摸界面。根据 Android 版本的不 同,在原生层次上可以使用多点触摸事件与单点触摸事件。但目前,Web 应用只能支持单 点触摸事件。 说明: 由于 Web 应用不支持多点触摸事件,因此原生应用在应用 UI 的实现上具有一定的优势。 未来这种状况极有可能得到改观,但现在我们还是要面对这样一种境况:某些 Android 设备 支持 Web 应用的多点触摸,而某些设备则不支持。 至少在未来几年中,我们编写的代码主要还是针对单点触摸,同时还会为那些在 Web 浏览器中支持多点触摸的设备提供改进的功能(如果时间允许)。 第 7 章将会深入介绍触摸事件。 1.1.3 地理位置 Android OS 可以通过各种不同的实现支持地理位置检测,包括 GPS(全球定位系统) 与手机信号塔,此外还有使用了 IP 嗅探等技术检测位置的 Internet 服务。在原生 API 层 次中,地理位置是在 android.location 包中实现的(请参阅 http://developer.android.com/ reference/android/location/package-summary.html),大多数桥接框架都是通过原生 API 公开 此功能的。 由于 HTML5 逐渐为人所接受并且已经部分实现了(完整实现需要等到规范完成时,这 还需要几年时间),因此我们无需桥接框架就可以直接在浏览器中访问位置信息了。这是通 过 HTML5 Geolocation API 实现的(www.w3.org/TR/geolocation-API)。第 6 章详细介绍了 HTML5 Geolocation API。 Android Web 应用高级编程 4 1.1.4 硬件传感器 现代智能手机最酷的特性之一就是配备了一系列的硬件传感器,随着技术的不断普 及,硬件传感器的使用率也将会不断攀升。目前使用最为广泛的传感器之一就是三轴加速 计,开发者编写的软件能以创新的方式使用它来追踪用户的交互。目前除了加速计以外, Android OS 还可以使用很多其他的硬件传感器,如果查看针对原生开发的硬件传感器 API 参考,就会发现原生 API 已经可以支持很多传感器了(参见 http://developer.android.com/ reference/android/hardware/Sensor.html)。表 1-1 列出了各种传感器,并且说明了桥接框架 PhoneGap 是否可以访问每个传感器。如果不熟悉表中所列的传感器,可参考维基百科,上 面有详细的说明信息——只需要搜索传感器名字。注意,虽然 Android SDK(软件开发包) 支持很多硬件传感器,但大多数都无法通过移动 Web 应用访问。 表 1-1 Android SDK 支持的传感器 传 感 器 PhoneGap 支 持 加速计 支持 陀螺仪 不支持 光 不支持 磁场 不支持 (续表) 传 感 器 PhoneGap 支 持 方向 支持 按压 不支持 接近度 不支持 温度 支持 原生开发优于 Web 开发最明显的佐证之一就是前者可以访问各种传感器,随着技术的 不断发展,向移动设备添加的传感器也会越来越多。虽然这是个不争的事实,但如果使用 桥接框架来构建 Web 应用,那么也可以访问一些常用的传感器。 此外,PhoneGap 是开源框架,支持用户编写插件(虽然不太容易找到有价值的信息), 因此完全可以使用它访问其他传感器。 1.1.5 本地数据库与存储 长久以来,移动设备都以各种形式支持本地存储,但直到最近才出现实现存储的标准 化技术(以及技术选择)。当然了,在原生 API 层次上,Android 通过 android.database.sqlite 包(参见 http://developer.android.com/reference/android/database/sqlite/packagesummary. html)实现了对 SQLite(http://sqlite.org)的支持。 SQLite 很快成为了嵌入式数据库事实上的标准,在实现面向 Web 技术的本地存储与数 据库时更是如此。如果客户端能够访问诸如 SQLite 这样的轻量级数据库,那么我们创建的 第 1 章 入 门 5 应用就可以同时存储和缓存“信息的位置拷贝”,而这些信息通常是保存在远程服务器上的。 有两个新的处于发展中的 HTML5 标准提供了除 JavaScript 以外无需任何外部服务就可以 持久化数据的机制。这些新的 API,HTML5 Web Storage(http://dev.w3.org/html5/webstorage) 与 Web SQL Database(http://dev.w3.org/html5/webdatabase)提供了一些优秀的工具使应用在 离线情况下依然可以运行。我们将在第 3 章详细介绍这些 API。 1.1.6 相机支持 在触摸成为移动设备最受欢迎的一个特性之前,合适的相机当然会成为影响用户购买 的一个因素了。这一点通过应用了相机的各种原生应用就能反映出来。在原生层次,访问 相 机 是 通 过 android.hardware.Camera 类 ( 参见 http://developer.android.com/reference/ android/hardware/Camera.html)实现的;但在浏览器中就无法访问了——而 HTML Media Capture 规范则正在制订过程中(参见 www.w3.org/TR/capture-api)。 在规范制订完毕前,Web 应用可以通过桥接框架访问设备上的相机与照片库。 1.1.7 消息与推送通知 在 Android 2.2 中的原生层次上实现了一个叫做 Cloud to Device Messaging(C2DM,参 见 http://code.google.com/android/c2dm/index.html)的服务。原生应用开发者可以凭借该服务 将应用注册为推送通知,这样当新的内容出现或是某些内容发生变化时移动用户就会收到 通知。 浏览器中推送通知的实现还尚需时日,因为前不久才开始有工作组就这个特定领域展 开讨论并给出提案(参见 www.w3.org/2010/06/notification-charter)。 但是,由于 C2DM 是面世不久的服务,因此桥接框架尚需要一些时间才能为 Android 实现该服务。 1.1.8 WebKit Web 浏览器 Android OS 实现了一个基于 WebKit 的浏览器。WebKit(http://webkit.org)是开源的浏览 器引擎,很多桌面与移动浏览器都在使用它。WebKit 引擎是众多流行的浏览器背后的基石, 例如桌面上的 Chrome 与 Safari 及移动设备上的移动版 Safari 和原生 Android 浏览器(仅举 几例)。单单这一点就可以说明为移动设备构建 Web 应用要优于原生应用。因为 Android 与 iPhone 都实现了原生的 WebKit 浏览器(Mobile Safari 的核心是 WebKit),因此只需要面 向 WebKit 进行开发,就可以轻松在这两种设备上实现了。 为何说拥有共同的 WebKit 很重要呢?考虑到 HTML5 与 CSS3 都是新规范,因此 Web 标准还需要经过多年的发展才会完善,移动浏览器也需要在多年之后才会统一。目前,在 两个占据统治地位的消费智能手机平台间有 WebKit 这个共同元素是一个巨大的优势。作 为开发者,我们构建的应用可以利用日趋稳定的 HTML5 组件(这些组件正被不断发展着的 浏览器引擎所实现,如 WebKit),并且可以在 Android 与 iPhone 上运行这些应用。接下来 不妨用原生的 Android Java 代码或 iPhone Objective-C 代码试试看。 Android Web 应用高级编程 6 说明: 将 WebKit 作为“移动浏览器的选择”这种势头正在不断增大。BlackBerry 背后的公司 Research In Motion(RIM)已经在其新的 BlackBerry Torch 中采用了 WebKit 与 HTML5。这对 于移动 Web 应用开发者来说是好消息,我相信未来的发展趋势将是跨平台的 Web 开发而非 原生开发。 1.1.9 进程管理 自从 Apple 的 iOS 4 发布后,Android 与 iOS 设备上的进程处理方式就很相似了;但在 这之前,当用户“退出”应用时,Android 与 iPhone 应用的行为有着很大差别。在 iPhone 上,一旦退出了应用,应用就会停止运行——这意味着应用无法在后台做任何事情。但在 Android 上,如果用户在没有停止的前提下退出了应用(包括 Web 应用),应用还会继续在 后台运行。 为了证实这一点,我们在 Android 手机上运行如下代码来确保当应用(在该示例中是浏 览器)处于不活动状态时请求依然可以得到处理。 上文使用 JavaScript 的 setInterval 调用表示每隔一秒钟请求一张图片(请求一张并不存 在的图片)。当代码运行时,每隔一秒钟(或大约一秒钟)就会向 Web 服务器发出一个图片请 求,无论 Web 浏览器是否是活动应用。此外,由于 Android 上的浏览器支持同时打开多个 窗口,因此即便浏览器处于活动状态,但选择了其他窗口作为当前窗口,请求还是会继续 执行。 这种后台处理能力给开发者带来了一些绝佳的机会。但重要的是要确保当应用处于后 台时,它不会下载不必要的信息或是消耗过多的电池电量。 1.1.10 Android OS 特性小结 表 1-2 列出了设备特性、支持这些特性的 Android 版本以及是否可以在浏览器中访问 第 1 章 入 门 7 这些特性的矩阵图。在某些情况下,浏览器支持列使用术语“桥接”。这指的是使用桥接框 架(如 PhoneGap、Rhodes 等)向浏览器公开原生设备的功能。 表 1-2 Android OS 特性与浏览器可访问性矩阵图 设备特性 OS 版本支持 浏览器访问 连接检测 ≥1.5 桥接 地理位置(GPS) ≥1.5 可以 硬件传感器 ≥1.5 桥接 触摸屏与触摸事件 ≥1.5 部分 本地存储与数据库 ≥1.5 可以 消息/通知 ≥2.2 不可以 相机 ≥1.5 桥接 1.2 准备开发环境 既然已经了解了与 Android 平台 Web 应用开发相关的知识,现在就来搭建开发环境吧, 这样在接下来的几章中我们就可以动手开发应用了。 Android 上高效的移动 Web 应用开发环境的搭建有多种方式。本节重点介绍的基本组 件有文本编辑器、Web 服务器以及 Android 模拟器(或手机)。也可以使用 IDE,如 Eclipse(参 见 http://eclipse.org)。 Eclipse 是一款面向 Java 开发的 IDE,Android 团队为 Eclipse 提供了原生的 Android 开 发工具。如果同时进行 Web 与原生 Android 开发,那么你可能更愿意使用 Eclipse 环境— —如果事实如此,那么本书所讲的一切内容也都是适用的。 说明: 虽然使用功能全面的 IDE 进行 Web 开发具有很多优势,但我个人更喜欢使用轻量级、 独立的工具。如果使用独立的 Web 服务器并且从设备的浏览器访问内容,则可以更轻松地 同时测试多个设备,这样做可以避免使用 IDE 提供的工具所产生的花费。 此外,如果打算将另一个移动设备作为主要的开发目标,则可以继续使用同样的工具集 来针对该平台进行开发。我预测移动领域中将会有两三个占据统治地位的厂商出现,同时有 十多个平台,因此能够跨越多种设备进行开发的方式无疑是颇具吸引力的。 Android Web 应用高级编程 8 1.2.1 文本编辑器与工作目录 为 Android 编写 Web 应用时,你可以使用熟悉的任何文本编辑器。如果不确定使用哪 一个文本编辑器,可以参考维基百科上的一个相当不错的比较列表(参加 http://en.wikipe- dia.org/wiki/Comparison_of_text_editors)。 选择好文本编辑器后,接下来就可以创建在阅读本书中所要使用的目录了。你可以自 己决定目录的实际位置,但我建议创建一个如下所示的目录结构,这将有助于你顺利使用 本书的示例: ● PROJECT_WORKING_DIR . css . img . js . snippets 可重用的 CSS、图片与 JavaScript 资源分别放在 css、img 与 js 目录中。在阅读本书的 过程中,我们将在 snippets 目录下为每一章创建一个目录。 1.2.2 Web 服务器 在开发过程中要是有 Web 服务器能够运行应用代码将有助于改进开发过程。本书主要 讲解的是客户端技术,因此对 Web 服务器的要求没那么高。这意味着使用任何 Web 服务 器都可以,因此如果你已经有了想要使用的 Web 服务器,用就好了。 如果还没有决定使用哪个 Web 服务器,我们将简要介绍一个名为 Mongoose 的 Web 服务 器,它可以运行在 Windows、Mac OS 及 Linux 上。Mongoose 的使用非常简单;只需要根据所 用平台的安装指南就可以创建好,如下所示(根据每个人配置的不同可能会有一些差别): 1. Windows 上的 Mongoose 首先,从项目下载页面 http://code.google.com/p/mongoose/downloads/list 下载 Mongoose 的独立可执行文件。 它有一个安装包,但将 Mongoose 安装为服务不如使用独立的可执行文件那么容易。 下载好文件后,请将可执行文件放到 path 上(建议但不是必须),然后直接转到本章的“运 行 Mongoose”一节。 2. Mac OS 上的 Mongoose 在 Mac OS 上安装 Mongoose 最简单的方式是使用 MacPorts(www.macports.org)。如果 没有安装 MacPorts,那么请按照 MacPorts 网站上的简单说明先来安装它。 安装好 MacPorts 后,请运行如下命令来安装 Mongoose: sudo port install mongoose 如果 MacPorts 安装正确,那么上述命令就会下载、构建并安装 Mongoose,完成之后 就可以使用了。接下来请转到本章的“运行 Mongoose”一节。 第 1 章 入 门 9 3. Linux 上的 Mongoose 由于 Mongoose 非常轻量级,因此在大多数 Linux 系统上从源代码来构建 Mongoose 是 非常简单的事情。如下说明针对的是 Ubuntu 系统,但只要做很少的修改就可以用到其他 Linux 系统上。 首先,从 wget(http://mongoose.googlecode.com/files/mongoose-2.8.tgz)下载 Mongoose 源代码。 接下来,解压缩下载的归档文件: tar xvzf mongoose-2.8.tgz 将目录切换到 Mongoose 源代码目录中: cd mongoose 然后运行 Linux 的 make 命令: Make linux 现在可以使用 Mongoose 可执行文件的全路径运行 Mongoose 了。如果不想使用全路径 运行 Mongoose,那么请将 Mongoose 可执行文件复制到诸如/usr/local/bin 这样的路径中: sudo cp mongoose /usr/local/bin/ 就是这些——现在可以运行 Mongoose 了。 4. 运行 Mongoose 运行 Mongoose 相当简单。默认配置就可以,这样从命令行运行 mongoose 就会启动一 个 Web 服务器并将该目录作为 Web 的根。 此外,Mongoose 会绑定到分配给计算机的所有 IP 地址,这意味着可以使用运行 Mongoose 的机器的 IP 地址(或是多个 IP 地址之一)从网络上的其他设备进行浏览。 现在来运行 Mongoose。打开一个命令提示/终端窗口,然后将目录切换到之前创建的 PROJECT_WORKING_DIR。如果 Mongoose 位于该路径上,那么就可以从命令行运行 mongoose;如果不在,就需要使用全路径来运行了。无论何种方式,只要运行了 mongoose 命令(无需命令行选项),就可以访问 http://localhost:8080/并查看之前创建的目录文件列表 (如图 1-1 所示)。 Android Web 应用高级编程 10 图 1-1 在 Mongoose 运行的情况下,可以查看之前创建的目录列表 5. 其他方式 还可以将文件复制到模拟的 SD 卡镜像中,然后使用 file://sdcard/语法 从镜像中加载文件。如果对如何创建 SD 卡镜像和如何向其中复制文件及将文件复制出来 感兴趣,我推荐阅读以下这篇文章 http://developer.android.com/guide/developing/tools/ emulator.html#sdcard。 1.2.3 模拟器 要想测试示例,需要使用 Android 手机或是与 SDK 绑定的 Android 模拟器。如果没有 SDK,那么可以从 http://developer.android.com/sdk 下载。根据 Android 站点上的说明进行, 但可以跳过安装 Eclipse 与 ADT 插件的说明(除非你已经安装好了并且习惯于使用它)。安 装好 Android SDK 后,便可以在 SDK 安装目录下的 tools 目录中找到模拟器及相关的工具。 1. 创建 Android 虚拟设备 使用 Android SDK 自带的 GUI 工具创建 Android Virtual Device(AVD)非常直接。首先, 找到 android 可执行文件并运行它。可执行文件的位置取决于 SDK 的安装路径,但你所要 找的是 SDK 安装目录下 tools 目录中的 android 文件(Windows 上是 android.exe)。这会启动 Android SDK and AVD Manager 应用,如图 1-2 所示。 第 1 章 入 门 11 图 1-2 Android SDK and AVD Manager 我们创建了一个设备来运行示例。无需任何特别的东西,只需要运行 2.1 或更高版本 SDK 的标准模拟器即可。单击 Add 按钮即可开始创建镜像。完成后,会出现类似图 1-3 所 示的界面。 图 1-3 为模拟器创建一个新的 AVD 在创建新的 AVD 文件时,至少要提供如下 3 方面的信息: Android Web 应用高级编程 12 ● 设备的名称(不允许有空格)。我们在这里创建一个名为“android_web_apps”的设 备。这是从命令行启动模拟器时所用的名字。 ● 开发的目标 Android API。在本书撰写之际,Android OS 2.1 与 2.2 拥有最高的市场 占有率,1.5 与 1.6 则是少数(参见 http://developer.android.com/resources/dashboard/ platformversions.html)。对于本书中的示例来说,我们主要使用的是 2.1 版的模拟 器。通过使用 2.1 版而非 2.2 版的模拟器,我们可以确保代码能在这两个 OS 版本 上正常运行——但在尽可能多的设备上进行测试是很重要的。 ● SD 卡的大小。如果需要,可以指定现有的 SD 卡镜像,但这对于运行书中的示例 来说并非必要。我建议指定 50MB 左右的大小。 其他信息,如皮肤值(与屏幕分辨率同义)会根据所选的 API 版本自动设定,但如果需 要也可以自己设定这些选项。本书中的所有示例都设定为标准的移动设备屏幕尺寸 320× 480,因此我建议你也这么做。 说明: 书中的一些示例阐释了标准 dpi(每英寸的点数)与高 dpi 之间的差别及其对应用的影响。 对于这些示例来说,需要一个配置有更高屏幕分辨率而非标准分辨率的 AVD。在配置该设 备时,请选择 WVGA800 之类的分辨率以使用高设备 dpi 来模拟设备。 2. 启动模拟器 创建好 AVD 之后,接下来就可以通过单击开始按钮启动设备了,该按钮位于设备镜 像的右边。之后会呈现几个选项(如图 1-4 所示),通常选择默认值就可以了(清除用户数据 可以重新回到初始状态)。 图 1-4 使用 AVD Manager 为模拟器启动一个新的虚拟设备 第 1 章 入 门 13 模拟器启动后会出现类似于图 1-5 所示的界面,这表示 Android 模拟器已经启动了。 图 1-5 Android 模拟器启动过程——可以歇一会喝杯咖啡了 请注意,模拟器的加载需要很长时间,因此一旦加载完毕请尽量不要关闭它。当加载 完毕后,会显示类似于图 1-6 所示的 Android 主界面。 Android Web 应用高级编程 14 图 1-6 Android 模拟器加载成功;请打开浏览器开始吧 在主界面上运行浏览器,之后就可以访问之前配置好的本地 Web 服务器了。 1.3 Hello World 在学习第 2 章介绍的移动 Web 应用与网站开发的细节内容之前,让我们先通过一个非 常简单的 Hello World 示例来确保开发环境已经成功搭建起来了。 首先创建一个简单的 HTML 文件,并使用它来验证可以在 Android 设备的移动浏览器 上查看开发文件: Hello World 将上述代码示例保存到名为 helloworld.html 的文件中,然后从终端或命令提示符访问 存储该文件的目录。从该位置运行 Mongoose(根据安装方式以及路径配置,请使用绝对的 安装路径或是 mongoose)。 图 1-7 显示了 Mongoose 成功运行后一些示例命令行输出的屏幕截图。 图 1-7 Mongoose Web 服务器示例输出显示了内容所对应的端口号与目录 第 1 章 入 门 15 虽然 Mongoose 会显示出它所运行的端口号,但还需要知道机器的 IP 地址,这样才能 从模拟器和通过 WiFi 连接到局域网的实际 Android 设备浏览服务器的内容。确定 IP 地址 最简单的方式之一是使用 ifconfig(Mac OS/Linux)与 ipconfig(Windows)命令。如果不熟悉这 些技术,那么下面的链接会带给你一些帮助。 ● PC: www.wikihow.com/Find-the-IP-Address-of-Your-PC ● Mac: www.wikihow.com/Find-Your-IP-Address-on-a-Mac ● Linux: linux-ip.net/html/tools-ifconfig.html 知道了 IP 地址后,就可以在模拟器(或 Android 设备)上查看测试页面了。图 1-8 展示 了 Android 浏览器上的示例屏幕截图,它呈现了浏览器上创建的 helloworld.html 文件以及 浏览器上显示的结果。 说明: 虽然当自己的机器就是 Web 服务器时,你可能习惯于使用 localhost(或 127.0.0.1),但在 使用 Android 模拟器(或设备)时,需要通过局域网中机器的 IP 地址来访问 Web 服务器。 Mongoose 在这一点上做得很好,它可以响应与机器关联的任何 IP(包括 127.0.0.1)地址的网 页请求。 Android Web 应用高级编程 16 图 1-8 浏览 Hello World 示例表明开发环境已经搭建成功 既然成功创建了 Hello World 示例,现在是时候了解移动 Web 应用或站点的组成了。 这将是第 2 章的主题。 说明: 出于简化的目的,该示例是在 helloworld.html 文件所处的目录中运行的 Mongoose。对 于其他示例来说,我们将使用稍微复杂的目录结构以确保可以在各章之间重复使用某些文 件。比如说,在 GitHub(http://github.com/sidelab/prowebapps-code)上的示例源代码仓库中,该 文件位于/snippets/01/helloworld.html 位置处。 本书的其他章节都将会使用这种目录结构,每一章的代码示例将会存储在 snippets 目录 下的各个目录中。某些较大的示例,比如第 9 章到第 11 章的地理空间游戏就会对这种结构 进行一些变化,但这是总的原则。 在后面的示例中,将在 snippets 的上一层目录中运行 Mongoose。这意味着浏览后面大多 数示例的路径将是:http://YOURIP:8080/snippets/CHAPTER/SAMPLE.html。 1.4 小结 本章介绍了 Android 设备的基本功能,同时谈及了在 Web 应用中使用的不同于原生应 用的功能。这包括通过标准的浏览器支持所能使用的功能,以及通过桥接框架将原生功能 扩展到嵌入在原生应用中的 Web 浏览器。 本章还介绍了构建 Android Web 应用所需的开发环境。此外,本章还介绍了一些工具, 当运行本书的示例以及稍后创建自己的应用时,这些工具可以帮助你调试代码。 第 2 章将会介绍一些用于创建适合于移动设备的网页的简单技术以及移动 Web 应用的 基础。首先将会介绍一些简单的可独立运行的示例,然后迅速地搭建一个实例:构建一个 简单的任务清单应用。我们还将在第 3 章与第 4 章继续探索并构建该示例。 HTML5 Storage API 第 2 章我们为任务清单应用创建了一个简单的移动网页和 Create Task 表单。本章将会 介绍在设备本地存储数据的各种方法。HTML5 引入了几个 API,用于在客户端存储数据。 之前在编写 Web 应用时,如果想将数据存储到数据库中,需要使用服务器端脚本(PHP、 Python、Ruby 等)才能做到。HTML5 API 新涌现出来的一些元素提供了客户端的解决方案, 本章将主要介绍这些元素以及如何高效地使用它们。 本质上,HTML5 规范提供了 3 种不同的客户端存储机制。 ● Web Storage:通常叫做 HTML5 本地存储,这是存储键/值对的一种客户端机制。 它很简单,但却非常强大(请参阅 http://w3.org/TR/webstorage/)。 ● Web SQL Database:它可以访问类似于 SQLite 的数据库,这是传统的服务器端 RDBMS 的客户端解决方案(请参阅 http://w3.org/TR/webdatabase/)。 ● Indexed Database:W3C 提出的草案规范,用于替代目前实现的 Web SQL Database 规范,其目的是提供一种“持有简单值与层级对象的记录数据库”(请参阅 http://w3.org/TR/IndexedDB/)。 本章将会重点介绍前两种存储 API,在介绍的同时还会编写一些示例代码。 虽然 W3C 已经提出了用于替代 Web SQL Database 的 Indexed DB API,但各个版本的 Android 都没有提供对该规范的支持。出于这个原因,我们将重点介绍你现在就可以实现 并使用的前两种方案——Web Storage 与 Web SQL Database。 我们坚信 WebKit 浏览器(如 Android 自带的浏览器)还将会继续支持 Web SQL Database 一段时间,因此即便 W3C 表示不再对该标准进行任何修订,你仍旧可以放心地使用该规范。 3.1 Web Storage API localStorage 与 sessionStorage 对象都实现了 Storage 接口,并且提供了如下方法: ● getItem ● setItem ● removeItem ● clear 第 章 3 0 Android Web 应用高级编程 40 这 4 个方法涵盖了 Storage API 的所有功能,虽然很简单,但却为我们提供了很多机会。 真正的魔力在于你可以将任意对象存储到 Storage 中。之后,只需要通过键就可以获取到 对应的值。 举个简单的例子,可以将字符串或其他简单的类型值保存到 Storage 中。先来查看如 下代码: localStorage.setItem("preferences-bgcolor", "#333333"); localStorage.setItem("preferences- textcolor", "#FFFFFF"); 这会在 localStorage 中创建两个值,用于追踪应用首选项的背景与文本颜色。然而,可 以通过将单一对象存储到 localStorage 中实现同样的效果,如下代码所示: localStorage.setItem("preferences", { bgcolor: "#333333", textcolor: "#FFFFFF" }); 只需要 通 过 调 用 getItem 就可以获取到 preferences 对象: var preferences = localStorage.getItem("preferences"); 接下来,可以从 preferences 对象中得到背景色 与文本颜色的首选项。现在来试一下,只是弹出一 个警告框来显示背景颜色: alert(preferences.bgcolor); 喔,有些尴尬。根据你阅读本书时浏览器所使 用的 Web Storage API 实现的不同,你会发现上述调 用只会显示出图 3-1 所示的 JavaScript 警告消息。 这意味着什么呢?只能在 localStorage 与 sessionStorage 中存储简单值么?是这样的,但可以 使用 JSON(JavaScript Object Notation)序列化对象, 然后再将其保存为字符串。这么做是值得的,因此 本章的第一个练习就采取了这种做法。 说明: 如果之前没有遇到过这种情况,那么欢迎你进入早期技术使用者的世界中来—事情已 经发生了变化。我们之前曾介绍过这一点,该示例证明了浏览器构建者认为 HTML5 的部分 图 3-1 访问 localStorage 中的对象可能 不会得到期望或是文档所描述 的结果 第 3 章 HTML5 Storage API 41 规范对于实现来说已经足够稳定了,但实际上还是会有一些问题。这很有趣,这类示例在本 书中还会继续出现。 如果你遇到某些程序的运行结果与预期不同,或是本书中的某些内容与当前的 HTML5 规范不一致,那么请在线提交反馈,地址是 www.apress.com。 3.1.1 使用 JSON 将对象保存到 Web Storage 中 现在来看看如何使用 JSON 将 JavaScript 对象保存到 Web Storage 中。JSON 提供了一 种优雅且高效的方法将 JavaScript 对象数据保存为普通的文本字符串。转换为字符串格式 后,数据就可以发送给外部服务或是作为示例使用。但在将对象保存为 JSON 格式前,需 要使用 Douglas Crockford 编写的 json2.js 库将对象序列化为 JSON 字符串以及将 JSON 字 符串反序列化为对象。该库的地址如下所示:http://json.org/json2.js。 可以将该文件下载到顶层的 js 目录中以供后续的其他练习使用。 说明: Douglas Crockford 在 json2.js 文件的第一行使用了一个警告,不建议开发者通过直接链 接到其站点的方式使用该库。你在运行练习中的代码示例前需要移除该警告。但如果忘记了 也没事,只不过在每次运行使用了 JSON 的练习时需要单击一下这个恼人的警告框而已。我 相信等到你阅读完本书时一定会移除该行警告。 现在来创建一个简单的页面,用于搭建测试的运行环境: Web Storage Tester Android Web 应用高级编程 42

Web Storage JSON Wrapper

  • Items in Storage (tap to remove)
  • New Item
上述代码中有 4 点值得注意。 ● 包含了通用的 snippets 样式表(css/snippets.css)。第 1 章曾说过,该样式表是可重用 代码组件的一部分,我们将在全书中使用并扩展它。该样式表的完整源代码位于 如下地址:http://sidelab.com/code/pawa/css/snippets.css。 ● 包含了 json2.js,这样我们就可以使用 JSON 序列化与解析了。 ● 包含了 prowebapps.js 库。我们会添加一些便利的包装器以将对象保存到 localStorage/sessionStorage,它通过 JSON 提供了自动的对象序列化/反序列化功能。 ● 包含了 webstorage-test.js,它包含了该练习所需的 JavaScript 代码。 现在继续编写代码。首先,我们将这些存储包装器函数添加到 prowebapps.js 库中。这 是通过向已有的 PROWEBAPPS 模块中添加一个 Storage 子模块实现的: var module = { Storage: (function() { function getStorageScope(scope) { if (scope && (scope == "session")) { return sessionStorage; } // if return localStorage; } // getStorageTarget return { get: function(key, scope) { // get the storage target var value = getStorageScope(scope).getItem(key); // if the value looks like serialized JSON, parse it return (/^(\{|\[).*(\}|\])$/).test(value) ? JSON.parse(value) : value; }, set: function(key, value, scope) { // if the value is an object, then stringify using JSON var serializable = jQuery.isArray(value) || jQuery.isPlainObject(value); 第 3 章 HTML5 Storage API 43 var storeValue = serializable ? JSON.stringify(value) : value; // save the value getStorageScope(scope).setItem(key, storeValue); }, remove: function(key, scope) { getStorageScope(scope).removeItem(key); } }; })() ... }; 虽然我们并不打算详细解释上述代码,但感兴趣的读者会发现我们将 HTML5 Storage 接口中的 getItem 与 setItem 方法包装到了 PROWEBAPPS.Storage 模块的 get 与 set 静态方 法中。我们使用一些 jQuery 辅助函数与正则表达式来确定是否需要使用 JSON 来存储/获取 数据,如果需要,那就使用 JSON 来做。 比了解上述代码的运行机制(如果将这些代码进行分解,你就会发现它其实很简单)更 为重要的是该如何使用这些代码。基本上,我们现在有 3 个函数可以实现更加复杂的值存 储功能,我们可以使用它们与 HTML5 Web Storage 功能进行交互。 ● PROWEBAPPS.Storage.get(key, scope) . key 是一个用于标识条目的字符串值。 . 如果你想存储到 session storage 而非 local storage,那么可以将 scope 指定为 “session”(以字符串形式)。 ● PROWEBAPPS.Storage.set(key, value, scope) . 与 get 方法一样,key 是用于标识条目的字符串值。 . value 是我们想要保存到 Web Storage 中的数据(可以是简单值、数组、对象等)。 数组与对象使用 JSON 进行序列化,然后存储为字符串。 . scope(可选)指定键值存储到 session 还是 local storage 中。如果不为该参数赋 值,那么值就会被存储到 local storage 中。 ● PROWEBAPPS.Storage.remove(key, scope) . key 是一个字符串值,用于标识将从 storage 中移除的条目。 . scope 参数(如果提供)用于指定使用 session 还是 local storage。如果不提供值, 那么默认将会使用 local storage。 介绍完这些功能后,现在来看看如何使用 PROWEBAPPS.Storage 函数存储简单的 JavaScript 对象与数组。我们通过创建 webstorage-test.js 文件做到这一点,它用于强化这个 简单的存储测试应用: $(document).ready(function() { // read the data from local storage for the items var items = PROWEBAPPS.Storage.get("listitems"); var loadTicks = new Date().getTime(); Android Web 应用高级编程 44 functiondisplayItems() { loadTicks = new Date().getTime(); $("#items li[class!='header']").remove(); if (items) { // create list items to display the current items for (var ii = 0; ii " + items[ii].title + " (created " + itemAge +"s ago)"); } // for } else { $("#items").append("
  • No items
  • "); // initialize the items array items = []; } // if..else } // displayItems // button click handlers go here displayItems(); }); 上述代码非常简单,用于从 localStorage 中获取条目数组(listitems)并将其显示在屏幕 上。使用之前编写的代码(没有定义 add 与 clear 功能),加载页面会生成 HTML 输出,如 图 3-2 所示。 图 3-2 Web Storage 测试应用页面 第 3 章 HTML5 Storage API 45 由于现在的 storage 中并没有条目(PROWEBAPPS.Storage.get 函数返回 null),因此 屏幕上显示出了“No items”。现在来实现 add 与 clear 按钮的单击处理程序以填充并保 存数组: $("#add").click(function() { items.push({ title: $("#newtitle").val(), created: new Date().getTime() }); // save the items PROWEBAPPS.Storage.set("listitems", items); displayItems(); }); $("#clear").click(function() { items = null; PROWEBAPPS.Storage.remove("listitems"); displayItems(); }); 同样,上述代码也非常简单。对于 add 处理程序来说,我们使用条目的标题与当前时 间(以毫秒表示)向数组中添加新的对象、将条目保存到 storage 中,然后刷新显示。 clear 处理程序更简单。我们将 items 变量的状态重置为 null、从 local storage 中移除 listitems,然后更新显示。 3.1.2 Local Storage 与 Session Storage 如上代码所示,使用 localStorage 与 sessionStorage 并没有什么差别——至少对于实际 的交互是这样的。事实上,唯一的差别在于数据存储的持续时间以及数据的拥有者是谁。 对于 local storage 来说,数据是持久化的,只有当用户要求删除时才会被删除(比如说 使用浏览器的设置)。 对于 session storage 来说,数据只存活在浏览上下文中(通常当浏览器窗口关闭时就会 终止)。此外,浏览器会为每个窗口或标签维护单独的 sessionStorage 对象;另一方面,窗 口与标签间会共享 local storage。 说明: 在对有多少个窗口/标签能与 Web Storage 交互进行详细分析时,我们发现目前 PROWEBAPPS.Storage 模块的实现有个瑕疵。现在,两个窗口可以在 localStorage 中覆盖彼 此的修改。但在 sessionStorage 中就不会发生冲突。 Android Web 应用高级编程 46 对于 localStorage 与 sessionStorage 来说,数据的存储是以每个源为基础的。所谓源指 的是页面加载的站点,这意味着站点 B 将无法访问站点 A 所创建的客户端数据(这么做很 有道理)。 3.2 Web SQL Database 既然已经使用了 Web Storage API,现在是时候了解一下 Web Storage 的兄弟——Web SQL Database 了。考虑到该 storage API 有些复杂,这里只是对其进行简单的介绍,之后在 后面构建 GeoMiner 应用时再来运行几个具体的示例。 Web SQL Database 的实现非常类似于与典型的 Ajax 处理程序的交互(使用 SQL),因为 与该 API 的交互通常都是异步操作。Web workers(在本书撰写之际,移动浏览器对其的支 持力度还很小)也可以访问同步实现——考虑到其线程执行环境,这么做很合理。 总的来说,我们可以将 HTML5 Web SQL Database 的实现总结为如下 3 个方法。 ● openDatabase:用于打开/创建数据库。该方法接收 5 个参数。 . databaseId:数据库的 ID。 . version:一个标识数据库版本的字符串。它可用于实现特定的版本更新脚本 (第 4 章将会详细介绍)。 . description:易读的数据库说明信息。 . estimatedSize:估算的数据库大小(以字节为单位)。 . creationCallback:一个回调函数,在打开/创建数据库时会得到执行(并非所有 浏览器都会实现该函数)。 ● transaction/readTransaction:当一条或多条 SQL 语句执行时用于开启事务。 transaction方法用于INSERT/UPDATE/DELETE 操作,readTransaction用于SELECT 语句。该方法只接收一个参数。 . callback:当开启事务时所调用的回调函数。执行该回调函数时,它会将事务 实例作为唯一的参数传递进来。 ● executeSql:用于在事务中运行 SQL 语句。该方法接收 4 个参数。 . sqlText:待执行的 SQL 语句。 . sqlParameters:包含在 sqlText 语句中的参数值数组。参数在 SQL 中使用问号 (?)指定。 . completionCallback:一个回调函数,当 SQL 语句成功执行完毕时会得到调用。 对于 SELECT 语句来说,返回的结果位于第 2 个参数位置处。如果指定了该 回调函数,那么在所有情况下 transaction 参数都会作为第 1 个参数返回。 . errorCallback:一个回调函数,如果 SQL 语句或数据库出现了问题就会得到 调用。 虽然这里并未涉及过多的细节信息,但你可能想看看之前使用 Web Storage 保存对象 的示例该如何通过 Web SQL Database 实现,因此我们提供了一个示例实现,地址如下: http://sidelab.com/code/pawa/snippets/03/webstorage-test-webdb.js。 第 3 章 HTML5 Storage API 47 考虑到要介绍的内容非常多,我们打算集中精力使用 Web SQL Database API 实现一个 本地的数据库读写。下面这个示例就将实现该功能。 3.2.1 使用客户端数据库保存任务清单条目 在该练习中,我们使用 Web SQL Database API 将任务清单条目保存到客户端数据库中 以供后续使用。我们将使用之前介绍过的方法来实现。 首先在 JavaScript 应用代码中创建一个名为 TODOLIST.Storage 的子模块。该模块负责 处理应用与数据库的交互。重要的是将该功能封装起来,因为这样后面就可以选择使用其 他的存储机制(比如 Web Storage API)来替代它,而不会影响到该应用代码的其他部分。 请将下面的代码添加到 todolist.js 文件的模块定义中(如果它不是模块数组的最后一个 成员,请别忘记加上逗号): Storage: (function() { // open/create a database for the application (expected size ~ 100K) var db = openDatabase("todolist", "1.0", "To Do List Database", 100 * 1024); // check that we have the required tables created db.transaction(function(transaction) { transaction.executeSql( "CREATE TABLE IF NOT EXISTS task(" + " name TEXT NOT NULL, " + " description TEXT, " + " due DATETIME);"); }); return { saveTask: function(task, callback) { db.transaction(function(transaction) { transaction.executeSql( "INSERT INTO task(name, description, due) VALUES (?, ?, ?);", [task.name, task.description, task.due] ); }); } }; })() 现在,TODOLIST.Storage 只完成了以下两个功能。 ● 创建/打开名为 todolist 的客户端数据库,确保所需的 task 表存在,根据需要创建。 ● 将任务清单条目保存到 task 表中。 接下来创建一个 DTO(数据传输对象,其他语言可能叫做 POJO,即 plain-old Java object) 来获取任务清单条目的详细信息。在后面的练习中,我们将其称做 POJO,因为对于 JavaScript 来说,其首字母缩写与 Java 是一样的。 根据之前的 Storage 代码,我们将 Task 添加到 todolist.js 的模块定义中: Android Web 应用高级编程 48 Task: function(params) { params = jQuery.extend({ name: "", description: "", due: null }, params); // initialize self var self = { id: null, name: params.name, description: params.description, due: params.due ? new Date(params.due) : null }; return self; } 说明: 虽然我们钟爱于使用 POJO 来获取应用中对象的详细信息,但这并非总是最佳的方式。 可以看到在上面的情况中,Task 最初并没有添加多少额外的值。我个人认为当应用达到了一 定的规模与复杂度时,使用这种方式来架构应用有好处,但一开始你会觉得这么做有些浪费 时间。 对于保存 Task 来说,我们将提供另外一种实现,该实现无需 POJO 就可以完成任务, 如果你钟爱这种方式那么就可以使用它了。 我们还需要一些支持代码,如果你曾经使用过 jQuery 与 Web 应用,那么你自己可能 编写过这样的代码。要想不再一遍又一遍地编写相同的代码从表单中提取出值并将其映射 到对象上,可以通过下面的代码简化该过程(假设对表单域与对象属性使用了相同的名字)。 请将下述代码添加到 prowebapps.js 文件的模块定义中(记住,根据代码添加到模块中 的位置,可能需要在代码末尾添加一个逗号): getFormValues: function(form) { var values = {}; // iterate through the form values using jQuery jQuery(form).find(":input").each(function() { // get the name of the field 第 3 章 HTML5 Storage API 49 var fieldName = this.name.replace(/^.*\[(\w+)\]$/, "$1"); // set the value for the field values[fieldName] = this.value; }); return values; } 既然完成了所有的构建模块,现在回头修改一下提交处理程序(在第 2 章结束时我们并 没有编写该处理程序)以便它可以完成任务清单的保存工作。 $("#taskentry").validate({ submitHandler: function(form) { // get the values from the form in hashmap var formValues = PROWEBAPPS.getFormValues(form); // create a new item to save to the database var item = new TODOLIST.Task(formValues); // now create a new task TODOLIST.Storage.saveTask(item); }, showErrors: function(errorMap, errorList) { // code from chapter 02 } }); 保存新的任务清单条目很简单,只需以下 3 步。 (1) 获取表单域的值并将其保存为 JavaScript 的键/值对数组。 (2) 从这些表单值中创建一个新的条目。 (3) 调用 Storage 模块保存该条目。 在 Android 手机或模拟器上运行上述代码的结果令人相当不满意,因为我们尚未构建 界面来显示这些条目。为了证明代码没问题,我们首先使用 Chrome 开发者工具在 Chrome 中检验效果。如第 1 章所述,对于调试移动应用来说,基于 WebKit 的桌面浏览器是非常 重要的工具——我个人钟爱 Chrome。 我们需要为表单提供一些数据。如图 3-3 所示,我的任务清单是在 2011 年底前完成对 草坪的修剪工作(这可不是我的强项)。 Android Web 应用高级编程 50 图 3-3 Chrome 中显示的带有示例数据的 Create Task 表单 当提交表单时,数据会经过我们在第 2 章创建的校验检查,然后根据提交处理程序中 的逻辑保存起来。查看开发者工具中的 Storage 页签,我们会发现数据实际上已经被保存 到了本地数据库当中。图 3-4 展示了 Chrome 开发者工具。 第 3 章 HTML5 Storage API 51 图 3-4 新任务被保存起来,并且可以通过 Chrome 开发者工具的 Storage 检查器查看 如前所述,如果你不喜欢使用 POJO 等将应用中的数据封装起来,那么可以将提交处 理程序的代码修改成下面这样: $("#taskentry").validate({ submitHandler: function(form) { // get the values from the form in hashmap var formValues = PROWEBAPPS.getFormValues(form); // now create a new to-do list item TODOLIST.Storage.saveTask(formValues); }, showErrors: function(errorMap, errorList) { // code from chapter 02 } }); 如果将 Storage 模块的代码修改成上面这样,那么日期在输入时就会被保存起来。 图 3-5 展示了数据库持有一个与输入相同的值,这表明该值被保存成了字符串。 Android Web 应用高级编程 52 图 3-5 在插入到数据库前没有将字符串转换为 Date 对象的结果 你可以看到第 2 个条目已经被添加进去了。事实上,第 1 个条目也是这样添加的,因 为这里所显示的只不过是 JavaScript Date 对象的 toString 表示(因为我们在 Task POJO 中将 表单值转换成了 Date)。 警告: 上述行为很有趣,我们已经在 CREATE TABLE 语句中显式地将 due 字段定义成了 DATETIME 类型。虽然这在应用中不会导致任何问题,但我们认为在当前 SQL Web Database 的实现下,除了写操作外,你还需要测试读操作的数据,因为你不能确定数据库中的数据就 是你所期望的类型。 为了证实该行为,请修改代码来看看是否可以将字符串值输入到 due 字段中。你会发现 是可行的。这与 SQLite 处理 DATETIME 列的方式是一致的。请参阅 SQLite 站点的 第 3 章 HTML5 Storage API 53 www.sqlite.org/datatype3.html 地址了解更多信息。 3.2.2 数据库的版本化与升级 Web SQL Database 的 HTML5 实现经过了深思熟虑,它提供了一种机制可以在部署应 用的新版本时对客户端数据库应用版本变更。这需要对开启的数据库使用 changeVersion 方法。可以使用该方法指定旧版本、新版本以及待执行的事务回滚,这样就可以在从一个 版本迁移到另一个版本时进行更新了。此外,还可以通过可选的错误与成功回调函数来监 控并响应这些特定的响应条件。 比如说,第 4 章将会向任务清单列表应用中添加一些功能以便用户可以将某些条目标 记为已完成状态。这个想法非常好,但显然数据库缺少存储已完成标记或日期的字段。这 时就需要解决这个问题并创建 1.1 版的数据库了。 下述代码展示了如何使用既能打开 1.1 版,也能打开 1.0 版的数据库,并且可以升级 1.0 版的方法来替换掉 TODOLIST.Storage 模块中已有的 openDatabase 调用: var db = null; try { db = openDatabase("todolist", "1.1", "To Do List Database", 100 * 1024); // check that we have the required tables created db.transaction(function(transaction) { transaction.executeSql( "CREATE TABLE IF NOT EXISTS task(" + " name TEXT NOT NULL, " + " description TEXT, " + " due DATETIME, " + " completed DATETIME);"); }); } catch (e) { db = openDatabase("todolist", "1.0", "To Do List Database", 100 * 1024); // check that we have the required tables created db.transaction(function(transaction) { transaction.executeSql( "CREATE TABLE IF NOT EXISTS task(" + " name TEXT NOT NULL, " + " description TEXT, " + " due DATETIME);"); }); db.changeVersion("1.0", "1.1", function(transaction) { transaction.executeSql("ALTER TABLE item ADD completed DATETIME;"); }); Android Web 应用高级编程 54 } 坦白地说,我们对这个实现并不满意。它确实可行,但我们想知道如果要对应用进行 第 13 次更新并且数据库也要进行相应的变更时,这种做法的弹性到底如何呢(当然了,每 次应用更新时都要修改数据库可不是件好事,但你应该明白我的意思)。 3.3 小结 本章介绍了在客户端存储数据的两种强大机制。虽然这并非 HTML5 最闪光的新功能, 但它却担当了破局者的角色。能将不同类型的数据存储在客户端的功能会为未来几年的移 动 Web 应用带来众多的机遇。 除了 Web Storage 与 Web SQL Database 外,我们还介绍了如何使用 JavaScript 的模块 机制对其以及 Web 应用中的其他功能进行高效封装的方法。这在使用 JavaScript 开发正式 应用时是非常重要的,但接下来的几章重点就不在这了。 第 4 章将会完成该任务清单应用,我们将添加一个视图来显示任务清单条目,还将提 供一种方式将其标记为已完成状态。现在,我们将从单页面示例转向到多视图/界面应用上, 并会探究构建这种功能的方式,还会简要介绍已经提供这种功能的库。 与云进行同步 本章将会介绍如何使用各种托管(或云)服务将任务清单列表中的任务同步到在线存储 中。虽然离线存储是一个非常便捷且强大的特性,但不容否认的是我们生活在互联的世界 中。数据应该共享并且可以在线访问。为了实现这个目标,我们在本地所搜集的所有数据 都应该与在线的解决方案进行同步。 本章将会介绍如何使用一些前沿技术如 NoSQL 云托管方案,这是一种不依赖于传统 关系模式的存储系统,提供了新的方式来处理数据存储。目前,我们有很多解决方案都封 装了这些新技术的复杂的部分,这样你就可以专注于应用的核心开发了。 但这并非全部——使用在线存储框架可以将移动 Web 应用托管到云中,这样全世界都 可以访问你的应用了。虽然原生应用位于设备当中,但移动 Web 应用却需要托管起来。也 就是说,应用的页面是由远程位置提供的服务——与移动原生浏览器进行通信的服务器。 虽然可以将应用托管给众多传统的 Web 托管提供商,但本章仍会介绍强大且实惠的云托管 解决方案。该解决方案可以托管存储机制与应用本身。 5.1 探索在线存储的选择 目前,将本地存储的数据与云中的中央存储进行同步的选择仍旧受限。但这种情况正 在逐步改变,未来我们将会拥有多种选择。但出于本章的目的,我们主要的需求是能与客 户端良好协作,特别是能与 JavaScript 良好协作,所以我们将使用 JSON(JavaScript Object Notation)格式来传输和存储数据。JSON 的另外一个好处在于它是基于 Web 的架构中最常 使用的一种数据交互格式(除了 XML 外),其中客户端会大量使用 JavaScript。让我们看看 对合适的在线数据同步存储的需求,它要确保离线数据能够镜像到 Web 上(这样其他人也 能访问)。 5.1.1 在线同步存储的需求 我们要看在线存储解决方案中的哪些内容呢?符合我们通常需求的同步解决方案要 第 章 5 0 Android Web 应用高级编程 84 满足如下几个特性。 ● 不要使用复杂且老式的 3 层架构(前端、后端与数据库) ● 有用户认证 ● 支持 JavaScript 库,可以同步存储在设备上的数据 此外,考虑到我们想给予用户最棒的体验,在线存储解决方案不应该再需要其他人/ 团队来创建或是维护。它应该既简单又具备可伸缩性。 请谨记简单性这个需求,我们将会深入探索上面所提的几个特性。 5.1.2 避免 3 层架构 经典 Web 应用基于 3 层框架,由如下 3 层构成。 ● 前端层,包含 HTML 页面、样式及其他资源 ● 后端层,由应用服务器托管(如面向企业级 Java 解决方案的 Tomcat 和 JBoss、面 向.NET 应用的 IIS) ● 数据库提供者,比如 MySQL、Oracle 或是 Microsoft SQL Server 这种架构要求至少掌握 3 种不同的开发技能:前端、后端以及数据库技术。这远远超 出了本书的讨论范围,因为我们想专注于构建 Web 应用——特别是前端。此外,使用现代 框架,我们可以将原来有问题的业务逻辑与存储抽取出来。 5.1.3 用户认证 现在,我们所构建的移动应用对于单个设备的用户使用来说没有问题。然而,将所有 信息存储到单独的设备上却并非最佳方案。如果将信息存储到 Internet 上,则需要一些身 份信息,这样才可以从第 2 台、第 3 台设备上获取这些信息。 由于我们所构建的是基于 Web 的应用而非原生应用,因此无法使用唯一的设备 IMEI(International Mobile Equipment Identity)号或是其他低层次信息。这是因为移动 Web 浏览器无法访问该信息。幸好,Web 端有很多选择可以用来管理身份信息。身份可以是很 多内容——OpenID(参见 http://openid.net)、Google 账号或是 Twitter 账号,等等。然而,人 们不需要针对不同应用反复创建的新账号,因为反复创建账号并非一个可重用和有效的解 决方案。 5.1.4 JavaScript 同步库 在理想情况下,我们想要寻求这样一种解决方案:它能获取本地存储的数据并进行一 些基于约定的同步处理,将数据推送到云中。一旦数据处于在线状态,接下来就可以从桌 面应用中使用它了,或是在运行着 Android 或 Chrome 的平板电脑中使用。 目前,这个领域中的方案还很难找到,因为对于大多数人来说,跨平台的 Web 应用还 是个新事物。另一个选择就是编写自己的同步库。这并不难,因为我们的需求非常简单, 上一章已经介绍过了如何与离线数据库进行通信。 第 5 章 与云进行同步 85 5.1.5 可能的同步解决方案 能够满足我们需求的一个解决方案就是 jsonengine(参见 http://code.google.com/p/ jsonengine)。下面是其最有价值的几个特性。 ● 在 Google App Engine 上运行(基于云的解决方案)。 ● 不需要后端脚本。 ● 使用 Google 账号机制和 OpenID 提供了身份管理。 ● 除了是个数据存储解决方案外,jsonengine 本身还可以托管和处理应用代码。 顾名思义,jsonengine 使用 JSON 格式来存储数据。这很有用,因为我们的应用大量使 用了 JavaScript,使用 JSON2 库可以很轻松地操纵 JSON 对象。 此外,jsonengine 提供了一种健壮的机制来通过 REST(http://en.wikipedia.org/wiki/ Representational_State_Transfer)API 插入数据。其 API 端点提供了使用标准 HTTP 方法(分 别是 GET、POST、PUT 和 DELETE)来读取、插入、更新和删除数据的能力。 5.2 Google App Engine 入门 jsonengine 实例最好放在 Google App Engine 上托管(http://appengine.google.com/),这 里 做一个简短的介绍。Google App Engine 是 Google 的云计算技术。它跨越了多个服务器与 数据中心来呈现 Web 应用。简而言之,这意味着应用不再运行在单一的物理位置(就像硅 谷中的数据中心),而是利用 Google 广泛的数据中心复制到全世界。 Google App Engine 是个平台即服务(PaaS)解决方案,与其他的云计算服务(如 Amazon Web Services)不同。主要的差别在于 Google App Engine 提供了基于约定而非配置的一整套 服务与工具,这样就可以快速部署应用了。Google App Engine 的另一个优势就是其定价模 型——对于一定程度的资源使用来说,它是免费的。 说明: PaaS 类似于其他类型的托管服务,但相对于提供一个空的、“通配的”服务器来说,它 提供了一套预先定义好的服务与硬件解决方案。它封装了很多复杂的部分与配置信息。代价 就是你的自由度将减少,必须得遵循方案提供商的指南才行。 在开始使用 jsonengine 前,必须搭建好 Google App Engine。首先需要下载 Google App Engine 框架,这样才能在将应用放到云中之前在本地对其进行测试和部署。Google App Engine 支持两种不同的框架,分别是 ● 基于 Python 的 SDK ● 基于 Java 的 SDK 由于 jsonengine 是基于 Java 的,因此要使用基于 Java 的版本。如果不熟悉 Java 也不 Android Web 应用高级编程 86 要紧——这并非是必要的。我们只需要部署 jsonengine,因此你不需要对其进行任何配置 或是实现。 现在开始安装 Google App Engine 吧。首先,下载其二进制文件,地址如下: http://code.google.com/appengine/downloads.html 然后阅读其安装指南,地址如下: http://code.google.com/appengine/docs/java/gettingstarted/installing.html jsonengine 站点上还提供了另一个安装 App Engine SDK 的很有价值的指南: http://code.google.com/p/jsonengine/wiki/HowToInstall 5.2.1 在本地部署 jsonengine 部署 jsonengine 实例非常简单:只需要下载提供的 WAR 归档并使用如下的 Google App Engine 命令就可以部署了。 dev_appserver location/to/you/war/folder 这会在本地启动 Google App Engine 服务器,打开喜欢的浏览器,访问 http://localhost:8080/samples/bbs.html。你现在应该可以看到 jsonengine 的示例应用。 就是这些!既然已经有了可运行、可伸缩的在线存储解决方案,便可以回到清单应用 上开始实现同步处理。 由于 jsonengine 是个 Web 应用而不仅仅是个存储解决方案,因此它可以处理 Web 内容, 这使得它非常适合于我们所构建的移动 Web 应用。我们所实现的所有代码(HTML、 JavaScript 与 CSS)都可以托管在 jsonengine 实例中。 查看一下 jsonengine 的分发包。它有一个 war 目录,包含了一个传统的 Web 应用结构 (CSS、JS 等)。如果将所有资源放到正确的目录中,那么只需使用 App Engine SDK 的一条 命令就可以完成应用的部署——这非常简洁。在部署应用时,war 目录会被打包到 WAR 归档中(WAR 归档只不过是 JAR 的企业版本,其功能上类似于 ZIP 文件)。 现在来做个测试以确保除了 JSON 数据外,jsonengine 还可以处理 Web 资源。我们使 用第 4 章的示例代码并将其粘贴到 war 目录中。目录结构应该如下所示: JSONEngine— |- war— |- css— |- proui.css |- js |- jquery-1.4.2.min.js |- jquery.validate.js |- json2.js |- prowebapps.js |- snippets— |- 04— |- todolist.html 第 5 章 与云进行同步 87 |- todolist.css |- todolist.js 浏览 http://localhost:8080/snippets/04/todolist.html,你会看到应用正在运行。 由于构建的是移动 Web 应用,因此还应该在 Android 模拟器上测试一下。正如第 1 章 所述,Android 模拟器无法通过 localhost 地址与机器上的 Web 服务器通信。因此,需要将 AppEngine 开发服务器绑定到机器上的其他网络地址(默认情况下,Google App Engine 本地 服务器会绑定到 localhost 地址)。 幸好,dev_appserver 命令带有一个参数,可以让服务器监听本地机器的所有 IP 地址 (0.0.0.0)。 停止服务器,然后重新启动,方式是在命令后以及 war 目录的路径前添加参数–a 0.0.0.0。完成后,就可以通过本地机器的 IP 从模拟器或移动设备中访问应用了。 说明: 如果后台运行着 Mongoose,那么 AppEngine 开发服务器就无法绑定到其他地址了。在 这种情况下,只需要关闭 Mongoose 服务器,然后再次启动 AppEngine 开发服务器即可。 本章结尾会介绍如何将应用部署到云中;这并不复杂,只需要一条命令就可以实现。 部署的在线应用将与本地服务器上的应用具有相同的行为——不需要任何配置,这会节省 大量时间。 5.2.2 选择合适的同步模式 在理想情况下,数据同步是双向的,比如说系统 A 创建/更新的记录会反映到系统 B 上,后者也可以更新/创建数据。然而,这会带来很多数据集成问题。比如说,如果系统 A 在更新记录时,系统 B 却正在删除该记录,这该如何处理呢?如果使用遗留的存储系统如 Oracle 或 MySQL,那么你可以使用锁、回滚及其他类似的特性来处理这类问题。但现在我 们使用的是与之不同且颇具创新性的方法,为了保持一定程度的简单性,可以选择一个简 单却强大的同步模式。 我们使用单向的同步,在这种情况下,离线数据库将成为主数据库,而在线存储则成 为从数据库。这意味着每条新记录最初都会存储在离线数据库中,只有当用户决定同步后 才会被同步到在线存储中。直接将新记录插入到 jsonengine 中是不可能的。 为了阐明这一点,假设有如下的用例:我们的任务清单应用变得越来越流行,某个知 名的科学家正在使用它,他在丛林中搜集着数据。一整天下来,数据都是离线存储的,但 当这位科学家晚上回到办公室后,他想与位于阿姆斯特丹总部的其他同事共享搜集到的信 息。他的同事拥有只读权限,但还有一些批处理会向世界各地的科学家们输出有趣的统计 数据。 Android Web 应用高级编程 88 5.2.3 向 jsonengine 发送离线数据 由于 jsonengine 要求输入为 JSON 格式,因此我们需要将离线 SQL 数据转换为 JSON 格式。整个流程非常简单。 (1) 选择所有离线记录。 (2) 将其转换为 JSON 格式。 (3) 将其发送给 jsonengine。 我们之前已经实现了选择所有的数据库记录,因此可以复制和粘贴现有的函数、做些 清理、添加一些 JSON 转换,最后将数据发送给在线存储服务,如下所示。 function synchronizeOnline(callback) { db.transaction(function(transaction) { transaction.executeSql( "SELECT rowid as id, * FROM task ", [], function (transaction, results) { var tasksSynchronized = 0; // initialise an array to hold the tasks // read each of the rows from the db, and create tasks for (var ii = 0; ii
  • Show All Tasks
  • Android Web 应用高级编程 90
  • Add New
  • Synchronize
  • 现在,需要将单击事件绑定到新的 synchronizeOnline()函数上: activateMain: function() { TODOLIST.Storage.getMostImportantTask(function(task) { if (task) { // the no tasks message may be displayed, so remove it jQuery("#main .notasks").remove(); // update the task details showTaskDetails("#main .task", task); // attach a click handler to the complete task button jQuery("#main .task-complete").unbind().click(function() { jQuery("#main .task").slideUp(); // mark the task as complete task.complete(); // save the task back to storage TODOLIST.Storage.saveTask(task, module.activateMain); }); jQuery("#main .synchronize").unbind().click(function() { TODOLIST.Storage.synchronizeTasks(); }); } else { jQuery("#main .notasks").remove(); jQuery("#main .task").slideUp().after("

    You have no tasks to complete

    "); } }); } 接下来,只需要通过 Storage 子模块将 synchronizeOnline 函数公开为 synchronizeTasks 函数: Storage: (function() { ... var subModule = { ... synchronizeTasks: synchronizeOnline, ... }; returnsubModule; })() 图 5-1 添加一个 Synchronize 按钮 第 5 章 与云进行同步 91 刷新应用,你会看到更新后的用户界面,如图 5-1 所示。 现在只需要单击 Synchronize 按钮,所有的离线数据就都会被放到现代化、可伸缩的 JSON 存储系统当中!这很棒,但到目前为止,我们还没有任何关于这个过程的反馈—— 这是在幕后发生的。要想解决这个问题,请放置一个信息条,一旦同步过程完毕,该信息 条就会显示出来。 首先,在按钮栈之上添加一个“info”div 元素: ...
    然后为该 div 添加一些样式: #info { margin: 8px 6px 2px 6px; padding: 6px 14px; background: -webkit-gradient(linear, left top, left bottom, color-stop(0.0, #71D90F), color-stop(0.7, #529E0B)); -webkit-border-radius: 4px; color: white; display: none; } 注意 display:none 属性,它确保直到出现理想的显式显示时,该 div 才会显示出来。 为了显示通知消息,我们需要对 activateMain 函数进行一些修改以便同步过程开始时 会显示一些信息。 activateMain: function() { TODOLIST.Storage.getMostImportantTask(function(task) { if (task) { ... jQuery("#main .synchronize").u nbind().click(function() { TODOLIST.Storage.synchron izeTasks(function(updateCount) { $("#info") .html("Completed : " + updateCount + " task(s) have been synchroni zed !") .show(); }); }); } 图 5-2 可视化的同步反馈 Android Web 应用高级编程 92 ... }); } 这里所做的就是更新 info div HTML。现在,当同步过程完成时,信息条就会显 示出来,如图 5-2 所示。 既然我们可以确保同步过程能够进行,那现在就需要一个客户端界面,它会从在线存 储解决方案中读取数据。 5.4 开发桌面界面 构建 Web 应用的一个好处在于你可以轻松地以各种不同的 Web 格式访问数据——比 如说,平板设备和更标准的桌面浏览器。首先,我们将注意力放在后者,看看如何将存储 解决方案连接到“常规的”桌面 Web 应用上。 从现有的移动界面创建桌面界面要比从现有的桌面 Web 应用创建移动界面简单多 了。为了证实这一点,看看各种移动应用界面是如何影响桌面应用的用户界面吧。最近 更新的 Twitter Web 界面就是移动用户界面影响到桌面界面以实现更好的用户体验的一 个例子。 因为我们只有单向的同步,因此桌面应用是只读的(由于构建桌面界面并非本书所讨论 的范畴,因此我们尽量简化)。如果回到之前科学家搜集数据的那个示例,那么就要考虑将 桌面应用作为其同事(想要了解他的最新发现)的报表工具。 通过本节的介绍,你应该知道构建一个依赖于健壮和标准的 Web 技术的移动 Web 应 用可以将其特性扩展到其他 Web 引擎,特别是桌面 Web 界面,比如说笔记本或桌面系统 上的浏览器。 我们所要做的第一件事就是从基于云的存储解决方案中获取到所存储的数据。 5.4.1 查询 jsonengine 实例 你已经看到使用 REST API 插入数据是多么的简单。与之类似,通过 REST API,使用 HTTP GET 也可以查询数据。首先需要创建一个新的 HTML 页面— 称之为 todolist-readonly.html。 Tasks page 第 5 章 与云进行同步 93

    Current tasks stored in jsonengine

    Name Description Due
    有趣的是当页面加载时所调用的 JavaScript 函数。我们使用了 jQuery 的 get 函数—— 其语法与 post 函数几乎一样,但现在传递的是排序参数。其结果是个 JSON 文档的数组。 JSON 文档是 Task 实例的 JSON 字符串表示。只需要遍历该数组并更新 HTML 表格来添加 新的一行即可。 在这个特定的实例中,首先搜集所有的新行并将其放在一个字符串中,然后通过一个 调用(使用 jQuery html 函数)替换掉表格的 body(tbody)。虽然可以追加找到的每一行,但尽 量少地更新 HTML 可以提升应用的速度。虽然在这种小页面中效果并不明显,但这却是一 个好习惯。 完成了 HTML 页面后,还要向 todolist-readonly.css 样式表中添加一些基本的样式: table, td { border: 1px solid #666666; Android Web 应用高级编程 94 width: 100%; } td { width:33%; padding: 2px; } 接下来转到新建的页面,如图 5-3 所示。 图 5-3 显示了同步任务的桌面应用 如你所见,离线数据库中的所有数据现在都处于在线状态——至少位于服务器上;我 们并没有将应用上传到云上。在这之前,需要向存储解决方案添加一些安全保障;如果不 这么做,那么每个人都可以访问该桌面网页了。幸好,jsonengine 带有一个安全的管理页 面,你可以设定存储文档的访问级别,如图 5-4 所示。 第 5 章 与云进行同步 95 图 5-4 jsonengine 安全管理页面 我们可以在这里添加 JSON “tasks”文档并将读取级别设为“protected”,这样使用 Google 账号或 OpenID 账号连接的用户就只能查看数据了。 5.4.2 将应用部署到云中 现在是时候让应用上线以便全世界都能使用你的移动应用了。但首先需要一个 Google账号(或是Gmail账号)。获取账号之后,就可以通过登录https:// appengine. google.com 访问 Google App Engine 了。你可以在上面创建新的应用,选择应用标识符(这也是访问应 用的 URL)还有标题。在上传应用之前,必须要确保应用标识符与本地应用中定义的相匹 配。为了做到这一点,打开 war/webing/appengine-web.xml 文件,查看下面这行: jsonengine 使用方才选择的应用 ID 替换掉 jsonengine。现在就可以部署应用了——进入 Google App Engine SDK 的 bin 目录,输入如下命令(在 Windows 上): appcfg.cmd update location/to/you/war/folder 在 Mac 和 Linux 上输入如下命令: appcfg.sh update location/to/you/war/folder 在提示框中输入 Google 用户名与密码。如果上传过程成功,那么就会看到消息“Update Android Web 应用高级编程 96 completed successfully.”。就是这些!我们的应用现在已经在云中了—请访问页面 http://my-appid.appspot.com/snippets/05/todolist-readonly.html 5.5 小结 到目前为止,我们已经实现了一些有趣的内容:我们的移动 Web 应用拥有很强大的特 性,可以离线存储数据,也可以将数据同步到在线环境中。使用强大的在线存储机制为移 动应用同步离线数据是个非常新颖且颇为创新性的概念。 现在我们所需的是能与原生应用竞争的一些特性。第 6 章将会添加一些有趣的用户界 面元素并处理离线的情况。
    还剩57页未读

    继续阅读

    下载pdf到电脑,查找使用更方便

    pdf的实际排版效果,会与网站的显示效果略有不同!!

    需要 6 金币 [ 分享pdf获得金币 ] 7 人已下载

    下载pdf

    pdf贡献者

    fairylight

    贡献于2013-05-05

    下载需要 6 金币 [金币充值 ]
    亲,您也可以通过 分享原创pdf 来获得金币奖励!
    下载pdf