PhoneGap-入门指南-全-

PhoneGap 入门指南(全)

原文:Beginning PhoneGap

协议:CC BY-NC-SA 4.0

零、简介

这本书是给谁的

这本书是为任何想要开始跨多个移动平台开发移动应用的人准备的。该书提供了关于 PhoneGap 的介绍和详细教程,并在以下方面为读者提供了帮助:

  1. 确定哪种 JavaScript UI 框架最适合他们
  2. 介绍 JavaScript UI 框架及其与 PhoneGap 的集成
  3. 解释插件的概念以及如何使用它来进行 OAuth 身份验证和云推送
  4. 解释如何编写自定义插件

这本书的结构

这本书首先解释了移动操作系统世界的碎片化以及它如何影响我们。它进一步讨论了如何弥合由于这种碎片化造成的差距,以及如何编写一次代码并跨移动平台部署它。

在 PhoneGap 背后的概念被弄清楚之后,这本书继续解释 PhoneGap 在 Android 上的用法,然后给出了如何在其他剩余的移动平台上做同样事情的说明。

接下来介绍了如何在 PhoneGap 上使用 JavaScript UI 框架,还讨论了在什么场景中使用哪个 JavaScript UI 框架。

最后,这本书将重点转移到插件上。它展示了几个如何用社区插件扩展 PhoneGap 框架的例子。然后解释了如何在 iOS、Android 和 BlackBerry 上构建这些插件。

下载代码

本书中提到的所有源代码都可以在[bitbucket.org/rohitghatol/apress-phonegap](https://bitbucket.org/rohitghatol/apress-phonegap)获得。这些章节本身就说明了这一点。也可在 Apress 网站[Apress.com](http://apress.com)上获得。

联系作者

可以通过作者的 LinkedIn 个人资料联系到他们:
Rohit Ghatol—[in.linkedin.com/in/rohitghatol](http://in.linkedin.com/in/rohitghatol)
Yogesh Patel—[www.linkedin.com/profile/view?id=19911394](http://www.linkedin.com/profile/view?id=19911394)

一、了解跨平台移动应用开发

这本书是关于移动应用开发的;更确切地说,是关于减轻移动应用开发的痛苦。市场上有许多智能手机平台:安卓、iPhone、黑莓、诺基亚、Windows 7 手机和 WebOS。更新的平台也在增加,比如三星的 Bada 和 Meego。

移动应用开发平台的数量之多似乎令人应接不暇。在处理移动应用开发时,这是你必须记住的第一点。

在 2000 年,我们在台式机领域看到了类似的情况。我们有微软的 Windows,苹果的 Mac,以及各种版本的 Linux 和 UNIX。那时,很难构建能在所有这些平台上运行的产品。由此产生的碎片通常通过内部解决方案来解决,方法是用 C++构建框架,抽象出特定于操作系统(OS)的模块。幸运的是,Sun 的 Java 拯救了我们,为我们提供了一个通用的构建平台。有了 Java 的“一次构建,随处运行”策略,构建桌面产品变得轻而易举。

在 2004 年到 2008 年之间,开发者社区看到了一种不同的分裂;这一次,它发生在浏览器世界。这是一场涉及非常流行的 Internet Explorer 6 与 Firefox 和 Safari 的分裂——然后,Chrome 和其他浏览器突然出现,导致了进一步的分裂。

然而,这种碎片化的本质是不同的,更温和一些:这主要是由于浏览器没有遵循万维网联盟(W3C)概述的规范。通常,这种分裂是通过写“如果浏览器是 IE,那么做这个做那个”或者“如果特性存在,那么做这个做那个”来解决的。

许多 JavaScript 库来帮忙编写跨浏览器的 web 应用。事情已经改善到这样的程度,所有的浏览器都在努力工作,越来越符合 W3C 规范。浏览器作为一个平台,现在是一个强有力的竞争者。

这本书是关于移动世界的碎片化。移动操作系统碎片化非常严重,因为在这个开发领域没有规范或标准。

2007 年,苹果和谷歌推出了他们的移动平台。2008 年,两家公司都推出了移动应用商店,允许智能手机用户下载移动应用。移动应用的时代已经开始;从此,再也没有回头。智能手机用户数量呈指数级增长。

公司开始专注于在新的智能手机平台上提供服务和内容。企业意识到他们需要将注意力转移到智能手机用户身上。不仅用户数量增加,智能手机的使用频率也增加了。

想象一下,您的开发人员日以继夜地在 iPhone、Android、BlackBerry、WebOS 和 Symbia 上发布相同的产品——现在,让我们将 Samsung Bada 添加到这个列表中!你可以看到这里的挑战。操作系统平台,从它们的开发环境开始,是如此的分散。对于 iPhone,你将需要 Mac 机,对于黑莓,你将需要 Windows。本章将更详细地讨论这些事情。

现在,对于那些刚接触移动应用开发的人来说,我们将从关注创建一个移动应用是什么样子开始。我们将回答诸如“移动应用与传统的基于网络或基于桌面的应用有何不同?”我们将研究为各种平台开发移动应用的挑战。

移动应用的类型

了解不同类型的移动应用非常重要。我将根据他们的工作把他们分为两类。

  1. 独立移动应用
  2. 移动应用(基于 web 服务)

独立移动应用是诸如闹钟、电话拨号器和离线游戏之类的应用。支持 Web 服务的移动应用包括电子邮件、日历、Twitter 客户端、在线游戏以及与 web 服务交互的应用。

这种移动应用之间的区别是本书上下文中所独有的。虽然 PhoneGap 可以用来实现独立的移动应用,但是基于 PhoneGap 的移动应用的本质通常属于“服务支持的移动应用”的范畴

了解 Web 服务

作为一名开发人员,当你看互联网上的 web 应用时,你需要考虑两种 web 开发。

  1. 可通过浏览器访问的 Web 应用(用于人机交互)
  2. 可以通过 RESTful Web 服务之类的协议访问的 web 服务(用于编程接口)

所有流行的网络应用,如谷歌、脸书、Twitter、LinkedIn、MySpace、Flickr 和 Picasa,都为它们的服务提供了 RESTful 界面。这类网站有很多在线词典。如果你访问[www.programmableweb.com](http://www.programmableweb.com),你会看到一个相当大的列表,列出了所有为编程接口提供此类服务的网络应用(见图 1–1)。

images

图 1–1。 可编程 Web API 目录

许多想要为多个平台开发移动应用的公司要么拥有自己的 web 服务,要么依赖于其他 web 服务。虽然 PhoneGap 可以用于独立的移动应用,但它非常适合使用 web 服务的移动应用。原因是 PhoneGap 应用主要是增加了设备功能的 web 应用。想象一下,一个 Flickr web 应用可以访问设备的摄像头或 Google Maps 应用,而后者又可以访问 GPS。另一个例子是 Foursquare,它可以访问你的 GPS,以及你手机的通讯录。

这或多或少意味着大多数基于 PhoneGap 的应用将使用 JavaScript 访问 web 服务。这使得使用 PhoneGap 的开发人员掌握使用 web 服务变得非常重要。

对于读过这本书后想要编写 PhoneGap 应用的开发人员,我建议在 ProgrammableWeb.com 上找到一些 web 服务,并为这些服务编写一个 PhoneGap 客户端作为练习。

这本书将提供一个这样的服务的例子;也就是 AlternativeTo.Net。

移动应用概述

虽然你们中的许多人至少有一些使用移动应用的经验,但你们中的许多人更熟悉非移动平台(例如,web 平台)。因此,这本书明确论述了移动应用的本质以及与之相关的挑战。如果您来自非移动背景,这将有助于您理解开发移动应用意味着什么。

移动应用功能

images

图 1–2。 移动应用不是 web 应用。

首先要注意的是,移动应用不是 web 应用。区别在于特性的性质和所提供的特性数量(参见图 1–3)。

  • 移动应用的功能可能会更少。
  • 您可以期待您的移动应用看起来与您的 web 应用非常不同。首先,智能手机的屏幕大小与桌面不同。在 web 应用中,屏幕越大,你就有越多的空间来放置菜单、工具栏和小部件。
    • 考虑到智能手机的屏幕尺寸限制,你会看到更多仪表板类型的主屏幕。
    • 智能手机用户需要通过不同级别的导航来找到他或她想要使用的功能。
  • 智能手机用户和网络用户有不同的意图。智能手机用户希望在旅途中使用应用,以最少的努力获得最大的生产力,而网络用户可能会花更多的时间使用网络应用。

由于上述差异,您将看到智能手机上最具生产力(或最常用)的功能被突出显示。无论移动应用提供所有的功能,还是其中的一个子集,这些小的生产(和最频繁使用的)功能集将在移动应用上以最容易访问的方式进行组织。

images

图 1–3。 移动功能与网络应用功能不同。

用户互动

相对于传统的 web 应用,用户与移动应用交互的方式非常不同(参见 Figure 1–4)。

随着智能手机的触摸屏功能和更加生动的用户交互,基于加速度计和指南针,移动应用必须以不同的方式构建。

想象一个汽车游戏应用,通过向左或向右倾斜手机来操纵汽车。这是基于加速度计。想象一个地图应用,当用户改变他或她的方向时,它总是指向北方。这是基于一个指南针。

虽然与应用交互的新方式增强了用户体验,但新的移动平台上缺少物理键盘给 power keyboard 用户增加了一些额外的限制。在详细阐述移动应用需求时,需要考虑到这一点。

除此之外,智能手机有两种显示模式:布局和纵向;这些在早期的浏览器中是闻所未闻的。记录需求规格的一个重要部分是定义当设备处于纵向或横向模式时应用的外观、感觉和行为。

images

图 1–4。 智能手机和网络应用有不同的用户输入界面。

位置感知

位置感知是智能手机与生俱来的功能。谷歌地图、本地搜索、Foursquare 和许多其他移动应用都利用了智能手机的精密 GPS。Web 应用也使用位置感知;然而,这些应用使用相对更粗粒度的 GPS 系统(例如,国家级)(见图 1–5)。

images

图 1–5。 与网络应用相比,智能手机应用的位置感知能力

推送通知

应用用户喜欢收到有用事件的通知,比如收到的电子邮件和消息。智能手机是最好的通知平台,因为它几乎无时无刻不在用户身边。

除了收到电子邮件或信息等通知,任何服务都可以向智能手机用户发送通知(见图 1–6)。考虑一个组织的工作流程。用户不必总是登录 web 应用来完成涉及他或她的工作流,应用通知用户他或她需要执行某个操作来完成工作流会更有效率。这样,无论用户是否靠近笔记本电脑或台式机,他/她都可以始终高效地工作。

images

图 1–6。 智能手机的推送通知功能(移动通知)

跨平台移动应用开发的挑战

虽然移动应用的开发令人兴奋,但考虑到移动操作系统(OS)数量的不断增长,开发移动应用会面临许多挑战。

让我们来看看这些挑战。

OS 碎片化

碎片化增加的趋势与移动平台数量的增长相一致(参见图 1–7)。首先是黑莓和塞班智能手机,然后是强大的 iPhone 和 Android 平台。可以肯定的是,移动平台并没有就此止步。惠普随 WebOS 而来;微软推出了 Windows 7 手机;而现在,三星要出 Bada 了。

这意味着公司必须不断推出新产品,让所有移动平台都能感受到它们的存在。

images

图 1–7。 由于移动操作系统数量的增长而导致的碎片化

假设你想开发一个移动应用,目标是 iPhone,Android,BlackBerry 等。由于每个移动平台的操作系统不同,请考虑以下因素:

  • 首先,你必须为每个平台设置不同的环境。
  • 第二,你需要对每个操作系统有一点专业知识。对于移动开发者来说,学习曲线可能会很长。
  • 不同的移动平台需要不同的编程语言。
  • 你需要熟悉每个移动平台支持的特性;参见图 1–10。

表 1-1 描述了移动应用开发所需的设置(针对各种移动平台)。

过去,我们已经看到过类似的操作系统碎片化,从 Windows、Linux 和 Mac 的跨桌面碎片化开始,随着 Sun 推出 Java,这一问题得到了解决。在更近的过去,我们面临浏览器碎片,这是通过跨浏览器 JavaScript 框架如 jquery、YUI 和 Google Web Toolkit 解决的。

移动操作系统碎片是所有碎片中最糟糕和最多样化的。这为在所有移动平台上启动移动应用增加了相当大的技术挑战。

多个团队/产品

如果我们选择使用多个团队为每个平台构建一个移动应用,我们会面临许多问题;增加团队会增加项目交付的风险;增加产品意味着产品管理团队承担更多责任(参见图 1–8)。由于所有移动平台上的功能也是分散的,产品管理人员必须对每个平台上的产品提出具体要求。

最终,增加更多的团队,增加多个团队之间的协调,以及增加多个产品将导致管理和开发团队的额外开销。

images

图 1–8为不同的移动操作系统增加多个团队带来了新的问题。

一致的用户体验

鉴于您希望您的应用在多个移动平台上保持一致,您的应用需要在所有平台上提供相似且一致的用户体验(参见 Figure 1–9)。这也与您的最终用户可能从一个平台迁移到另一个平台有关,或者他们可能存在于多个平台上。考虑一个拥有 Android 智能手机和 iPhone iPad 的用户。用户可以在家或在办公室使用 iPad,也可以在外出时使用 Android 智能手机。

这是你的应用必须提供跨移动平台的相似用户体验的众多原因之一;当然,由于设备特性和功能的分散,用户体验会因移动平台而有所不同。

images

图 1–9。 为跨平台的应用最终用户提供统一的用户体验

特征碎片化

设备特性和功能因平台而异(参见图 1–10)。这意味着,虽然一些机器人和 iPhones 有嵌入式指南针来显示方向,但其他智能手机没有。这可能意味着其他智能手机上的导航应用可能无法像 Android 或 iPhone 应用那样旋转地图。

总的来说,同一个应用在一些移动平台上会关闭一些功能,这是事实;应用的逻辑需要以这种方式编写。

images

图 1–10。 针对不同移动操作系统的特性碎片化

开发环境碎片化

开发环境是一个特别重要的片段。如果您想开发一个面向以下平台的移动应用,您至少需要两个操作系统:Windows(最好是 Windows 7)和 Mac(最好是 Leopard ):

  1. ios
  2. 机器人
  3. 黑莓
  4. WebOS
  5. 智能移动终端操作系统
  6. Windows 7

此外,您将不得不使用各种 ide 和编程语言,如 Java、C++和 Objective C。此外,您将使用许多 ide,如 Xcode 和 Eclipse。

表 1-1 显示了开发环境的要求(针对各种移动平台)。

Images

PhoneGap 的跨平台移动应用战略

PhoneGap 之所以成为可能,是因为所有移动平台之间的共性。如果没有这个通用组件,PhoneGap 就不可能存在。

浏览器组件作为通用平台

直到几年前,浏览器世界还很分散。当时,不同的浏览器在不同程度上遵循 W3C 标准。Firefox 和 Safari 浏览器在遵守标准方面走在了前列,而其他浏览器则落在了后面。

从那以后,很多事情都变了。现在,浏览器在遵守标准方面看起来更好了(在移动平台上更是如此)。这也是事实,因为大多数现代移动平台都有相同的基于 webkit 的浏览器。

此外,桌面和智能手机上的新浏览器已经开始遵循 HTML5/CSS3 等新标准。这为浏览器世界增加了更多功能,减少了移动平台之间的碎片化(参见图 1–11)。

images

图 1–11。 手机浏览器

我们来看表 1-2 ,表中列出了移动平台及其对应的浏览器平台。如你所见,除了 Windows 7 手机,所有的移动平台都使用基于 webkit 的浏览器。虽然 Windows 7 手机有自己的浏览器,但好消息是,这里列出的所有浏览器都已经遵守 HTML5/CSS3 标准,随着时间的推移,它们的遵守程度将继续提高。

PhoneGap 使用这些现代浏览器作为构建基于 HTML5/CSS3 的应用的平台。把所有 PhoneGap 应用想象成具有嵌入式浏览器并运行这些基于 HTML5/CSS3 的应用。

移动应用网页浏览量

所有这些移动平台都支持在应用中嵌入浏览器。这意味着移动应用的一个屏幕实际上可以是一个显示 HTML 页面的浏览器。

这些嵌入式浏览器通常被称为网络视图。这意味着您可以将应用的一个屏幕定义为 webview。

假设您的应用有一个名为“关于我们”的屏幕“关于我们”屏幕显示贵公司的信息。现在,让我们假设,例如,关于贵公司的“关于我们”的信息经常改变。您的移动应用的要求之一是显示最新的“关于我们”的信息。因此,您可以显示一个指向贵公司“关于我们”页面的 webview,而不是显示一个硬编码的“关于我们”屏幕(最好是网页的移动版本)。它将从网上加载“关于我们”页面。此外,webview 可用于加载和显示本地存储在移动设备上的 HTML 页面。我们可以进一步发展这个概念:我们可以显示与 web 服务交互的基于 Ajax 的 web 页面,而不是静态 web 页面。

本机挂钩暴露设备能力

既然我们知道浏览器可以嵌入到 web 应用中,让我们把注意力转移到通过这些嵌入式浏览器来公开设备功能上。

假设您正在开发一个基于 Flickr API 的 Flickr 应用。在这些 API 的帮助下,你可以登录 Flickr,列出图库,下载和展示你的图片。

虽然这对于 web 应用来说是一个好主意,但是当我们在手机上显示相同的应用时,请记住手机通常有一个摄像头。允许 Flickr 应用从相机中拍摄一张照片并上传到 Flickr 是非常合理的。

为了做到这一点,我们可以让嵌入式浏览器(或 webview)公开 JavaScript API,当调用该 API 时,它会让相机拍摄一张照片,并将该照片的二进制数据返回给我们(参见图 1–12)。

images

图 1–12。 JavaScript 到本地通信,反之亦然

从技术上讲,所有这些平台都支持在 webview 中将本机模块暴露给 JavaScript。这意味着,从程序上讲,所有这些平台都允许 JavaScript 代码调用本地 Java/C++/Objective C 代码,反之亦然。

让我们看一个例子。我们的 webview 有一个 HTML 页面,显示的是谷歌地图。我们希望根据手机的 GPS 位置将地图居中。为了做到这一点,我们需要编写一个本地组件,向设备查询 GPS 位置。

然后,我们编写代码,从 webview 中公开这个本机模块。webview 中的 JavaScript 代码调用此代码来访问 GPS 坐标。一旦代码获得了 GPS 坐标,它就相应地将地图居中。这是 PhoneGap 框架背后的主要原则。

HTML5 和 CSS3:编写应用的标准

HTML5 和 CSS3 是新兴的 web 技术。他们使网络应用更具交互性,功能更丰富。

HTML5 不仅为更强大的多媒体支持增加了新的标记;它还增加了一些特性,比如用于后台处理的 web worker、离线支持、数据库支持等等。

CSS3 是无缝、丰富的用户界面(UI)的新标准。设计师被要求在按钮或边框上制作简单的圆角或渐变的日子已经一去不复返了。有了 CSS3,事情变得更容易、更快、更好。

有了对动画的支持,CSS3 站点现在可以与基于 flash 的站点竞争了。不仅如此,只需更改 CSS 文件,门户网站就可以轻松地转换为移动网站。此外,打印预览现在可以实现不同的 CSS 文件。

众所周知,移动浏览器是 W3C 标准的早期采用者。这意味着手机是 HTML5/CSS3 应用的合适平台。

单一原产地政策不适用

对于那些使用过基于 Ajax 的应用的人来说,你知道托管在“abc.com”上的 web 应用不能对托管在“xyz.com”上的 web 服务进行 Ajax 调用。这意味着如果有人正在开发一个基于 Ajax 的应用,比如托管在 myphotobook.com 上,他或她将不能对 flickr.com 进行 Ajax 调用。

这被称为单一原产地政策——你可以在[en.wikipedia.org/wiki/Same_origin_policy](http://en.wikipedia.org/wiki/Same_origin_policy)进一步了解单一原产地政策。

对于 PhoneGap 应用来说,情况并非如此。PhoneGap 应用捆绑了所需的 HTML、JavaScript 和 CSS 文件,PhoneGap 应用没有像“abc.com”这样的域。这使得 PhoneGap 成为一个易于开发混搭的平台,可以自由地对各种其他站点进行 Ajax 调用。

想象一下,您的 PhoneGap 应用将脸书、Twitter 和 Flickr 集成到一个 mashup 中,只需要几行 JavaScript 代码。

这使得 PhoneGap 成为为在 programmableweb.com 上市的网络服务创建移动应用的理想平台。

这些限制在图 1–13 中说明:

images

图 1–13。 单一原产地政策

结论

PhoneGap 使用 HTML5、JavaScript 和 CSS3 来开发移动应用。这些是网络世界的标准技术。通过使用 PhoneGap,很少或没有母语背景的开发人员可以开始为所有流行的移动平台开发移动应用。

尽管 PhoneGap 提供了对移动应用标准原生功能的访问,但它的插件框架足够灵活,可以根据需要扩展和添加新功能。

PhoneGap 是一项正在发展的技术,用于开发跨移动平台应用。

二、PhoneGap 入门

PhoneGap 是一个 HTML5 应用框架,用于通过 web 技术开发本地应用。这意味着开发人员可以利用他们现有的 HTML、CSS 和 JavaScript 知识开发智能手机和平板电脑应用。有了 PhoneGap,开发人员就不必为 iPhone 学习像 Objective-C 这样的语言了。

使用 PhoneGap 开发的应用是混合应用。这些应用不完全基于 HTML/JavaScript,也不是本地的。应用的各个部分,主要是 UI、应用逻辑和与服务器的通信,都是基于 HTML/JavaScript 的。与设备(手机或平板电脑)进行通信和控制的应用的另一部分基于该平台的本地语言。PhoneGap 提供了从 JavaScript 世界到平台本地世界的桥梁,允许 JavaScript API 访问和控制设备(手机或平板电脑)。

PhoneGap 本质上为 JavaScript API 提供了对设备(手机或平板电脑)功能的访问,如摄像头、GPS、设备信息等。这些 API 将在第四章中详细介绍。

本章首先为您提供理解 PhoneGap 整体架构的适当信息。然后,我们将在 PhoneGap 示例中应用这些信息。在本章的最后,我们将使用 PhoneGap 编写一个小的 Hello World 应用。

注: PhoneGap 是一个框架;它没有为编码提供任何 ide 或特殊的开发环境。您将需要使用 Eclipse 和 Android SDK 来为 Android 开发 PhoneGap 应用;您需要使用 Xcode 为 IPhone 开发 PhoneGap 应用。

PhoneGap 架构

images

图 2–1。 PhoneGap 应用架构

PhoneGap 框架主要是一个 JavaScript 库,允许 HTML/JavaScript 应用访问设备功能。PhoneGap 框架也有一个本地组件,它在幕后工作,并在设备(手机或平板电脑)上完成实际工作。

请参考图 2–1 了解 PhoneGap 的整体架构。使用 PhoneGap 构建的应用主要由两部分组成:

  1. JavaScript 业务逻辑部分,驱动 UI 及其功能。
  2. JavaScript 部分,用于访问和控制设备(手机或平板电脑)。

考虑一个脸书应用。该应用的主要部分将是登录页面,并下载照片库。现在你想添加一个你可以拍照并上传到脸书的模块。为了做到这一点,您可以调用 PhoneGap 的相机 API 来访问手机的相机,拍摄照片,并获取图片文件。下一步是对脸书服务器的 AJAX 调用,以便上传图片。另一个可以应用的例子是使用 PhoneGap 在数据库中存储朋友列表,这样我们就可以搜索当地的朋友。

前面的描述给人的印象是,在 PhoneGap 中开发移动应用需要编写更多的业务逻辑和 UI,而很少访问设备的功能,这是正确的。这本书不仅解释了 PhoneGap APIs,也是创建基于 HTML5/CSS3 的移动应用的指南。

在 Android 上设置环境

创建 PhoneGap 应用的第一步是设置一个移动开发环境。我们将从 Android 开始,因为 Android 应用是用 Java 开发的,Java 基于 Eclipse,支持 PhoneGap 的几乎所有功能。

您需要下载并安装 Android 的以下必备软件:

  1. JDK 1.6 以上
  2. Eclipse 3.4 到 3.6
  3. 采用 Android 2.2 平台的 Android SDK
  4. Eclipse 的 Android ADT 插件
  5. Android 2.2 版的 Android AVD
  6. Android 版 PhoneGap SDK 1.1.0

由于 Android 是用 Java 编程的,所以我们需要 JDK 1.6+和 Eclipse 3.4+。然后我们将安装 Android SDK。Android SDK 是一个通用的 SDK,不支持任何平台。一个平台就是一个操作系统版本,例如 2.2 版的 Froyo、2.3 版的津嘉·布雷德和 3.0 版的蜂巢。为了创建、构建和运行 Android 项目,需要下载这些平台。这个插件叫做 Android ADT 插件。

一旦 Eclipse、Android SDK 和 Android ADT (Eclipse 插件)都设置好了,我们就需要为 Android 创建一个模拟器环境。这被称为准备 Android AVD (Android 虚拟设备)。如果我们正在为 Android 开发一个针对 2.2 Froyo 的 PhoneGap 应用,我们需要一个相同 Android 平台的 AVD。

以下步骤将解释如何创建一个 Android 项目并将 PhoneGap 库注入到 Android 中。

PhoneGap Android 项目的必需安装

  1. 安装 Eclipse 的 3.4 版本。
  2. 安装 Android SDK。
  3. 为 Eclipse 安装 Android ADT 插件。
  4. 为模拟器创建 AVD。
  5. 安装 PhoneGap 库。
第一步:安装 Eclipse

这一步假设您已经安装了 Java SDK 1.6。安装完成后,从[www.eclipse.org/downloads/](http://www.eclipse.org/downloads/)下载 Eclipse。参见图 2–2 查看 eclipse 下载页面。我们需要一个支持 JDT (Java 开发环境)的 Eclipse IDE 版本 3.4+。您应该为 Java 开发人员安装 Eclipse IDE。

images

图 2–2。 月食下载页面

第二步:安装 Android SDK

设置 Android 开发环境的一些步骤是依赖于平台的。为了避免任何混淆,我们将解释如何以特定于平台的方式执行每个步骤。

[developer.android.com/sdk/index.html](http://developer.android.com/sdk/index.html)开始下载 Android SDK(参见图 2–3)。

images

图 2–3。 Android SDK 下载页面

针对 Windows 的说明

使用 Android 安装程序,安装程序 r11-windows.exe 安装 Android SDK。这是推荐的 Windows 安装技术。另一种方法是下载 android-sdk r11-windows.zip 文件,并将其解压缩到一个文件夹中。我们假设 Android SDK 被提取到 c:\android_sdk。

【Linux 操作说明

下载 Archie Android-dk _ r11-Linux _ x86 . tgz 归档文件并解压到一个文件夹中。

Mac OSX 英特尔指令

下载存档 android-sdk_r11-mac_x86.zip 文件,并将其解压缩到文件夹中。

这个 Android SDK 可以支持目前已经发布的所有 Android 平台。这些平台包括 Android 1.1 平台到最近的 Android 3.0(蜂巢)平台。由于没有人需要所有的平台,Android SDK 没有预装任何平台。

对于本书,我们将只关注 SDK 平台:Android 2.2、API 8 和 revision 3。

由于没有预装平台,下一步是安装您感兴趣的平台。转到 Android SDK 位置(在我们的例子中是 c:\android_sdk),在 tools 文件夹中打开一个名为 Android 的可执行文件。如果您有带宽限制,不要下载所有平台,只下载 Android 2.2 平台(SDK 平台 Android 2.2、API 8 和修订版 3)。

这将打开在图 2–4 中看到的以下屏幕。选择可用的包选项,检查 Android 存储库,然后单击 Install。

images

图 2–4。 可以安装的可用平台包

既然您已经下载了平台,那么您就拥有了为目前已经发布的所有 Android 版本创建应用的必要工具。

建议您安装所有可用的包,这样您就可以有工具为已经发布的任何 Android 平台创建 Android 项目。

如果你想开发一个适用于 Froyo (Android 2.2)的移动应用,你需要有 Froyo (Android 2.2。)列在已安装的软件包中。

步骤 3:为 Eclipse 安装 Android ADT 插件
  1. 启动 Eclipse 并点击 Help->Install New Software 打开可用软件对话框。
  2. 在“使用”文本框中,输入 URL [dl-ssl.google.com/android/eclipse](https://dl-ssl.google.com/android/eclipse),如图 2–5 所示。
  3. 当您看到安装开发人员工具的选项时,单击它,选中开发人员工具复选框中的所有复选框,然后单击下一步。

images

图 2–5。 为 Eclipse 安装 Android ADT 插件

  1. 用之前安装的 Android SDK 的位置配置 Android ADT 插件。通过单击 Windows-> Windows 的首选项和 Eclipse-> Mac 的首选项打开 Eclipse 的首选项。如果您收到未签名内容警告对话框,您可以安全地忽略它。

  2. In the Preferences pane, click and expand the Android option. You will see Android Preferences pane as shown in Figure 2–6. In the Android Preferences pane, put in the location of the Android SDK in the SDK Location text box, and hit Apply.

    如果 Android SDK 的位置是正确的,您应该在 Target Name 下看到许多选项,包括 Android 2.2。

images

图 2–6。 在 Android 首选项屏幕中设置 Android SDK 的位置。

步骤 3:为 Android 2.2 平台创建 Android AVD
  1. Open Eclipse and create a workspace for the Android PhoneGap. The next step is to create an emulator for Android. Since Android comes with many platform versions, we have to create an Android Virtual Device (AVD) for each platform that is targeted. In Figure 2–7, you will see your eclipse as depicted in the screen.

    请注意,Android 模拟器运行的是 Android 虚拟设备(AVD)。

    images

    图 2–7。 带 ADT 插件的 Eclipse。

  2. Click on the images button on the toolbar to open the Android SDK and the AVD Manager. Choose the Virtual Devices option as depicted in Figure 2–8. images

    图 2–8。Android SDK 和 AVD 管理器

  3. 点击新建按钮创建一个新的 AVD。选择 Android 2.2 平台,也就是 Froyo。选择 128 MB 的 SD 卡大小,并选择皮肤内置为 HVGA。填写完所有内容后,单击 Create AVD。参见图 2–9 查看“AVD 屏幕”的样子。

images

图 2–9。 创建一个新的 Android 虚拟设备(AVD)在 Android 模拟器中运行

您将看到创建的 AVD,如图 2–10 所示。

images

图 2–10。 安卓 2.2 平台的 AVD(Froyo)

步骤 4:安装 PhoneGap SDK
  1. Download the PhoneGap SDK 1.1.0 from the following link, [phonegap.googlecode.com/files/phonegap-1.1.0.zip](http://phonegap.googlecode.com/files/phonegap-1.1.0.zip). After this zip is extracted you should see a directory structure, as seen in Figure 2–11. images

    图 2–11。 PhoneGap SDK 1.1.0 目录结构

  2. 选择 Android 目录,你会看到 phonegap-1.1.0.jar 和 phonegap-1.1.0.js 文件(见图 2–12)。

images

图 2–12。PhoneGap SDK 内的 Android 文件夹。

这就完成了 Android PhoneGap 的设置。

创建新项目

本书中的第一个应用是 Hello World 应用。加载 PhoneGap 框架后,Hello World PhoneGap 移动应用会在屏幕上显示 Hello World。

第一步:创建一个 Android 项目

打开 Eclipse,点击文件->新建项目->Android 项目。这将打开一个 Android 项目对话框,如图 2–13 和图 2–14 所示。这显示在以下步骤中:

  1. 将 PhoneGap-helloworld 作为项目名称。
  2. 确保您已经选择了 Android 2.2 作为构建目标。
  3. 输入 Helloworld 作为应用名称。这是应用的可读名称。
  4. 输入org.examples.phonegap.sample作为包名。Android market 中的一个应用是由包名唯一标识的。Android market 上不能有两个具有相同包名的 Android 应用。
  5. 选中“创建活动”复选框,并输入 helloworld 作为活动名称。Android 中的活动是一个屏幕。并且活动名也是活动的类名。
  6. 在 min SDK 版本中放 7。这意味着您将允许所有 Android 2.1 设备平台(也称为 clair Android 手机)搜索和安装该应用。

images

图 2–13。 安卓项目创建

images

图 2–14。 Android 项目创建。

步骤 2:将 PhoneGap 库添加到项目中

一旦创建了 Android 项目,就该将 PhoneGap 框架注入到 Android 项目中了。正如我们之前提到的,PhoneGap 有三个主要组件:原生组件、XML 插件和 JavaScript 文件。

  1. 要在 Android 中安装原生组件,在项目中创建一个名为 lib 的目录,并将 PhoneGap jar 复制到其中。您可以将phonegap-1.1.0.jar拖放到 lib 文件夹中,也可以将其复制并粘贴到 Eclipse IDE 的 lib 文件夹中。接下来,通过右键单击 Build Path - > Add to Build Path,将 PhoneGap jar 添加到类路径中。这在图 2–15 中突出显示。

  2. Copy the XML directory from the PhoneGap’s Android Directory into the res folder. images

    图 2–15。 突出显示 Android 项目中 PhoneGap jar 的位置

  3. 一旦 PhoneGap Jar 被添加到 Android 项目中,就该将 PhoneGap 的 JavaScript 文件注入到项目中了。我们将在 Android 项目的 Assets 文件夹下创建一个 www 文件夹。Assets 文件夹类似于 Android 应用的 media 文件夹。在我们的例子中,我们将把基于浏览器的应用的所有文件放在 www 文件夹中。首先,将 PhoneGap JavaScript 文件添加到 www 文件夹中,该文件夹位于 Assets 文件夹中。这在图 2–16 中突出显示。

images

图 2–16。 突出显示 PhoneGap JavaScript 文件在 Android 项目中的位置

第三步:修改 Android 权限

在 Android 应用中,主文件是 Android 清单文件。在这个文件中有许多特定的东西,比如包名,它们唯一地标识了市场上的应用。主文件包含一个名为 permissions 的部分。Android 使用这一部分来通知用户应用将使用手机的某些功能。假设一个应用打算使用互联网获取数据;需要获得许可才能安装应用。当用户安装应用时,Android market 会向他显示该应用将被允许使用互联网。

对于 PhoneGap,需要添加以下权限:

  1. 向 Android 清单 XML 添加以下权限:<uses-permission android:name=*"android.permission.CAMERA"* />            <uses-permission android:name=*"android.permission.VIBRATE"* />            <uses-permission android:name=*"android.permission.ACCESS_COARSE_LOCATION"* />            <uses-permission android:name=*"android.permission.ACCESS_FINE_LOCATION"* />            <uses-permission android:name=*"android.permission.ACCESS_LOCATION_EXTRA_COMMANDS"* />            <uses-permission android:name=*"android.permission.READ_PHONE_STATE"* />            <uses-permission android:name=*"android.permission.INTERNET"* />            <uses-permission android:name=*"android.permission.RECEIVE_SMS"* />            <uses-permission android:name=*"android.permission.RECORD_AUDIO"* />            <uses-permission android:name=*"android.permission.MODIFY_AUDIO_SETTINGS"* />            <uses-permission android:name=*"android.permission.READ_CONTACTS"* />            <uses-permission android:name=*"android.permission.WRITE_CONTACTS"* />            <uses-permission android:name=*"android.permission.WRITE_EXTERNAL_STORAGE"* />            <uses-permission android:name=*"android.permission.ACCESS_NETWORK_STATE"* />
  2. 我们还需要在清单文件中添加 supports-screen 选项,如下所示:<supports-screens       android:largeScreens=*"true"*       android:normalScreens=*"true"*       android:smallScreens=*"true"*       android:resizeable=*"true"*           android:anyDensity=*"true"* />
  3. android:configChanges=orignetation|keyboardHidden添加到 Android 清单中的活动。这告诉 Android 当用户翻转手机,屏幕从纵向切换到横向时,不要取消和重新创建活动,反之亦然。
  4. 通过应用以下 XML 片段,在前一个活动之后添加第二个活动:

<activity android:name="com.phonegap.DroidGap" android:label="@string/app_name" android:configChanges="orientation|keyboardHidden">         <intent-filter> </intent-filter> </activity>

一旦您按照前面的说明修改了 Android Manifest,就会出现一个 Android Manifest XML。将会看到如下内容:

`
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
     package="org.examples.phonegap.helloworld" android:versionCode="1"
     android:versionName="1.0">
     <supports-screens android:largeScreens="true"
          android:normalScreens="true" android:smallScreens="true"
          android:resizeable="true" android:anyDensity="true" />
     <uses-permission android:name="android.permission.CAMERA" />
     <uses-permission android:name="android.permission.VIBRATE" />
     <uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
     <uses-permission android:name="android.permission.ACCESS_LOCATION_EXTRA_COMMANDS" />
     <uses-permission android:name="android.permission.READ_PHONE_STATE" />
     <uses-permission android:name="android.permission.INTERNET" />
     <uses-permission android:name="android.permission.RECEIVE_SMS" />
     <uses-permission android:name="android.permission.RECORD_AUDIO" />
     <uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
     <uses-permission android:name="android.permission.READ_CONTACTS" />
     <uses-permission android:name="android.permission.WRITE_CONTACTS" />
     <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
     <uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
     <uses-sdk android:minSdkVersion="7" />

<application android:icon="@drawable/icon" android:label="@string/app_name">
          <activity android:name="HelloWorld" android:label="@string/app_name"
               android:configChanges="orientation|keyboardHidden">                
                    <action android:name="android.intent.action.MAIN" />
                    <category android:name="android.intent.category.LAUNCHER" />
               

          
          <activity android:name="com.phonegap.DroidGap" android:label="@string/app_name"
               android:configChanges="orientation|keyboardHidden">
               
               

          
     
`

第 4 步:修改主活动

在 Android 中,一个名为 activity 的类代表一个屏幕。为了让我们在 Android 中使用 PhoneGap,我们将把屏幕从活动更改为 DroidGap。DroidGap 是一个特殊的活动,它允许我们显示 HTML 页面。该类如 HelloWorld 类的图 2–17 所示。

注意:我们告诉 DroidGap 在 Android 资产中加载 index.html 文件。

`package org.examples.phonegap.helloworld;

import android.os.Bundle;

import com.phonegap.DroidGap;

public class HelloWorld extends DroidGap {
     /** Called when the activity is first created. */
     @Override
     public void onCreate(Bundle savedInstanceState) {
          super.onCreate(savedInstanceState);
          super.loadUrl("file:///android_asset/www/index.html");
     }
}` images

图 2–17。 扩展 DroidGap 类的活动

编写 HelloWorld 应用

PhoneGap 应用是一个 HTML/JavaScript 应用。参见图 2–18。下面是 index.html。

  1. 在 HTML 页面中包含 PhoneGap JavaScript 库版本 1.1.0。
  2. 用主体的 onload 事件注册init()方法。
  3. init()函数中,用 DeviceReady 事件注册 JavaScript 回调函数 onDeviceReady。
  4. 在 onDeviceReady 回调函数中,将 ID 为“helloworld”的h1元素的内容更改为文本“hello World!已加载 PhoneGap 框架!”

这里列出了完整的源代码:

`

  

PhoneGap

<script type="text/javascript" src="phonegap-1.1.0.js">     
  
  

...

` ![images](https://gitee.com/OpenDocCN/vkdoc-js-zh/raw/master/docs/begin-phonegap/img/9781430239031_Fig02-18.jpg)

图 2–18。PhoneGap 项目的 Index.html??

你可以从[bitbucket.org/rohitghatol/apress-phonegap/src/67848b004644/android/PhoneGap-Helloworld](https://bitbucket.org/rohitghatol/apress-phonegap/src/67848b004644/android/PhoneGap-Helloworld)下载本章的完整源代码

部署到模拟器

为了运行 Android 应用,右键单击 PhoneGap-helloworld 项目,选择 Run As,然后选择 Android 应用。

这将启动带有我们之前创建的 AVD 的模拟器。当应用加载时,您将看到以下屏幕。随着应用的启动,您将在屏幕上看到…,如图图 2–19 所示。

images

图 2–19。PhoneGap 应用加载了…几秒钟。

加载 PhoneGap 框架后,您会看到应用显示一条消息,如图 Figure 2–20 所示。

images

图 2–20。PhoneGap 框架加载部署到设备应用后,PhoneGap 应用显示一条消息。

到目前为止,我们已经看到了如何在模拟器上测试应用。但是,有些功能不能在模拟器上测试。为了测试 GPS、相机、加速度计、罗盘和实际用户感知,需要测试实际设备。

部署到设备

将 Android 应用部署到设备需要两个步骤:

第一步:准备好设备。
  1. Unlock your device and press the Menu key. This will give you a view that looks like Figure 2–21. images

    图 2–21。 进入安卓手机的设置

  2. Click on the Settings and the screen in Figure 2–22 will appear. Choose the Applications option. images

    图 2–22。 进入应用设置

  3. Now we must ensure that we can deploy non-Market applications on our device. This is done by clicking the Unknown sources, seen in Figure 2–23. images

    图 2–23。 点击未知来源,我们可以添加非市场应用

  4. 下一步是进入开发选项(见图 2–24)并启用 USB 调试(见图 2–25)。这允许你将 USB 线的一端插入你的 Android 设备,另一端插入你的 PC 或 Mac。使用 Eclipse 调试设备上运行的应用。

images

图 2–24。 进入开发选项

images

图 2–25。启用 USB 调试

现在,您的设备已经准备好部署应用。

Android ADT 插件为 Android 提供了一个 Dalvik 调试监控服务器(DDMS)。DDMS 有许多功能,例如列出当前可用于部署和调试 Android 应用的设备/仿真器,允许用户查看部署在设备/仿真器上的应用的日志消息,以及浏览设备/仿真器的文件系统。

步骤 2:在应用启动类型中,提供我们打算部署到设备的信息。
  1. Plug the USB cable into your device, and plug the USB end into your development machines. Now open Eclipse and go into the DDMS perspective (Go to Eclipse->Windows->Open Perspective->DDMS). You will see a screen like Figure 2–26. This screen depicts that we have an Android emulator running, and that we also have an Android device plugged into the machine’s USB input. images

    图 2–26。DDMS 显示我们有一个仿真器在运行,一个设备插入了 USB。

  2. 当您点击您的 Android 项目的任何 Android 应用的 Run As 时,您将看到图 2–27 中的屏幕。您看到这个屏幕是因为您有一个仿真器和一个可用的设备。这里 Eclipse 会询问您将应用部署到哪里。如果你只有一个设备插入,没有模拟器运行,这个屏幕将不会出现。

images

图 2–27。 如果有多个设备或仿真器,DDMS 会提示用户选择部署应用的位置。

探索 PhoneGap 功能

本节将探索 PhoneGap 的更多特性。

下面是 PhoneGap 支持的功能的简短总结:

  1. PhoneGap 的加速计 API 使应用能够感知设备方向的变化,因此,它能够相应地采取行动。这在创建具有气泡水平仪的应用时很有用(确保手机与地面水平对齐)。可以选择读取一次器件方向的变化读数,或者连续接收器件方向的变化。
  2. PhoneGap 的相机 API 允许应用从相机中检索图片(这对脸书和 Picasa 应用非常有用),或者从现有的照片库中获取图像。
  3. PhoneGap 的 compass API 帮助应用了解手机的方位。这被证明对于地图和导航应用是有用的,因为地图随着用户改变电话的方位而旋转,存在获取设备航向变化的一个读数或连续接收设备航向变化的选项。
  4. PhoneGap 的联系人 API 是应用读写联系人的一种方式。许多社交应用可以从同步手机联系人和社交频道上的联系人中受益。
  5. PhoneGap 的文件 API 允许应用读取、写入和列出目录和文件系统。如果应用计划更改手机文件系统中的文件内容,这将非常方便。这个 API 也可以帮助编写文件浏览器应用。
  6. 地理位置 API 有助于检索设备的地理位置。这对许多应用都有好处,包括基于地图的应用,以及像 foursquare 这样的应用,用户可以通过使用他们的 GPS 位置来签到。可以选择读取一次设备地理位置的变化,或者连续接收设备地理位置的变化。
  7. 媒体 API 允许应用控制设备上的媒体传感器和应用。这个 API 允许应用记录和回放音频和视频记录。
  8. PhoneGap 的网络 API 为应用提供了查看网络状态的能力。这种状态不只是在线和离线,而是告诉应用设备是在 2G/3G/4G 网络上还是在 Wi-Fi 网络上。这些信息通常有助于应用决定何时检索某些类型的信息。
  9. 通知 API 允许应用通过发出哔哔声、振动或提供可视警报来通知用户发生了什么事情。
  10. PhoneGap 的存储 API 为应用提供了一个内置的 SQL 数据库。应用可以通过 SQL 语句插入、检索、更新和删除数据。应用可以查询数据库中的数据,并在本地存储的电子邮件列表中搜索特定的电子邮件。

PhoneGap 教程

并非所有的 PhoneGap 教程都可以在 Android 模拟器上完成,因此我们将解释以下检查教程的方法:

  • 可以在 Android 模拟器上完成的教程。
  • 需要安卓手机才能工作的教程。

仿真器示例

获取设备信息

PhoneGap 允许以编程方式读取设备信息。为此,您需要确保 PhoneGap 框架已经加载。一旦加载了框架,就可以使用 JavaScript 提取设备信息。设备信息的所有属性在表 2–1 中列出。

image

下面的代码将让您访问设备的信息。同样参见图 2–28。

`

  

PhoneGap

<script type="text/javascript" src="phonegap-1.1.0.js">


  
  
    

Device Info


    
        
        
                      
      
        
        
                           
      
        
        
              
      
        
        
             
      
        
        
                                
      
    
Device Name
PhoneGap Version
Mobile Platform
Platform Version
UUID

  

` ![images](https://gitee.com/OpenDocCN/vkdoc-js-zh/raw/master/docs/begin-phonegap/img/9781430239031_Fig02-28.jpg)

图 2–28。??【PhoneGap 设备信息】HTML 源代码

运行这段代码时,图 2–29 中的屏幕应该出现在您的 Android 模拟器上。

images

图 2–29在模拟器上运行 PhoneGap 的设备信息

您可以从[bitbucket.org/rohitghatol/apress-phonegap/src/67848b004644/android/PhoneGap-DeviceInfo](https://bitbucket.org/rohitghatol/apress-phonegap/src/67848b004644/android/PhoneGap-DeviceInfo)下载这个例子的完整源代码。

可以参考[docs.phonegap.com/en/1.1.0/phonegap_device_device.md.html#Device](http://docs.phonegap.com/en/1.1.0/phonegap_device_device.md.html#Device)的设备 API 官方文档。

抓取设备的联系人

我们将使用 PhoneGap 来获取设备的地址簿联系号码。在我们在 Android 模拟器上做这些之前,我们需要用一些联系信息来设置 Android 模拟器。

  1. Click on the dialer application icon images seen in Figure 2–30. images

    图 2–30。 点击 Android 模拟器上的 PhoneGap 应用。

  2. This will open up the dialer application. Click on the Contacts tab, seen in Figure 2–31. images

    图 2–31。 在手机应用中添加联系人

  3. Click Menu and choose New Contact. In the new contact add the first name, last name, and phone number. Refer to Figures 2–32 and 2–33. images

    图 2–32。点击菜单中的点击新增联系人

    images

    图 2–33。 添加联系人,点击完成。

  4. 输入所需的值后,点击完成,您应该会在联系人列表中看到一个联系人(如图 Figure 2–34 所示)。

images

图 2–34。 联系人列表

为了使用 PhoneGap 访问联系人列表,我们需要使用以下 API:

navigator.service.contacts.find(contactFields, contactSuccess, contactError, contactfindOptions);

Table 2–2 提供了每个参数的描述。

image

下面提供了生成获取联系人的完整代码的步骤:

  1. 创建 ContactFindOptions 对象。通过使 options.filter 等于"",我们说我们想要获取所有联系人。如果 options.filter 是 Bob,这意味着我们希望过滤我们的搜索,以便所有结果都必须在 contact 字段的某个位置包含关键字 Bob。<ins>var</ins> options = new ContactFindOptions(); options.filter="";

  2. 我们需要定义想要获取的联系人字段。当我们搜索联系人时,会出现一个联系人列表。接触本身是接触字段的关联数组。这意味着,如果我们指定只获取姓名和电话号码联系人字段中的联系人,我们将只获取这些信息。var fields = [“name”,”phoneNumbers”];

  3. 定义成功和失败的回叫方法。当我们调用方法
    navigator . service . contacts . find()时,我们需要提供两个回调。这是因为 find()方法是一个异步方法。function onSuccess(contacts) {     for(<ins>var</ins> index=0;index<contacts.length;index++){         <ins>var</ins> contact= contacts[index];         var contactName = contact.name.formatted;     } } function onError(error) { } navigator.service.contacts.find(fields, onSuccess, onError, options);

  4. Use Android Linkify to allow us to dial numbers. While we list the contacts in the address book as a part of the HTML list (as seen in the following code), `

    • Rohit Ghatol
    we can make the listing more useful by using a link for the contact. This is depicted as follows: `

    当用户点击链接 Rohit Ghatol 时,Android 会读取要成为 tel 的 URL,并打开拨号器拨打该号码。

    这在图 2–36 和图 2–37 中有所描述,它们显示了应用在 Android 模拟器上的外观。

    使用下面的代码来应用我们到目前为止所学的内容。参考图 2–35 获取 index.html 源代码。

`

  
    PhoneGap
    <script type="text/javascript" src="phonegap-1.1.0.js">
    
  
  
    

Contacts


    

        

  

` ![images](https://gitee.com/OpenDocCN/vkdoc-js-zh/raw/master/docs/begin-phonegap/img/9781430239031_Fig02-35.jpg)

图 2–35。 PhoneGap 联系人应用 HTML/JavaScript 源代码

images

图 2–36。 清单联系人

images

图 2–37。 点击联系人打开电话拨号器。

您可以从[bitbucket.org/rohitghatol/apress-phonegap/src/67848b004644/android/PhoneGap-Contacts](https://bitbucket.org/rohitghatol/apress-phonegap/src/67848b004644/android/PhoneGap-Contacts).下载这个例子的完整源代码

可以参考[docs.phonegap.com/en/1.1.0/phonegap_contacts_contacts.md.html#Contacts](http://docs.phonegap.com/en/1.1.0/phonegap_contacts_contacts.md.html#Contacts)的 Contacts API 官方文档。

获取 SD 卡列表

本节将解释如何列出 Android 设备的 SD 卡。本节结合使用了 W3C 标准和 PhoneGap API。

列出 Android 设备的 SD 卡有两个步骤。这些步骤如下:

  1. 我们解析目录file:///sdcard并获得对目录条目的访问权。
  2. 当我们访问 directoryentry 时,我们可以从 directoryentry 创建一个 directoryreader,并获取目录(SD 卡)的内容。

在步骤 1 中,我们调用以下函数:

window.resolveLocalFileSystemURI("file:///<ins>sdcard</ins>", onResolveSuccess, onError);

为了响应前面的函数,onResolveSuccess 回调将被激活。onResolveSuccess 回调可以在下面的代码中看到。一旦我们获得了对 fileEntry 的访问权,我们就从它创建一个 directoryReader,并在这个 directoryReader 上调用 readEntries。

`function onResolveSuccess(fileEntry){

var directoryReader = fileEntry.createReader();

directoryReader.readEntries(onSuccess,onError);
}`

当路径file:///sdcard被成功解析时,onSuccess 方法被调用。onSuccess 方法如下:

function onSuccess(entries){ document.getElementById("loading").innerHTML=""; <ins>var</ins> <ins>ul</ins> = document.getElementById("file-listing"); for(<ins>var</ins> index=0;index<entries.length;index++){ <ins>var</ins> <ins>li</ins> = document.createElement('<ins>li</ins>'); li.innerHTML = entries[index].name; ul.appendChild(<ins>li</ins>);       }

部署到模拟器

以下代码提供了如何部署到模拟器的完整示例:

`

        <br>             PhoneGap<br>         
        
        
    


        


            List SDCard Contents
        


        

            

        

            Loading ..
        

    

`

该代码如图 2–38 所示。

images

图 2–38。sd 卡上的清单文件

您可以从[bitbucket.org/rohitghatol/apress-phonegap/src/67848b004644/android/PhoneGap-DirectoryListing](https://bitbucket.org/rohitghatol/apress-phonegap/src/67848b004644/android/PhoneGap-DirectoryListing)下载本章的完整源代码。

可以参考[docs.phonegap.com/en/1.1.0/phonegap_file_file.md.html#File](http://docs.phonegap.com/en/1.1.0/phonegap_file_file.md.html#File)的 File API 官方文档。

写入和读取文件

这一节将向您展示如何使用 PhoneGap APIs 来操作文件系统。

注意: PhoneGap 实际上正在实现和支持在[www.w3.org/TR/file-system-api/](http://www.w3.org/TR/file-system-api/)提到的 W3C 的文件系统规范。

让我们熟悉一下文件系统 API 中的一些关键概念。这些关键概念是:

  1. 本地文件系统
  2. 文件系统
  3. 文件入口
  4. 目录项
本地文件系统

LocalFileSystem 为我们提供了对本地文件系统及其文件和目录的访问。有两种类型的文件系统:

  1. 本地文件系统。PERSISTENT :在没有用户明确授权的情况下,除了响应删除 API 调用之外,存储在持久性文件系统中的数据不应被 UA 删除。
  2. 本地文件系统。临时:UA 可以自行删除临时文件系统中存储的数据,无需应用或用户干预。

localFileSystem 方法是从 window 对象中访问的。这是通过以下方式完成的:

  1. window . request file system()–用于获得对根文件系统的访问权限。
  2. window . resolvelocalfilesystemuri()–用于直接访问 FileEntry 或 DirectoryEntry 对象,前提是 URI 可用于该目录或文件。
文件系统

文件系统代表文件系统。它有两个主要属性:

  1. 名称–在两个选项“永久”或“临时”之间选择。如果您希望文件即使在应用被终止时也能持久保存,请选择“持久保存”。
  2. Root—文件系统的根目录(DirectoryEntry)。

您需要使用以下 API 来访问文件系统:

void requestFileSystem (        short type,  // LocalFileSystem.PERSISTENT or                       //LocalFileSystem.TEMPORARY        long long size, //Size for TEMPORARY FS        FileSystemCallback successCallback,  //Success Callback        optional ErrorCallback errorCallback);  // Failure Callback

获得文件系统访问权限的代码片段如下:

window.requestFileSystem(LocalFileSystem.PERSISTENT               ,0 //size               ,function(fileSystem){ // success callbac                      alert(“Got FileSystem “+fileSystem);               },               function(err){ //failure callback                      alert(“Got Error requesting FileSystem”);               }        );

您需要使用以下 API 来访问 fileEntry 或 directoryEntry 对象(代表文件或目录的对象):

void resolveLocalFileSystemURL (               DOMString url,  //url of file or directory on                                         //filesystem               EntryCallback successCallback,  //Success Callback               optional ErrorCallback errorCallback); //FailureCallback

档案输入

为了操作一个文件,你需要一个 fileEntry 对象。有许多方法可以获得 fileEntry,但是假设您知道文件的 URI,您可以如下获得该对象:

window.resolveLocalFileSystemURL(               “file:///sdcard/read-write.txt”,               function(fileEntry){               },               function(err){               }               );

为了查找 fileEntry 的所有属性和方法,您需要访问 fileEntry 的附录。有两种方法可以获得这种访问权限:

  1. createWriter():创建可用于写入文件的 FileWriter 对象。
  2. file():创建一个包含文件属性的 File 对象,包括读取其内容。
导演

为了列出目录中的文件,您需要一个 directoryEntry 对象。有许多方法可以获得 directoryEntry,但是假设您知道目录的 URI,您可以如下获得该对象:

window.resolveLocalFileSystemURL(               “file:///sdcard/mydir/”,               function(directoryEntry){               },               function(err){               }               );

为了查找 directoryEntry 的所有属性和方法,您需要访问 directoryEntry 的附录。有一种方法可以获得这种访问:getFile():在给定目录中创建文件,或者从给定目录中获取文件。

程序布局

我们的文件读写程序很简单。它包含一个文本区域,我们可以在这里读取文件内容,也可以将文本区域的内容写入文件。我们使用两个按钮 Read 和 Write 来读/写一个名为 read-write.txt 的文件。

下面是程序的代码。

`

             PhoneGap                  
    
    
        

Read Write File


        
            
                
            
            
                
            
                
                
            
        

                    /sdcard/read-write.txt
                

                    
                

                    
                

                    
                

    

`

运行时的该代码在图 2–39 中说明。

images

图 2–39。 读写文件。

现在,让我们实现 readFile()方法来读取文件并在 TextArea 中显示其内容。步骤非常简单:

第一步:解析网址file:///sdcard/read-write.txt

步骤 2: 如果解决了,使用 fileEntry.file()方法创建一个读取器。

步骤 3: 如果没有解决,向用户显示一条消息,告诉用户他需要先写文件,然后才能读取它..参见图 2–40。

`function readFile(){

window.resolveLocalFileSystemURI(    //filename to be read
              filePath,    //success callback
              function(fileEntry){
                     fileEntry.file(
                            function(file){
                                   var fileReader = new FileReader();
                                   fileReader.onloadend =
                                   function(evt){
                                   document.getElementById("textarea").value
                                = evt.target.result;
                                   };
                                   fileReader.readAsText(file);
                                          },
                                          function(error){
                                                 alert("Got error while reading "+filePath);
                                          })
                },    //error callback
                function(error){
                    alert(filename + " not present, please add content and click
                        Save first");
                }
        );

}` images

图 2–40。 由于没有写入文件,无法读取。

让我们实现 writeFile()方法读取 TextArea 中的文本,并将其写入file:///sdcard/read-write.txt

步骤 1: 访问文件系统根目录。

步骤 2: 从文件系统根目录 Entry 创建文件 read-write.txt,如果它还不存在的话。

第三步:创建 fileWriter,将 TextArea 的内容写入文件。这显示在以下代码中:

`function saveFile() {

window.requestFileSystem(
    LocalFileSystem.PERSISTENT, 0,
    //Success Callback

function (fileSystem) {
        var sdcardEntry = fileSystem.root;
        sdcardEntry.getFile(
        filename,
        //Flag telling create file
        {
            create: true
        },
        //Success callbacks

function (fileEntry) {
            fileEntry.createWriter(

function (fileWriter) {
                fileWriter.onwrite = function (evt) {
                    alert("Write was successful!");
                    document.getElementById("textarea").value = "";
                };
                fileWriter.write(document.getElementById("textarea").value);
            },
            //Error callback

function (error) {
                alert("Failed to get a file writer for " + filename);
            });

},
        //Error Callback

function (error) {
            alert("Got error while reading " + filename + " " + error);
        });

}, function (error) {
        alert("Got Error while gaining access to file system");
    });

}`

当用户在文本区域中键入文本并点击 Write 时,内容被写入文件并显示一条警告消息,提示 Write 成功。你会注意到当你点击 Write 时,程序会清空文本区。参见图 2–41 查看文件写入成功时的信息。

images

图 2–41。 写成功了

现在,点击 read 按钮,尝试读取您写入文件的内容。你写的内容应该出现在文本区。参考图 2–42 查看当读取成功时,文本区域是如何填充的。

images

图 2–42。 阅读成功

您可以从[bitbucket.org/rohitghatol/apress-phonegap/src/62c45e339662/android/PhoneGap-FileReadWrite](https://bitbucket.org/rohitghatol/apress-phonegap/src/62c45e339662/android/PhoneGap-FileReadWrite)下载这个例子的完整源代码。

可以参考[docs.phonegap.com/en/1.1.0/phonegap_file_file.md.html#File](http://docs.phonegap.com/en/1.1.0/phonegap_file_file.md.html#File)的 File API 官方文档。

写入和读取数据库

本节将解释如何读取、存储和操作数据库。我们将使用在数据库中存储联系人列表(名字和姓氏)的例子,然后读取它,并删除条目。

有三个表:一个用于标题列,一个用于内容,还有一个允许用户向联系人列表添加条目。

以下是从数据库中读取和写入的 index.html 代码:

`

             PhoneGap DB                           
    
    
        

Read Write DB


        
            
                
                
                
            
        

                    First Name
                

                    Last Name
                

                    Action
                

        
        

        
            
                
                
                
                     

                    
                    
                

                    
                    
                

                    
                

    

`

如果运行该程序,您应该会看到类似于图 2–43 所示的布局

images

图 2–43。 读写数据库

现在,让我们添加一些代码来读取、写入和删除数据库中的联系人条目。第一步是访问数据库对象。这是按如下方式完成的:

`var firstNameBox = null;
var lastNameBox = null;
var db = null;
var dataTable = null;
/** Called when phonegap javascript is loaded */
function onDeviceReady(){
       var addButton = document.getElementById("add");
       firstNameBox = document.getElementById("firstName");
       lastNameBox = document.getElementById("lastName");
       dataTable = document.getElementById("data-table");

db = window.openDatabase("contactDB", "1.0", "Contact
**                  Database", 1000000);**
                  //name,version,display name, size

}`

让我们把重点放在创建数据库对象的 API 上。这是按如下方式完成的:

         window.openDatabase(                   databaseName,                   versionNumber,                   displayName,                   sizeInBytes);

现在让我们看一下代码(参考下面的 addButton.addEventListener ),它解释了单击 Add 按钮并向数据库中的 contacts 表添加条目的过程。

为了向 contacts 表添加条目,我们将在数据库对象上使用 transaction()方法。下面是这个方法的 API:

db.transaction(        function(tx){   //Function to execute the sql statements               //Use tx to execute sql statements        },        function(err){  // Error callback               //Use err.code and err.message to understand the error        },        function(){  //Success callback               // Update the UI, log a message        }        );

我们希望在用户点击按钮时添加新条目。因此,我们的添加功能位于添加按钮的 click 事件侦听器中。这可以在下面的代码中看到:

`addButton.addEventListener(
       "click",
       function(){

db.transaction(
                     //function sql statements
                     function (tx){
                            ensureTableExists(tx);
                            var firstName = firstNameBox.value;
                            var lastName = lastNameBox.value;

var sql = 'INSERT INTO Contacts  
                         ( firstName, lastName ) VALUES
                         ("' + firstName + '","' + lastName + '")';

tx.executeSql(sql);

},
                      //error callback
                      function (err){
                             alert("error callback "+err.code);

},
                      //success callback
                      function (){
                             loadFromDB();
                      }               );

},false);

function ensureTableExists(tx){
       tx.executeSql('CREATE TABLE IF NOT EXISTS Contacts (id
                     INTEGER PRIMARY KEY, firstName,lastName)');

}`

我们使用一个名为 ensureTableExists(tx)的方法,它确保我们在数据库中执行 DB CRUD 操作之前拥有数据库表。

我们在数据库事务的 SQL 函数中使用 SQL insert 语句,以便在数据库中创建一个条目。

注意:默认情况下,SQLite 中的主键是自动递增的,因此,我们只写名字和姓氏,数据库实际上是递增 ID。

一旦数据库成功添加了操作,我们就调用 loadFromDB()方法从数据库表中填充 HTML 表。

请参考 Figure 2–44 查看添加联系人功能的 UI 屏幕。

images

图 2–44。 添加条目

现在,让我们来看看 loadFromDB()方法。这里我们使用相同的 tx.executeSql,但是在这种情况下,我们期望得到一些不同的结果,所以我们使用下面版本的 tx.executeSql:

tx.executeSql(        sqlStatement,        options,        successCallbackWithResultSet,        errorCallback);

successCallbackWithResultSet 是一个接收两个内容的函数:

  1. 结果集

`function loadFromDB(){

db.transaction(
       //function sql statements
       function (tx){
              ensureTableExists(tx);
              tx.executeSql('SELECT * FROM Contacts',
                        [],
                        //success callback
                                       function(tx, results){
                                        var htmlStr="";
                                                    for(var index=0;index<results.rows.length;index++){
                                                        var item =
results.rows.item(index);

htmlStr = htmlStr +""+
                                   item.firstName+""
                                   +item.lastName
                                   +"<button
                                   onclick="deleteEntry('"
                                   +item.id+
                                   "');">X";

}

dataTable.innerHTML=htmlStr;
                        },
                        //error callback
                        function(err){
                                alert("Unable to fetch result from Contacts
                                       Table");
                        }
                );

},
        //error callback
        function (err){
                alert("error callback "+err.code+" "+err.message);

},
        //success callback         function (){
               firstNameBox.value="";
               lastNameBox.value="";
        });

}`

当我们现在运行代码时,我们可以在 HTML 表中看到之前添加的条目。

参考图 2–45,它显示了之前在 html 表格中添加的行。

images

图 2–45。 从数据库中读取所有条目

最后要做的事情是当用户单击 X 按钮时删除条目。当我们填充 HTML 表时,我们在 HTML 中定义按钮如下:

<button onclick="deleteEntry(‘”+item.id+"’);’>X</button>

参考 Figure 2–46 查看这在 HTML 中的样子。

当用户单击 X 按钮时,调用 deleteEntry 函数,传递要删除的条目的主键。这里,我们使用 Delete SQL 语句删除主键。

function deleteEntry(id){        db.transaction(               //function <ins>sql</ins> statements               function (tx){ `                ensureTableExists(tx);

tx.executeSql('Delete FROM Contacts where id='+id);

},
              //error callback
              function (err){
                     alert("error callback "+err.code+" "+err.message);

},
              //success callback
              function (err){
                     loadFromDB();
              }
       );

}` images

图 2–46。 从数据库中删除条目

现在,当用户单击 X 按钮时,条目被删除,并调用 loadFromDB(),从数据库表刷新 HTML 表。

images

图 2–47。 删除条目被反映

完整的 index.html 资料如下:

`


        <br>             PhoneGap DB<br>         
        
        
        
    


        


            Read Write DB
        


        
            
                
                
                
            
        

                    
                        First Name
                    

                

                    
                        Last Name
                    

                

                    
                        Action
                    

                

        
        

        
            
                
                
                
            
        

                    
                    
                

                    
                    
                

                    
                

    

`

您可以从[bitbucket.org/rohitghatol/apress-phonegap/src/62c45e339662/android/PhoneGap-DB](https://bitbucket.org/rohitghatol/apress-phonegap/src/62c45e339662/android/PhoneGap-DB)下载这个例子的完整源代码。

可以参考[docs.phonegap.com/en/1.1.0/phonegap_storage_storage.md.html#Storage](http://docs.phonegap.com/en/1.1.0/phonegap_storage_storage.md.html#Storage)的存储 API 官方文档。

获取手机或无线网络的详细信息

通常,移动应用需要连接到某个服务器,以便获取某些数据。现代智能手机可能会使用 3G/4G 网络或 Wi-FI 网络下载数据。一个好的应用会尊重这种区别,在 3G/4G 网络上下载某些类型的数据,只有在智能手机在 Wi-Fi 网络上时才下载大量数据。

本节将解释如何使用 PhoneGap 来找出智能手机正在使用哪种网络。

我们将需要使用以下 API:

      navigator.network.connection.type

这将返回连接的类型。不同连接类型的值如下所述:

Connection.UNKNOWN = "unknown"; Connection.ETHERNET = "ethernet"; Connection.WIFI = "wifi"; Connection.CELL_2G = "2g"; Connection.CELL_3G = "3g"; Connection.CELL_4G = "4g"; Connection.NONE = "none";

下面列出了 index.html 的完整源代码:

`

    
        PhoneGap DB
        <script type="text/javascript" src="phonegap-1.1.0.js">
        
        
    
    
        

Phone Network Info


        

        

    

`

当该源运行时,它将显示网络信息,如图 2–48 所示。

images

图 2–48。 PhoneGap 网络信息显示网络连接属于 3G 类型

您可以从[bitbucket.org/rohitghatol/apress-phonegap/src/67848b004644/android/PhoneGap-Network](https://bitbucket.org/rohitghatol/apress-phonegap/src/67848b004644/android/PhoneGap-Network)下载这个例子的完整源代码。

可以参考[docs.phonegap.com/en/1.1.0/phonegap_connection_connection.md.html#Connection](http://docs.phonegap.com/en/1.1.0/phonegap_connection_connection.md.html#Connection)的连接 API 官方文档。

设备示例

鉴于这些特性的性质,以下示例只能在真实的 Android 设备上运行。Android 模拟器不支持以下功能。

获取地理位置

在本例中,我们将尝试获取设备的地理位置。为此,我们将使用 navigator.geolocation API。这个 API 是一个异步 API,这意味着一旦我们请求地理定位的 API,API 将通知被调用的程序,并使用向 API 注册的两个回调中的一个。

调用的 API 如下:

navigator.geolocation.getCurrentPosition(onSuccessCallback, onErrorCallback);

当 API 能够获取 GPS 坐标时,API 调用 onSuccessCallback 函数。当获取 GPS 坐标出现问题时,API 将调用 onErrorCallback。

onSuccessCallback 将获得一个名为 position 的参数。该位置将包含有关地理位置的详细信息,如表 2–3 所示。

Images

完整的代码示例如下所示:

`

  
    PhoneGap
    <script type="text/javascript" src="phonegap-1.1.0.js">
    
  
  
    

GeoLocation


    
                     
                  
                    
                  
    
Latitue
Longitude
Altitude
Timestamp

    
  

`

该代码如图 2–49 所示。

images

图 2–49。PhoneGap 地理位置运行示例

您可以从[bitbucket.org/rohitghatol/apress-phonegap/src/67848b004644/android/PhoneGap-GeoLocation](https://bitbucket.org/rohitghatol/apress-phonegap/src/67848b004644/android/PhoneGap-GeoLocation)下载这个例子的完整源代码。

可以参考[docs.phonegap.com/en/1.1.0/phonegap_geolocation_geolocation.md.html#Geolocation](http://docs.phonegap.com/en/1.1.0/phonegap_geolocation_geolocation.md.html#Geolocation)的地理定位 API 官方文档。

获取加速度计

在本例中,我们将尝试观察设备的加速度计读数。现代智能手机中的加速度计功能为用户提供了 x、y 和 z 坐标中的运动方向。

调用的 API 如下:

navigator.accelerometer.watchAcceleration(onSuccessCallback, onErrorCallback,accelerometerOptions);

该 API 将持续监控加速度计读数,并以预定义的时间间隔调用 onSuccessCallback,直到调用 navigator . accelerator . clear watch()为止。通过以{"frequency":"3000"}的形式提供值,在 accelerometerOptions 中定义间隔。间隔的单位是毫秒。如果 accelerometerOptions 不提供默认的间隔 1000,则使用毫秒。

正如前面的例子中提到的,当获取 GPS 坐标出现问题时,API 将调用 onErrorCallback。

onSuccessCallback 将获得一个名为 acceleration 的参数。加速度将包含设备运动的详细信息,如表 2–4 所示。

Images

完整的代码示例如下:

<!DOCTYPE HTML> <ins><html></ins>   <head>     <title>PhoneGap</title>     <script type="text/<ins>javascript</ins>" <ins>src</ins>="<ins>phonegap-1.1.0.js</ins>"></script>     <script type="text/<ins>javascript</ins>">        /** Called when <ins>phonegap</ins> <ins>javascript</ins> is loaded */ `function onDeviceReady(){
                var options = { frequency: 1000 };  // Update every 1 seconds
                  navigator.accelerometer.watchAcceleration(onSuccess,
                                                onError,options);
            }

function onSuccess(acceleration) {
                document.getElementById('x').innerHTML = acceleration.x;
                document.getElementById('y').innerHTML = acceleration.y;
                document.getElementById('z').innerHTML = acceleration.z;
                document.getElementById('timestamp').innerHTML
                = acceleration.timestamp;
      }

function onError(error) {
             alert('code: '    + error.code    + '\n' +
                              'message: ' + error.message + '\n');
            }

/** Called when browser load this page*/
       function init(){
          document.addEventListener("deviceready", onDeviceReady, false);
       }
    
  
  
    

Accelerometer


    
                               
                               
                               
               
    
X
Y
Z
Timestamp

    
  

`

该代码如图 2–50 所示。

images

图 2–50。 PhoneGap 加速计示例

在图 2–51 中可以找到相同加速度计的图示版本。通过将手机平放在某个表面上,加速度计可用于检查该表面的水平。

你可以在[code.google.com/p/beginingphonegap/downloads/list](http://code.google.com/p/beginingphonegap/downloads/list)找到这个例子的图形。

images

图 2–51。 使用 PhoneGap 加速计 API 构建气泡应用

在前一个例子中使用了四个图像。这些图像从圆形气泡变化到椭圆形气泡。完整的代码示例如下所示:

`

  
    PhoneGap
    <script type="text/javascript" src="phonegap-1.1.0.js">
    
  
  
    

Accelerometer


      <img id="circle" src="accelerometer-circle-bubble.png"
        style="position:absolute">
      <img id="x-y-base" src="x-y-accelerator-base.png">
    


      <img id="x-base" src="z-accelerator-base.png">
      <img id="oval" src="accelerometer-circle-oval.png"
        style="position:absolute;left:0px">
    

`

您可以从[bitbucket.org/rohitghatol/apress-phonegap/src/67848b004644/android/PhoneGap-Accelerometer-Image](https://bitbucket.org/rohitghatol/apress-phonegap/src/67848b004644/android/PhoneGap-Accelerometer-Image)下载这个例子的完整源代码。

可以参考[docs.phonegap.com/en/1.1.0/phonegap_accelerometer_accelerometer.md.html#Accelerometer](http://docs.phonegap.com/en/1.1.0/phonegap_accelerometer_accelerometer.md.html#Accelerometer).的加速度计 API 官方文档

获取罗盘方位

让我们来看一个与加速度计功能相似的应用。一个指南针。像加速度计一样,指南针提供设备在 x、y 和 z 轴上的运动。指南针提供设备在顺时针方向上相对于正北方向以度为单位方向。

你可以在[code.google.com/p/beginingphonegap/](http://code.google.com/p/beginingphonegap/)找到这个例子的图形。

调用的 API 如下:

navigator.compass.watchHeading (onSuccessCallback, onErrorCallback,compassOptions);

这个 API 将持续监视指南针前进的方向,并以预定义的间隔调用 onSuccessCallback,直到 navigator.compass.clearwatch()被调用。间隔在 compassOptions 中定义,并以{"frequency":"3000"}的形式提供一个值。间隔的单位是毫秒。如果未提供 compassOptions,则使用默认的时间间隔 1000 毫秒。

正如前面的例子中提到的,当获取 GPS 坐标出现问题时,API 将调用 onErrorCallback。

onSuccessCallback 将获得一个名为 heading 的参数。航向是 0 度到 360 度之间的度数,从正北开始顺时针方向测量。

完整的示例如下。这个例子使用 CSS3 直观地显示一个指南针。为了做到这一点,我们使用了指南针指针图像。该图像如图 2–52 所示。

images

图 2–52。PhoneGap 示例中使用的指南针图像

首先,我们用 navigator.compass.watchHeading()方法注册 onSuccess 方法。当我们的 onsuccess 方法被调用时,我们使用 css3 旋转变换来改变指南针图像指向的方向。这是一个视觉罗盘应用。

此处提到了该应用的完整 index.html:

`

  
    PhoneGap
    <script type="text/javascript" src="phonegap-1.1.0.js">
    
  
  
    

Compass


    
        
                
                
                
        
    
Compass Heading
                        
....
                
Degrees

<img id="compass" src="compass.png"
        style="width:400px;height:400px;margin-left:auto;margin-
        right:auto;auto;display:block">

`

该代码如图 2–53 所示。

images

图 2–53。 PhoneGap 视觉罗盘应用

2–53 中的图可以从这个网址下载- [beginingphonegap.googlecode.com/files/compass.png](http://beginingphonegap.googlecode.com/files/compass.png)

您可以从[bitbucket.org/rohitghatol/apress-phonegap/src/67848b004644/android/PhoneGap-Compass](https://bitbucket.org/rohitghatol/apress-phonegap/src/67848b004644/android/PhoneGap-Compass)下载这个例子的完整源代码。

可以参考[docs.phonegap.com/en/1.1.0/phonegap_compass_compass.md.html#Compass](http://docs.phonegap.com/en/1.1.0/phonegap_compass_compass.md.html#Compass.).的 Compass API 官方文档

从相机中捕捉图像

本章的最后一节是关于从相机中捕捉图像。这是一个很酷的特性,它确实为基于 HTML 的应用增加了很多价值。让我们看看如何使用这个特性。

调用的 API 如下:

navigator.camera.getPicture (onSuccessCallback, onErrorCallback,cameraOptions);

虽然在 cameraOptions 中有许多选项,但我们只关注一个名为 quality 的属性。cameraOption 看起来会像{"quality":75}。

使用上面的 camera 选项,onSuccess()方法将获得一个 base64 编码的二进制图像。

相机应用的完整示例如下:

`

  
    PhoneGap
    <script type="text/javascript" src="phonegap-1.1.0.js">
    
  
  
    

Camera


    

`

该代码如图图 2–54 和图 2–55 所示。

images

图 2–54。 PhoneGap 相机应用显示按钮捕捉图像

images

图 2–55。 拍摄图像后应用 PhoneGap 相机

您可以从[bitbucket.org/rohitghatol/apress-phonegap/src/67848b004644/android/PhoneGap-Camera](https://bitbucket.org/rohitghatol/apress-phonegap/src/67848b004644/android/PhoneGap-Camera)下载这个例子的完整源代码。

可以参考[docs.phonegap.com/en/1.1.0/phonegap_camera_camera.md.html#Camera](http://docs.phonegap.com/en/1.1.0/phonegap_camera_camera.md.html#Camera)的相机 API 官方文档。

三、设置环境

PhoneGap 环境可以通过以下两种方式建立:

  • 您机器上的本地开发环境
  • PhoneGap Build 上的云构建环境

本地开发环境设置包括开发人员为其想要启动 PhoneGap 应用的每个移动平台设置环境。本章详细介绍了本地环境设置,希望读者不需要任何其他文档就可以在每个平台模拟器上运行 PhoneGap 应用。

为了在特定平台的设备上运行 PhoneGap 应用,用户需要查看特定平台的文档。本章提供了该文档的参考资料。

另一方面,称为“PhoneGap Build”的云构建环境允许您在不需要本地开发环境的情况下构建 PhoneGap 应用。这意味着开发人员将只编写应用的 PhoneGap 部分,这需要 HTML、JavaScript 和 CSS。这段代码将被提供给 PhoneGap 构建服务。PhoneGap 构建服务将为每个平台构建所需的二进制文件,开发者可以下载这些文件。我们将在本章中更详细地研究这一过程。

当地发展环境

本地开发环境很像我们在第二章中为 Android 所做的。在本章中,我们将了解如何在您的开发机器上为以下平台设置 PhoneGap 环境:

  1. ios
  2. 黑莓
  3. 智能移动终端操作系统
  4. 操作系统

需要注意的是,iOS 只能在使用 Xcode 的 Mac 上运行,而黑莓的首选操作系统是 Windows。

先决步骤

在我们继续之前,我们将预先完成所有平台的通用步骤。第一步是下载 PhoneGap。

下载语音间隙

可以从[www.phonegap.com](http://www.phonegap.com)下载 PhoneGap sdk。这本书采用了 PhoneGap 1 . 1 . 0 版本,这是当时的最新版本。

下载 PhoneGap sdk 并解压缩后,您会看到如图图 3–1 所示的文件夹结构。

images

图 3–1。 PhoneGap SDK 目录结构

PhoneGap 支持的每个平台都有一个单独的目录。每个目录包含每个平台的库、工具和源代码,以帮助设置本地开发环境。

使用 Xcode 4 为 iOS 设置环境

为了使用 iOS,你需要一台基于英特尔的电脑,安装 Mac OS X 雪豹(10.6)。

为了在设备上测试 PhoneGap 应用,您还需要以下内容:

  1. 像 iPhone、iPad 或 iPod Touch 这样的苹果设备
  2. iOS 开发者帐户和证书

接下来,您需要执行以下安装步骤:

  1. 安装 Xcode 和 PhoneGap。Xcode 安装程序可以从苹果开发者门户网站[developer.apple.com/xcode/index.php](http://developer.apple.com/xcode/index.php)下载。请注意,你需要一个苹果开发者账户。或者,你可以从 iTunes 上购买并下载 Xcode 4,价格约为 5 美元。

  2. 导航到您提取 PhoneGap sdk 的 iOS 目录。运行 PhoneGap 安装程序,直到完成。

  3. Create a new PhoneGap project. Open Xcode and create a new project. This will present the following dialog box. Select the “PhoneGap Based Application” option and select the Next button (see Figure 3-2). images

    图 3-2。 创建新的 iOS PhoneGap 项目

  4. On the next screen (shown in Figure 3–3), provide the product name and company identifier to the project creation wizard. Click next. images

    图 3–3。 创建一个新的 iOS PhoneGap 项目

  5. Select the appropriate directory for the project and click the Create button.

    Xcode 提供了一个为项目创建 git 存储库的选项。点击图 3–4 中所示的源控制复选框,可启用或禁用该功能。

images

图 3–4。 创建一个新的 iOS PhoneGap 项目

现在你应该看到 Xcode 中的 HelloWorld 项目了。

  1. Input PhoneGap's HTML and JavaScript

    请注意,我们的项目中没有 www 文件夹。要创建一个 www 文件夹,点击 Xcode 左上角的运行按钮。它将构建项目并在模拟器中启动它。不要担心您的模拟器中显示“index.html 未找到”的错误。这是意料之中的,因为我们还没有把我们的 HTML 放到文件中。

  2. Open the project in Finder (see Figure 3–5). images

    图 3–5。 为 PhoneGap 定制 iOS 项目

您将在项目文件夹旁边看到一个 www 文件夹。我们需要将这个文件夹复制到 Xcode 项目中。

  1. Drag and drop the www folder into Xcode. Xcode will now prompt with a few options. Select Create Folder References for any added folders and click the Finish button. Now you should see the project structure shown in Figure 3–6 in Xcode. images

    图 3–6。iOS 项目中的 PhoneGap WWW 文件夹

  2. Write PhoneGap Application

    您可以通过修改 index.html 文件来编写 PhoneGap 应用。为了打开 index.html,打开 www 文件夹并在编辑器中打开 index.html 页面。在 index.html 文件中键入您的内容。您还可以在 index.html 页面上指定关联的 Javascript 和 CSS 文件。

  3. 部署到模拟器。确保在左上角的菜单中选择模拟器版本作为活动 SDK。

  4. Click the Run button in the Xcode project header to build the project and launch the application in the Simulator (see Figure 3–7). images

    图 3–7。 运行在 iOS 上的 PhoneGap 示例应用

  5. Deploy to Device

    您可以在开发人员设备上启动 PhoneGap 应用。为了在设备上运行应用,打开 HelloWorld-info.plist 并更改 BundleIdentifier 。如果你有开发者许可证,你可以从苹果获得 BundleIdentifier。

  6. 确保在左上角的菜单中选择 Device-version 作为 Active SDK,然后点按 Xcode 项目标题中的“运行”按钮。它将在设备中构建项目并启动应用。

为黑莓手机设置环境

您需要一台基于英特尔的计算机,运行 Windows XP (32 位)或 Windows 7 (32 位和 64 位)来运行 BlackBerry。您需要在您的电脑上安装以下软件:

  1. Java se 6 jdk 32 位
  2. 阿帕奇人 ant
  3. BlackBerry webworks sdk v2.0+版
  4. 任何 Java IDE 环境
  5. BlackBerry 开发者专区帐户
  6. 安装 j2sdk 6 (32 位)

你可以从[www.oracle.com/technetwork/java/javasebusiness/downloads/java-archive-downloads-javase6-419409.html](http://www.oracle.com/technetwork/java/javasebusiness/downloads/java-archive-downloads-javase6-419409.html)下载 J2SDK。运行 J2SDK 安装程序,直到完成。在 PATH 环境变量中添加 Installation _ directory/J2SDK/bin。

接下来,您需要执行以下安装步骤:

  1. Install Apache ant

    你可以从[ant.apache.org/bindownload.cgi](http://ant.apache.org/bindownload.cgi)下载 Apache ant 包。Apache ant 包是一个 zip 文件。提取 ant zip 文件并将 Extracted–directory/apache-ant-1.8.2/bin 放在 PATH 环境变量中。

  2. Install BlackBerry SDK

    [bdsc.webapps.blackberry.com/html5/download/sdk](https://bdsc.webapps.blackberry.com/html5/download/sdk)网站下载用于智能手机的 BlackBerry Webworks SDK。运行 BlackBerry 安装程序,直到完成。通常,BlackBerry Webwork SDK 安装程序会将文件安装在 C:\BBWP 文件夹中。如果您将其更改为其他目录,请记住安装路径,因为您将需要在后面的步骤中使用它。我们建议使用路径“c:\BBWP”,因为我们在本章的其余部分将使用该路径。

  3. Create New PhoneGap project

    为了创建 BlackBerry PhoneGap 应用,PhoneGap 框架提供了一个 ant 脚本。

    • 导航到 PhoneGap BlackBerry 目录
    • 在命令提示符下运行“ant create–d project . path = C:\ Dev \ Sample”命令

    请注意,如果您无法运行上述命令,请尝试从[github.com/callback/callback-blackberry/downloads](https://github.com/callback/callback-blackberry/downloads)下载 PhoneGap 的 BlackBerry 回调,并将其解压缩到 PhoneGap 的 BlackBerry-WebWorks 目录中。

    images

    图 3–8。 创建黑莓 PhoneGap 项目

    这将创建项目文件夹,如图 3–9 所示。请注意 www 文件夹。该文件夹包含 html 文件和 PhoneGap JavaScript 文件。

    images

    图 3–9。 黑莓 PhoneGap 项目目录结构

    您必须将 project.properties 文件中的 bbwp.dir 属性的值更改为C:\\BBWP。如果您在安装期间更改了 BlackBerry 安装目录,如步骤 3 所述,请确保您为 bbwp.dir 属性键入了相同的目录(参见图 3–10)。

    images

    图 3–10。 配置 project.properties 指向 BlackBerry Works SDK 目录

  4. Write a PhoneGap Application

    编写 PhoneGap 应用就像修改 index.html 文件一样简单。要打开 index.html,请打开 www 文件夹,并在您喜欢的编辑器中打开 index.html 页面。在 index.html 文件中包含您的 CSS 和 JavaScript 文件。

  5. Deploy to Simulator

    在 BlackBerry 模拟器上部署需要以下步骤。

    • 启动 BlackBerry 模拟器,运行 ant 目标,如下所示。这将启动黑莓模拟器
      C:\Dev\Sample>ant load-simulator
    • 选择模拟器上的 BlackBerry 按钮
    • 选择下载文件夹

    在那里,您将看到 PhoneGap 示例应用。选择它将其打开(参见图 3-11 )。

    images

    图 3–11。 从黑莓模拟器下载运行 PhoneGap 应用

  6. Deploy to Device

    为了在 BlackBerry 设备上部署 PhoneGap 应用,您需要来自 RIM 的签名密钥。您可以使用以下网站获取您的签名密钥[www.blackberry.com/SignedKeys](https://www.blackberry.com/SignedKeys)

    导航到项目目录,并在命令提示符下运行以下 ant 命令:

    C:\Dev\Sample>ant load-device

为 Symbian 设置环境

为了使用 Symbian,您需要一台基于 Intel 的计算机,并安装 Windows 操作系统。虽然 PhoneGap 的官方文档声称 Symbian 应用可以在所有操作系统上开发,但我们还是建议在 Windows 上使用诺基亚 Symbian s60 sdk 在 Symbian 仿真器上测试 PhoneGap 应用。

接下来,您需要执行以下安装步骤:

  1. Install Cygwin

    为了设置 Symbian 的环境,我们需要在 Windows 上安装 Cygwin。从[cygwin.com/install.html](http://cygwin.com/install.html)下载 Cygwin.exe 文件,开始安装直到完成。请注意,在安装 Cygwin 时,您必须选择两个包,即 zip 包和 make 包。

  2. Install the Symbian s60 sdk

    [www.forum.nokia.com/info/sw.nokia.com/id/ec866fab-4b76-49f6-b5a5-af0631419e9c/S60_All_in_One_SDKs.html](http://www.forum.nokia.com/info/sw.nokia.com/id/ec866fab-4b76-49f6-b5a5-af0631419e9c/S60_All_in_One_SDKs.html)下载 Symbian s60 sdk。请注意,这个 sdk 大约 800+ mb,安装需要大约 3+ gb 的空间。安装大约需要 30 多分钟。

  3. Create a New PhoneGap project

    PhoneGap 目录里面有一个名为 Symbian 的文件夹;该目录是一个模板项目。为了创建一个新的 Symbian PhoneGap 项目,只需复制这个目录并粘贴到您希望创建新的 Symbian PhoneGap 项目的位置。该目录的内容如图 3–12 所示。

    images

    图 3-12。 创建新的 PhoneGap 项目

  4. Write PhoneGap Application

    打开 www 文件夹,在您喜欢的编辑器中打开 index.html。编辑 HTML 内容,并根据需要包含 CSS 和 JavaScript。

  5. Deploy to Simulator

    Symbian PhoneGap 使用 makefile 来构建项目。在 Mac 或 Linux 机器上,只需在终端中运行 make 就可以构建它们。在 Windows 中,您将需要 Cygwin 来构建。只需在 terminal/Cygwin 中运行“make ”, Symbian 项目就构建好了,“wgz”文件也创建好了。这些步骤如图 3–13 所示。

    images

    图 3–13。打造 Symbian 项目

    images

    图 3–14。 打造塞班项目

    app.wgz 文件需要加载到 Symbian 模拟器中。使用仿真程序的文件选项导入。wgz 文件。这将提示您安装应用。选择“是”安装应用。

    一旦应用安装完毕,我们需要点击 Symbian 模拟器中间底部的按钮来查看所有已经安装的应用。从这个屏幕启动我们的应用。一旦你启动了这个应用,你将会得到提示,关于运行这个应用所需要的权限。允许应用使用所需的功能。

  6. Deploy to Device

    您需要使用蓝牙或电子邮件将 Symbian PhoneGap 项目部署到设备上。使用蓝牙或电子邮件将 app.wgz 加载到设备中,然后启动应用。

为 webOS 设置环境

您可以在 Windows、Mac 和 Linux 上开发 webOS 应用。您需要在开发箱上安装以下软件:

  1. Java se 6 jdk 32 位
  2. 虚拟机版本 3.0 到 3.2
  3. webOS sdk 版本 3.0.4

接下来,您需要执行以下安装步骤:

  1. Install java se 6 jdk 32-bit

    你可以从[www.oracle.com/technetwork/java/javasebusiness/downloads/java-archive-downloads-javase6-419409.html](http://www.oracle.com/technetwork/java/javasebusiness/downloads/java-archive-downloads-javase6-419409.html)下载 J2SDK。运行 J2SDK 安装程序,直到完成。在 PATH 环境变量中添加 Installation _ directory/J2SDK/bin。

  2. Install Virtual Box

    [www.virtualbox.org/wiki/Download_Old_Builds_4_0](http://www.virtualbox.org/wiki/Download_Old_Builds_4_0)下载虚拟盒子 3.0–3.2。开始安装,直到完成。

  3. Install webOS SDK

    [developer.palm.com/content/resources/develop/sdk_pdk_download.html](https://developer.palm.com/content/resources/develop/sdk_pdk_download.html)下载 webOS sdk。开始安装,直到完成。

  4. Install Cygwin for Windows only

    如果您使用的是 Windows,您必须安装 Cygwin 来构建和部署用于 webOS 的 PhoneGap 应用。请看 Symbian 安装的第一步。

  5. Create new PhoneGap project

    PhoneGap 目录包含一个名为 webOS 的目录。这是 PhoneGap webOS 的模板项目。为了创建 PhoneGap webOS 项目,请将此目录复制到您的项目区域。

  6. Write PhoneGap application

    打开 www 文件夹,在你最喜欢的编辑器中打开 index.html。编辑 HTML 内容,并根据您的需要包含 CSS 和 JavaScript。

  7. Deploy to Simulator

    在部署项目之前,请确保 webOS 模拟器正在运行。从应用文件夹/开始菜单运行 palm-emulator。

    在项目文件夹中运行“make”。这将创建最终的 javaScript 文件。将项目打包到 webOS 移动应用包中,并将其安装在 webOS 模拟器中。

  8. Deploy to Device

    为了在 webOS 设备中部署 PhoneGap 项目,您必须启用“开发人员模式”并将其插入。在 Cygwin 终端的项目文件夹中运行“make”。

使用 PhoneGap Build 构建云环境

到目前为止,我们认为在不同的移动平台上构建 PhoneGap 应用很乏味。虽然 PhoneGap 开发减轻了跨平台移动应用开发的痛苦,但对于开发人员来说,在每个移动平台上构建 PhoneGap 仍然是乏味的。

为了缓解这种痛苦,PhoneGap 推出了 PhoneGap Build。PhoneGap Build 是一个云构建服务。开发人员将他们的 PhoneGap 应用代码提交给 PhoneGap Build,PhoneGap Build 根据以下内容开发应用:

  1. ios
  2. 机器人
  3. 黑莓
  4. 操作系统
  5. 智能移动终端操作系统

在本节中,我们将了解如何在 PhoneGap 上建立一个帐户,并在其上构建应用。

向 PhoneGap Build 注册

  1. 第一步是获得 PhoneGap beta 的帐户。转到[build.phonegap.com](http://build.phonegap.com)并提供您的详细信息。
  2. 提交您的详细信息后,您将收到 PhoneGap 的电子邮件。在这封电子邮件中,PhoneGap 将提供测试代码。您需要在 PhoneGap 的注册页面提供测试代码。

虽然 PhoneGap Build 会为您构建应用,但您需要拥有这些应用。这是必要的,因为您将在 Appstore、Android Market 和 BlackBerry market 上发布从 PhoneGap Build 获得的版本。

让我们试着理解拥有你的应用意味着什么。一个应用需要被某些证书签名,在应用上印上你的所有权。对于 iOS 这样的平台,你需要获得一个开发者账号,并从苹果获得证书。

PhoneGap 需要您提供这些证书来构建以下应用:

  1. ios
  2. 机器人
  3. 黑莓

以下部分将演示如何生成这些证书并将其提供给 PhoneGap Build。

向 PhoneGap Build 注册您的应用

构建 PhoneGap 的第一步是向 PhoneGap Build 注册您的应用。有三种方法可以注册 PhoneGap Build。

  1. 在 PhoneGap 服务器上创建一个新的 git 存储库,并将代码推送到那里。
  2. 从现有的 git 存储库中提取代码。
  3. 上传你的 PhoneGap 应用的档案。

为了简单起见,我们将采用第二种选择,即从 PhoneGap git 存储库中提取 starter PhoneGap 项目(如图 3–15 所示)。

注意: PhoneGap Build 需要访问您的源代码,因为它是一个构建工具。这意味着你需要与 PhoneGap Build 共享你的源代码。这就像说我们想在他们的 git 存储库中构建 PhoneGap 提供的 PhoneGap 样本代码。这样,我们不需要提供任何源代码,我们将使用示例源代码进行练习。

images

图 3–15。 从 PhoneGap 存储库中取出 PhoneGap 启动代码

现在,您可以看到您的应用列在“您的应用”部分。注意,对于每个平台,都有一个下载图标(如图图 3–16 所示)。单击 download 按钮允许您下载特定于平台的二进制文件。所有这些二进制文件都构建在 PhoneGap Build server 上,因此不需要您为 iOS、Android、BlackBerry、Symbian 和 webOS 设置环境。

images

图 3–16。 PhoneGap Starter 项目构建下载屏幕

请注意图 3–16 中 iOS 版本的橙色警告。对于 iOS,构建需要由开发者证书和预置描述文件签名。预置描述文件链接到 Apple 开发者帐户。如果开发人员希望在 iOS 设备上测试应用,该设备需要使用此预置描述文件进行注册。

现在,在上面列出的 5 个平台中,我们需要为 3 个平台的 PhoneGap Build 提供某种开发者私钥。这是获得特定于平台的版本所必需的,这些版本可以是:

  1. 安装在设备上。
  2. 上传到各自的应用商店。

这些平台是:

  1. 机器人
  2. ios
  3. 黑莓

设置 Android 构建环境

Android 应用在发布到 Android 市场之前由自签名密钥库进行签名。Android 不需要一个中央机构来认证开发者的应用。但是,如果应用的版本 1 是用 xyz 开发人员密钥库签名的,那么应用的下一个版本必须用相同的开发人员密钥库签名。如果不这样做,将导致下一个版本的应用被 Android Market 拒绝。

以下是配置 PhoneGap Build 的步骤,以生成可以在 Android Market 上部署的正确的 Android build:

  1. 创建私有密钥库
  2. 将私有密钥库上传到 PhoneGap Build
  3. 运行 PhoneGap 构建
1。创建私有密钥库

首先要理解在[developer.android.com/guide/publishing/app-signing.html#cert](http://developer.android.com/guide/publishing/app-signing.html#cert)列出的 Android 应用发布指南。在本节中,我们将引导您完成创建私有密钥库所需的步骤。

创建私有密钥库的要求是您需要在您的机器上安装 java jdk 1.6 和更高版本。通过打开终端/命令提示符并键入以下命令来确认这一点:

$> keytool

如果这给了你某种帮助,那么你就可以走了。如果这告诉您没有名为 keytool 的工具,请确保您的 java bin 目录在 path 中。

现在,要实际创建您的私有密钥库,请遵循下面提供的步骤:

$> keytool -genkey -v -keystore my-release-key.keystore

系统将提示您输入密码。输入密码,并写下来。

$>Enter keystore password: welcome

您将被要求重新输入密码。

$>Re-enter new password: welcome

接下来,将会询问您一些问题,以便在私有密钥库中记录您的身份。请回答这些问题。

$ >您的名和姓是什么?

`  [Unknown]:  Rohit Ghatol
$>What is the name of your organizational unit?
  [Unknown]:  Engineering

>Whatisthenameofyourorganization?[Unknown]:QuickOffice>What is the name of your City or Locality?
  [Unknown]:  Pune
>WhatisthenameofyourStateorProvince?[Unknown]:Maharahstra>What is the two-letter country code for this unit?
  [Unknown]:  IN`

现在将要求您确认到目前为止您输入的数据是否正确。

$>Is CN=Rohit Ghatol, OU=Engineering, O=QuickOffice, L=Pune, ST=Maharahstra, C=IN correct?   [no]:  yes

注意:工具将询问您创建自签名证书的密码。要保持这个密码与之前的密码相同,只需按 enter 键。

$>Generating 1,024 bit DSA key pair and self-signed certificate (SHA1withDSA) with a validity of 90 days         for: CN=Rohit Ghatol, OU=Engineering, O=QuickOffice, L=Pune, ST=Maharahstra, C=IN $>Enter key password for <mykey>         (RETURN if same as keystore password): [Storing my-release-key.keystore]

现在创建了名为“my-release-key.keystore”的私有密钥库文件,并保存在同一个目录中。

2。将私有密钥库上传到 PhoneGap Build

在 PhoneGap Build 上导航到您的应用,点击编辑按钮,您将看到如图图 3–17 所示的屏幕。

images

图 3–17。 PhoneGap 构建编辑应用屏幕

导航到签名部分,并在该屏幕上上传 Android 密钥库信息(如图 3–18 所示)

images

图 3–18。进入安卓发布密钥库详情

在此为您的密钥库提供一个标题。在 PhoneGap Build 上,您可以提供多个密钥库,并选择使用哪个来构建您应用。标题是在浏览 PhoneGap Build 上上传的各种密钥库时识别密钥库。

接下来上传密钥库文件并提供任何别名。在密码字段(两者)中,您需要输入用于创建密钥库和私有证书的密码(在我们的例子中是“welcome”)。

3。运行 PhoneGap 构建

最后,点击 create 和 PhoneGap Build 将代表您存储这个密钥库。

现在,您应该会看到一个类似于 Figure 3–19 的屏幕,告诉您“我的发布密钥”密钥库用于构建 Android 版本。

images

图 3–19。PhoneGap app 注册的安卓密钥库

设置 iOS 构建环境

在我们开始之前,让我们注意一下在 PhoneGap 上构建 iOS 的先决条件列表。

  1. 苹果开发者计划([developer.apple.com/programs/ios/ 0](http://developer.apple.com/programs/ios/0))或苹果企业开发者计划([developer.apple.com/programs/ios/enterprise/](http://developer.apple.com/programs/ios/enterprise/))的苹果开发者账户。选择最符合您要求的一个。
  2. 带有 Xcode 的 Mac 计算机,用于提取开发者证书和预置描述文件。提取这些信息后,开发人员可以在任何操作系统上使用 PhoneGap Build。

接下来的步骤如下:

  1. Get iOS key
  2. Provide iOS key to PhoneGap build
1。获取 iOS 密钥

关于如何使用 iOS 开发者帐户配置 Xcode 的完整信息在[tiny.cc/appleprov](http://tiny.cc/appleprov)中有详细描述。

遵循上述步骤,并确保您能够在模拟器上构建和安装示例 iPhone 应用,最好是在 iOS 设备上(已经添加到您的预置描述文件中)。

现在下一步是从 Xcode 中导出开发者证书和预置描述文件,并将它们放入 PhoneGap Build 中。

2。为 PhoneGap Build 提供 iOS 密钥

在正确设置了 iOS 开发环境之后,我们需要提取开发者证书和移动配置文件,并将它们上传到 PhoneGap Build。

第一步是从 Mac 的钥匙串访问中提取开发者证书。打开“钥匙串访问”,找到开发者证书并将其导出。导出时,会要求您输入文件夹和密码。记下该密码,因为当您在 PhoneGap Build 网站上上传开发者证书时将需要该密码。

之后,我们将继续提取配置文件。预置描述文件在 Xcode 内部。打开 Xcode,进入窗口->管理器,启动它。从这里,导出“团队配置文件”。请注意,预置描述文件是您告诉 Apple 您已将 iPhone/iPod/iPad 注册为开发人员设备来测试您的应用的地方。PhoneGap Build 需要此配置文件来签署您的应用(ipa)。

我们现在已经在名为 ios-Keys 的目录中提取了开发人员证书和移动配置文件。

images

图 3–20。 包含开发者证书和预置描述文件的目录

我们需要在 PhoneGap Build 上上传这些密钥。再次访问应用的编辑屏幕,导航到签名部分,对于 iOS,单击添加密钥(下面的选择密钥下拉列表)。

images

图 3–21。 iOS 添加键屏幕

这将打开 iOS 证书和预配描述文件对屏幕。上传所需的密钥和用于导出开发者证书的密码。

images

图 3–22。 为 PhoneGap Build 提供开发者证书和配置文件

一旦你启动 PhoneGap Build,你应该注意到 iOS ipa build 上的橙色警告消失了,Build 变成了绿色。点击 ipa 按钮将为您带来 iOS ipa。

设置 BlackBerry 构建环境

设置 BlackBerry 构建环境所需的主要操作如下:

  1. 去拿黑莓钥匙
  2. 向 PhoneGap Build 提供 BlackBerry 密钥
1。获取 BlackBerry 密钥

PhoneGap Build 为构建 BlackBerry 应用提供现成的支持,这些应用可以安装在您的设备上。然而,为了上传这些应用进行分发,您需要 rim 提供密钥。为了获得这些密钥,您需要使用此网站- [www.blackberry.com/SignedKeys/](https://www.blackberry.com/SignedKeys/)向 rim 注册。

注册后,你会收到 rim 发来的电子邮件,其中提到了在黑莓开发环境中安装这些密钥的步骤。由于法律禁止共享这些指令,我们无法详细说明这些指令。

2。向 PhoneGap Build 提供 BlackBerry 键

下一步是从 BlackBerry 开发环境中提取密钥。可以使用 eclipse 或独立的 BlackBerry web works 来设置 BlackBerry 开发环境。BlackBerry 密钥位于 BlackBerry 的 sdk 目录中。

第一个任务是定位 sdk 目录。

如果 BlackBerry 开发环境是使用 eclipse 安装的,您应该可以在<<eclipse location>>\plugins\ net.rim.ejde.componentpackX.X.X_X.X.X.X \components找到 BlackBerry sdk 目录。这方面的一个例子是 d:\ work soft \ eclipse-Helios \ net . rim . ejde . component pack 5 . 0 . 0 _ 5 . 0 . 0 . 25 \ components。

如果 BlackBerry 开发环境是使用 BlackBerry Widget/web works Packager Standalone SDK 安装的(就像我们在本章前面部分展示的那样),安装目录就是 SDK 目录。这在本章前面部分显示为“c:\BBWP”目录。

代码签名文件/密钥位于 sdk 目录中。

`<<webworks_sdk_dir>\bin\sigtool.csk

<<webworks_sdk_dir>\bin\sigtool.db`

现在,我们已经获得了访问 BlackBerry 密钥的权限,请进入 PhoneGap Build 上的应用,单击编辑,然后转到签名部分。从下拉菜单中,为 BlackBerry 选择“添加密钥”选项。

images

图 3–23。 iOS 添加键屏幕

在如下所示的对话框中上传 BlackBerry 密钥。使用您按照说明创建的相同密码,该密码是从 BlackBerry 的电子邮件中获得的。这为 PhoneGap Build 提供了构建 BlackBerry 应用所需的所有信息,这些应用可以在 BlackBerry 分发渠道上分发。

images

图 3–24。 黑莓钥匙文件上传

启动 PhoneGap 构建

PhoneGap 构建可以通过两种方式启动:

  1. 手动点击 PhoneGap Build 上的“重建全部”,如下图所示。这将在 PhoneGap 构建服务器上对构建进行排队。
  2. 第二种方法是使用 PhoneGap Build restful api 来创建应用、更新代码和启动构建。您可以使 PhoneGap 构建成为您的 cit 构建的一部分(从 bamboo 或 jerkins 或任何 cit 系统)。让 cit 构建脚本调用 PhoneGap Build restful api。PhoneGap 构建 api 的细节可以在[build.phonegap.com/docs/api](https://build.phonegap.com/docs/api)找到。

结论

随着云在托管 web 服务和应用方面越来越受欢迎,许多公司都在寻求基于云的 saas 服务来帮助他们的开发周期。

像 pivotal tracker 这样的公司用于敏捷规划,bitcode 用于托管源代码,甚至还有在线 cit 构建(例如 jira studio)。PhoneGap Build 的出现并不令人惊讶。

不仅仅为了 cit 而购买额外的 Windows 和 Mac 电脑,从而节省基础设施成本是有意义的。保持低成本和租用基础设施(就像我们支付电费一样)正成为当今的趋势。

PhoneGap Build 适合中小型公司的需求,这些公司希望摆脱基础设施负担,使用基于云的 saas 服务来构建 PhoneGap 应用。

四、通过 jQuery Mobile 使用 PhoneGap

虽然 PhoneGap 提供了一个允许 JavaScript 应用访问原生手机功能的平台,但还有许多其他因素有助于移动 HTML 应用。

移动 HTML 应用最重要的部分之一是 UI。您可以使用 HTML、JavaScript 和 CSS 手工编写整个 UI。然而,任何 web 开发人员都会告诉您,这种方法存在许多问题,包括以下问题:

  1. 不是所有的浏览器都一样;你需要一个跨浏览器的框架才能成功。即使大多数移动浏览器都是基于 webkit 的,最好还是使用一个框架,从开发者那里抽象出浏览器的差异。
  2. 如果您是手工编码,您的大部分代码将是绘制 UI、修改 DOM 和进行 Ajax 调用。一个让你少写多做的框架将会帮助你真正关注业务逻辑。
  3. 创建美观的 HTML 用户界面需要设计师的技能。同时,大多数移动客户端都有预定义的主题或模式。如果一个框架能提供开箱即用的好看的 UI,这将对开发者有所帮助。这样,开发人员可以专注于业务逻辑。

话虽如此,使用 PhoneGap 编写 UI 的最简单的框架之一是 jQueryMobile。首先,jQueryMobile 是建立在非常流行的 jQuery 之上的。众所周知,jQuery 是一个 JavaScript 库,可以提高开发人员的工作效率,并帮助开发人员实现跨浏览器兼容性。同时,jQuery 提供了许多免费的插件来做很多事情。

jQueryMobile 是一个为移动 UI 构建的 UI 框架。它有一个声明式 UI,这意味着你不必用 JavaScript 编写你的 UI,而是可以用 HTML 声明它。jQueryMobile 还提供了一个开箱即用的非常漂亮的 UI。

所有这些使得 jQueryMobile 成为最容易使用的 JavaScript UI 框架,也是最适合中等复杂程度的移动 UI 的框架。

话虽如此,jQueryMobile 为智能手机和平板电脑提供了相同的 UI。如果你的需求不一样,需要智能手机和平板电脑不同的布局,那就看第五章。

熟悉 jQuery

jQuery 是最好的 JavaScript 库之一,它可以帮助您执行 Ajax 调用、在 HTML DOM 中搜索特定元素以及修改 DOM。它也有自己的插件框架。最棒的是它的跨浏览器框架,消除了浏览器差异的困扰。可以参考下面的 jQuery 教程:[www.w3schools.com/jquery/default.asp](http://www.w3schools.com/jquery/default.asp)

jQuery 初始化

jQuery 初始化是一个两步过程。

  1. 在 HTML 页面中包含 jQuery JavaScript。
  2. 声明一个回调,当 jQuery 的库被加载时,jQuery 将调用这个回调。

这是必要的,因为 HTML 页面可能包含许多文件,如 CSS、JavaScript 和图像。浏览器将下载所有这些资源,并开始执行所有的 JavaScript 块。如果在正确初始化之前开始使用 jQuery API 调用,将会出现错误。因此,您声明一个回调,这是我们的应用的入口点,jQuery 将调用这个回调并引导应用。

通常,当开发人员不使用 jQuery 时,他会按照以下方式编写代码:

`window.onload = function(){
     alert("Page Loaded");
}

When using jQuery the same code would look like follows:

              //Step 1 – include jquery library         
        
    


        


            jQuery Demo
        


    

`

当您在任何浏览器中运行这段代码时,您会看到在页面加载时弹出一个警告。警告会说“jQuery loaded”

jQuery 选择器

既然您已经了解了如何初始化 jQuery 和注册 onload()方法,那么让我们继续了解如何查找 HTML DOM 元素。

通常,开发人员会使用以下代码来获得 id 为“占位符”的 div

document.getElementById("placeholder").innerhtml = "hello world";

在 jQuery 中,您可以将上面的函数写成

$("#placeholder").html("hello world");

jQuery 提供了许多定位 HTML 元素的方法。一个例子是$("#placeholder "),它返回一个 jQuery 元素,包装 id 为" placeholder "的元素。一旦获得了这个 jQuery 包装器,就可以调用 jQuery 函数来操作 DOM。在上面的例子中,您将它的 HTML 内容更改为“hello world”

让我们参考一个代码示例来回顾一些其他有用的选择器示例:

`


        


            JQUERY SELECTOR Tutorial
        


        


            simple paragraph
        


        


            Paragraph with class title
        


        


            another paragraph
        


        

                

  •                 Element based - $("p")
                

  •             

  •                 Id based - $("#selector")
                

  •             

  •                 CSS Class based - $(".title")
                

  •             

  •                 Element + Class based - $("p.title");
                

  •             

  •                 Element+ID+Position - $("ui#selectorli:first)
                

  •         

    

`
基于元素的选择器

$("p")选择所有段落:

`

simple paragraph

Paragraph with class title

another paragraph

`
基于 ID 的选择器

$("#selector")选择 id 为“选择器”的元素请记住,要搜索的 id 前面会添加#号。为此选择器选择了以下元素:

<ul id="selector">

基于 CSS 的选择器

$(".title")选择具有类“title”的元素记住,一个“.”在类名之前调用以搜索该类的元素。为此选择器选择了以下元素:

`

JQUERY SELECTOR Tutorial

Paragraph with class title

`
选择器的组合

以下是一些如何混合和匹配选择器来定位特定元素的示例:

$("p.title")选择类别为“title”的段落元素,这将选择以下元素:

<p class="title">Paragraph with class title</p>

$("ul#selector li:first")用 id“选择器”从 UI 中选择第一个 li 元素这将选择以下元素:

<li>Element based                    - $("p")</li>

jQuery 选择器的完整列表可以在下面的链接中找到:[www.w3schools.com/jquery/jquery_ref_selectors.asp](http://www.w3schools.com/jquery/jquery_ref_selectors.asp)

jQuery DOM 操控

首先,让我们看看如何从 HTML 中检索值。您可以检索元素或内部 HTML 的值。

如果您执行 javascript $(ul#selector).html(),,您将得到以下文本:

`

  • Element based                    - $("p")
  • Id based                             - $("#selector")
  • CSS Class based                 - $(".title")
  • Element + Class based       - $("p.title");
  • Element+ID+Position         - $("ui#selector li:first)
  • `

    以下示例显示了如何从 jQuery 选择器结果中提取值。首先,这个例子展示了当您假设 jQuery 选择器只返回一个值时会发生什么。也就是说,假设$("p")只返回了一段。然后,您将尝试使用 html()函数获取段落的值。注意$("p")返回一个 jQuery 选择器,html()函数属于 jQuery 选择器。在这种情况下,jQuery 将对("p")html()("p").HTML(), $("p")`时,将定位如下的第一段,并给出值“简单段落”

    <p>simple paragraph</p>

    现在你知道$("p")应该给你很多值,因为在前一个例子中有很多段落,如下所示:

    `

    simple paragraph

    Paragraph with class title

    another paragraph

    `

    为了迭代任何列表,jQuery 提供了一个方法:“each()”为了使用“each()”,您需要在 jQuery 选择器上调用它,就像我们的例子$(“p”)一样。“each()”方法有两个参数:第一个是索引(迭代中的位置),第二个是该位置的实际条目。

    因此,当你说$(“p”)的时候。each(function(index,element){}),元素是每个段落上实际的 jQuery 选择器。

    ``

    现在,让我们继续修改 HTML DOM。我们将很快介绍操作 DOM 的最简单的方法。

    当您执行下面的 JavaScript 时,它会将段落的内容修改为“更改为 123”

    <script type="text/javascript"> $(document).ready(function() {     $("p.title").html("Changed to 123"); }); </script>

    jQuery HTML 操作的完整列表可以在[www.w3schools.com/jquery/jquery_ref_html.asp](http://www.w3schools.com/jquery/jquery_ref_html.asp)找到。

    jQuery Ajax 调用

    jQuery 为 Ajax 调用提供了许多有用的方法。

    下面是一个对 URL 进行 Ajax GET 调用的例子。这是“少写多做”的经典例子。以下代码执行 Ajax GET 调用 service/employee/details.txt,并将内容放入 id 为“details”的 div 中:

    $.get("service/employee/details.txt", function (result) {     $("div#details").html(result); });

    以下是对 URL 进行 Ajax POST 调用的示例,发布数据{name:employeeName}:

    $.post("service/employee/details", {     name: employeeName }, function (result) {     alert("Post successful"); });

    jQuery HTML 操作的完整列表可以在 [www.w3schools.com/jquery/jquery_ref_ajax.asp](http://www.w3schools.com/jquery/jquery_ref_ajax.asp)找到。

    熟悉 jQueryMobile

    jQueryMobile 通过提供一个通用的 UI 平台来开发跨许多流行的移动平台的移动应用,将 jQuery“少写多做”的概念推向了一个新的高度。

    jQueryMobile 建立在非常流行和健壮的 jQuery 和 jQuery UI 框架之上。jQueryMobile 提供了现成的、可触摸的移动小部件,比如列表视图、带有后退按钮的标题、导航动画等等。这些小部件具有专业而精致的外观和感觉,使得开发现成的应用变得更加容易。

    jQueryMobile 的主页是[jquerymobile.com/](http://jquerymobile.com/)

    而且,jQueryMobile 提供了五种开箱即用的主题供你选择。下面的示例展示了按钮在不同主题中的外观。总的来说,我们有五个主题——主题 a、主题 b、主题 c、主题 d 和主题 d——如图图 4–1 所示。

    images

    图 4–1。 jQueryMobile 主题

    此外,jQueryMobile 还为表 4–1 中列出的平台提供坡度支持。

    Images

    在移动应用中包含 jQueryMobile

    [jquerymobile.com/download/](http://jquerymobile.com/download/)下载 jquery.mobile-1.0rc2.zip 并解压。解压缩后,您会看到如图图 4–2 所示的文件夹结构。它包含两对 jQueryMobile JavaScript 和一个 CSS 文件。顾名思义,您可以使用。最小化生产中的 JavaScript 和 CSS 文件,因为它们是缩小的 JavaScript 和 CSS 文件。

    除此之外,你还需要在手机应用中包含图片文件夹。

    images

    图 4–2。 jQueryMobile 文件夹结构

    以下是 jQueryMobile 示例的 HTML 模板:

    `


            <br>             jQuery Mobile Demo<br>         
            
            
            
        


            …
        

    `

    jQueryMobile 声明式用户界面

    声明式 UI 构建是 jQueryMobile 最好的部分。您不需要编写复杂的 JavaScript 代码来构建 UI。UI 构建就像添加带有一些 jQueryMobile 特定属性及其值的普通 HTML 元素一样。

    页面和对话框

    您在上一节中看到了 HTML 模板。现在,您将向其中添加 jQueryMobile 布局和小部件。

    您可以在 body 标记内的 div 元素中使用数据角色属性来声明页面。因此,在 body 标记中,您可以通过声明以下内容来声明许多页面:

    <div data-role="page"></div>

    同样,您可以通过将数据角色声明为“页眉”、“内容”和“页脚”来声明页面的组件以下是 jQueryMobile 中的一个页面示例:

    `

    `

    此处显示了一个完整的页面示例。

    `

    <html>     <head>         <title>jQuery Mobile Demo</title>         <link href="jquery.mobile-1.0rc2.min.css" rel="stylesheet" type="text/css"/>         <script src="jquery-1.6.4.min.js"></script>         <script src="jquery.mobile-1.0rc2.min.js"></script>     </head>


            
            


                
                

                    

    Page Title


                

                


                


                    


                        Page content goes here.
                    


                

                


                


                    


                        Page Footer
                    


                

                
            

            
        

    `

    在浏览器中运行该 html 时,会显示一个如图 4–3 所示的屏幕。

    images

    图 4–3。 jQueryMobile pages

    现在,您已经了解了 jQueryMobile 中页面的概念,让我们来看一个拥有多个页面或对话框的场景。

    典型的应用有多个页面和对话框。jQueryMobile 最棒的一点是,您可以在同一个 HTML 页面中定义所有这些不同的页面和对话框。

    下面是怎么做的。在一个 HTML 页面中定义多个 div,并赋予它们以下内容:

    1. 一个名为 data-role 的属性设置为 page。这看起来像这样:data-role="page "
    2. 一个名为“id”的属性,用于在代码中标识它们

    您可以使用链接和按钮导航到这些页面和对话框。最简单的方法是执行以下操作:

    1. 将 href 定义为#+ <>
    2. 给该链接一个 data-role="button "

    注意,页面和对话框的声明是相同的。事实上,没有什么叫做对话框,但是你可以在弹出窗口中加载一个页面作为对话框。

    下面是一个页面链接的示例。当单击此链接时,会转换到 id 为“page2”的页面。还要注意,因为链接被赋予了 data-role="button ",所以它看起来像一个按钮。

    <a data-role="button" href="#page2">Page Navigation</a>

    将页面作为对话框打开与导航到页面非常相似。您只需要向链接添加两个属性:

    1. data-rel="dialog "
    2. data-transition="pop "(这是动画效果)

    <a data-role="button" href="#dialog1"  data-rel="dialog" data-transition="pop">Open Dialog </a>

    这里有一个完整的例子供你尝试。图 4–4 显示的是“主”页面,图 4–5 显示的是“第二页”页面,图 4–6 显示的是“对话 1”页面,相当于一个对话框:

    `


            jQuery Mobile Demo
            
            
            
        


            
            


                

                    


                        Main Page
                    


                

                

                    


                        Page Nav and Dialog Example
                    


                    Page Navigation
                    Open Dialog
                

                

                    


                        Main Page Footer
                    


                

            

            
            
            

                

                    


                        Second Page
                    


                

                

                    


                        Second Page
                    


                

                

                    


                        Click back to go back to main page
                    


                

            

            
            
            

                

                    


                        Dialog Title
                    


                

                

                    v
                    


                        Dialog body
                    


                

                

                    


                        Click close button to go back to main page
                    


                

            

            
        

    ` ![images](https://gitee.com/OpenDocCN/vkdoc-js-zh/raw/master/docs/begin-phonegap/img/9781430239031_Fig04-04.jpg)

    图 4–4。jquerymobile page navigation

    images

    图 4–5。jquerymobile page navigation

    images

    图 4–6。 jQueryMobile 对话框

    工具栏和按钮

    在 jQueryMobile 中,有两种类型的工具栏:

    1. 标题栏
    2. 页脚栏

    一般来说,创建工具栏就像在标题栏或页脚栏中声明一些按钮一样简单。这在图 4–7 中进行了描述。

    `


            jQuery Mobile Demo
            
            
            
        


            
            


                

                    Cancel
                    


                        Edit Contact
                    


                    Save
                

                

                    


                        Header Footer Toolbar Example
                    


                

                

                    Remove
                    Add
                    Up
                    Down
                

            

            
        

    ` ![images](https://gitee.com/OpenDocCN/vkdoc-js-zh/raw/master/docs/begin-phonegap/img/9781430239031_Fig04-07.jpg)

    图 4–7。 jQueryMobile 工具栏和按钮

    表格元素

    jQueryMobile 中的表单元素是典型的 HTML 表单元素——它们只是看起来不同。这意味着您可以使用您的 HTML JavaScript 技能来呈现好看、精美的 jQueryMobile 小部件,并使用传统的事件处理技术来快速编写您的移动 web 应用。

    让我们看几个表单元素的例子。在第一个例子中,我们使用了一个输入文本、一个文本区域和一个搜索框。对于所有这些,都分配了一个标签。它们被包装在一个字段集中,形成一组标签和相关的小部件。请参见 Figure 4–8 以了解表单元素在 jQueryMobile 中的外观。

    `


            jQuery Mobile Demo
            
            
            
        


            
            


                

                    Cancel
                    


                        Edit Contact
                    


                    Save
                

                

                    

                        


                            Simple Form Elements
                        


                        

                            
                            
                        

                        

                            
                            
                        

                        

                            
                            
                        

                    

                

                

                    Remove
                    Add
                    Up
                    Down
                

            

            
        

    ` ![images](https://gitee.com/OpenDocCN/vkdoc-js-zh/raw/master/docs/begin-phonegap/img/9781430239031_Fig04-08.jpg)

    图 4–8。 jQueryMobile 表单元素

    在第二个例子中,HTML select 被包装在一个好看的开/关开关中。请注意,当您以编程方式从中获取值时,您将把它用作 HTML 选择框。您还用滑块包装了一个文本框。滑块的值放在文本框中。这相当于用户在文本框中填写一个数字,但是使用 jQueryMobile,用户可以使用滑块在给定的范围内选择一个值。参见 Figure 4–9 来看看这个 HTML 是如何呈现的。

    `


            jQuery Mobile Demo
            
            
            
        


            
            


                

                    Cancel
                    


                        Edit Contact
                    


                    Save
                

                

                    

                        


                            Simple Form Elements
                        


                        

                            
                            
                        

                        

                            
                            
                        

                    

                

                

                    Remove
                    Add
                    Up
                    Down
                

            

            
        

    ` ![images](https://gitee.com/OpenDocCN/vkdoc-js-zh/raw/master/docs/begin-phonegap/img/9781430239031_Fig04-09.jpg)

    图 4–9。 jQueryMobile 表单元素

    在下面的例子中,你可以看到将 HTML 单选按钮和复选框包装成好看的小部件是多么容易。这是通过使用数据角色“控制组”和数据类型“水平”来完成的请参见图 4–10 了解单选和多选表单元素的外观:

    `


            jQuery Mobile Demo
            
            
            
        


            
            


                

                    Cancel
                    


                        Edit Contact
                    


                    Save
                

                

                    

                        


                            Single and MultiSelect Form Elements
                        


                        

                            

                                
                                    Choose a base:
                                

                                
                                
                                
                                
                                
                                
                            

                        

                        

                            

                                
                                    Choose Pizza toppings
                                

                                
                                
                                
                                
                                
                                
                                
                                
                            

                        

                        

                            

                                
                                    Non Veg topping:
                                

                                
                                
                                
                                
                                
                                
                            

                        

                        

                            

                                
                                    Payment Type:
                                

                                
                                
                                
                                
                                
                                
                            

                        

                    

                

                

                    Remove
                    Add
                    Up
                    Down
                

            

            
        

    ` ![images](https://gitee.com/OpenDocCN/vkdoc-js-zh/raw/master/docs/begin-phonegap/img/9781430239031_Fig04-10.jpg)

    图 4–10。 jQueryMobile 表单元素

    列表视图

    到目前为止,您已经看到了用带有数据角色和 CSS 类的简单 HTML 元素来声明各种 UI 小部件是多么容易。在 jQueryMobile 中,列表视图也不例外。下面的例子将向你展示如何将一个 HTML 列表转换成一个移动可滚动列表。

    `


          

    •         USA
          

    •     

    •         UK
          

    •     

    •         Russia
          
    `

    下面是如何在 HTML 中声明列表的完整代码示例。对于动态数据,您所要做的就是在运行时将 li 元素追加到 ul 元素中。请参考 Figure 4–11 查看 jQueryMobile 中的列表视图。

    `


            jQuery Mobile Demo
            
            
            
        


            
            


                

                    


                        Header
                    


                

                

                    

                          

    •                         USA
                          

    •                     

    •                         UK
                          

    •                     

    •                         Russia
                          

    •                 

                

                

                    


                        Footer
                    


                

            

            
        

    ` ![images](https://gitee.com/OpenDocCN/vkdoc-js-zh/raw/master/docs/begin-phonegap/img/9781430239031_Fig04-11.jpg)

    图 4–11。 jQueryMobile 列表视图

    您可以在 jQueryMobile 网站上了解更多相关信息。点击此链接查看演示和文档:jquerymobile.com/demos/1.0a4.1/

    jQueryMobile 事件处理

    在 jQueryMobile 中,事件处理可以分为两个方面:

    1. 非 jQueryMobile 小部件生成的事件。例如文本框、文本区域、按钮、单选按钮等等。
    2. jQueryMobile 小部件和框架生成的事件。这些事件的例子是触摸事件、方向改变事件、滚动事件和页面生命周期事件。

    正常事件

    普通事件应该像 jQuery 通常做的那样处理。在 jQuery 中,jQuery 选择器提供了一个通用方法 bind,它允许我们绑定到任何事件。

    `
        …


            
        

    $("#mybutton").bind("click",function(event){
        alert("clicked mybutton");
    });
    jQuery also provides many convenience method for events like a short hand of above method is click method, which binds the callback to click event.

    $("#mybutton").click(function(event){
        alert("clicked mybutton");
    });`

    您可以在以下网站上阅读有关 jQuery 事件的更多信息:

    api.jquery.com/category/events/

    现在,转到 jQueryMobile 生成的事件。请注意,所有事件,无论其来源如何,都需要以上述方式处理。这同样适用于下面记录的事件。

    下一节将讨论由 jQueryMobile 框架和小部件生成的各种事件。

    触摸事件

    手机或平板电脑上的触摸事件与点击或双击等传统鼠标事件有很大不同。以类似的方式,在传统的鼠标事件中,手势是不可能的。jQueryMobile 提供了一组用于触摸手势的新事件。这些事件在表 4–2 中描述。

    images

    以下是 jQueryMobile 如何处理触摸事件的示例。参见图 4–12 中的示例。

    `


            jQuery Mobile Touch Events Demo
            
            
            
            
        


            
            


                

                    


                        Touch Events
                    


                

                

                    


                        Touch Events example
                    


                    


                        Tap here
                    


                    


                        Tap and hold here
                    


                    


                        Swipe in this area.
                    


                    


                        Swipe Left <-- in this area.
                    


                    


                        Swipe Right -- > in this area.
                    


                

                

                    


                        Footer
                    


                

            

            
        

    ` ![images](https://gitee.com/OpenDocCN/vkdoc-js-zh/raw/master/docs/begin-phonegap/img/9781430239031_Fig04-12.jpg)

    图 4–12。 jQueryMobile 触摸事件。

    方位变化事件

    移动设备和平板设备都可以检测方位变化并做出反应。这非常有用,因为在纵向模式和横向模式下,移动设备或平板电脑的长宽比是不同的。jQueryMobile 允许开发人员使用这种方向更改来在两种模式下充分利用屏幕空间。为此,您需要侦听窗口元素上的“orientationchange”事件。下面的示例显示了这一点。参见图 4–13 和 4–14。

    `


            jQuery Mobile Touch Events Demo
            
            
            
                \((document).ready(function(){                 \)(window).bind('orientationchange', function(event){
                        $("#placeholder").html("Orientation changed to "+event.orientation);
                    });
                });
            
        


            
            


                

                    


                        Touch Events
                    


                

                

                    


                        Orientation Events example
                    


                    

                    

                

                

                    


                        Footer
                    


                

            

            
        

    ` ![images](https://gitee.com/OpenDocCN/vkdoc-js-zh/raw/master/docs/begin-phonegap/img/9781430239031_Fig04-13.jpg)

    图 4–13。 jQueryMobile 方向改变事件

    images

    图 4–14。 jQueryMobile 定向变更事件

    滚动事件

    移动设备的一个重要方面是滚动和滚动时在后台做事情的能力。想想惰性加载列表,它在用户滚动列表时获取数据。

    为此,您需要滚动事件(参见 Table 4–3)。jQueryMobile 提供滚动事件。请注意,当 scrollstart 事件在 iOS 上不按预期工作时,我们建议不要依赖 iOS 上的 scrollstart 事件。

    页面事件

    jQueryMobile 有页面的概念。jQueryMobile 中的页面被创建、显示和/或隐藏。jQueryMobile 提供了事件,这样开发人员可以在页面创建之前、创建之后以及页面显示和隐藏之前和之后进行适当的处理。所有这些事件都记录在表 4–4 中。

    images

    PhoneGap jQueryMobile 集成

    既然您已经了解了 jQueryMobile 的工作原理,那么让我们来集成 PhoneGap 和 jQueryMobile 的特性来创建应用。

    请注意,当您将 jQueryMobile 与 PhoneGap 一起使用时,有三个 JavaScript 框架,每个框架都有自己的引导。

    1. PhoneGap 框架
    2. jQuery 框架
    3. jQueryMobile 框架

    虽然所有框架都提供了自己的引导机制,但最好按照以下顺序引导这些框架:

    1. 语音间隙
    2. 框架
    3. jQueryMobile(如果真的需要的话)。

    这显示在以下示例中:

    ``

    使用 jQueryMobile 和 PhoneGap 进行本地搜索

    让我们来测试一下 jQueryMobile 和 PhoneGap。您将构建 PhoneGap geo、compass 和数据库特性的混搭,并结合使用 jQueryMobile 构建的 UI 和 Google Maps Places API。参见图 4–15。

    这种混搭被称为本地搜索。这个混搭具有以下特性:

    1. 允许用户在他/她的当前位置的给定半径内搜索感兴趣的地方。
    2. 允许用户查看该地点的详细信息并访问该地点的网站。
    3. 允许用户将地点保存为收藏夹,并将其从收藏夹中删除。
    4. 允许用户浏览他保存的收藏夹。
    5. 允许用户在谷歌地图上查看所有的搜索结果。

    images

    图 4–15。使用 jQueryMobile 和 PhoneGap 的本地搜索

    该屏幕中最重要的两个特征如下(参见图 4–16):

    1. 搜索按钮用于搜索用户位置 5 公里半径内的比萨饼
    2. 收藏夹按钮向用户显示所有标记为收藏夹的地方

    images

    图 4–16。使用 jQueryMobile 和 PhoneGap 的本地搜索

    搜索结果可以在地图上显示为列表或标记。Figure 4–17 显示了结果如何在中显示为列表。

    images

    图 4–17。本地搜索结果

    当用户单击其中一个搜索结果时,他会被带到企业详细信息页面。该页面显示了详细信息,如名称、地址和电话号码,以及企业附近。它还允许用户将业务条目添加到他/她的收藏夹中,或者从收藏夹中删除它。这显示在图 4–18 中。

    images

    图 4–18。本地搜索业务明细

    当用户将企业添加到他的收藏夹时,他可以从主页导航到收藏夹页面。在这里,用户将看到保存为收藏夹的地点/企业。这些条目实际上存储在应用的内部数据库中(由 PhoneGap 提供)(参见图 4–19)。

    images

    图 4–19。本地存储的收藏夹。

    同样,当用户点击其中一个条目时,他会被带到详细信息页面。如果您能观察到,我们现在可以看到“移至收藏夹”按钮,因为该企业/地点已经是收藏夹的一部分。参见图 4–20。

    images

    图 4–20。最喜欢的细节。

    最后,但并非最不重要的是,整个搜索结果绘制在谷歌地图上。为此,您需要进入主页,点击搜索,然后点击地图选项卡。您将看到如图图 4–21 所示的屏幕。

    images

    图 4–21。地图上的本地搜索结果

    引导 PhoneGap 和 jQuery

    引导按以下顺序完成:

    1. 当调用 PhoneGap JavaScript 库时,PhoneGap 被引导来调用 onDeviceReady()函数
    2. 在 onDeviceReady 中,jQuery 被引导,以便在加载 jQuery 时调用匿名函数

    <script> function onDeviceReady(){                 $(document).ready(function(){                         //Register event handlers                 }); } document.addEventListener(deviceready,onDeviceReady); </script>

    安装必要的 JavaScript 库

    对于这个项目,您将需要以下 JavaScript 库

    1. jQuery: [docs.jquery.com/Downloading_jQuery#Download_jQuery](http://docs.jquery.com/downloading_jquery#download_jquery)
    2. jQueryMobile: [jquerymobile.com/download/](http://jquerymobile.com/download/)
    3. jQuery ui 映射:??]
    4. 语音间隙:[www.phonegap.com/download/](http://www.phonegap.com/download/)

    假设您的应用 JavaScript 名为 app.js,应用 CSS 名为 app.css,您的 www 文件夹应该如图 Figure 4–22 所示。注意,images 文件夹属于 jQueryMobile 库。

    images

    图 4–22。本地搜索项目结构

    本地搜索的布局

    本地搜索页面是应用中的主页面。进行本地搜索需要三个输入。

    1. PhoneGap 的地理位置
    2. 从搜索文本框中搜索关键字
    3. jQueryMobile 滑块的搜索半径

    一旦完成,它将获取搜索结果并显示在搜索结果页面中。

    `

        
            

                Local Search         

        
             
            
                                      
            
                                      
            
                         
            
                Favorites             About us         
        
        
    `

    寻找当地企业

    为了搜索感兴趣的地方,我们使用谷歌地图的位置 API。可以通过对 Google Maps Places 服务端点进行 restful 调用来搜索感兴趣的地方。进行这个 restful 调用需要很多参数;这些显示在表 4–5 中。

    images

    此处显示的 URL 带有每个参数的填充符:

    https://maps.googleapis.com/maps/api/place/search/json?location={<ins>latitude,longi tude</ins>}&radius={<ins>radius</ins>}&types=food&name={<ins>search_keyword</ins>} &sensor=false&key={<ins>api_key</ins>}.

    对于以下 URL,会出现 JSON 响应:

    https://maps.googleapis.com/maps/api/place/search/json?location=- 33.8670522,151.1957362&radius=500&types=food&name=harbour&sensor=true&key=<<api key>>.

    JSON 的回应如下:

    {     "html_attributions": ["Listings by \u003ca href=\"http://www.yellowpages.com.au/\"\u003eYellow Pages\u003c/a\u003e"],     "results": [{         "geometry": {             "location": {                 "lat": -33.8719640,                 "lng": 151.1985440             }         },         "icon": "http://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png",         "id": "aefbc59325ffd5f3e93d67932375d20d143289de",         "name": "Toros Restaurant Darling Harbour",         "reference": "CoQBdgAAAE6oRybc13OZYNH0WeuwKzTfzjYXO8nuWyGqCqSTBogR_BZxE30fgXsybOl_wIR0s_uuHLZqq- 17DTgpGHZoSehSbOG73dfIxO3rpQak2OmNuBb5Kg63rPN_afbH_PnbILiofw6WSODYOCkqhFl38qSXyujAPkQKZU 76NJypgT6mEhCg1MhyNAuyark4X8YfRg4YGhTn_MXr0gelHUHPe3JMCic-cHlu3A",         "types": ["restaurant", "food", "establishment"],         "vicinity": "Darling Dr, Sydney"     }, …] }

    注意,在前面的 JSON 中,有一个 id 和引用。您需要更好地理解这些,以便能够构建您的应用。

    Id 是一个地方的唯一标识符。当您在数据库中存储地点/企业时,您将使用 id 作为主键。但是,id 不能用于获取最新信息。

    Reference 是一个键,用于从 Google Places 服务器获取一个地点/企业的详细信息。但是,请注意,引用在多个搜索结果中不是唯一的。

    因此,您将在数据库中存储 id(作为主键)和 reference(作为字符串),这样您就可以唯一地标识一个地点/企业(使用 id ),并随时使用 reference 从 Google Places 获取信息。

    HTML 中的整体布局

    下面是应用的总体布局。在应用的所有五个页面中:

    1. 搜索页面
    2. id 为“列表”的搜索结果页面
    3. id 为“详细信息”的详细信息页面
    4. id 为“收藏”的收藏列表页面
    5. id 为“Map”的地图页面

    `


            PhoneGap
            
            
            
            
            
            
            
            
        


            
            


                

                    


                        Local Search
                    


                

                
                

                    

                        
                        
                    

                    

                        
                        
                    

                    

                        
                    

                    

                        Favorites
                        About us
                    

                

                
            

            
            
            

                

                    


                        Result
                    


                

                
                

                    

                      

                

                
                

                    
                    
                

                
            

            
            
            

                

                    


                        Map
                    


                

                
                

                    

                    

                

                
                

                    
                    
                

                
            

            
            
            

                

                    


                        Favorites
                    


                

                
                

                    
                    

                      

                

                
                

                    
                

                
            

            
            
            

                

                    


                        Business Details
                    


                

                
                

                    
                        
                        
                            
                                
                            
                            
                                
                            
                        
                        
                            
                                
                                
                            
                            
                                
                                
                            
                            
                                
                                
                            
                            
                                
                                
                            
                        
                    

                            


                                Business Details
                            


                        

                                    

                                        
                                    

                                    

                                        
                                    

                                

                                    Visit HomePage
                                

                                    Name
                                

                                    …
                                

                                    Address
                                

                                    …
                                

                                    Phone
                                

                                    …
                                

                                    Rating
                                

                                    …
                                

                

                
            

            
        

    `

    获取并显示搜索结果

    函数启动的搜索将搜索按钮事件与实际执行搜索的函数绑定在一起。

    以下是搜索的事件流程:

    1. 向用户显示加载图标,让他/她知道正在进行长时间操作。这是通过调用$ .mobile.showPageLoadingMsg()来实现的;。

    2. 使用 PhoneGap 函数 navigator . geolocation . getcurrentposition(success callback,failureCallback)获取用户的当前位置。

    3. 在上述调用的 successCallback 中,使用以下参数完成了对 Google Places 的 JSON 请求:

      1. 地理定位
      2. 搜索关键字
      3. 地理位置的搜索半径
      4. Google Places 的开发者 Api 密钥
      5. var
    4. jQuery 的getJSON()GooglePlacesAjaxJSONPhoneGap注册 successCallback 和 failureCallback。getJSON()函数。

    5. 在上述调用的 successCallback 中,获取位置响应并将其附加到 id 为“result-list”的 ul 元素中。这个 ul 元素在 HTML 代码中被注释为 jQueryMobile 列表视图。一旦您将必要的 li 元素添加到 ul 元素中,我们将调用$(“结果列表”)。listView(“refresh”)将 ul 元素重绘为 jQueryMobile 列表。

    6. 注意,您将每个地方的 JSON 响应的引用部分作为链接的 id(一个元素)。这样做是为了当用户点击这个条目时,他会被带到详细信息页面。注意,这个链接的 href 部分实际上是“#details”

    7. 最后,您将一个 click 处理程序与 place 上的 click 事件绑定在一起,这样您就可以实际调用 Google Places 服务器,在将用户导航到详细信息页面之前获取企业/地点条目的详细信息。

    8. 最后一步是通过调用$.mobile.hidePageLoadingMsg()移除加载图标;。

    `/**
      * Binding Search button handler to go and fetch place results
      */
    function initiateSearch(){
            \(("#search").click(**function**(){ **try** {                 \).mobile.showPageLoadingMsg();

    navigator.geolocation.getCurrentPosition(function(position){

    var radius = $("#range").val() * 1000;
    mapdata = new google.maps.LatLng(position.coords.latitude, position.coords.longitude);
    var url = "https://maps.googleapis.com/maps/api/place/search/json?location=" +
    position.coords.latitude + "," + position.coords.longitude + "&radius=" + radius +
    "&name=" + ("#searchbox").val() + "&sensor=false&key=AIzaSyC4vCfT_Knq1SGuNMahZqyrmZFiTuBsdlY";.getJSON(url, function(data){
    cachedData = data;
                            \(("#result-list").html(""); **try** {                             \)(data.results).each(function(index, entry){

    var htmlData = "<a href="#details" id="" + entry.reference + ""><img src="" +
    entry.icon + "" class="ui-li-icon">

     " + entry.name +
    "

     vicinity:" + entry.vicinity + "

    ";
    var liElem = $(document.createElement('li'));

    $("#result-list").append(liElem.html(htmlData));

    $(liElem).bind("tap", function(event){
    event.stopPropagation();
    fetchDetails(entry);
    return true;
                                    });

    });
                                                            $("#result-
    list").listview('refresh');
                            }
    catch (err) {
    console.log("Got error while putting search result on result page " + err);
                            }

    .mobile.changePage("list");.mobile.hidePageLoadingMsg();
                        }).error(function(xhr, textStatus, errorThrown){
    console.log("Got error while fetching search result : xhr.status=" + xhr.status);

    }).complete(function(error){
                             $.mobile.hidePageLoadingMsg();
                        });
                    }, function(error){
    console.log("Got Error fetching geolocation " + error);
                    });

    }
    catch (err) {
    console.log("Got error on clicking search button " + err);
                }

    });

    }`

    显示一个地方/商业的细节

    您已经看到,当用户单击显示地点搜索结果列表中的地点条目时,会调用 fetchDetails()函数。

    fetchDetails()函数的流程如下:

    1. 向用户显示加载图标。这是通过调用$ .mobile.showPageLoadingMsg()来完成的。
    2. 明细位的所有字段都被重置为空白。例如$(“# name”)。html();。
    3. 创建一个详细信息位置请求的 URL(details URL ),并使用 jQuery $进行 Ajax 调用。getJSON()调用。
    4. 在$的成功回调中。getJSON(),您将获得页面的详细信息。在这里,您首先检查给定的地点是否已经被用户存储为收藏夹。基于此,向用户显示“添加到收藏夹”或“从收藏夹移除”按钮。
    5. 此页面的字段已填充。
    6. 加载图标通过调用$.mobile.hidePageLoadingMsg()来移除。

    `/**
         * Fetch the details of a place/business. This function is called before user navigates to details page
         * @param {Object} reference
         */
    function fetchDetails(entry){

    currentBusinessData = null;

    .mobile.showPageLoadingMsg();**var** detailsUrl = "https://maps.googleapis.com/maps/api/place/details/json?reference=" +entry.reference + "&sensor=true&key=<API_Key>";("#name").html("");
            ("#address").html("");("#phone").html("");
            ("#rating").html("");("#homepage").attr("href", "");

    $.getJSON(detailsUrl, function(data){
    if (data.result) {
    currentBusinessData = data.result;

    isFav(currentBusinessData, function(isPlaceFav){
                                            console.log(currentBusinessData.name+" is fav
    "+isPlaceFav);
    if (!isPlaceFav) {

    ("#add").show();("#remove").hide();
                        }
    else {

    ("#add").hide();("#remove").show();
                        }
                        ("#name").html(data.result.name);("#address").html(data.result.formatted_address);
                        ("#phone").html(data.result.formatted_phone_number);("#rating").html(data.result.rating);
                        $("#homepage").attr("href", data.result.url);

    });
                }
            }).error(function(err){
    console.log("Got Error while fetching details of Business " + err);
            }).complete(function(){
                $.mobile.hidePageLoadingMsg();
            });

    }`

    在收藏夹中添加和删除地点/企业

    下一步是实际观察

    1. 我们如何将一个地方添加到收藏夹列表中?
    2. 我们如何从收藏夹列表中删除一个位置?
    3. 我们如何找到一个给定的地方是我们的收藏夹列表的一部分?

    这里需要注意的是,您使用 PhoneGap 的数据库 API 来存储、检索和删除位置。所有这些信息都使用 PhoneGap 的数据库 API 存储在应用的数据库中。您将把收藏夹存储在名为“favorite”的表中

    initiateFavButton()将“添加到收藏夹”和“从收藏夹中删除”按钮的点击处理程序绑定到实际的处理程序。“添加到收藏夹”按钮位于 id 为“添加”的 div 中,“从收藏夹中移除”位于 id 为“移除”的 div 中您可以通过隐藏或显示这些 div 来控制按钮的可见性。您还可以相应地调用 addToFavorite()和 removeFromFavorite()方法来实际执行添加和删除操作。

    `/**
     * Called to bind the "Add to Favorite" Button
     */

    function initiateFavButton() {
        $("#removefav").click(function () {

    try {
                if (currentBusinessData != null) {
                    removeFromFavorite(currentBusinessData);
                    ("#add").show();("#remove").hide();

    }
            } catch (err) {
                console.log("Got Error while removing " + currentBusinessData.name + " error " + err);
            }

    });
        \(("#addfav").click(function () {         try {             if (currentBusinessData != null) {                 addToFavorite(currentBusinessData);                 \)("#add").hide();
                    $("#remove").show();
                }
            } catch (err) {
                console.log("Got Error while adding " + currentBusinessData.name + " error " + err);
            }

    });

    }`

    ensureTableExists()是所有其他数据库函数使用的公共函数。此函数确保您在对数据库执行任何插入、选择或删除操作之前,执行 SQL 脚本“如果不存在则创建表收藏夹(id 唯一、引用、名称、地址、电话、评级、图标、邻近)”。

    `/**
     * Ensure we have the table before we use it
     * @param {Object} tx
     */

    function ensureTableExists(tx) {
        tx.executeSql('CREATE TABLE IF NOT EXISTS Favorite (id unique, reference,
    name,address,phone,rating,icon,vicinity)');
    }`

    addToFavorite()是一个函数,它实际上为 favorites 表中的给定位置执行数据库插入。请注意,您正在数据库表“favorite”中存储 id、reference、name、icon、formatted_address、formatted_phone_number、rating 和 neighborhood。

    `    /**
         * Add current business data to favorite
         * @param {Object} data
         */

    function addToFavorite(data) {
            var db = window.openDatabase("Favorites", "1.0", "Favorites", 20000000);

    db.transaction(function (tx) {
                ensureTableExists(tx);
                var id = (data.id != null) ? ('"' + data.id + '"') : ('""');
                var reference = (data.reference != null) ? ('"' + data.reference + '"') : ('""');
                var name = (data.name != null) ? ('"' + data.name + '"') : ('""');
                var address = (data.formatted_address != null) ? ('"' + data.formatted_address + '"') : ('""');
                var phone = (data.formatted_phone_number != null) ? ('"' + data.formatted_phone_number + '"') : ('""');
                var rating = (data.rating != null) ? ('"' + data.rating + '"') : ('""');
                var icon = (data.icon != null) ? ('"' + data.icon + '"') : ('""');
                var vicinity = (data.vicinity != null) ? ('"' + data.vicinity + '"') : ('""');
                var insertStmt = 'INSERT INTO Favorite (id,reference,
    name,address,phone,rating,icon,vicinity) VALUES (' + id + ',' + reference + ',' + name + ',' + address + ',' + phone + ',' + rating + ',' + icon + ',' + vicinity + ')';
                tx.executeSql(insertStmt);

    }, function (error) {
                console.log("Data insert failed " + error.code + "   " + error.message);
            }, function () {
                console.log("Data insert successful");
            });

    }`

    removeFromFavorite()是从“Favorite”表中删除收藏夹的函数。它只需要 id 就可以这样做。

    `/**
     * Remove current business data from favorite
     * @param {Object} data
     */

    function removeFromFavorite(data) {
        try {
            var db = window.openDatabase("Favorites", "1.0", "Favorites", 20000000);

    db.transaction(function (tx) {
                ensureTableExists(tx);
                var deleteStmt = "DELETE FROM Favorite WHERE id = '" + data.id + "'";
                console.log(deleteStmt);
                tx.executeSql(deleteStmt);

    }, function (error) {
                console.log("Data Delete failed " + error.code + "   " + error.message);
            }, function () {
                console.log("Data Delete successful");
            });
        } catch (err) {
            console.log("Caught exception while deleting favorite " + data.name);
        }

    }`

    isFav()是查询表“favorite”的函数,以找出给定的地点/企业是否已经存在于表中,并因此被用户标记为 favorite。

    `    /**
         *
         * @param {Object} reference
         * @return true if place is favorite else false
         */

    function isFav(data, callback) {
            var db = window.openDatabase("Favorites", "1.0", "Favorites", 200000);
            try {
                db.transaction(function (tx) {
                    ensureTableExists(tx);
                    var sql = "SELECT * FROM Favorite where id='" + data.id + "'";
                    tx.executeSql(sql, [], function (tx, results) {

    var result = (results != null && results.rows != null && results.rows.length > 0);

    callback(result);
                    }, function (tx, error) {

    console.log("Got error in isFav error.code =" + error.code + " error.message = " + error.message);
                        callback(false);

    });
                });
            } catch (err) {
                console.log("Got error in isFav " + err);
                callback(false);
            }
    }`

    载入你最喜欢的地方

    到目前为止,您已经在收藏夹中添加和删除了一个位置。您还看到了如何检查一个地方是否被用户设置为他/她的最爱。现在,让我们看看检索用户所有最喜欢的地方的代码。

    当用户点击主页上的“收藏夹”按钮时,就会调用这个代码。

    这段代码与 isFav()非常相似,只不过在这里您将从“favorite”表中获取所有条目,获取结果集,并用“fav-list”id 填充 UL。

    填充部分类似于显示搜索结果。

    请注意,每次用户导航到“收藏夹位置”时,您都会从数据库表“收藏夹”中获取结果这是通过监听页面的“pagebeforeshow”事件来完成的。“pagebeforeshow”事件在向用户显示 jQueryMobile 页面之前触发。

    `/**
     * Called each time before user navigates to Favorites
     */

    function initiateFavorites() {
        $("#fav").live("pagebeforeshow", function () {

    var db = window.openDatabase("Favorites", "1.0", "Favorites", 200000);
            try {
                db.transaction(function (tx) {
                    tx.executeSql('SELECT * FROM Favorite', [], function (tx, results) {

    $("#fav-list").html("");
                        if (results != null && results.rows != null) {
                            for (var index = 0; index < results.rows.length; index++) {
                                var entry = results.rows.item(index)

    var htmlData = "<a href="#details" id="" + entry.reference + ""><img src="" + entry.icon + "" class="ui-li-icon">

     " + entry.name + "

     vicinity:" + entry.vicinity + "

    ";

    var liElem = $(document.createElement('li'));

    $("#fav-list").append(liElem.html(htmlData));

    $(liElem).bind("tap", function (event) {
                                    event.stopPropagation();
                                    fetchDetails(entry);
                                    return true;
                                });

    }
                            $("#fav-list").listview('refresh');
                        }
                    }, function (error) {
                        console.log("Got error fetching favorites " + error.code + " " + error.message);
                    });
                });
            } catch (err) {
                console.log("Got error while reading favorites " + err);
            }

    });
    }`

    在地图上显示搜索结果

    这个练习的最后一部分是在谷歌地图上显示一个地方的搜索结果。这有助于用户更好地了解一个地方的位置。在 initiateSearch()函数中,当接收到搜索结果时,将结果缓存在一个名为“cachedData”的 JavaScript 变量中。在这种情况下,您实际上将使用相同的数据在地图上绘制标记。

    请注意,每次用户导航到“收藏夹位置”时,您都要重新绘制地图并从 cachedData 中绘制标记这是通过监听页面的“pagebeforeshow”事件来完成的。

    `  /**
       * Called to initiate Map page
       */

    function initiateMap() {
          $("#map").live("pagebeforecreate", function () {
              try {

    \(('#map_canvas').gmap({                   'center': mapdata,                   'zoom': 12,                   'callback': function (map) {                       \)(cachedData.results).each(function (index, entry) {
                              \(('#map_canvas').gmap('addMarker', {                               'position': new                               google.maps.LatLng(entry.geometry.location.lat, entry.geometry.location.lng),                               'animation': google.maps.Animation.DROP                           }, function (map, marker) {                               \)('#map_canvas').gmap('addInfoWindow', {
                                      'position': marker.getPosition(),
                                      'content': entry.name
                                  }, function (iw) {
                                      $(marker).click(function () {
                                          iw.open(map, marker);
                                          map.panTo(marker.getPosition());
                                      });
                                  });
                              });

    });
                      }

    });
                  console.log("Map initialized");
              } catch (err) {
                  console.log("Got error while initializing map " + err);
              }

    });`

    完整的源代码

    index.html 的完整来源如下:

    `


            PhoneGap
            
            
            
            
            
            
            
            
        


            
            


                

                    


                        Local Search
                    


                

                
                

                    

                        
                        
                    

                    

                        
                        
                    

                    

                        
                    

                    

                        Favorites
                        About us
                    

                

                
            

            
            
            

                

                    


                        Result
                    


                

                
                

                    

                      

                

                
                

                    
                    
                

                
            

            
            
            

                

                    


                        Map
                    


                

                
                

                    

                    

                

                
                

                    
                    
                

                
            

            
            
            

                

                    


                        Favorites
                    


                

                
                

                    <!--

      </ul>                 -->                 <ul id="fav-list" data-role="listview" data-theme="g">                 </ul>             </div>             <!-- /content -->             <div data-role="footer" data-id="result-footer" data-position="fixed"             class="ui-bar-a ui-footer ui-footer-fixed fade ui-fixed-overlay" role="contentinfo" style="top: - 1263px; ">                 <!-- /navbar -->             </div>             <!-- /footer -->         </div>         <!-- /page -->         <!-- Business Details Page -->         <div data-role="page" id="details">             <div data-role="header">                 <h1>                     Business Details                 </h1>             </div>             <!-- /header -->             <div data-role="content">                 <table summary="Business Details">                     <caption>                         <h3>                             Business Details                         </h3>                     </caption>                     <tfoot>                         <tr>                             <td colspan="2">                                 <div id="remove">                                     <button id="removefav" data-role="button">                                         Remove to Favorite                                     </button>                                 </div>                                 <div id="add">                                     <button id="addfav" data-role="button">                                         Add to Favorite                                     </button>                                 </div>                             </td>                         </tr>                         <tr>                             <td colspan="2">                                 <a id="homepage" data-role="button" href="">Visit HomePage</a>                             </td>                         </tr>                     </tfoot>                     <tbody>                         <tr>                             <th scope="row">                                 Name                             </th>                             <td id="name">                                 …                             </td>                         </tr>                         <tr>                             <th scope="row">                                 Address                             </th>                             <td id="address">                                 …                             </td>                         </tr>                         <tr>                             <th scope="row">                                 Phone                             </th>                             <td id="phone">                                 …                             </td>                         </tr>                         <tr>                             <th scope="row">                                 Rating                             </th>                             <td id="rating">                                 …                             </td>                         </tr>                     </tbody>                 </table>             </div>             <!-- /content -->         </div>         <!-- /page -->     </body> `

      app.js 的完整源代码如下。请注意,您需要用自己的钥匙替换<api_key>。可以从[code.google.com/apis/maps/documentation/places](http://code.google.com/apis/maps/documentation/places)获取 API 密钥。</api_key>

      `var mapdata = null;

      var cachedData = null;

      var currentBusinessData = null;

      /**
           * Fetch the details of a place/business. This function is called before user
      navigates to details page
           * @param {Object} reference
           */

      function fetchDetails(entry) {

      currentBusinessData = null;

      .mobile.showPageLoadingMsg();    var detailsUrl ="https://maps.googleapis.com/maps/api/place/details/json?reference=" + entry.reference + "&sensor=true&key=<API_Key>";("#name").html("");
          ("#address").html("");("#phone").html("");
          ("#rating").html("");("#homepage").attr("href", "");

      $.getJSON(detailsUrl, function (data) {
              if (data.result) {
                  currentBusinessData = data.result;

      isFav(currentBusinessData, function (isPlaceFav) {
                      console.log(currentBusinessData.name + " is fav
      " + isPlaceFav);
                      if (!isPlaceFav) {

      ("#add").show();("#remove").hide();
                      } else {

      ("#add").hide();("#remove").show();
                      }
                      ("#name").html(data.result.name);("#address").html(data.result.formatted_address);
                      ("#phone").html(data.result.formatted_phone_number);("#rating").html(data.result.rating);
                      $("#homepage").attr("href", data.result.url);

      });
              }
          }).error(function (err) {
              console.log("Got Error while fetching details of Business " + err);
          }).complete(function () {
              $.mobile.hidePageLoadingMsg();
          });

      }

      //-------------------------------
      /**
       * Called to initiate Map page
       */
      function initiateMap() {
          $("#map").live("pagebeforecreate", function () {
              try {

      \(('#map_canvas').gmap({                 'center': mapdata,                 'zoom': 12,                 'callback': function (map) {                     \)(cachedData.results).each(function (index, entry) {
                              \(('#map_canvas').gmap('addMarker', {                             'position': new google.maps.LatLng(entry.geometry.location.lat, entry.geometry.location.lng),                             'animation': google.maps.Animation.DROP                         }, function (map, marker) {                             \)('#map_canvas').gmap('addInfoWindow', {
                                      'position': marker.getPosition(),
                                      'content': entry.name
                                  }, function (iw) {
                                      $(marker).click(function () {

      iw.open(map, marker);

      map.panTo(marker.getPosition());
                                      });
                                  });
                              });

      });
                      }

      });
                  console.log("Map initialized");
              } catch (err) {
                  console.log("Got error while initializing map " + err);
              }

      });

      }
      //--------------------------------------------------------------------------------
      /**
       * Called to bind the "Add to Favorite" Button
       */

      function initiateFavButton() {
          $("#removefav").click(function () {

      try {
                  if (currentBusinessData != null) {
                      removeFromFavorite(currentBusinessData);
                      ("#add").show();("#remove").hide();
                  }
              } catch (err) {
                  console.log("Got Error while removing " + currentBusinessData.name + " error
      " + err);
              }

      });
          $("#addfav").click(function () {
              try {
                  if (currentBusinessData != null) {

      addToFavorite(currentBusinessData);
                      ("#add").hide();("#remove").show();
                  }
              } catch (err) {
                  console.log("Got Error while adding " + currentBusinessData.name + " error "

      • err);
                }

      });

      }
      //--------------------------------------------------------------------------------------

      /**
       * Called each time before user navigates to Favorites
       */

      function initiateFavorites() {
          $("#fav").live("pagebeforeshow", function () {

      var db = window.openDatabase("Favorites", "1.0", "Favorites", 200000);
              try {
                  db.transaction(function (tx) {
                      tx.executeSql('SELECT * FROM Favorite', [], function (tx, results) {

      $("#fav-list").html("");
                          if (results != null && results.rows != null) {
                              for (var index = 0; index < results.rows.length;
                              index++) {

      var entry = results.rows.item(index)

      var htmlData = "<a href="#details" id="" +
      entry.reference + ""><img src="" + entry.icon + "" class="ui-li-
      icon">

       " + entry.name +"

       vicinity:" +
      entry.vicinity + "

      ";

      var liElem = $(document.createElement('li'));

      ("#fav-list").append(liElem.html(htmlData));(liElem).bind("tap", function (event) {
                                      event.stopPropagation();
                                      fetchDetails(entry);
                                      return true;
                                  });

      }
                              $("#fav-
      list").listview('refresh');
                          }
                      }, function (error) {
                          console.log("Got error fetching Favorites " + error.code + " " +
      error.message);
                      });
                  });
              } catch (err) {
                  console.log("Got error while reading Favorites " + err);
              }

      });
      }
      //--------------------------------------------------------------------------------
      /**
       * Ensure we have the table before we use it
       * @param {Object} tx
       */

      function ensureTableExists(tx) {
          tx.executeSql('CREATE TABLE IF NOT EXISTS Favorite (id unique,
      reference, name,address,phone,rating,icon,vicinity)');
      }
      //-----------------------------------------------------------------------
      /**
       * Add current business data to Favorite
       * @param {Object} data
       */

      function addToFavorite(data) {
          var db = window.openDatabase("Favorites", "1.0", "Favorites", 20000000);

      db.transaction(function (tx) {
              ensureTableExists(tx);
              var id = (data.id != null) ? ('"' + data.id + '"') : ('""');
              var reference = (data.reference != null) ? ('"' + data.reference + '"') :
      ('""');
              var name = (data.name != null) ? ('"' + data.name + '"') : ('""');
              var address = (data.formatted_address != null) ? ('"' + data.formatted_address +
      '"') : ('""');
              var phone = (data.formatted_phone_number != null) ? ('"' +
      data.formatted_phone_number + '"') : ('""');
              var rating = (data.rating != null) ? ('"' + data.rating + '"') : ('""');
              var icon = (data.icon != null) ? ('"' + data.icon + '"') : ('""');
              var vicinity = (data.vicinity != null) ? ('"' + data.vicinity + '"') : ('""');
              var insertStmt = 'INSERT INTO Favorite (id,reference,
      name,address,phone,rating,icon,vicinity) VALUES (' + id + ',' + reference + ',' + name +
      ',' + address + ',' + phone + ',' + rating + ',' + icon + ',' + vicinity + ')';
              tx.executeSql(insertStmt);
          }, function (error) {
              console.log("Data insert failed " + error.code + "   " + error.message);
          }, function () {
              console.log("Data insert successful");
          });

      }
      //----------------------------------------------------------------------------------
      /**
       * Remove current business data from Favorite
       * @param {Object} data
       */

      function removeFromFavorite(data) {
          try {
              var db = window.openDatabase("Favorites", "1.0", "Favorites", 20000000);

      db.transaction(function (tx) {
                  ensureTableExists(tx);
                  var deleteStmt = "DELETE FROM Favorite WHERE id
      = '" + data.id + "'";
                  console.log(deleteStmt);
                  tx.executeSql(deleteStmt);

      }, function (error) {
                  console.log("Data Delete failed " + error.code + "   " + error.message);
              }, function () {
                  console.log("Data Delete successful");
              });
          } catch (err) {
              console.log("Caught exception while deleting Favorite " + data.name);
          }

      }

      //--------------------------------------------------------------------
      /**
       *
       * @param {Object} reference
       * @return true if place is Favorite else false
       */

      function isFav(data, callback) {
          var db = window.openDatabase("Favorites", "1.0", "Favorites", 200000);
          try {
              db.transaction(function (tx) {
                  ensureTableExists(tx);
                  var sql = "SELECT * FROM Favorite where id='" + data.id + "'";
                  tx.executeSql(sql, [], function (tx, results) {

      var result = (results != null && results.rows != null &&
      results.rows.length > 0);

      callback(result);
                  }, function (tx, error) {
                      console.log("Got error in isFav error.code =" + error.code + "
      error.message = " + error.message);
                      callback(false);

      });
              });
          } catch (err) {
              console.log("Got error in isFav " + err);
              callback(false);
          }

      }

      //-------------------------------------------------------------------------------
      /**
       * Binding Search button handler to go and fetch place results
       */

      function initiateSearch() {
          \(("#search").click(function () {         try {             \).mobile.showPageLoadingMsg();

      navigator.geolocation.getCurrentPosition(function (position) {

      var radius = $("#range").val() * 1000;
                      mapdata = new
                      google.maps.LatLng(position.coords.latitude, position.coords.longitude);
                      var url =
      "https://maps.googleapis.com/maps/api/place/search/json?location=" +
      position.coords.latitude + "," + position.coords.longitude + "&radius=" + radius +
      "&name=" + ("#searchbox").val() + "&sensor=true&key=<API_Key>";.getJSON(url, function (data) {
                          cachedData = data;
                          $("#result-
      list").html("");
                          try {

      $(data.results).each(function (index, entry) {

      var htmlData = "<a href="#details" id="" +
      entry.reference + ""><img
      src="" + entry.icon + "" class="ui-li-icon">

       " + entry.name +
      "

       vicinity:" + entry.vicinity + "

      ";

      var liElem = $(document.createElement('li'));

      $("#result-list").append(liElem.html(htmlData));

      $(liElem).bind("tap", function (event) {

      event.stopPropagation();
                                      fetchDetails(entry);

      return true;
                                  });
                              });

      $("#result-list").listview('refresh');
                          } catch (err) {

      console.log("Got error while putting search result on result
      page " + err);
                          }
                          .mobile.changePage("list");.mobile.hidePageLoadingMsg();
                      }).error(function (xhr, textStatus, errorThrown) {
                          console.log("Got error while fetching search result : xhr.status=" +
      xhr.status);

      }).complete(function (error) {
                          $.mobile.hidePageLoadingMsg();
                      });
                  }, function (error) {
                      console.log("Got Error fetching geolocation " + error);
                  });

      } catch (err) {
                  console.log("Got error on clicking search button " + err);
              }
          });

      }

      //--------------------------------------------------------------

      function bind() {
          initiateMap();
          initiateFavorites();
          initiateSearch();
          initiateFavButton();
      }
      //---------------------------------------------------

      function onDeviceReady() {
          $(document).ready(function () {
              bind();
          });
      }
      document.addEventListener("deviceready", onDeviceReady);
      //-------------------------

      The complete source of the app.css is as follows

      map, .map-content, #map_canvas {

      width: 100%;
          height: 100%;
          padding: 0;
      }

      map_canvas {

      height: min-height: 100%;
      }`

      jQueryMobile 的优点

      jQueryMobile 是一个面向移动应用开发人员的易于使用的 JavaScript UI 框架。jQueryMobile 最好的部分是它的声明式 UI 编程。使用 HTML 标签来创建 UI,并添加“数据角色”页面来将 HTML 标签注释为页面、页眉、页脚、内容、列表和按钮,这使得快速编程 UI 布局变得非常容易。

      jQueryMobile 的另一个优点是页面的概念。页面在 HTML 页面中被声明为 div。此外,jQueryMobile 内置了导航部分和历史管理部分。这消除了添加历史管理部分的麻烦。

      虽然 jQueryMobile 为小部件和工具栏提供了很好的支持,但是这些的编程方面需要开发人员进行 DOM 操作。

      jQueryMobile 最大的优势在于它建立在健壮的 jQuery 核心框架之上。jQueryMobile 的另一个优势是它支持 iOS、Android、BlackBerry、HP WebOS、诺基亚/Symbian、Windows Mobile、Opera Mobile/Mini、Firefox mobile 以及所有现代桌面浏览器。

      jQueryMobile 为移动和平板应用提供了触摸事件。jQueryMobile 也提供了很好的主题支持。在主题之间切换就像改变 HTML 标签的属性一样简单。

      jQueryMobile 的缺点

      jQueryMobile 是一个非常好的轻量级框架,使用 jQuery 来操作 DOM 允许用户以一种简单的方式构建应用。然而,随着应用复杂性的增加,以及对数据模型和相应视图的需求,用 jQueryMobile 编程更多的是用 JavaScript 实现自己的 MVC 框架。

      简而言之,当您的应用很复杂时,jQueryMobile 很难使用。缺乏 MVC 框架甚至模型和 JavaScript 视图,使得 jQueryMobile 中的 JavaScript UI 编程与 Sencha Touch 等其他框架相比非常痛苦。

      结论

      如果移动应用相当简单,jQueryMobile 是一个很好的 JavaScript 移动 UI 开发框架。随着您的移动应用 UI 的复杂性增加,用 jQueryMobile 编程将变得更加繁琐。

      五、使用 PhoneGap 和 Sencha Touch

      Sencha Touch 是一家名为“ExtJS”的公司的产品。“ExtJS”是 Ajax RIA 世界中一家受欢迎的公司,它提供了一个丰富的、经过打磨的 JavaScriptui 框架,名为“ExtJS”。该公司的热门产品有“ExtJS”JavaScript UI 框架,“Ext-GWT”,GWT UI 框架(Ext js 的 GWT 计数器部分),以及“Sencha Touch”移动端 JavaScript 库。

      “ExtJS”公司最近更名为“Sencha”。因此,虽然名称是新的,但是“Sencha Touch”库中的内容是基于多年来使用 JavaScript 构建 UI 的经验。

      如果你熟悉“ExtJS”,你会注意到“ExtJS”和“Sencha Touch”有很多相似之处,尤其是在基础类。然而,“Sencha Touch”是专为移动应用设计的。

      为什么要用 Sencha Touch?

      Sencha Touch 允许您为 iPhone、Android 和 BlackBerry 开发基于浏览器的应用,具有原生的外观和感觉。还有,Sencha Touch 是基于 HTML5 的。

      Sencha Touch 为您提供以下优势:

      1. 触摸优化的丰富小部件集,以及支持点击、双击、滑动、点击并按住、挤压并旋转、滑动和手势的触摸事件。
      2. 新时代网络标准 HTML5 和 CSS3。
      3. 与 PhoneGap 集成。
      4. 支持 iOS、Android 和黑莓,以及这些设备的原生主题
      5. 对 Ajax、JSONP 和 Yahoo!查询语言(YQL),以及支持本地存储来支持小部件。

      总之,Sencha Touch 是目前移动应用开发最好的 JavaScript ui 库之一。如果你没有从事过 ExtJS,那么学习曲线一开始可能会有点陡。

      煎茶触摸的优点

      Sencha Touch 的优点远远超过缺点。首先,Sencha Touch 基于 web 标准,如 HTML5 和 CSS3,而不是基于任何专有技术。Sencha Touch 的社区支持也很好。商业使用是免费的。

      你可以在 Sencha Touch 中构建一个应用,它可以检测我们是在平板电脑上还是在手机上,并且你可以编写代码来使相同的应用以不同的方式工作。例如,看看厨房的水槽。当您在平板电脑和移动设备上查看它时,您将会看到该示例根据实际情况进行了自我调整。

      小部件集相当丰富。用 JavaScript 构建整个小部件确保了与用户的高度交互性。你有更多的控制权。

      Sencha Touch 的性能很好,并且随着每个版本的发布而不断改进。此外,随着更新版本的 iOS 和 Android 操作系统的发布,这些操作系统附带的 webkit 在性能上也有所提高。

      有对国际化的支持,有像网格和传送带这样的小部件,它们是非常新时代的可视化辅助工具。

      煎茶触摸的缺点

      Sencha Touch 最大的缺点是它的学习曲线。使用 Sencha Touch,您很少使用任何预渲染的 HTML。一切都是通过 JavaScript 添加到 DOM 中的。对一些人来说,这可能是观念上的转变。

      如果你的应用仅仅是几个带有导航的页面,视图主要是列表视图、表单和工具栏,那么 Sencha Touch 就太过了。

      下载 Sencha Touch

      从 Sencha 的网站-[www.sencha.com/products/touch/](http://www.sencha.com/products/touch/)下载 Sencha 触摸库。一旦你下载并解压 sdk,你会看到如图 5-1 所示的结构。

      images

      图 5-1。 森查摸目录结构

      集成 Sencha 和 PhoneGap

      让我们从将 Sencha Touch 与 PhoneGap 项目集成开始。本章将假设它是针对 Android 平台的。其他平台的步骤类似。

      参考第二章和第三章为你的目标平台设置 PhoneGap 项目。

      如图 5-2 所示,从 Sencha Touch sdk 中,您需要添加以下文件:

      1. 将 sencha-touch.js JavaScript 文件添加到 www/lib。
      2. 将 resources/css 文件夹添加到 www/lib
      3. 将所有应用代码放在一个名为 app/app.js 的文件中,这将是我们的主 JavaScript 文件。

      出于本章和示例的考虑,请从 sencha-touch-1 . 1 . 0/examples/map 文件夹中复制 icon.png、phone_startup.png 和 tablet_startup.png。

      images

      图 5-2。 PhoneGap 和 Sencha Touch 项目结构

      使用 Sencha Touch 构建本地搜索应用

      本地搜索应用的要求类似于我们在第五章中的要求。用户通过关键字输入搜索,从他/她的当前位置搜索范围,用户获得列表视图中列出的本地位置。用户可以点击其中一个项目,并查看该地方的详细信息。在详细信息屏幕上,用户可以选择将该地点放入他/她的收藏夹列表(存储在应用数据库中以供离线访问)。用户还可以在地图视图中看到搜索结果。

      最后但同样重要的是,用户可以点击收藏夹按钮(星形图标)来查看他/她的收藏夹列表。

      让我们开始构建应用。请记住,Sencha Touch 相当大,本章将带您浏览该应用所需的 Sencha Touch API 的子集。

      初始化煎茶触摸

      第一步是确保 index.html 有 Sencha 触摸库,PhoneGap 库,和 CSS 链接。注意我们的身体是空的。这是因为,在 Sencha Touch 中,我们用 JavaScript 构建整个 ui。注意,我们包含了以下 JavaScript 和样式表。

      1. Sencha Touch 样式表
      2. 谷歌地图 JavaScript
      3. 我们的应用 JavaScript

      `


              Local Search
              
              
              
              
              
              
          


          

      `

      现在让我们转到 app . js。PhoneGap 应用是在函数 Ext.setup()中设置的。根据经验,记住所有 Sencha Touch 函数都采用 JSON 结构作为配置。

      我们将对 Ext.setup 进行同样的操作。我们将为应用提供一些图标,以及手机闪屏和平板电脑闪屏。但这些都不是我们想在本章重点讨论的部分。

      这里最重要的部分是 onReadyfunction()。这就好比 jQuery 的文档就绪,PhoneGap 的设备就绪功能。我们可以在这个函数中开始绘制 Sencha Touch 的 UI。

      Ext.setup({     tabletStartupScreen: 'tablet_startup.png',     phoneStartupScreen: 'phone_startup.png',     icon: 'icon.png',     glossOnIcon: false,     onReady: function () {         //Sencha Touch framework has initialized here.         //Create Panels and bind event handlers.     } });

      创建布局(应用框架)

      下一步是将一个面板声明为主面板。为此,我们将创建一个新面板(new Ext。Panel())并在其配置 JSON 中,我们将声明以下内容:

      1. 布局:“卡片”-布局是卡片布局,这意味着它是一叠卡片,我们一次只展示一张卡片
      2. full screen:true–指定此面板将占用 100%的可用宽度和高度,并自动将其自身绘制到页面上
      3. items: [searchPanel,tabResultPanel,favourites,result detail panel]–要添加到此面板的子组件数组。由于我们使用的是“卡片”布局,它将一次显示一个子组件。我们的主面板中有四个子面板:searchPanel、tabResultPanel、favourites 和 resultDetailPanel。searchPanel 和 tabResultPanel 的声明方式与 mainPanel 相同。默认情况下,searchPanel 是可见的卡片,而其他面板隐藏在 searchPanel 后面
      4. docket items:[]–用于声明停靠的小部件,通常用于工具栏按钮。
      5. dockedItems 内部有一个由 JSON 表示声明的工具栏。这个工具栏有两个按钮和一个分隔它们的间隔。
        1. 对于主页按钮,我们使用图标:“主页”
        2. 对于最喜欢的按钮,我们使用图标:“星形”

      对于这两个按钮,我们都声明了一个处理程序,当单击按钮时会调用这个处理程序。

      `//Main Panel with CardLayout
      var mainPanel = new Ext.Panel({
          layout: 'card',
          fullscreen: true,
          items: [searchPanel, tabResultPanel, favorites, resultDetailPanel],
          dockedItems: [{
              xtype: 'toolbar',
              title: 'Local Search',
              dock: 'top',
              items: [{
                  iconMask: true,
                  ui: 'round',
                  iconCls: 'home',
                  handler: function () {

      }

      }, {             xtype: 'spacer'
              }, {
                  iconMask: true,
                  ui: 'round',
                  iconCls: 'star',
                  handler: function () {}

      }]
          }]
      });`

      在没有任何子部件的情况下,图 5-3 显示了该面板的外观。

      images

      图 5-3。 带工具栏按钮的主应用面板

      接下来,我们声明搜索面板。搜索面板有一个文本框,用户可以在其中输入搜索关键字,并有一个范围选择器,允许用户选择他/她的搜索范围。最后,搜索面板有一个带有搜索按钮的工具栏。声明如下:

      **var searchPanel = new Ext.form.FormPanel({** **    layout: 'fit',** **    fullscreen: true,** **    scroll: 'vertical',** **    standardSubmit: false,** **    //Adding form field** **    items: [{** `**        xtype: 'fieldset',**
      **        title: 'Local Search',**
      **        items: [{**
      **            xtype: 'textfield',**
      **            name: 'search',**
      **            label: ‘Search',**
      **            value: ‘Pizza',**
      **            userClearIcon: true,**
      **            autoCapitalize: false**
      **        }, {**
      **            xtype: 'sliderfield',**
      **            name: 'range',**
      **            label: 'Range (0-10 Kms)',**
      **            value: 5,**
      **            minValue: 0,**
      **            maxValue: 10**
      **        }]**

      **    }],**
      **    //Docking a toolbar at bottom**
      **    dockedItems: [{**
      **        xtype: 'toolbar',**
      **        dock: 'bottom',**
      **        items: [{**
      **            xtype: 'spacer'**
      **        }, {**
      **            text: 'Search',**
      **            iconCls: 'search',**
      **            title: 'Search',**
      **            iconMask: true,**
      **            ui: 'confirm',**
      **            handler: function () {**

      **            }**
      **        }]**
      **    }]**
      });`

      图 5-4 显示了搜索面板的显示方式。

      images

      图 5-4。 应用搜索面板

      当用户进行搜索时,用户会看到两个视图。

      1. 显示搜索结果的列表视图
      2. 显示搜索结果的地图视图

      这两个视图都封装在选项卡面板中。我们将选项卡面板声明如下:

      `var tabResultPanel = new Ext.TabPanel({
      **    layout: 'fit',**
      **    tabBar: {**
      **        dock: 'bottom',**
      **        layout: {**
      **            pack: 'center'**
      **        }**
      **    },**
      **    items: [result, map],**

      });`

      没有任何子面板的选项卡面板如图 5-5 所示。请注意,我们在配置 JSON 中定义了将选项卡栏放置在两个选项卡“result”和“map”的底部。默认情况下,将选择“结果”选项卡。当我们创建“结果”和“映射”对象时,我们将为这两个选项卡定义标签和图标。

      images

      图 5-5。 搜索结果面板带标签

      现在,我们将看到当用户进行搜索时如何显示搜索结果。在本章的后面部分,我们将会谈到如何使用 AJAX 调用。

      出于本章的考虑,假设您有一个来自 Google place 服务器的 JSON,如下所示:

      {     "status": "OK",     "results": [{         "name": "Zaaffran Restaurant - BBQ and GRILL, Darling Harbour",         "vicinity": "Darling Drive, Darling Harbour, Sydney",         "types": ["restaurant", "food", "establishment"],         "geometry": {             "location": {                 "lat": -33.8712950,                 "lng": 151.1984770             }         },         "icon": "http://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png",         "reference": "CpQBiwAAANM1CkdWcBxiExHinloJpp7kX2D3nyb_D0qoQ_-RuBhq9cwJKYvU8- sRJUaXF4U2kET_OH3Oh3Yz4tf5_6gBgcsFAPyRappCrJ5WksvMkXrT5lA7q9U_S0ZI0u3mrsvTtXnTDMKlBMywE_ 5Yy6lbshqPIatWZ6QkPZBNdmkifyN3vM7H2vL- 300iY6EoartWuxIQNckbM0Bs4D946thThmKOsBoUCmGgFrtYgtO0CIUc79fQi3waO0w",         "id": "677679492a58049a7eae079e0890897eb953d79b"     }, {         "name": "Toros Restaurant Darling Harbour",         "vicinity": "Murray Street, Sydney", "types": ["restaurant", "food", "establishment"],         "geometry": {             "location": {                 "lat": -33.8714080,                 "lng": 151.1975410             }         },         "icon": "http://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png",         "reference": "CoQBdQAAALFujBuIMYXsG8Qlus2zSHeikZQNCsSbeII0-55zkhCiArbPkACXRU- CcLZbeKsXaBpoBNH5iyYJg6Nquct2LTE127X4CD1YtKpozmbjZpyCRFrJ_V5DI4IDGLCWeY_8NMxznbiqb9prR8m XJoAKv7jNz6KEMxAuGLRAXbi7G6CYEhBeR6Ur-x2ABlS3pKXsKXLvGhRWFzL3Q5TO0xe-gm_LJm9cgtzYJw",         "id": "aefbc59325ffd5f3e93d67932375d20d143289de"     }, {         "name": "Strike Bowling Bar Darling Harbour",         "vicinity": "Sydney",         "types": ["restaurant", "food", "establishment"],         "geometry": {             "location": {                 "lat": -33.8662990,                 "lng": 151.2016580             }         },         "icon": "http://maps.gstatic.com/mapfiles/place_api/icons/restaurant-71.png",         "reference": "CoQBeAAAAO-prCRp9Atcj_rvavsLyv- DnxbGkw8QyRZb6Srm6QHOcww6lqFhIs2c7Ie6fMg3PZ4PhicfJL7ZWlaHaLDTqmRisoTQQUn61WTcSXAAiCOzcm0 JDBnafqrskSpFtNUgzGAOx29WGnWSP44jmjtioIsJN9ik8yjK7UxP4buAmMPVEhBXPiCfHXk1CQ6XRuQhpztsGhQ U4U6-tWjTHcLSVzjbNxoiuihbaA",         "id": "0a4e24c365f4bd70080f99bb80153c5ba3faced8"     }     ...additional results...],     "html_attributions": ["Listings by \u003ca href=\"http://www.yellowpages.com.au/\"\u003eYellow Pages\u003c/a\u003e"] }

      现在我们已经看到了 Google places 结果的 JSON 结构,我们将创建面板来显示结果。在本例中,我们扩展了一个组件,并在组件中声明了一个模板(tpl)。

      模板化是 Sencha Touch 的一个特性,你可以在标签中声明一个 html。在我们的例子中,我们传递上述 JSON 的结果对象。结果对象实际上是一个数组。在我们的模板代码中,请注意。这是在告诉 Sencha 模板引擎迭代结果中的所有对象。

      在 html 的后面部分,您会注意到占位符,如{reference}、{icon}、{name}等。如果你们中的任何一个人使用过 java 的消息格式,你会注意到这是非常相似的。这些{}条目将被 JSON 中相应的数据替换。

      {name}将被结果->条目->名称中的名称替换。

      为了用数据填充这个面板,我们将调用以下 API:

      //This will call the template engine and draw the AJAX's response     //result. Here ‘result' is the Component object to show the results     //HTML and response.results is the JSON array. result.update(response.results);

      现在,让我们看看用来创建结果面板的代码。

      `var result = new Ext.Component({

      **    title: 'Search Result',**
      **    iconMask: true,**
      **    iconCls: 'organize',**
      **    cls: 'timeline',**
      **    scroll: 'vertical',**
      **    tpl: ['',**
      **          '

      ',**
      **          '
      <imgsrc="{icon}" />
      ',**
      **          '
      ', '

      {name}

      ',**
      **          '

      {vicinity}

      ',**
      **          '
      ',**
      **          '
      ',**
      **          '']**
      **    listeners: {**
      **        el: {**
      **            tap: detailClickHandler,**
      **            //function which**
      **            //will handle tap event**
      **        }**
      **    }**
      });`

      注意最后的听众和 el 部分。这告诉 Sencha Touch,我们有兴趣接收关于该组件元素的事件。此外,我们告诉它,我们专门寻找点击事件。这段代码的结果是,每当用户点击结果中列出的任何地方,它都会调用 detailClickHandler 函数。

      images

      图 5-6。 搜索结果面板

      Sencha Touch 的地图小工具让生活变得简单多了。否则,我们将不得不使用谷歌地图应用编程接口。我们简单地创建一个新的 Ext。映射并给它一些选项。这是制作地图最简单的方法。请注意,“地图”对象将在 AJAX 回调中使用,以在其上添加位置标记。AJAX 调用在“获取地点列表”中有描述。

      **var map = new Ext.Map({** **    iconMask: true,** **    iconCls: 'maps',** **    title: 'Map',** **    // Name that appears on this tab** **    mapOptions: {** **         // Used in rendering map** **        zoom: 12** **    }** **});** images

      图 5-7。地图面板显示地名

      接下来是面板,显示一个地方的细节。注意,一旦用户点击搜索条目,应用将从 Google places 服务器获取详细信息。该请求的 JSON 响应如下所示:

      {     "status": "OK",     "result": {         "name": "Google Sydney",         "vicinity": "Pirrama Road, Pyrmont",         "types": ["establishment"],         "formatted_phone_number": "(02) 9374 4000",         "formatted_address": "5/48 Pirrama Road, Pyrmont NSW, Australia",         "address_components": [{             "long_name": "48",             "short_name": "48",             "types": ["street_number"]         }, {             "long_name": "Pirrama Road",             "short_name": "Pirrama Road",             "types": ["route"]         }, {             "long_name": "Pyrmont",             "short_name": "Pyrmont",             "types": ["locality", "political"]         }, {             "long_name": "NSW",             "short_name": "NSW",             "types": ["administrative_area_level_1", "political"]         }, {             "long_name": "2009",             "short_name": "2009",             "types": ["postal_code"]         }],         "geometry": {             "location": {                 "lat": -33.8669710,                 "lng": 151.1958750             }         },         "rating": 4.5,         "url": "http://maps.google.com/maps/place?cid=10281119596374313554",         "icon": "http://maps.gstatic.com/mapfiles/place_api/icons/generic_business-71.png",         "reference": "CmRRAAAAUgylGnuntxKOuZy9_c5zxdFi6e491_Fv0m1hks5YkeaH7k1SP9ujAkG4GROr1XCHFnMsDhuEIgQQq2W Wyd33oGRAT8Vwr8rjTWEYEMvCZ1RxTzXSVDZ4gEFqLZcRyAw_EhBS8uZHidMMbYHuf9KHapRyGhQQ1dnf3uMghMR BlXqJE6ygh_a3ag",         "id": "4f89212bf76dde31f092cfc14d7506555d85b5c7"     },     "html_attributions": [] }

      这个想法是以表格的方式向用户显示上述信息。为此,我们将结合使用以下方法:

      1. 将上述 JSON 显示为表的一部分的模板
      2. 一个按钮,将允许用户添加这个地方作为收藏或删除它作为收藏。

      因此,我们使用一个名为 resultDetailPanel 的包装面板。这个面板有一个 vbox 布局(垂直堆叠部件)。第一个子元素是 placeDetailsPanel(见下),第二个是 button。

      该按钮的文本从“添加到收藏夹”变为“从收藏夹中移除”,这取决于用户是否已经将该位置设为收藏夹。应用中为同一定义了一个名为 isFav()的函数。

      **var resultDetailPanel = new Ext.Panel({** **    layout: {** **        type: 'vbox',** **    },** **    items: [** **    placeDetailsPanel,** **    {** **        xtype: 'button',** **        text: 'Add to Favorite',** **        handler: function (button, event) {** **            if (button.text == "Add to Favorite") {** **                addCurrentToFav();** **                button.setText("Remove from Favorite");** **            } else {** **                removeCurrentFromFav();** `**                button.setText("Add to Favorite");**
      **            }**

      **        }**

      **    }],**
      **    dockedItems: [{**
      **        xtype: 'toolbar',**
      **        dock: 'bottom',**
      **        items: [{**
      **            ui: 'round',**
      **            text: 'Back',**
      **            handler: function () {}**

      **        }]**
      **    }]**

      });`

      这是以表格形式显示 JSON 结果的面板。它使用相同的模板。模板是 html 模板,它有由{ < >}表示的占位符。使用模板代码,占位符由实际值替换:

      **var placeDetailsPanel = new Ext.Panel({** **    tpl: ['<table>',** **          '<tr>',** **          '<td>',** **          '</td>',** **          '<td>',** **          '<h1 class="bold">Business Details</h1>',** **          '</td>',** **          '</tr>',** **          '<tr>',** **          '<td>',** **          '<h1 class="bold">Name</h1>',** **          '</td>',** **          '<td>',** **          '<h1>{name}</h1>',** **          '</td>',** **          '</tr>',** **          '<tr>',** **          '<td>',** **          '<h1 class="bold">Address</h1>',** **          '</td>',** **          '<td>',** **          '<h1>{formatted_address}</h1>',** **          '</td>',** **          '</tr>',** **          '<tr>',** **          '<td>',** **          '<h1 class="bold">Phone</h1>',** **          '</td>',** `**          '',**
      **          '

      {formatted_phone_number}

      ',**
      **          '',**
      **          '',**
      **          '',**
      **          '',**
      **          '

      Rating

      ',**
      **          '',**
      **          '',**
      **          '

      {rating}

      ',**
      **          '',**
      **          '',**
      **          '',**
      **          '',**
      **          '

      Home Page

      ',**
      **          '',**
      **          '',**
      **          'Home Page',**
      **          '',**
      **          '',**
      **          ''**

      **    ]**
      });`

      位置细节面板如图 5-8 中的所示。

      images

      图 5-8。 地点详情面板

      现在我们已经了解了如何在收藏夹中添加或删除地点,让我们来看看列出用户收藏地点的面板。是的,这个面板与结果面板非常相似。唯一的区别是结果面板被赋予了来自 Google places 服务器的 JSON,而收藏夹面板被赋予了来自数据库的 JSON。

      `var favorites = new Ext.Component({
      **    title: 'Favotites',**
      **    iconMask: true,**
      **    iconCls: 'organize',**

      **    cls: 'timeline',**
      **    scroll: 'vertical',**
      **    tpl: ['',**
      **          '

      ',**
      **          '
      <imgsrc="{icon}" />
      ',**
      **          '
      ',**
      **          '

      {name}

      ',**
      **          '

      {vicinity}

      ',**
      **          '
      ',**
      **          '
      ',**
      **          ''],**
      **    listeners: {**
      **        el: {**
      **            tap: detailClickHandler,**
      **        }**
      **    }**
      });` images

      图 5-9。 最喜欢的地方面板

      面板之间的切换

      随着您探索更多的应用代码,您将需要从一个面板切换到另一个面板。这是我们在主面板上使用“卡片”布局的主要原因。

      为了在卡片布局中从一个面板切换到另一个面板,我们将使用下面的代码。注意 mainPanel 的 items 中提供的小部件的第一个参数索引号。第二个论点是动画效果。

      mainPanel.setActiveItem(0, "slide"); mainPanel.setActiveItem(1, {type: 'slide', direction: 'right'});

      此外,主面板拥有工具栏。我们需要改变这个工具栏的标题,让用户知道他在哪里。这是使用以下代码完成的:

      mainPanel.dockedItems.items[0].setTitle('Details');

      获取地点列表

      当用户点击搜索面板上的搜索按钮时,我们需要调用 Ajax 来获取 Google places 的结果。下面的函数展示了如何在 Sencha Touch 和 PhoneGap 中进行同样的操作。

      步骤很简单。

      1. 从 PhoneGap 获取地理位置
      2. 在 getcurrentPosition 的成功调用中,通过调用 Ext.ajax.request(url,successCallback,failureCallback)来发起 Ajax 调用
      3. 在 Ext.ajax.request 的 successCallback 中,会得到 JSON 字符串。
        1. 将这个 JSON 字符串转换成 JSON 对象
        2. 通过调用 result.update(obj.results)填充结果面板;
        3. 在谷歌地图上填充市场

      `var fetchFromGoogle = function () {

      **        var keyword = searchPanel.items.items[0].items.items[0].value;**
      **        var range = searchPanel.items.items[0].items.items[1].value * 1000;**
      **        navigator.geolocation.getCurrentPosition(**

      **        function (position) {**
      **            var lat = position.coords.latitude;**
      **            var lng = position.coords.longitude;**

      **            map.update({**
      **                latitude: lat,**
      **                longitude: lng**
      **            });**

      **            var googlePlaceUrl = 'https://maps.googleapis.com/maps/api/place/search/json?location='**
      **                  + lat + ',' + lng + '&radius=' + range + '&types=food&name=' + keyword + '&sensor=true&key=API_Key';**
      **            //Note that you will need to replace the API_Key with your own key. You //can get API Key from    **
      **            //http://code.google.com/apis/maps/documentation/places/**
      **            Ext.Ajax.request({**
      **                url: googlePlaceUrl,**
      **                success: function (response, opts) {**

      **                    var obj = Ext.decode(response.responseText);**

      **                    result.update(obj.results);**
      **                    var data = obj.results;**
      **                    for (var i = 0, ln = data.length; i < ln; i++) { // Loop to add points to the map**
      **                        var place = data[i];**

      **                        if (place.geometry && place.geometry.location) {**
      **                            var position = new google.maps.LatLng(place.geometry.location.lat, place.geometry.location.lng);**
      **                            addMarker(place.name, place.reference, position); // Call addMarker function with new data**
      **                        }                     }**

      **                },**
      **                failure: function (response, opts) {**
      **                    console.log('server-side failure with status code ' + response.status);**

      **                }**
      **            }, function (err) {**
      **                console.log('Failed to get geo location from phonegap ' + err);**
      **            });**
      **        })**
      **    }**`

      取件地点详情

      从 Google places 服务器获取地点信息甚至更容易。你需要的东西甚至更少。

      1. 通过调用 Ext.ajax.request(url,successCallback,failureCallback)启动 Ajax 调用
      2. 在 Ext.ajax.request 的 successCallback 中,会得到 JSON 字符串。
        1. 将这个 JSON 字符串转换成 JSON 对象
        2. 通过调用 placedetailspanel . update(obj . result)填充结果面板;
        3. 此外,我们将通过调用 isFav()函数来检查这个地方是否是收藏夹
          1. 如果这个地方是一个收藏,我们将这个按钮重命名为“从收藏中删除”
          2. 否则,我们将该按钮重命名为“添加到收藏夹”

      `var cachedDetails = null;

      /**
      ** * Ensure we have the table before we use it**
      ** * @param {Object} tx**
      ** /*
      var ensureTableExists = function (tx) {
      **    tx.executeSql('CREATE TABLE IF NOT EXISTS Favourite (id unique, reference,**
      **                          name,address,phone,rating,icon,vicinity)');**
      }

      /**
      ** * Add currentDetails to DB**
      ** /*
      var addCurrentToFav = function () {
      **    addToFavorite(cachedDetails);**
      }

      /****  * Remove currentDetails from DB**
      ** /*
      var removeCurrentFromFav = function () {
      **    removeFromFavorite(cachedDetails);**
      }

      /**
      ** * Add current business data to favourite**
      ** * @param {Object} data**
      ** /*
      var addToFavorite = function (data) {
      **    var db = window.openDatabase("Favourites", "1.0", "Favourites", 20000000);**

      **    db.transaction(function (tx) {**

      **        ensureTableExists(tx);**

      **        var id = (data.id != null) ? ('"' + data.id + '"') : ('""');**
      **        var reference = (data.reference != null) ? ('"' + data.reference + '"') : ('""');**
      **        var name = (data.name != null) ? ('"' + data.name + '"') : ('""');**
      **        var address = (data.formatted_address != null) ? ('"' + data.formatted_address + '"') : ('""');**
      **        var phone = (data.formatted_phone_number != null) ? ('"' + data.formatted_phone_number + '"') : ('""');**
      **        var rating = (data.rating != null) ? ('"' + data.rating + '"') : ('""');**
      **        var icon = (data.icon != null) ? ('"' + data.icon + '"') : ('""');**
      **        var vicinity = (data.vicinity != null) ? ('"' + data.vicinity + '"') : ('""');**

      **        var insertStmt = 'INSERT INTO Favourite (id,reference,**
      **                          name,address,phone,rating,icon,vicinity) VALUES**
      **                          (' + id + ',' + reference + ',' + name + ',' + address + ','**
      **                         + phone + ',' + rating + ',' + icon + ',' + vicinity + ')';**

      **        tx.executeSql(insertStmt);**

      **    }, function (error) {**
      **       console.log("Data insert failed " + error.code + "   " + error.message);**
      **    }, function () {**
      **        console.log("Data insert successful");**
      **    });**

      }

      /**
      ** * Remove current business data from favourite**
      ** * @param {Object} data**
      ** /*
      var removeFromFavorite = function (data) {
      **    try {**
      **        var db = window.openDatabase("Favourites", "1.0", "Favourites", 20000000);         db.transaction(function (tx) {**
      **            ensureTableExists(tx);**
      **            var deleteStmt = "DELETE FROM Favourite WHERE id = '" + data.id + "'";**
      **            console.log(deleteStmt);**
      **            tx.executeSql(deleteStmt);**

      **        }, function (error) {**
      **            console.log("Data Delete failed " + error.code + "   " + error.message);**
      **        }, function () {**
      **            console.log("Data Delete successful");**
      **        });**

      **    } catch (err) {**
      **        console.log("Caught exception while deleting favourite " + data.name);**
      **    }**

      }

      /**
      ** ***
      ** * @param {Object} reference**
      ** * @return true if place is favourite else false**
      ** /*
      var isFav = function (data, callback) {

      **    var db = window.openDatabase("Favourites", "1.0", "Favourites", 200000);**

      **    try {**
      **        db.transaction(function (tx) {**
      **            ensureTableExists(tx);**

      **            var sql = "SELECT * FROM Favourite where id='" + data.id + "'";**

      **            tx.executeSql(sql, [], function (tx, results) {**
      **                var result = (results != null && results.rows != null && results.rows.length > 0);**
      **                callback(result);**
      **            }, function (tx, error) {**
      **                var fetchDetails = function (reference) {**
      **                    placeDetailsPanel.update({**
      **                        name: "",**
      **                        formatted_address: "",**
      **                        formatted_phone_number: "",**
      **                        rating: "",**
      **                        url: ""**
      **                    });**
      **                    Ext.Ajax.request({**
      **                        url: 'https://maps.googleapis.com/maps/api/place/details/json?reference='**
      **                            + reference + '&sensor=true&key=API_Key',**
      **                        success: function (response, opts) {**
      **                            var obj = Ext.decode(response.responseText);**
      **                            //global variable to store the current place                                 cachedDetails = obj.result;**
      **                                isFav(obj.result, function (result) {**
      **                                    if (result) {**

      **                                        resultDetailPanel.items.items[1].setText("Remove from Favorite");**
      **                                    } else {**

      **                                        resultDetailPanel.items.items[1].setText("Add to Favorite");**
      **                                    }**
      **                                    placeDetailsPanel.update(obj.result);**
      **                                });**

      **                            },**
      **                            failure: function (response, opts) {**
      **                                console.log('server-side failure with status code ' + response.status);**
      **                            }**
      **                        })**
      **                    }**

      **                    console.log("Got error in isFaverror.code =" + error.code + "**
      **                          error.message = " + error.message);**
      **                    callback(false);**
      **                });**
      **            });**

      **        } catch (err) {**
      **            console.log("Got error in isFav " + err);**
      **            callback(false);**
      **        }**
      **    }**`

      从数据库中存储和检索收藏夹

      该应用的最后一个重要部分是定义函数来完成以下任务:

      1. 向收藏表添加位置
      2. 从收藏夹表中移除一个位置
      3. 检查某个位置是否已经在收藏表中
      4. 从收藏夹表中获取所有条目。

      为了在各种函数调用之间传递位置条目,我们在该应用中所做的是声明一个名为 cachedDetails 的变量。当我们在显示地点细节的页面中时,我们在 cachedDetails 中缓存当前地点。cachedDetails 用于将一个位置添加到收藏夹,将其从收藏夹中删除,以及检查它是否已经是用户收藏夹的一部分。

      `var cachedDetails = null;

      /**
      ** * Ensure we have the table before we use it**
      ** * @param {Object} tx**
      ** /*
      var ensureTableExists = function (tx) {
      **    tx.executeSql('CREATE TABLE IF NOT EXISTS Favourite (id unique, reference,**
      **                          name,address,phone,rating,icon,vicinity)');**
      }

      /**
      ** * Add currentDetails to DB**
      ** /*
      var addCurrentToFav = function () {
      **    addToFavorite(cachedDetails);**
      }

      /**
      ** * Remove currentDetails from DB**
      ** /*
      var removeCurrentFromFav = function () {
      **    removeFromFavorite(cachedDetails);**
      }

      /**
      ** * Add current business data to favourite**
      ** * @param {Object} data**
      ** /*
      var addToFavorite = function (data) {
      **    var db = window.openDatabase("Favourites", "1.0", "Favourites", 20000000);**

      **    db.transaction(function (tx) {**

      **        ensureTableExists(tx);**

      **        var id = (data.id != null) ? ('"' + data.id + '"') : ('""');**
      **        var reference = (data.reference != null) ? ('"' + data.reference + '"') : ('""');**
      **        var name = (data.name != null) ? ('"' + data.name + '"') : ('""');**
      **        var address = (data.formatted_address != null) ? ('"' + data.formatted_address + '"') : ('""');**
      **        var phone = (data.formatted_phone_number != null) ? ('"' + data.formatted_phone_number + '"') : ('""');**
      **        var rating = (data.rating != null) ? ('"' + data.rating + '"') : ('""');**
      **        var icon = (data.icon != null) ? ('"' + data.icon + '"') : ('""');**
      **        var vicinity = (data.vicinity != null) ? ('"' + data.vicinity + '"') : ('""');**

      **        var insertStmt = 'INSERT INTO Favourite (id,reference,**
      **                          name,address,phone,rating,icon,vicinity) VALUES**
      **                          (' + id + ',' + reference + ',' + name + ',' + address + ',' + phone**
      **                          + ',' + rating + ',' + icon + ',' + vicinity + ')';         tx.executeSql(insertStmt);**

      **    }, function (error) {**
      **        console.log("Data insert failed " + error.code + "   " + error.message);**
      **    }, function () {**
      **        console.log("Data insert successful");**
      **    });**

      }

      /**
      ** * Remove current business data from favourite**
      ** * @param {Object} data**
      ** /*
      var removeFromFavorite = function (data) {
      **    try {**
      **        var db = window.openDatabase("Favourites", "1.0", "Favourites", 20000000);**

      **        db.transaction(function (tx) {**
      **            ensureTableExists(tx);**
      **            var deleteStmt = "DELETE FROM Favourite WHERE id = '" + data.id + "'";**
      **            console.log(deleteStmt);**
      **            tx.executeSql(deleteStmt);**

      **        }, function (error) {**
      **            console.log("Data Delete failed " + error.code + "   " + error.message);**
      **        }, function () {**
      **            console.log("Data Delete successful");**
      **        });**

      **    } catch (err) {**
      **        console.log("Caught exception while deleting favourite " + data.name);**
      **    }**

      }

      /**
      ** ***
      ** * @param {Object} reference**
      ** * @return true if place is favourite else false**
      ** /*
      var isFav = function (data, callback) {

      **    var db = window.openDatabase("Favourites", "1.0", "Favourites", 200000);**

      **    try {**
      **        db.transaction(function (tx) {**
      **            ensureTableExists(tx);**

      **            var sql = "SELECT * FROM Favourite where id='" + data.id + "'";             tx.executeSql(sql, [],**
      **                function (tx, results) {**
      **                    var result = (results != null && results.rows != null && results.rows.length > 0);**
      **                    callback(result);**
      **                },**
      **                function (tx, error) {**
      **                    console.log("Got error in isFaverror.code =" + error.code + "**
      **                          error.message = " + error.message);**
      **                    callback(false);**
      **                });**
      **            });**

      **    } catch (err) {**
      **        console.log("Got error in isFav " + err);**
      **        callback(false);**
      **    }**
      }`

      这包括应用的布局部分,如何从 Google places 服务器填充数据,以及如何导航和使用数据库。

      请参考下面列出的完整示例,以了解事件是如何处理的。

      1。index.html

      `

                   Sencha Touch Layout                                                             `
      2。app.js

      `Ext.setup({
          tabletStartupScreen: 'tablet_startup.png',
          phoneStartupScreen: 'phone_startup.png',
          icon: 'icon.png',
          glossOnIcon: false,
          onReady: function () {
              var lastPanelId = 0;

      var SEARCHPAGE = 0;
              var TABPAGE = 1;
              var FAVPAGE = 2;
              var DETAILSPAGE = 3;

      var cachedDetails = null;

      var searchPanel = newExt.form.FormPanel({
                  layout: 'fit',
                  fullscreen: true,
                  scroll: 'vertical',
                  standardSubmit: false,
                  //Adding form field
                  items: [{
                      xtype: 'fieldset',
                      title: 'Local Search',
                      items: [{
                          xtype: 'textfield',
                          name: 'search',
                          label: 'Search',
                          value: 'Pizza',
                          useClearIcon: true,
                          autoCapitalize: false
                      }, {
                          xtype: 'sliderfield',
                          name: 'range',
                          label: 'Range (0-10 Kms)',
                          value: 5,
                          minValue: 0,
                          maxValue: 10
                      }]
                  }] //Docking a toolbar at bottom dockedItems: [{
                      xtype: 'toolbar',
                      dock: 'bottom',
                      items: [{
                          xtype: 'spacer'
                      }, {
                          text: 'Search',
                          iconCls: 'search',
                          title: 'Search',
                          iconMask: true,
                          ui: 'round',
                          ui: 'confirm',
                          handler: function () {
                              lastPanelId = TABPAGE;
                              fetchFromGoogle();

      mainPanel.dockedItems.items[0].setTitle('Search Results');
                              mainPanel.setActiveItem(lastPanelId);
                          }
                      }]
                  }]
              });

      var detailClickHandler = function (event) {
                      var reference = event.getTarget(".place").id;
                      fetchDetails(reference);
                      mainPanel.dockedItems.items[0].setTitle('Details');
                      mainPanel.setActiveItem(DETAILSPAGE, "slide");
                  }

      var result = new Ext.Component({

      title: 'Search Result',
                  iconMask: true,
                  iconCls: 'organize',
                  cls: 'timeline',
                  scroll: 'vertical',
                  tpl: ['',
                        '

      ',
                        '
      <imgsrc="{icon}" />
      ',
                        '
      ',
                        '

      {name}

      ',

      '

      {vicinity}

      ', '
      ', '
      ', ''

      ],
                  listeners: {
                      el: {
                          tap: detailClickHandler,
                          delegate: '.place'

      }
                  }

      });

      var favorites = new Ext.Component({ title: 'Favotites',
                  iconMask: true,
                  iconCls: 'organize',

      cls: 'timeline',
                  scroll: 'vertical',
                  tpl: ['',
                        '

      ',
                        '
      <imgsrc="{icon}" />
      ',
                        '
      ',
                        '

      {name}

      ',
                        '

      {vicinity}

      ',
                        '
      ',
                        '
      ',
                        ''],
                  listeners: {
                      el: {
                          tap: detailClickHandler,
                          delegate: '.place'

      }
                  }

      });

      var map = new Ext.Map({
                  iconMask: true,
                  iconCls: 'maps',
                  title: 'Map',
                  // Name that appears on this tab
                  fullscreen: true,
                  mapOptions: { // Used in rendering map
                      zoom: 12
                  }
              });

      var tabResultPanel = new Ext.TabPanel({
                  layout: 'fit',
                  tabBar: {
                      dock: 'bottom',
                      layout: {
                          pack: 'center'
                      }
                  },
                  items: [result, map],

      });

      var placeDetailsPanel = new Ext.Panel({
                  //layout: 'fit',
                  tpl: ['

      ',
                        '',
                        '',
                        '',
                        '',
                        '',
                        '',
                        '',
                        '',
                        '',
                        '',
                        '',
                        '',
                        '',
                        '',
                        '',
                        '',
                        '',
                        '',
                        '',
                        '',
                        '',
                        '',
                        '',
                        '',
                        '
      ',
                        '
      ',
                        '

      Business Details

      ', '
      ',
                        '

      Name

      ',
                        '
      ',
                        '

      {name}

      ',
                        '
      ',
                        '

      Address

      ',
                        '
      ',
                        '

      {formatted_address}

      ',
                        '
      ',
                        '

      Phone

      ',
                        '
      ',
                        '

      {formatted_phone_number}

      ',
                        '
      ',
                        '

      Rating

      ',
                        '
      ',
                        '

      {rating}

      ',
                        '
      ',
                        '

      Home Page

      ',
                        '
      ',
                        'Home Page',
                        '
      '

      ]
              });

      var resultDetailPanel = new Ext.Panel({
                  layout: {
                      type: 'vbox',
                  },
                  items: [
                  placeDetailsPanel,
                  {
                      xtype: 'button',
                      text: 'Add to Favorite',
                      handler: function (button, event) { if (button.text == "Add to Favorite") {
                              addCurrentToFav();
                              button.setText("Remove from Favorite");
                          } else {
                              removeCurrentFromFav();
                              button.setText("Add to Favorite");
                          }

      }

      }],
                  dockedItems: [{
                      xtype: 'toolbar',
                      dock: 'bottom',
                      items: [{
                          ui: 'round',
                          text: 'Back',
                          handler: function () {

      if (lastPanelId == 0) {
                                  mainPanel.dockedItems.items[0].setTitle('Home Page');
                              } else if (lastPanelId == 1) {
                                  mainPanel.dockedItems.items[0].setTitle('Search Results');
                              } else if (lastPanelId == 2) {
                                  fetchFromDB();
                                  mainPanel.dockedItems.items[0].setTitle('Favourites');
                              } else if (lastPanelId == 3) {
                                  //Shouldn't happen
                                  mainPanel.dockedItems.items[0].setTitle('Details');
                              }

      mainPanel.setActiveItem(lastPanelId, {
                                  type: 'slide',
                                  direction: 'right'
                              });
                          }

      }]
                  }]

      });

      //Main Panel with CardLayout
              var mainPanel = new Ext.Panel({
                  layout: 'card',
                  fullscreen: true,
                  items: [searchPanel, tabResultPanel, favorites, resultDetailPanel],
                  dockedItems: [{
                      xtype: 'toolbar',
                      title: 'Local Search',
                      dock: 'top',
                      items: [{

      iconMask: true,
                          ui: 'round',
                          iconCls: 'home',
                          handler: function () { lastPanelId = SEARCHPAGE;

      mainPanel.dockedItems.items[0].setTitle('Home Page');
                              mainPanel.setActiveItem(lastPanelId, "slide");
                          }

      }, {
                          xtype: 'spacer'
                      }, {

      iconMask: true,
                          ui: 'round',
                          iconCls: 'star',
                          handler: function () {
                              fetchFromDB();
                              lastPanelId = FAVPAGE;
                              mainPanel.dockedItems.items[0].setTitle('Favourites');
                              mainPanel.setActiveItem(lastPanelId, "slide");
                          }

      }]
                  }]
              });

      // These are all Google Maps APIs
              var addMarker = function (name, reference, position) {

      var marker = new google.maps.Marker({
                          map: map.map,
                          position: position,
                          clickable: true,
                          optimized: true,
                          title: name
                      });
                      google.maps.event.addListener(marker, 'click', function () {
                          fetchDetails(reference);

      mainPanel.dockedItems.items[0].setTitle('Details');
                          mainPanel.setActiveItem(DETAILSPAGE, "slide");

      });

      };

      var fetchFromGoogle = function () {

      var keyword = searchPanel.items.items[0].items.items[0].value;
                      var range = searchPanel.items.items[0].items.items[1].value * 1000;
                      navigator.geolocation.getCurrentPosition(

      function (position) {
                          var lat = position.coords.latitude;
                          var lng = position.coords.longitude; map.update({
                              latitude: lat,
                              longitude: lng
                          });

      var googlePlaceUrl = 'https://maps.googleapis.com/maps/api/place/search/json?location='
                               + lat + ',' + lng + '&radius=' + range + '&types=food&name=' + keyword + '&sensor=true&key=API_Key';
                          //Note that you will need to replace the API_Key with your own key. You
                          //can get API Key from //http://code.google.com/apis/maps/documentation/places/
                          Ext.Ajax.request({
                              url: googlePlaceUrl,
                              success: function (response, opts) {

      var obj = Ext.decode(response.responseText);

      result.update(obj.results);
                                  var data = obj.results;
                                  for (var i = 0, ln = data.length; i < ln; i++) { // Loop to add points to the map
                                      var place = data[i];

      if (place.geometry && place.geometry.location) {
                                          var position = new google.maps.LatLng(place.geometry.location.lat, place.geometry.location.lng);

      addMarker(place.name, place.reference, position); // Call addMarker function with new data
                                      }
                                  }

      },
                              failure: function (response, opts) {
                                  console.log('server-side failure with status code ' + response.status);

      }
                          }, function (err) {
                              console.log('Failed to get geo location from phonegap ' + err);
                          });
                      })
                  }

      var fetchFromDB = function () {
                      var db = window.openDatabase("Favourites", "1.0", "Favourites", 200000);
                      try {
                          db.transaction(function (tx) {
                              tx.executeSql('SELECT * FROM Favourite', [], function (tx, results) {
                                  var arr = [];
                                  for (var i = 0; i < results.rows.length; i++) { var data = results.rows.item(i)
                                      arr[i] = data;

      }

      favorites.update(arr);

      }, function (error) {
                                  console.log("Got error fetching favourites " + error.code + " " + error.message);
                              });
                          });
                      } catch (err) {
                          console.log("Got error while reading favourites " + err);
                      }

      }

      var fetchDetails = function (reference) {
                      placeDetailsPanel.update({
                          name: "",
                          formatted_address: "",
                          formatted_phone_number: "",
                          rating: "",
                          url: ""
                      });
                      Ext.Ajax.request({
                          url: 'https://maps.googleapis.com/maps/api/place/details/json?reference=' + reference + '&sensor=true&key=API_Key',
                          success: function (response, opts) {
                              var obj = Ext.decode(response.responseText);
                              cachedDetails = obj.result;
                              isFav(obj.result, function (result) {
                                  if (result) {

      resultDetailPanel.items.items[1].setText("Remove from Favorite");
                                  } else {

      resultDetailPanel.items.items[1].setText("Add to Favorite");
                                  }
                                  placeDetailsPanel.update(obj.result);
                              });

      },
                          failure: function (response, opts) {
                              console.log('server-side failure with status code ' + response.status);
                          }
                      })
                  } /**
                   * Ensure we have the table before we use it
                   * @param {Object} tx
                   */
              var ensureTableExists = function (tx) {
                      tx.executeSql('CREATE TABLE IF NOT EXISTS Favourite (id unique, reference, name,address,phone,rating,icon,vicinity)');
                  }

      var addCurrentToFav = function () {
                      addToFavorite(cachedDetails);
                  }

      var removeCurrentFromFav = function () {
                      removeFromFavorite(cachedDetails);
                  }

      /**
                   * Add current business data to favourite
                   * @param {Object} data
                   */
              var addToFavorite = function (data) {
                      var db = window.openDatabase("Favourites", "1.0", "Favourites", 20000000);

      db.transaction(function (tx) {
                          ensureTableExists(tx);
                          var id = (data.id != null) ? ('"' + data.id + '"') : ('""');
                          var reference = (data.reference != null) ? ('"' + data.reference + '"') : ('""');
                          var name = (data.name != null) ? ('"' + data.name + '"') : ('""');
                          var address = (data.formatted_address != null) ? ('"' + data.formatted_address + '"') : ('""');
                          var phone = (data.formatted_phone_number != null) ? ('"' + data.formatted_phone_number + '"') : ('""');
                          var rating = (data.rating != null) ? ('"' + data.rating + '"') : ('""');
                          var icon = (data.icon != null) ? ('"' + data.icon + '"') : ('""');
                          var vicinity = (data.vicinity != null) ? ('"' + data.vicinity + '"') : ('""');
                          var insertStmt = 'INSERT INTO Favourite (id,reference, name,address,phone,rating,icon,vicinity) VALUES (' + id
                             + ',' + reference + ',' + name + ',' + address + ',' + phone + ',' + rating + ',' + icon + ',' + vicinity + ')';
                          tx.executeSql(insertStmt);

      }, function (error) {
                          console.log("Data insert failed " + error.code + "   " + error.message);

      }, function () {
                          console.log("Data insert successful");

      });

      } /**
                   * Remove current business data from favourite
                   * @param {Object} data
                   */
              var removeFromFavorite = function (data) {
                      try {
                          var db = window.openDatabase("Favourites", "1.0", "Favourites", 20000000);

      db.transaction(function (tx) {
                              ensureTableExists(tx);
                              var deleteStmt = "DELETE FROM Favourite WHERE id = '" + data.id + "'";
                              console.log(deleteStmt);
                              tx.executeSql(deleteStmt);

      }, function (error) {
                              console.log("Data Delete failed " + error.code + "   " + error.message);
                          }, function () {
                              console.log("Data Delete successful");
                          });
                      } catch (err) {
                          console.log("Caught exception while deleting favourite " + data.name);
                      }

      }

      /**
              *
              * @param {Object} reference
              * @return true if place is favourite else false
              */
              var isFav = function (data, callback) {

      var db = window.openDatabase("Favourites", "1.0", "Favourites", 200000);

      try {
                          db.transaction(function (tx) {
                              ensureTableExists(tx);

      var sql = "SELECT * FROM Favourite where id='" + data.id + "'";

      tx.executeSql(sql, [], function (tx, results) {
                                  var result = (results != null && results.rows != null && results.rows.length > 0);

      callback(result);
                              }, function (tx, error) {
                                  console.log("Got error in isFaverror.code =" + error.code + " error.message = " + error.message);
                                  callback(false);
                              });
                          });

      } catch (err) {
                          console.log("Got error in isFav " + err);
                          callback(false);                 }

      }

      }
      });`

      结论

      如果你正在构建一个相当复杂的移动应用,你应该使用 Sencha Touch。jQueryMobile 适用于较小的、不太复杂的 Ajax 应用。虽然 jQueryMobile 可以用于更复杂的应用,但是您必须自己处理 DOM,这样事情会变得更复杂。

      Sencha Touch 性能不错,widget 集丰富。它的一些小部件使用数据存储来与服务器组件对话。你可以在 Sencha Touch 中使用 mvc 设计模式,甚至可以将你的应用分成几个部分。用于改进模块化代码的 js 文件。

      六、在 GWT 中使用 PhoneGap

      Google Web toolkit (GWT)是 Google 提供的一个框架,可以用来开发基于浏览器的应用。GWT 允许开发人员用 Java 编写代码并生成基于 JavaScript 的应用。

      GWT 应用本质上是跨浏览器兼容的,它们是最小和最快的基于浏览器的应用。

      本章将重点介绍如何使用 PhoneGap 为手机开发一个 GWT 应用。这些步骤基于 Daniel Kurka 开发的 GWT PhoneGap 库。你可以从[code.google.com/p/gwt-phonegap/](http://code.google.com/p/gwt-phonegap/).下载这个库

      如何开发基于 GWT 的应用的知识是必不可少的。如果你是 GWT 应用的新手,你可以访问[code.google.com/webtoolkit/doc/latest/tutorial/index.html](http://code.google.com/webtoolkit/doc/latest/tutorial/index.html)来了解更多关于 GWT 的信息。

      为什么使用 GWT 进行用户界面开发?

      在我们开始讨论如何一起使用 GWT 和 PhoneGap 之前,让我们首先考虑一下为什么 GWT 是用户界面开发的最佳选择:

      • GWT 允许开发人员编写基于浏览器的应用,而不必担心跨浏览器问题、JavaScript 中的内存泄漏以及 JavaScript 语言本身。
      • GWT 允许开发人员用 Java 编写代码,并将用 Java 编写的用户界面和业务逻辑编译成 JavaScript。
      • GWT 还允许您使用诸如延迟绑定之类的概念(这就像 JavaScript 世界的运行时多态性)。这种方法允许开发人员创建一个应用,该应用可以使用不同的类为移动浏览器提供服务,并使用另一组类为桌面浏览器提供服务。
      • GWT 确保您可以创建最小和最快的 JavaScripts。
      • GWT 是一项被许多公司和大部分开发者社区广泛接受的技术。GWT 正在成为大型、复杂的基于 Ajax 的应用的事实上的选择。

      除了上述优势,许多轻量级、现成的小部件对于 GWT 来说都是现成的。此外,还有专业的 GWT 库,像 EXT-GWT 和 Smart-GWT,它们使用户界面看起来非常专业和完美。想象一下,您的 Java 开发人员使用他们现有的 Java 技能,按照最佳设计实践轻松编写基于浏览器的应用。GWT 消除了编写基于浏览器的应用的痛苦,同时提供了最好的浏览器应用。

      了解 GWT PhoneGap

      GWT 提供了一个名为 JavaScript Native Interface (JSNI)的机制,它允许 GWT 包装现有的 JavaScript 库。这种能力允许开发人员用 Java 编写代码,而不必担心底层 JavaScript 函数是如何被调用的。

      GWT PhoneGap 是 PhoneGap 库的 GWT 包装器。下一节将演示如何使用 GWT PhoneGap 编写一个 helloworld 应用。

      构建 PhoneGap GWT 应用

      构建 GWT PhoneGap 应用有两个主要步骤。第一步是建立一个 GWT 项目。一旦您构建了 GWT 项目,开发人员将编译 GWT 项目来创建一个 web 应用(一组 HTMLs 和 JavaScripts)。

      第二步是构建一个 Android PhoneGap 应用(使用 PhoneGap 的 0.9.4 版本),并将 GWT web 应用嵌入 PhoneGap 应用中。

      构建 GWT 应用

      在构建 GWT 应用之前,您需要以下工具:

      • JDK 1.6 以上
      • 日蚀 3.6 太阳神
      • Eclipse Google 插件
      • PhoneGap 0.9.4 库
      • GWT PhoneGap 0.8 版本库
      • 用于测试的 Chrome 浏览器 12+版本

      创建一个新的 web 应用项目(Google web application ),并在向导中填入如图图 6–1 所示的值。您需要选中“使用谷歌网络工具包”,取消选中“使用谷歌应用引擎”

      images

      图 6–1。 创建 GWT 项目

      为项目创建一个名为“lib”的文件夹。从[code.google.com/p/gwt-phonegap/downloads/detail?name=gwt-phonegap-0.8.jar](http://code.google.com/p/gwt-phonegap/downloads/detail?name=gwt-phonegap-0.8.jar)下载 GWT-PhoneGap 库,并将其复制到应用的 lib 文件夹中。将 gwt-phonegap-0.8.jar 添加到类路径中,方法是右键单击 jar,然后单击“构建路径”——>“添加到构建路径”

      现在打开 PhoneGap _ GWT _ hello world . gwt . XML 文件。在该文件中,添加以下条目:

      <inherits name='de.kurka.phonegap.PhoneGap' /> <set-property name="user.agent" value="safari" />

      请注意,通过将 user.agent 的 set-property 添加到 safari,GWT 将只为基于 webkit 的浏览器生成 JavaScript。Chrome 浏览器将专门用于这种情况下的测试。

      您的 PhoneGap _ GWT _ hello world . gwt . XML 文件现在应该如下所示:

      `

        
        
        
        
        
        
        
        
        


        
         


        
        
        
        

      `

      现在打开 PhoneGap_GWT_Helloworld.html,位于项目的 war 文件夹中,并进行以下更改:

      `<!doctype html>

             


          
          
               
          
          
          Gwt PhoneGap Demo


          
          
          
          
          
        


        
        
        
        
        

      `

      如果您计划在 Android 上运行这个示例,您应该在 phonegap _ gwt _ hello world/phonegap _ gwt _ hello world . no cache . js 标记之后添加以下内容:

      <script type="text/javascript"> document.addEventListener("deviceready", (function(){ PhoneGap.available = true;}), false); </script>

      现在,打开 src 文件夹中的 PhoneGap_GWT_Helloworld.java,并进行以下更改:

      `package com.phonegap.example.gwt.helloworld.client;
      import com.google.gwt.core.client.EntryPoint;
      import com.google.gwt.user.client.ui.Label;
      import com.google.gwt.user.client.ui.RootPanel;

      /**
       * Entry point classes define onModuleLoad().
       */
      public class PhoneGap_GWT_Helloworld implements EntryPoint {

      /**
               * This is the entry point method.
               */
              public void onModuleLoad() {
                      RootPanel.get().add(new Label("GWT PhoneGap Demo"));
              }
      }`

      默认创建的 GWT 项目有一个用于客户端服务器通信的 RPC 组件,这个应用不需要它。因此,您可以从项目中移除以下条目:

      • 客户包里的 GreetingService.java 和 GreetingServiceAsync.java
      • 共享包和服务器包
      • web.xml 中的任何 servlets

      现在,运行 GWT 项目(运行方式-> Web 应用),您应该会看到如图图 6–2 所示的屏幕。请注意,这个示例将在浏览器中运行,以确保您的 GWT 项目设置正确。

      images

      图 6–2。 在 Chrome 浏览器中运行 GWT 项目

      下一步是实际利用 PhoneGap API 将 GWT 项目编译成一个 web 应用。

      PhoneGap GWT 库的一个好处是,当作为 GWT web 应用启动时,它模仿 PhoneGap 库。该库根据以下指令提供替代功能:

      1. 如果在 Android 或 iPhone 上运行,请使用 PhoneGap JavaScript。
      2. 否则,使用内部模拟类并给出虚拟值。

      首先使用延迟绑定创建 PhoneGap 的对象:

      PhoneGap PhoneGap = (PhoneGap)GWT.create(PhoneGap.class);

      下一步是在 PhoneGap 框架中注册以下回调:

      • phonegavailablehandler:当一切正常并且 PhoneGap 被正确初始化时,这个回调就会发生。总之,这是一次成功的回调。
      • Phonegaptimeouthandler: 当 PhoneGap 没有在给定的时间限制内初始化时,会发生这个回调,可能是由于未能初始化 PhoneGap 框架。简而言之,这是一次失败回调。

      最后,您必须通过调用PhoneGap.initializePhoneGap().来初始化 PhoneGap 框架。调用这个 API 将导致上面的一个回调。

      主代码将被写成PhoneGapAvailableHandler回调,如下所示。使用 PhoneGap 变量是安全的,因为 PhoneGap 已经被正确初始化。在下面的代码中,您从 PhoneGap 获得设备的处理程序,然后在一个网格(2 列 5 行的表格)中打印设备信息值:

      `Device device = phoneGap.getDevice();
      Grid grid = new Grid(5, 2);
      //Add a row mentioning Name Property of Device
      grid.setWidget(0, 0, new Label("Name"));
      grid.setWidget(0, 1, new Label(device.getName()));

      //Add a row mentioning Platform Property of Device
      grid.setWidget(1, 0, new Label("Platform"));
      grid.setWidget(1, 1, new Label(device.getPlatform()));

      //Add a row mentioning Version Property of Device
      grid.setWidget(2, 0, new Label("Version"));
      grid.setWidget(2, 1, new Label(device.getVersion()));

      //Add a row mentioning Name Property of Device
      grid.setWidget(3, 0, new Label("PhoneGapVersion"));
      grid.setWidget(3, 1, new Label(device.getPhoneGapVersion()));

      //Add a row mentioning Name Property of Device
      grid.setWidget(4, 0, new Label("UUID"));
      grid.setWidget(4, 1, new Label(device.getUuid()));

      grid.setBorderWidth(1);
      RootPanel.get().add(grid);`

      以下是完整的示例:

      `package com.phonegap.example.gwt.helloworld.client;
      import com.google.gwt.core.client.EntryPoint;
      import com.google.gwt.core.client.GWT;
      import com.google.gwt.user.client.Window;
      import com.google.gwt.user.client.ui.Grid;
      import com.google.gwt.user.client.ui.Label;
      import com.google.gwt.user.client.ui.RootPanel;

      import de.kurka.phonegap.client.PhoneGap;
      import de.kurka.phonegap.client.PhoneGapAvailableEvent;
      import de.kurka.phonegap.client.PhoneGapAvailableHandler;
      import de.kurka.phonegap.client.PhoneGapTimeoutEvent; import de.kurka.phonegap.client.PhoneGapTimeoutHandler;
      import de.kurka.phonegap.client.device.Device;

      /**
       * Entry point classes define onModuleLoad().
       */
      public class PhoneGap_GWT_Helloworld implements EntryPoint {

      /**
           * This is the entry point method.
           */
          public void onModuleLoad() {
              final PhoneGap phoneGap = GWT.create(PhoneGap.class);
              phoneGap.addHandler(new PhoneGapAvailableHandler() {

      public void onPhoneGapAvailable(PhoneGapAvailableEvent event) {
                      Device device = phoneGap.getDevice();

      Grid grid = new Grid(5, 2);
                      //Add a row mentioning Name Property of Device
                      grid.setWidget(0, 0, new Label("Name"));
                      grid.setWidget(0, 1, new Label(device.getName()));
                      //Add a row mentioning Platform Property of Device
                      grid.setWidget(1, 0, new Label("Platform"));
                      grid.setWidget(1, 1, new Label(device.getPlatform()));
                      //Add a row mentioning Version Property of Device
                      grid.setWidget(2, 0, new Label("Version"));
                      grid.setWidget(2, 1, new Label(device.getVersion()));
                      //Add a row mentioning Name Property of Device
                      grid.setWidget(3, 0, new Label("PhoneGapVersion"));
                      grid.setWidget(3, 1, new Label(device.getPhoneGapVersion()));
                      //Add a row mentioning Name Property of Device
                      grid.setWidget(4, 0, new Label("UUID"));
                      grid.setWidget(4, 1, new Label(device.getUuid()));
                      grid.setBorderWidth(1);
                      RootPanel.get().add(grid);

      }
              });

      phoneGap.addHandler(new PhoneGapTimeoutHandler() {
                  public void onPhoneGapTimeout(PhoneGapTimeoutEvent event) {
                      Window.alert("can not load phonegap");
                  }
              });

      phoneGap.initializePhoneGap();    
          }
      }`

      您可以使用“run as -> web application”从 Eclipse 运行这个示例,并在浏览器中查找代码。

      您将看到图 6–3 中所示的表格。如上所述,当在浏览器中运行代码时,模拟值由 GWT PhoneGap 显示,而不是在 Android 或 iPhone 上。

      images

      图 6–3。 在 Chrome 浏览器中运行 GWT PhoneGap 项目

      最后一步是将这个项目编译成 web 应用。右键单击项目,选择 Google 选项,然后单击名为“GWT 编译”的菜单选项您将看到如图图 6–4 所示的对话框。单击编译。

      images

      图 6–4。 GWT 编译屏幕

      编译完成后,在 Eclipse 中刷新您的项目,您应该会看到如图 Figure 6–5 所示的目录结构。在 war 文件夹中,您应该会看到一个 phonegap_gwt_helloworld 文件夹,其中包含许多 HTML 和 JavaScript 文件,如下所示。

      images

      图 6–5。 GWT 战争目录结构 GWT 编译后

      构建 PhoneGap Android 应用

      构建 PhoneGap GWT 应用的最后一步是创建一个 Android PhoneGap 项目,如图 6–6 和图 6–7 所示,然后将 GWT 生成的 web 应用复制到 assets/www 文件夹中。

      第一步是创建一个 Android 项目。

      images

      图 6–6。 Android 创建项目屏幕

      images

      图 6–7。 Android 创建项目屏幕

      现在,将 PhoneGap 0.9.4 库注入 Android 项目。

      [phonegap.googlecode.com/files/phonegap-0.9.4.zip](http://phonegap.googlecode.com/files/phonegap-0.9.4.zip)下载 PhoneGap 0.9.4 库。撤消 zip 文件,您将看到如图图 6–8 所示的文件夹结构。

      images

      图 6–8。 PhoneGap 0.9.4 目录结构

      在 Android 项目文件夹中创建一个 lib 文件夹,在 lib 文件夹中复制 PhoneGap.0.9.4.jar 文件,然后将其添加到 Eclipse 类路径中。(右键单击 jar 文件,转到“构建路径”,然后单击“添加到构建路径”)

      下一步是在 assets 文件夹中创建一个 www 文件夹,并将 PhoneGap.0.9.4.js 文件复制到 www 文件夹中。然后,您需要将 GWT 项目中的以下文件复制到同一文件夹中:

      • PhoneGap_GWT_Helloworld.html
      • PhoneGap_Gwt_Helloworld.css
      • phonegap_gwt_helloworld 文件夹

      您的文件夹结构现在应该类似于图 6–9 中的示例。编译项目时会生成名为“gwt”的文件夹中的文件。

      images

      图 6–9。 使用 Android 的 GWT PhoneGap 项目目录结构

      现在您需要修改以下文件:

      • HelloWorld.java 文件
      • PhoneGap_GWT_Helloworld.html

      此外,您需要检查 HelloWorld.java 文件是否类似于以下内容:

      package com.phonegap.gwt.helloworld; import android.os.Bundle; import com.phonegap.DroidGap; public class HelloWorld extends DroidGap {     /** Called when the activity is first created. */     @Override     public void onCreate(Bundle savedInstanceState) {         super.onCreate(savedInstanceState);         super.loadUrl("file:///android_asset/www/PhoneGap_GWT_Helloworld.html");     } }

      在 PhoneGap_GWT_Helloworld.html 文件夹中,您需要进行一些代码更改。

      首先添加 Android 版 PhoneGap JavaScript 库 0.9.4 版本,如下:

      <script type="text/javascript" src=”phonegap.0.9.4.js”></script>

      接下来,侦听 deviceready 事件,以确定 PhoneGap 库是否可以使用:

      <script type="text/javascript">     document.addEventListener(         "deviceready",         (function() {             PhoneGap.available = true;         }),         false); </script>

      在此将 PhoneGap.available 变量显式设置为 true。这是 Android 平台的必经步骤。

      以下是 PhoneGap_gWT_Helloworld.html 的完整源代码:

      `<!doctype html>


              
              
              
              
              
              
              
              
              <br>             Gwt PhoneGap Demo<br>         
              
              
              
              
              
              
              
              
          
          
          
          
          
          


          

      `

      运行此代码后,仿真器上的屏幕应显示为图 6–10 中的示例。

      images

      图 6–10。GWT PhoneGap 应用显示设备信息

      与上面的代码一样,您可以编写代码来访问其他 PhoneGap APIs,也可以编写一个通过 PhoneGap 访问本地电话功能的 GWT 应用。

      GWT PhoneGap 参考

      下面列出了 GWT PhoneGap 项目中使用的文档和源代码的链接。丹尼尔·库尔卡是这个图书馆的作者。

      主页—[code.google.com/p/gwt-phonegap/](http://code.google.com/p/gwt-phonegap/)

      入门—[code.google.com/p/gwt-phonegap/wiki/GettingStarted](http://code.google.com/p/gwt-phonegap/wiki/GettingStarted)

      下载 jar-[gwt-phonegap.googlecode.com/files/gwt-phonegap-0.8.jar](http://gwt-phonegap.googlecode.com/files/gwt-phonegap-0.8.jar)

      下载 Javadocs—[gwt-phonegap.googlecode.com/files/gwt-phonegap-0.8-javadoc.jar](http://gwt-phonegap.googlecode.com/files/gwt-phonegap-0.8-javadoc.jar)

      源代码—[code.google.com/p/gwt-phonegap/source/browse/](http://code.google.com/p/gwt-phonegap/source/browse/)

      当前功能-[code.google.com/p/gwt-phonegap/wiki/Features](http://code.google.com/p/gwt-phonegap/wiki/Features)

      七、PhoneGap 模拟器和远程调试

      简介

      我在开发 PhoneGap 应用时经历的最大痛苦是以下循环:

      1. 开发一个 eclipse/xcode 或无关的 IDE
      2. 编译二进制可执行文件并将其放在设备/仿真器上
      3. 在设备/模拟器上测试 PhoneGap 应用
      4. 调整代码并从第 1 步开始重复

      显然,这一循环非常耗时且令人沮丧。如果你是一个有经验的 JavaScript 开发人员,这对你来说将是一场噩梦。

      JavaScript 开发人员习惯于在以下现代浏览器上使用便捷的工具:

      1. 火狐浏览器
      2. 旅行队
      3. 微软公司出品的 web 浏览器

      (注意,对于 iPhone 和 Android 开发,ie 浏览器没什么用。我们建议 iPhone 和 Android 开发使用 Firefox、Safari 或 Chrome。)

      这些工具很少是开发人员的扩展。Firefox 有自己的 firebug,这是 javascript/html 调试工具系列的第一款,它不仅允许您调试页面元素(DOM 结构),还允许您调试脚本、样式表和网络。它还允许您动态地更改这些内容,并立即进行测试。Chrome 和 Safari 自带开发工具。Internet Explorer 也不例外,它有自己的扩展来做类似的事情。

      显然,我们在开发 PhoneGap 时需要类似的东西。这里列出我们的两个要求。我们需要以下内容:

      1. 使用 PhoneGap 模拟器在浏览器世界中创建和测试 PhoneGap 之外的应用的能力
      2. 一旦我们将 PhoneGap 应用部署在某个仿真器或设备上,就可以对其进行调试

      本章我们将讨论如何使用 PhoneGap 模拟器和远程调试工具。

      Chrome PhoneGap 模拟器–使用 Ripple

      Ripple 是一个多平台移动平台模拟器,来自一家名为 tinyHippos 的公司。最近,这家公司被动态研究公司(RIM)收购。Ripple 出现的主要原因是为了减少当今移动 web 开发人员面临的挑战,这些挑战是由移动操作系统世界的巨大碎片化造成的。

      Ripple 是一个 Chrome 扩展,提供以下模拟:

      1. 语音间隙
      2. Webworks(来自黑莓)
      3. web works-平板电脑-操作系统(来自黑莓)
      4. 移动网络
      5. 陆军妇女队
      6. 歌剧
      7. 沃达丰

      出于本书的目的,我们将只关注 Ripple 的 PhoneGap 仿真。

      安装波纹

      Ripple 唯一的先决条件是你需要 Chrome 浏览器。任何支持扩展的版本都可以,不用担心安装哪个版本的 Chrome。但是,我们建议使用最新版本。

      打开 Chrome 并访问网站—[ripple.tinyhippos.com](http://ripple.tinyhippos.com)/

      您将看到如图图 7–1 所示的页面;你所需要做的就是点击“获取波纹”按钮。

      images

      图 7–1。 涟漪首页

      这将带您进入图 7–2 中显示的页面。现在点击“安装”

      images

      图 7–2。安装 Ripple 作为 Chrome 扩展

      正如我们之前提到的,Ripple 是 Chrome 的扩展。点击“安装”进入 Chrome 网上商店。当你点击“添加到 Chrome”时,扩展/插件实际上是从 Chrome 网络商店下载的,并自动安装在你的 Chrome 浏览器中(参见 Figure 7–3)。

      images

      图 7–3。 从 Chrome 网上商店安装 Ripple

      一旦插件安装完毕,你会看到一个类似于图 7–3 所示的屏幕。

      images

      图 7–4。 在 Chrome 上安装波纹

      为了验证插件已经正确安装,在 Chrome 中打开一个类似[www.google.com](http://www.google.com)的站点,右键点击页面。如果插件已经正确安装,您应该会看到一个“模拟器”启用/禁用选项。这在图 7–5 中进行了描述。

      images

      图 7–5。 在 Chrome 上右击选项打开波纹模拟器

      继续点击“模拟器”并选择“启用”如果你看到图 7–6 中的屏幕,你的 Ripple 插件工作正常。要退出该屏幕,只需右击并选择仿真器- >禁用。

      images

      图 7–6。 第一次发射涟漪

      为 PhoneGap 有效地使用 Chrome

      在我们继续在 Chrome 上使用 Ripple 之前。让我们来了解一下 PhoneGap 如何有效地使用 Chrome。PhoneGap 与移动网络应用有相似之处,但在许多方面也有所不同。让我们列出两个主要的区别。

      1. PhoneGap 应用是基于 HTML/JavaScript 的应用,但它们从不被托管。它们被捆绑为本地移动应用的一部分,显示在应用内部的嵌入式浏览器中。这类似于在 Chrome 上从磁盘测试 HTML/JavaScript 应用。Chrome 需要调整以支持从磁盘运行 html/javascript 应用。
      2. PhoneGap 没有任何与之相关联的域名。这就是为什么他们不遵循单一原产地政策。如果您想为 PhoneGap 应用模拟不遵守单一来源策略,该应用由文件系统或本地 web 服务器托管,您需要调整 Chrome 以支持关闭单一来源策略。

      为了从本地文件系统测试 PhoneGap 应用,并使其不遵循单一来源策略,您需要使用下面几段中讨论的命令行参数启动 Chrome。

      Windows

      在 Windows 中,创建一个名为 chrome.cmd 的. cmd 文件,并将以下脚本复制到该文件中。现在使用 chrome.cmd 启动 chrome。

      **chrome.exe --disable-web-security** -–allow-file-access-from-files

      Mac 和 Linux

      在 Mac 上,创建一个名为 chrome.sh 的脚本,并将以下脚本复制到其中。现在用 chrome.sh 启动 chrome。

      open "/Applications/Google Chrome.app" --args --disable-web-security -–allow-file-access-from-files

      更改 chrome.sh 脚本的权限,使其可执行(需要运行脚本)。

      $>chmod +x chrome.sh Run the script from terminal as follows: $>./chrome.sh

      利用波纹

      现在我们将了解如何使用 Ripple,以及在 Ripple 中运行 PhoneGap 应用需要做哪些更改。

      以下是在 Ripple 中使用 PhoneGap 应用的先决条件:

      1. 该应用需要是一个没有插件的纯欧米茄应用。
      2. 您需要从所有 HTML 文件中删除对 PhoneGap JavaScript 的任何引用。

      针对 Ripple 调整您的应用

      让我们举一个来自第二章的代码例子。

      我们将致力于指南针应用的例子。从这个 URL-[beginingphonegap.googlecode.com/files/compass.png](http://beginingphonegap.googlecode.com/files/compass.png)获取指南针图像。该图像显示在图 7–7 中。

      images

      图 7–7。PhoneGap 应用中使用的指南针图像

      现在,让我们修改 index.html 文件,如下所示。请注意,我们已经删除了对 phonegap.js 的任何引用。这是目前使用 Ripple 的先决条件。Ripple 正在与 PhoneGap 合作,以消除这种变化。希望在即将发布的版本中,我们会看到这个需求消失。

      `


              <br>             PhoneGap<br>         
          


              


                  Compass
              


              
                  
                      
                      
                      
                  
              

                          Compass Heading
                      

                          

                              ....
                          

                      

                          Degrees
                      

              
              
          

      `

      你的应用目录看起来会像图 7–8。它包含应用的所有 HTML、JavaScript 和 CSS 文件。

      images

      图 7–8。 指南针 App 的应用目录

      用特殊标志启动 Chrome

      下一步是用特殊的标志启动 Chrome(就像我们在本章前面开始的那样)。

      Start Chrome with --disable-web-security -–allow-file-access-from-files flags.

      Chrome 启动后,进入窗口->扩展,找到“Ripple Mobile Environment Emulator”扩展,启用“允许访问文件 URL”复选框(参见图 7–9)。

      images

      图 7–9。 允许访问文件的网址为波纹扩展名

      在 Chrome 中加载应用

      现在在 Chrome 中加载指南针应用。在右上角,您会看到波纹图标。点击该项目,为该应用启用 Ripple。这显示在图 7–10 中。

      请注意,因为我们用上述标志启动了 Chrome,这就是 Chrome 能够从本地文件系统加载 HTML 文件的原因。此外,如果 PhoneGap 应用使用 Ajax 加载数据,它也可以在 Chrome 中工作,因为我们已经禁用了单一来源策略。

      images

      图 7–10。 在 Chrome 浏览器中加载指南针 app

      启用纹波

      第一次启用 Ripple 时,您会看到 Figure 7–11。我们需要选择 PhoneGap 选项。

      images

      图 7–11。 为指南针 app 启用纹波

      玩波纹设置

      现在我们已经启用了 Ripple,我们将看到网页已经改变。以前占据整个屏幕的应用现在看起来不同了。这是因为现在 Ripple 是主要应用。Ripple 在 iframe 中加载我们自己的应用,并注入自身来模拟 PhoneGap 环境。在主页上,Ripple 提供了许多控件来改变 PhoneGap 模拟的设备的状态和属性(参见图 7–12)。

      images

      图 7–12。Chrome 浏览器中加载的指南针应用启用了 Ripple

      用纹波测试应用

      测试中的应用是一个 compass 应用。为了测试该应用,我们将使用右下角红色标记的地理和指南针控件(参见图 7–12)。如果我们改变方向,这意味着我们正在模拟用户移动他/她的设备的罗盘方位。

      正如您在图 7–13 中看到的,当我们改变航向时,我们可以看到罗盘图像围绕中心旋转。

      images

      图 7–13。 用 Ripple 模仿 PhoneGap 的 Compass api

      模拟 PhoneGap 只是涟漪的一个角度。Ripple 允许开发人员使用常规的浏览器工具来调试和更改应用的 DOM 和 CSS。在图 7–14 中,我们右击图像并点击“检查元素”然后我们可以检查被检查的 DOM 元素的 css 样式,在我们的例子中是 html img。

      images

      图 7–14。 使用 Chrome 的开发者工具查看 HTML DOM 变化

      远程调试—debug.phonegap.com

      虽然使用 Ripple 来模拟、测试和调试 PhoneGap 应用是可行的,并且非常有帮助,但是没有什么比得上在实际的仿真器或设备上进行调试。

      在实际的仿真器或设备上进行调试的问题是,用于显示 PhoneGap 应用的 webkit webview 非常孤立,无法从外部访问。与 Chrome、Firefox 或 Safari 相比,在这些浏览器中,用户可以检查 html 元素。但是在网络视图中运行的应用(HTML/Javascript)比如 PhoneGap 应用不能被检查。

      这就是远程调试发挥作用的地方。参见图 7–15 更好地理解这个概念。基本思想是将调试 JavaScript 注入到我们的 PhoneGap 应用中。这打开了一个与 debug.phonegap.com 服务器的通道。然后开发人员在浏览器中打开 debug.phonegap.com 的并检查运行在设备/模拟器上的 PhoneGap 应用。

      images

      图 7–15。 远程调试架构

      设置远程调试

      进行远程调试的第一步是在浏览器中打开[debug.phonegap.com](http://debug.phonegap.com)。在这里你可以提供一个你自己的向导(就像我们一样)或者使用服务器随机分配的向导。

      images

      图 7–16。?? 使用debug.phonegap.com调试你的 PhoneGap 应用

      在 PhoneGap 应用中注入远程调试

      下一步是复制来自[debug.phonegap.com](http://debug.phonegap.com)的 JavaScript 片段,并将其注入到 PhoneGap 应用中。这在下面用下划线表示。

      `

                   PhoneGap                  
              
          
          
              

      Device Info


              
                  
                      
                      
                  
                  
                      
                      
                  
                  
                      
                      
                  
                  
                      
                      
                  
                  
                      
                      
                  
              

                          Device Name                 

                      

                          PhoneGap Version
                      

                      

                          Mobile Platform
                      

                      

                          Platform Version
                      

                      

                           UUID
                      

                      

          

      `

      调试和修改 DOM 元素

      下一步是在模拟器或设备上启动 PhoneGap 应用。当这个应用启动时,运行在其中的 JavaScript 将与 debug.phonegap.com 服务器通信。然后,我们将为远程调试做好准备(参见图 7–17)。

      images

      图 7–17。 在安卓模拟器中加载设备信息应用

      最后一步是在浏览器中打开[debug.phonegap.com/client/#begining-phonegap](http://debug.phonegap.com/client/#begining-phonegap)(参见图 7–18)。请记住,我们是从[debug.phonegap.com](http://debug.phonegap.com)网站获得这个 URL 的。

      images

      图 7–18。 日志消息显示一个 Android 应用连接到了debug.phonegap.com

      现在移动到元素选项卡(参见图 7–19)。在这里,您将能够在 PhoneGap 应用中看到页面的 DOM 元素。

      images

      图 7–19。 上检查 DOM

      乐趣不止于此。现在您可以更改 DOM 元素(参见 Figure 7–20)。为此,双击任何 DOM 元素(在我们的例子中是 TD)并手动输入样式部分。在我们的例子中,我们将向包含 0.9.5 文本的 td 添加一个样式“style='background:red '”。现在,我们将切换到运行在模拟器中的 PhoneGap 应用,以查看即将生效的更改。

      images

      图 7–20。 在【http://debug.phonegap.com上改变一个 DOM 属性

      现在,包含“0.9.5”的 TD 元素的背景色变为红色(参见图 7–21)。这种调试帮助我们在设备/仿真器上实时调试应用。

      images

      图 7–21。 [debug.phonegap.com](http://debug.phonegap.com)所做的更改反映在一个 Android 模拟器上

      debug.phonegap.com的问题

      到目前为止,我们已经看到[debug.phonegap.com](http://debug.phonegap.com)的使用在检查真实设备或仿真器内部运行的内容时非常有用。但是,我们不想使用它,原因如下:

      1. 在开发过程中,我们不想使用外部服务器。
      2. 我们希望节省带宽并提高调试速度。

      注:A Weinre(web Inspectorremote)服务器 powerdebug.phonegap.com。PhoneGap 的人也开发了 Weinre,他们已经很好地记录了它。

      安装当地debug.phonegap.com

      虽然 Weinre 的安装和部署超出了本书的范围,但是我将以链接的形式给出一些关于如何在本地部署 Weinre 的说明。

      Weinre 的文档非常好,如果您遵循它,在本地安装和使用 Weinre 应该没有问题。

      访问[phonegap.github.com/weinre/Installing.htm](http://phonegap.github.com/weinre/Installing.htm) l 获取安装文件(参见图 7–22)。

      images

      图 7–22。 关于如何安装 Weinre 的说明

      访问[phonegap.github.com/weinre/Running.html](http://phonegap.github.com/weinre/Running.html)获取如何运行的说明(参见图 7–23)。

      images

      图 7–23。 关于如何运行的说明

      结论

      当开发 PhoneGap 应用时,使用 iPhone/Android 模拟器来测试代码是非常耗时和令人沮丧的。为了节省时间和精力并简化开发,请使用 Ripple PhoneGap 模拟器。充分利用 Chrome 的开发者工具,加速你的开发。对于远程调试,使用[debug.phonegap.com](http://debug.phonegap.com)或本地安装的 Weinre 服务器。这有助于您了解当应用在 iPhone/Android 模拟器或实际设备上运行时,HTML DOM 实际上发生了什么。

      八、使用 PhoneGap 插件

      PhoneGap 附带了一组 JavaScript APIs,用于访问原生电话功能,如摄像头、存储、联系人、地理定位等。,构建跨移动应用。如果您想做 PhoneGap API 中没有的事情,您可以利用 PhoneGap 插件。

      在任何技术中,重用已经可用并经过测试的特性都是常见的做法。PhoneGap 有很多重要的第三方插件。例如,访问脸书的认证机制、访问用于移动推送通知的第三方服务等。

      什么是 PhoneGap 插件?

      PhoneGap 插件是 PhoneGap 功能的扩展。它访问手机上的一项功能。插件功能可能只能访问手机的本机功能,也可能提供访问云服务的功能。

      任何 PhoneGap 插件都至少包含两个文件:

      • JavaScript 文件
      • 本地语言文件

      插件的 JavaScript 文件是 PhoneGap 应用和 PhoneGap 插件之间的接口。JavaScript 文件使用 JavaScript 函数来访问插件的功能。

      PhoneGap 框架使用本地语言文件来与电话交互以访问本地功能。作为插件用户,我们需要将本机代码放入我们的项目结构中。在下一节中,我们将详细研究如何使用这个插件来设置项目。

      脸书认证并抓取好友

      让我们利用 PhoneGap 插件构建一个小应用,登录到脸书并从脸书获取朋友的信息。

      我们的 PhoneGap 应用将使用脸书本地应用通过脸书-PhoneGap 插件为用户执行单点登录(SSO)。

      为 Android 设置环境

      首先,我们需要为 Android 建立 PhoneGap 项目。请参考第二章为 Android 设置您的项目。一个 Android 项目配置如图图 8–1 所示。

      images

      图 8–1。 Eclipse Android 项目配置

      [github.com/davejohnson/phonegap-plugin-facebook-connect/downloads](https://github.com/davejohnson/phonegap-plugin-facebook-connect/downloads)下载脸书连接插件。

      脸书连接插件是一个 zip 文件。解压到你喜欢的文件夹里。文件夹结构应类似于图 8–2。

      images

      图 8–2。 脸书-连接插件文件夹结构

      接下来,您需要执行以下安装步骤:

      1. Register Facebook Plug-in

        在 plugins.xml 文件中添加以下 XML 元素作为“plugins”元素的子元素,如 Figure 8–3 所示。您可能需要使用以下命令在 res 文件夹中创建一个 xml 文件夹:

        <plugin name="com.phonegap.facebook.Connect" value="com.phonegap.facebook.ConnectPlugin" /> images

        图 8–3。 脸书外挂注册

      2. Include the native part of the plug-in into the project. Copy the libs and src folder from a Facebook-connect-plug-in folder as shown in Figure 8–4 and paste it into the root of our PhoneGap application, i.e., “FaceBookPluginExample”. images

        图 8–4。 脸书-连接-安卓插件原生文件夹

      3. 在项目中包含插件的 JavaScript 部分

      有两个 JavaScript 文件需要包含在我们来自脸书的项目中——连接插件。

      • /www/pg-plugin-fb-connect.js
        pg-plugin-fb-connect.js文件在脸书插件的 www 文件夹下。将其复制并粘贴到我们项目的 assets/www 文件夹中。
      • /lib/facebook_js_sdk.js
        facebook_js_sdk.js文件在脸书插件的 lib 文件夹下。将其复制并粘贴到我们项目的 assets/www 文件夹中。

      一旦你完成了这三个步骤,你将会看到如图图 8–5 所示的 FaceBookPluginExample 项目结构。

      images

      图 8–5。 FaceBookPluginExample 项目结构

      初始化脸书连接插件

      第一步是确保 index.html 有一个脸书连接库,PhoneGap 库,并且是 CSS 链接的。请注意,我们包含了以下 JavaScript 文件。

      1. JavaScript 语音间隙
      2. 脸书插件 JavaScript
      3. 脸书 SDK JavaScript

      `

      <head></head> <body> <div id=”friends”></div> <!--phonegap --> <script src="phonegap-1.1.0.js"></script> <!--phonegapfacebook plugin --> <script src="pg-plugin-fb-connect.js"></script> <!--facebookjssdk -->` `<script src="facebook_js_sdk.js"></script> </body> </html>`

      现在,我们将在 index.html 页面中定义 JavaScript 函数,以登录到脸书并获取好友列表。以下是登录函数的代码片段:

      function login() {     FB.login(function(response) {…},                     { perms: "email" }              ); }

      login()函数调用脸书 SDK 的登录函数 FB.login()。脸书的 FB.login()有两个参数。第一个是回调 JavaScript 函数,第二个是 JSON 对象,用于指定权限。我们将' function(response){…} '和' { perms: "email" } '传递到 FB.login()。脸书的 FB.login()提示用户登录。成功登录后,它调用回调 JavaScript 函数。回调函数获取“响应”对象来标识登录状态。“perms”用于指定用户权限。你可以在脸书开发者网站上找到更多关于脸书登录 API 和用户权限的细节,网址是 http://developers . Facebook . com/docs/reference/API/permissions。

      接下来,我们将看到用于获取好友列表的代码片段。为此,我们将创建一个 JavaScript 函数 getFriendList()。

      function getFriendList(){     FB.api('/me/friends', function(response) {            if (response.error) {     alert(JSON.stringify(response.error));            } else {                var friends = document.getElementById('friends');                response.data.forEach(function(item) {                var d = document.createElement('div');                d.innerHTML = item.name;                data.appendChild(d);                                    });            } }); }

      在 getFriendList()函数中,调用了脸书 API FB.api()。第一个参数是脸书提供的图形 API 的路径。在我们的示例中,'/me/friends '用于获取登录用户的朋友列表。第二个参数是接收响应的 JavaScript 回调函数。回调方法中执行以下操作:

      1. 使用“response.error”检查响应状态是否成功
      2. 如果响应成功,那么 response 中可用的结果数据将被迭代。
      3. 为每个项目创建“div”元素,并将其附加到在 index.html 定义的“friends”div 中

      接下来,我们将修改 login()函数,以便在成功登录时调用 getFriendList()。

      function login() { FB.login( function(response) {         if (response.session) {             getFriendList();         } else {             alert('not logged in');         }               }, { perms: "email" }     ); }

      这里我们检查“response.session”值的成功响应。如果它是有效的,我们就调用 getFriendList()函数。

      现在,最后一步是在 PhoneGap 的初始化事件中使用 JavaScript 函数。

      document.addEventListener('deviceready', function() {     try {         /* Initialize the Facebook plug-in. Note that you need to replace the         <app_id>by your Facebook's app_id */         FB.init({ appId: "<app_id>", nativeInterface:PG.FB });         document.getElementById('data').innerHTML = "";         login();     } catch (e) {          alert(e);     } }, false);

      最后,要运行应用,您需要将脸书的 app_secret 密钥放入 AndroidManifest.xml 文件,如 Figure 8–6 所示。

      images

      图 8–6。 脸书 app _ 秘钥

      您可以从脸书开发者网站[developers.facebook.com/apps](https://developers.facebook.com/apps).获取脸书应用 id 和应用密码。

      在模拟器上将 FacebookPluginExample 作为 Android 应用运行。第一个屏幕将显示脸书登录页面,如图 8–7 所示。

      images

      图 8–7。 脸书登录界面

      成功登录后,您将看到您的好友列表,如 Figure 8–8 所示。

      images

      图 8–8。脸书好友列表

      您可以使用 jQueryMobile 或 Sencha Touch 以及脸书 PhoneGap 插件来开发一个有吸引力的脸书应用。此外,您可以通过使用脸书插件调用其他脸书图形 API 来添加更多功能。

      C2DM 插件,用于向 PhoneGap 发送移动推送通知

      推送通知或服务器推送是从服务器向客户端发送数据的最新方式。您是否注意到 Gmail 是如何接收和显示您收件箱中的新邮件的?你不需要刷新浏览器或者点击一些刷新按钮来发送请求和从服务器接收最新的数据。

      最近,轮询是接收通知的一种流行技术。轮询技术定期向服务器发送请求,并用收到的响应刷新 UI。您可以将它视为一个后台进程,它以特定的预定义间隔发送请求,并从服务器接收更新或通知。这种方法有许多已知的缺点。轮询方法的主要缺点是识别合适的间隔来发送请求。如果间隔更短,可能会有不必要的请求和响应行程,从而导致带宽和服务器资源的损失。更长的时间间隔可能达不到轮询的目的,因为在接收通知时可能会有延迟,并且它不再符合发送通知的目的。如果没有新数据可用,这种方法会消耗移动电话的电池。

      服务器推送允许服务器向客户端发送通知或更新,而无需等待请求。在推送技术中,客户端没有任何后台进程来发出定期请求。在服务器有更新的任何时候,它都可以将更新推送到所有注册的客户端。如果客户端是一个移动应用,这种技术被称为移动推送。

      您的 PhoneGap 应用也可以通过 PhoneGap 插件利用移动推送技术。让我们为 Android 平台创建一个小的 PhoneGap 应用来接收来自 C2DM 服务的推送通知。

      为 Android 设置环境

      首先,我们需要为 Android 设置 PhoneGap 项目。参考第二章为 Android 设置您的项目。一个 Android 项目配置如图图 8–9 所示。

      images

      图 8–9。 Eclipse Android 项目配置

      我们将使用 Android 云到设备消息(C2DM)框架进行推送通知。你可以在[code.google.com/android/c2dm/#intro](http://code.google.com/android/c2dm/#intro)了解更多关于 C2DM 服务的信息。

      [github.com/awysocki/C2DM-PhoneGap/downloads](http://github.com/awysocki/C2DM-PhoneGap/downloads)下载 C2DM PhoneGap 插件。C2DM-PhoneGap 插件是一个 zip 文件。解压到你喜欢的文件夹里。文件夹结构应该类似于图 8–10 中的列表。

      images

      图 8-10。C2DM-插件文件夹结构

      接下来,您需要执行以下安装步骤:

      1. Register C2DMPlug-in

        在 plugins.xml 文件中添加以下 XML 元素作为“plugins”元素的子元素,如 Figure 8–11 所示。您可能需要在“res”文件夹下创建一个 xml 文件夹,并从 PhoneGap Android 示例应用中复制 plugins.xml。

        <plugin name="C2DMPlugin" value="com.plugin.C2DM.C2DMPlugin" /> images

        图 8–11。 插件注册

      2. Include the native part of the plug-in into the project

        从 C2DM-plug-in 文件夹中复制 src 文件夹,如图 8–12 所示,并将其粘贴到我们的 PhoneGap 应用的根目录中,即“MobilePushPluginExample”。

        images

        图 8–12。C2DM-插件原生部分

      3. Include the JavaScript part of the plug-in into the project

        如 Figure 8–13 所示,从 C2DM-plug-in 文件夹中复制以下文件,并将其粘贴到我们应用的 assets 文件夹中。

        • C2DMPlugin.js
        • jquery_1.5.2 最小 js
        • PG_C2DM_script.js
        • index.html

        注意,即使 index.html 不是插件的一部分,我们也在项目中使用它来节省创建和包含 js 文件的时间。

        images

        图 8–13。 JavaScript 部分插件

        注意,我们没有包括 Phonegap.0.9.5.js 文件,因为我们使用的是 PhoneGap-1.1.0。我们将对 PhoneGap-1.1.0 进行必要的修改。

      4. 最后,我们需要在 AndroidManifest.xml 文件中添加 C2DM 所需的权限。清单文件应该类似于下面的列表:

      `


                 
                 
                 
                 
                 
                 
                 
                 
                 
                 
                 
                 
                 


                 


                 


                 
                 <uses-sdkandroid:minSdkVersion="8" />


                            
                                       
                                                  
                                                  
                                       

                            

                            
                            


                            
                                       
                                                  
                                                  
                                       

                                       
                                       
                                                  
                                                  
                                       

                            

                            
                 

      `

      完成前三个步骤后,您将看到 MobilePushPluginExample 项目结构,如图 Figure 8–14 所示。

      images

      图 8–14。 MobilePushPluginExample 项目结构

      修改 PhoneGap 插件-1.1.0

      在写这本书的时候,一个 C2DM 插件是基于 PhoneGap 版本 0.9.5 的。要在 PhoneGap-1.1.0 中使用它,我们必须完成一些修改。

      1. C2DMPlugin.jsfiles.

        打开 C2DMPlugin.js 并转到以下函数定义:

        `PhoneGap.addConstructor(function() {
              //Register the javascript plugin with PhoneGap
              PhoneGap.addPlugin('C2DM', new C2DM());

        //Register the native class of plugin with PhoneGap
              PluginManager.addService("C2DMPlugin",
        "com.plugin.C2DM.C2DMPlugin");

        //alert( "added Service C2DMPlugin");
        });`

        删除以下行:

        //Register the native class of plugin with PhoneGap PluginManager.addService("C2DMPlugin","com.plugin.C2DM.C2DMPlugin");

        在 PhoneGap-1.1.0 中,插件必须在 plugin.xml 文件中注册。PhoneGap-1.1.0 中不再提供 PluginManager。我们已经在 plugin.xml 文件中注册了 C2DM-plugin。

        现在,修改后的函数如下所示:

        `PhoneGap.addConstructor(function() {
              //Register the javascript plugin with PhoneGap
              PhoneGap.addPlugin('C2DM', new C2DM());

        //alert( "added Service C2DMPlugin");
        });`

      2. Move C2DMReceiver.java

        我们必须将 C2DMReceiver.java 从 com.phonegap.c2dm 包中移到我们的应用项目中,即 org . examples . mobile push plugin . example,为此,将 C2DMReceiver.java 从 com.phonegap.c2dm 包中拖放到 org . examples . mobile push plugin . example 中,如 Figure 8–15 所示。

      images

      图 8-15。C2DMReceiver.java所在地

      我们还必须修改 index.html 文件中的 PhoneGap 版本,因为我们是从插件文件夹中复制的。为此,在脚本标记中用 phonegap-1.1.0 替换 phonegap.0.9.5.js。

      注册 C2DM 服务

      前往[code.google.com/android/c2dm/signup](http://code.google.com/android/c2dm/signup)并填写表格以注册发送者。你必须提到 Android 应用的包名和发件人帐户电子邮件以及其他信息。对于我们的应用,包名应该是“org . examples . mobilepushplugin . example ”,我们应该使用 Google 帐户作为发件人帐户电子邮件。我们必须在 PhoneGap 应用中使用发件人帐户电子邮件 id 来注册接收通知的设备。

      在 PhoneGap 中使用 C2DM 发送者帐户

      C2DM 插件附带了现成的 PhoneGap deviceready 实现。我们必须使用我们的 C2DM 发送者帐户来注册通知设备。

      打开 PG_C2DM_script.js 文件,转到 PhoneGap deviceready 事件实现。将“your_c2dm_account@gmail.com”修改为我们的 c2dm 发件人帐户,如图 8–16 所示。

      images

      图 8–16。 向 C2DM 发送方帐户注册应用

      支持 C2DM 服务的 Android 模拟器

      您需要使用目标为“Google API(Google Inc .)-API Level 8”的 AVD (Android 虚拟设备)来运行支持 C2DM 的 Android 应用。请参考第二章从“Android SDK 和 AVD 管理器”创建新的 AVD。

      你还必须在模拟器中添加你的谷歌账户。为此,运行模拟器并打开设置,如图 8–17 所示。

      images

      图 8–17。 安卓设置选项

      转到“帐户和同步”并点击添加帐户。你必须输入你的谷歌帐号和密码。注意,它不是 C2DM 发送者帐户。这是你的谷歌账户,你用它来从你的安卓手机中检索你的电子邮件和其他东西。

      现在您已经准备好测试 C2DM 插件了。将 MobilePushPluginExample 作为 Android 应用运行。确保目标模拟器是 Google API 的模拟器。您将在模拟器上看到图 8–18 所示的屏幕。

      images

      图 8–18。??【模拟器上的 mobilepushpluginsampleoutput】??

      为了理解屏幕上出现的输出,我们将查看 PG_C2DM_script.js 文件中的 devicereadyevent 回调函数。

      这里,window.plugins.C2DM.register()调用插件的方法将设备或模拟器注册到 C2DM 服务中。注册成功后,C2DM 服务器返回注册 Id (REGID)。该 REGID 用于推送通知消息。但是等等,我们的设备不应该推送信息。它是一个通知接收器,对吗?在这里,我们必须理解在手机上运行的 PhoneGap 应用和在 Google 上托管的 C2DM 服务之间的应用服务器的角色。

      我们用一个例子来了解一下 C2DM 推送的工作原理。假设 MobilePushPluginExample 安装在多部 Android 手机上。现在,每部手机都从 C2DM 服务接收 REGID。本质上,每个 REGID 都是唯一的。在发送 REGID 之前,C2DM 服务存储关于设备和网络的所有必需信息,以便在发送通知时进一步使用。C2DM 向设备发送通知。现在,中间的应用服务器负责识别更新的数据,并要求 C2DM 向实际的移动设备发送通知。为此,我们的服务器必须知道 REGIDs。

      通常,支持 C2DM 的移动应用会将 REGID 发送到我们的服务器。服务器存储运行该应用的所有手机的注册 id。一旦服务器决定发送通知,它就使用 REGID 来请求 C2DM 服务这样做。

      我们从 C2DM 服务收到了 REGID,这可以在图 8–18 中看到。现在,您可以使用这个 REGID 发送推送通知。您可以使用 Java servlet 或 php 创建服务器端代码来发送消息。要了解有关如何发送推送通知的更多信息,请访问 Android C2DM 网站:[code.google.com/android/c2dm/index.html#push](http://code.google.com/android/c2dm/index.html#push)

      此外,还有一个命令行工具可用于模拟服务器。转到[curl.haxx.se/download.html](http://curl.haxx.se/download.html)并下载特定于平台的 curl 工具。发送通知有两个步骤:

      1. Get the authentication key

        在控制台上运行以下命令:

        D:\cURL>curl https://www.google.com/accounts/ClientLogin -d Email=<C2DM Sender Account>-d "Passwd=<password>" -d accountType=GOOGLE -d source=org.examples. mobilepushplugin.example -d service=ac2dm –k

        你必须用你注册的谷歌账号和密码替换

        运行以上命令后,您将获得类似于以下清单的身份验证密钥:

        Auth=DQAAAMEAAABrqkqH2KYjDfCD93tndEF7n81lKgf5vczCwELPSXgW6xm_9EACDu0lsJFGud7fNBI HcRV1Q6zUmLwxFFJqosdn1nYYmGah0yu7fpT8vfjNLAVx8hs5aymz9OULg-pzKOyWWa1-6BDci1TBCoP 2q6ZwJqEjzH6rArHSlD9DhruEKBrogjfBAWyeIm2fs9THvEkilSMO2Q8utoqyfG0id9keCQad5QPV7oO vNSe6urKOV4ZWEKxG7KAlXCsjW18u_m2Az6jj7DlUoVD89MeLvX0W

      2. Send the message to your application running on a simulator

        要发送通知,我们将使用身份验证密钥,并通过以下 curl 命令注册:

        S:\cURL>curl --header "Authorization: GoogleLoginauth=<Auth Key>" "https://android.apis.google.com/c2dm/send" -d registration_id=<REGID>-d “data.message=This is a test message" -d "data.msgcnt=1" -d collapse_key=0 –k

        替换为从步骤 1 接收的认证密钥,并由模拟器上的应用接收 REGID。我们正在向模拟器发送“这是测试消息”文本作为推送通知。

      我们将在模拟器上看到我们的应用收到的通知,如图 8–19 所示。

      images

      图 8–19。 模拟器上的推送消息

      如果您想利用 iPhone-PhoneGap 应用的推送通知服务,您可以使用 PhoneGap 插件,该插件位于:[github.com/urbanairship/ios-phonegap-plugin](https://github.com/urbanairship/ios-phonegap-plugin)。它使用城市飞艇服务来推送移动通知。

      结论

      PhoneGap 插件动态地扩展 PhoneGap 应用,使其包含越界特性。通过使用插件,PhoneGap 应用几乎可以使用任何本机功能。

      插件是 PhoneGap 的好朋友,但社区对插件的支持还处于早期阶段。与此同时,PhoneGap 背后的组织正在使流行的插件成为官方的。然而,插件支持还远远不够完善。这方面的一个例子是尝试为 iPhone 编写一个脸书连接应用。当我们试图在 PhoneGap 1.1.0 中使用这个插件时,我们发现它不起作用。我们还发现包含这个插件非常麻烦。我们猜测,对于即将发布的 PhoneGap 来说,对插件的支持将会得到改善,插件将更容易捆绑并在 PhoneGap 应用中使用。

      在本章中,我们只讨论了用于脸书连接和云推送的 Android PhoneGap 插件。出于同样的原因,我们添加了关于 iPhone 插件的指针,这些插件需要改进,才能方便有效地使用。

      九、扩展 PhoneGap

      到目前为止,我们已经看到 PhoneGap 有两个部分

      1. 我们从 PhoneGap 应用中调用的 JavaScript 部分
      2. 我们在 PhoneGap 项目中包含的一个本机部分,用于公开本机电话功能。

      这两个部分适用于我们希望访问常见电话功能的情况,包括以下内容:

      1. 照相机
      2. 加速计
      3. 文件系统
      4. 地理位置
      5. 存储服务

      然而,我们经常需要超越这些特性。

      JavaScript 限制

      我们已经看到 JavaScript 在过去十年中性能有所提高;它比五年前快了 100 倍。然而,即使这是真的,有时应用需要做大量的照明,在后台做事情,或者做复杂的操作。出于性能原因,这些最好在本机代码中完成。

      例如,如果我们想下载一个多部分文件,它涉及到并行下载文件的不同部分,然后检查其校验和。这一部分最好用 Java 为 Android 编写,用 Objective-C 为 iPhone 编写。

      如果你还记得第一章的话,我们说过 PhoneGap 是 JavaScript 世界和本地世界之间的桥梁。整个 PhoneGap 框架是基于插件架构的。这意味着 PhoneGap 提供了一种将 JavaScript 函数(以及参数、返回类型和回调)映射到本机代码的机制。

      我们可以向 PhoneGap 应用添加本机代码,并使用 JavaScript 轻松公开代码。为此,我们需要两部分

      1. 完成繁重工作的本机代码
      2. 公开此本机代码的 JavaScript 代码

      两者都被 PhoneGap 框架粘合在一起。

      建筑

      PhoneGap 架构如图 9–1 所示。正如我们所观察到的,PhoneGap 有两个部分:PhoneGap JavaScript 引擎和 PhoneGap 原生引擎。我们将本机代码作为插件添加到 PhoneGap 本机引擎,并将 JavaScript 代码作为插件添加到 PhoneGap JavaScript 引擎。

      images

      图 9–1。 PhoneGap 架构

      范围

      本章重点介绍如何扩展 PhoneGap 功能,以暴露更多的本机代码。

      但是,请注意,即使您编写了 PhoneGap 插件,注入插件的唯一方法是将插件源代码添加到您的项目中。目前还没有办法将插件构建到包中,并将包添加到 PhoneGap 项目中。当您使用 PhoneGap build 时,这会阻止您使用自定义插件。

      对于这一章,让我们保持插件的本质非常简单。我们称之为 helloworld 插件。我们向插件传递一个名称,然后我们得到一个字符串“Hello !现在的时间是”。

      这样,我们主要关注插件的桥方面。

      为 Android 扩展 PhoneGap

      首先,我们将插件创建为 Android PhoneGap 应用的一部分,然后将插件提取出来。这是必需的,因为

      1. 该插件需要 PhoneGap jar。
      2. 我们需要测试插件。

      一个插件有两个部分,PhoneGap 框架(桥)的两边各有一个。我们有一个原生部分(一个类扩展插件)和一个使用 PhoneGap 的 JavaScript 框架的 JavaScript 文件。

      在我们开始之前,让我们创建一个 Android PhoneGap 项目(参见图 9-2)。第二章中有说明。

      images

      图 9–2。 新安卓项目

      然后我们需要为 PhoneGap 配置基本的 Android 项目。

      1. 更改 MainScreen 类以扩展 DroidGap。
      2. 将 PhoneGap jar 添加到类路径中。
      3. 将 PhoneGap JavaScript 库添加到 assets/www 文件夹。

      Android 项目看起来如图 9–3 所示。

      images

      图 9–3。 安卓项目结构

      声明插件的本机部分

      现在我们为插件添加一个合适的包,比如“org.examples.phonegap..plugins.simpleplugin”然后我们声明一个名为 Simple Plug-in 的类,它扩展了 PhoneGap 的 com.phonegap.api.Plugin 类,如图 Figure 9–4 所示。

      images

      图 9–4。 声明插件的原生部分

      一旦你点击“完成”按钮,你将得到如下所示的代码

      `package org.examples.phonegap.plugins.simpleplugin;

      import org.json.JSONArray;

      import com.phonegap.api.Plugin;
      import com.phonegap.api.PluginResult;

      /**
       * @author rohit
       *
       */
      public class SimplePlugin extends Plugin {

      /* (non-Javadoc)          * @see com.phonegap.api.Plugin#execute(java.lang.String, org.json.JSONArray,
      java.lang.String)
               */
              @Override
              public PluginResult execute(String action, JSONArray data, String callbackId) {
                      // TODO Auto-generated method stub
                      return null;
              }

      }`

      当我们扩展com.phonegap.api.Plugin类时,我们必须实现 execute 方法。execute 方法的参数是

      1. 动作:要执行的动作。例如,对于基于文件的插件,可以是打开、关闭、读取、写入等。
      2. 数据:插件 JavaScript 端传过来的数据。这是从 PhoneGap 的 JavaScript 应用传递到本机代码的数据。例如,对于基于文件的插件,可以是文件名、数据等。
      3. CallbackId :回调 JavaScript 函数时使用。

      execute 方法的返回类型是 PluginResult。PluginResult 通常接受一个状态枚举和一个描述原因或更多信息的其他参数。

      例如,新的 PluginResult(状态。OK);

      状态枚举有许多值;所有这些都描述如下(名称不言自明)

      1. 没有结果
      2. 类未找到异常
      3. 非法访问异常
      4. 实例化 _ 异常
      5. 格式错误 _URL_EXCEPTION
      6. io _ exception(io _ 异常错误)
      7. 无效 _ 操作
      8. JSON _ exception(JSON _ 异常)
      9. 错误

      下面是 hello 插件的实现,它接受一个名称并返回“Hello !时间是

      `package org.examples.phonegap.plugins.simpleplugin;

      import java.util.Date;

      import org.json.JSONArray;
      import org.json.JSONException;

      import com.phonegap.api.Plugin;
      import com.phonegap.api.PluginResult;
      import com.phonegap.api.PluginResult.Status;

      /**
       * @author rohit
       *
       */
      public class SimplePlugin extends Plugin {

      public static String ACTION_HELLO="hello";

      /*
               * (non-Javadoc)
               *
               * @see com.phonegap.api.Plugin#execute(java.lang.String,
               * org.json.JSONArray, java.lang.String)
               */
              @Override
              public PluginResult execute(String action, JSONArray data, String callbackId) {
                       PluginResult pluginResult = null;
                       if (ACTION_HELLO.equals(action)) {

      String name = null;
                               try {
                                       name = data.getString(0);

      String result = "Hello " + name + "! The time is "
                                                       + (new Date()).toString();

      pluginResult = new PluginResult(Status.OK, result);

      return pluginResult;
                               } catch (JSONException e) {
                                       pluginResult = new PluginResult(Status.JSON_EXCEPTION,
       "missing argument name");
                               }
                       } else {
                               pluginResult = new PluginResult(Status.INVALID_
                              ACTION,
                                                "Allowed actions is hello");
                       }
                       return pluginResult;
              }
      }`

      您可以在上面的代码中看到,在处理请求之前,我们显式地检查了一个操作。如果动作不是由插件处理的,我们返回Status.INVALID_ACTION。第二个检查是针对参数的。如果我们在获取第一个字符串参数时得到任何 JSON 异常,我们返回Status.INVALID_JSON。当动作和参数正确时,我们创建一个字符串“Hello < name >!时间是<时间>,用Status.OK返回。

      请注意,您不必从这个方法中产生任何线程。您的整个方法可以是同步的。这将不会传递调用该代码的 JavaScript 插件调用。这是由 PhoneGap 内部处理的,这就是为什么我们在 JavaScript 中有成功和失败回调(您将在下一节看到)。

      声明插件的 JavaScript 部分

      这个插件的 JavaScript 部分是在一个名为 simpleplugin.js 的文件中声明的。

      1. Plug-in Registration

        在 PhoneGap 插件的 JavaScript 部分,事情是从调用在 PhoneGap 中添加插件开始的。

        PhoneGap.addConstructor(**function**() {                 // Register the <ins>Javascript plug-in</ins> with PhoneGap                 PhoneGap.addPlugin('SimplePlugin', **new** SimplePlugin()); });

        插件在/res/xml/plugins.xml 文件中注册。将以下 XML 元素添加为 plugins.xml 文件中“plugins”元素的子元素:

        <plugin name="SimplePlugin" value="org.examples.phonegap.plugins.simpleplugin.SimplePlugin" />

        注意:这里我们做了两件事

        1. 将 JavaScript 对象注册为名为“SimplePlugin”的插件
        2. 将 PhoneGap Java 类注册为名为“SimplePlugin”的服务您可以将此视为类名“org.examples.phonegap.plugins.simpleplugin.Simple Plugin.”的别名
      2. Create the JavaScript object SimplePlugin.

        这是通过声明一个 JavaScript 函数来完成的。

        var SimplePlugin = function() { }

      3. Add a plug-in function.

        在这一步中,我们将添加插件函数,我们的 JavaScript 将调用该函数。在下面的函数中,我们实际上将调用委托给本机 PhoneGap 桥,要求它实际调用“SimplePlugin”服务,即“org.examples.phonegap.plugins.simpleplugin.SimplePlugin”类。此外,我们注册了两个回调:一个是调用成功时的回调,另一个是调用失败时的回调。然后我们声明我们想要调用的动作。您可能还记得,我们的插件类中有处理“hello”服务的代码。最后,记住我们插件类的执行方法需要一个参数JSONArray;在这里,我们将其作为[name]传递。

      `SimplePlugin.prototype.hello = function(name, successCallback, failureCallback) {

      PhoneGap.exec(
      successCallback, // Success Callback
      failureCallback, // Failure Callback
      SimplePlugin’,  // Registered plug-in name
      ‘hello’, // Action
      [name] //Argument passed in
      );
           };`

      完整的 JavaScript 文件 simpleplugin.js 如下所示:

      `/**
       *
       * @return Instance of SimplePlugin
       * /
      var SimplePlugin = function() {

      }

      /**
       * @param name
       *              The name passed in
       * @paramsuccessCallback
       *            The callback that will be called when simple plugin runs
       *            successfully
       * @paramfailureCallback
       *            The callback that will be called when simple plugin
       *            fails
       */
      SimplePlugin.prototype.hello = function(name, successCallback, failureCallback) {
        PhoneGap.exec(successCallback, // Success Callback
                     failureCallback, // Failure Callback
                     'SimplePlugin',  // Registered Plug-in name
                     'hello',                   // Action
                     [ name ]);        // Argument passed in
      };

      /**
       *


         *
      • Register the Simple Listing Javascript plugin.

      */
      PhoneGap.addConstructor(function() {
              // Register the Javascript plug-in with PhoneGap
              PhoneGap.addPlugin('SimplePlugin', new SimplePlugin());
      });`

      调用插件

      是时候测试我们的插件了。为此,我们需要以下内容:

      1. HTML 文件
      2. PhoneGap js 文件
      3. 插件 js 文件
      4. 插件 Java 文件

      您的 Android 项目应该如 Figure 9–5 所示。

      images

      图 9–5。 Android PhoneGap 插件项目结构

      您的 index.html 文件应该如下所示:

      `


              PhoneGap
              
              script type="text/javascript" charset="utf-8" src="simpleplugin.js">
              
          


              


                  Simple Plugin Demo
              


              
                  
                      
                      
                  
                  
                      
                      
                  
                  
                      
                  
              

                          Enter Name
                      

                          
                          
                      

                          
                              Output:
                          

                      

                          

                          

                      

                          
                      

          

      `

      在这里,您应该注意到插件的调用如下。我们首先传递包含名称的文本,然后注册一个成功的回调和一个失败的回调。

      window.plugins.SimplePlugin.hello(     text,     //success callback     function (result) {         output.innerHTML = result;     },     //failure callback     function (err) {         output.innerHTML = "Failed to invoke simple plugin";     } );

      最后,当我们运行这个 Android 项目时,我们会看到下面的输出,如 Figure 9–6 所示。

      images

      图 9–6。Android 上的 PhoneGap 插件输出

      分享 Android PhoneGap 插件

      就 PhoneGap framework 1 . 1 . 0 版(写这本书时的版本)而言,没有办法打包和共享你的插件。

      共享插件的唯一方式是通过

      1. 共享 Java 源文件
      2. 共享 JavaScript 源文件
      3. 自述文件,说明如何使用插件

      PhoneGap 插件通常在[github.com/phonegap/phonegap-plugins](https://github.com/phonegap/phonegap-plugins).上传。如果您希望贡献您的工作,您可以与 PhoneGap 团队合作,将您的插件添加到这个存储库中。

      为 iPhone 扩展 PhoneGap

      PhoneGap 为 XCode 提供插件,用于创建基于 PhoneGap 的应用。在写这本书的时候,PhoneGap 从 0.9.5 版本升级到了 1.1.0 版本。iPhone-PhoneGap 插件框架有一些变化。本章重点介绍 1.1.0 插件开发。

      安装 1.1.0 XCode 扩展的步骤:

      1. 下载 PhoneGap 1.1.0 zip 并解压。
      2. 转到 iOS 文件夹,安装 PhoneGapInstaller.pkg。

      安装 PhoneGap 的 1.1.0 XCode 插件后,从 XCode 创建一个基于 PhoneGap 的应用,如图图 9–7 和图 9–8 所示。

      images

      图 9–7。 创建新的 iOS PhoneGap 项目

      images

      图 9–8。 创建一个新的 iOS PhoneGap 项目

      按照第三章中的步骤将 www 文件夹添加到项目中。现在运行项目,并确保您能够看到基于 iPhone PhoneGap 的应用。

      声明插件的本机部分

      PhoneGap 1.1.0 插件的本机部分需要添加到插件文件夹中。这在图 9–9 中进行了描述。

      images

      图 9–9。 iPhone 插件原生部分

      在插件文件夹中创建一个 Objective-C 类。让我们把这个类命名为 SimplePlugin。SimplePlugin 扩展了 PGPlugin。SimplePlugin.h 文件如下所示。

      `#import <Foundation/Foundation.h>

      ifdef PHONEGAP_FRAMEWORK

      import <PhoneGap/PGPlugin.h>

      else

      import "PGPlugin.h"

      endif

      @interface SimplePlugin :PGPlugin {

      }
      /**
       * Sets the idleTimerDisable property to true so that the idle timeout is disabled
       */

      • (void) hello:(NSMutableArray)arguments withDict:(NSMutableDictionary)options;

      @end`

      这里我们声明一个函数名“hello”,它有如下签名:

      - (void) hello:(NSMutableArray*)arguments withDict:(NSMutableDictionary*)options;

      这个函数不返回任何东西。相反,它需要两个参数

      1. 争论
      2. 选择

      插件或输入(在我们的例子中是名称)的任何参数都使用“参数”传递

      现在让我们实现 SimplePlugin 的 hello 函数。在第一个版本中,我们将从插件返回一个字符串“hello world”。此外,我们将解释如何访问传递的参数,以及如何调用成功和失败的回调。

      注意插件是从 JavaScript 调用的,如下所示:

      window.plugins.SimplePlugin.hello(         “Bob”, //success callback function(result){                 alert(“plugin returned “+result); }, //failure callback, function(err){                 alert(“got error when invoking the plugin”);         } );

      下面是插件方法的框架代码。

      插件可以访问输入参数,在我们的例子中是“Bob”,从 arguments 对象中提取它。注意,arguments 数组中的第一个对象总是 callbackId,用于回调 JavaScript 回调函数。我们可以从索引 1 开始提取实际参数(在我们的例子中,只有“Bob”)。

      NSString * name = [arguments objectAtIndex:1];

      如果我们有另一个参数,我们将在索引 2 处访问它。

      现在让我们关注如何调用成功或失败的 JavaScript 回调函数。这从 PluginResult 对象的声明开始。接下来声明另外两个对象,一个用于 callbackId(帮助我们调用回调函数),另一个是一个字符串,JavaScript 字符串,我们将把它嵌入到 HTML 页面中以实际调用回调。

      NSString* jsString = nil; NSString* callbackId = [arguments objectAtIndex:0];

      现在让我们来看一下成功和失败条件的代码流。这显示在下面的代码中。

      如果一切顺利,我们创建一个状态为 PGCommandStatus_OK 的结果对象。然后我们从结果中创建 jsString 对象,传递 callbackId。最后,我们编写 JavaScript,通过调用[self writeJavascript:jsString]来实际调用成功回调。

      在失败的情况下,我们创建一个状态不是 PGCommandStatus_OK 的 PluginResult 对象,并为错误/失败回调创建 jsString。最后,我们使用[self:write JavaScript:jsString]调用错误/失败回调:

      `PluginResult* result=nil;
      NSString* jsString=nil;
      NSString* callbackId=[argumentsobjectAtIndex:0];

      if(success){
      result=[PluginResultresultWithStatus:PGCommandStatus_OK];
         jsString=[resulttoSuccessCallbackString:callbackId];
      }
      else{
      result=[PluginResultresultWithStatus:PGCommandStatus_ILLEGAL_ACCESS_EXCEPTION];
         jsString=[resulttoErrorCallbackString:callbackId];
      }

      [selfwriteJavascript:jsString];`

      如果我们希望在调用成功或失败回调时传递数据,我们可以通过在创建 PluginResult 对象时传递一个附加参数来实现。这里我们通过调用 PluginResult 的 resultWithStatus:messageAsString 函数来传递一个字符串。

      result = [PluginResultresultWithStatus:PGCommandStatus_OK messageAsString:@”Hello World”];

      完整的 SimplePlugin 如下所示。注意,这里我们没有负路径,因此,我们只为成功回调创建 jsString。

      `#import "SimplePlugin.h"

      @implementation SimplePlugin

      • (void) hello:(NSMutableArray)arguments withDict:(NSMutableDictionary)options {
            PluginResult* result = nil;
            NSString* jsString = nil;
            NSString* callbackId = [arguments objectAtIndex:0];
            NSString* name = [arguments objectAtIndex:1];
            NSDate* date = [NSDate date];
            NSDateFormatter* formatter = [[[NSDateFormatteralloc] init] autorelease];

      //Set the required date format

      [formatter setDateFormat:@"yyyy-MM-ddhh:mm:ss"];

      //Get the string date

      NSString* dateStr = [formatterstringFromDate:date];

      NSString* returnStr = [NSStringstringWithFormat:@"Hello %@.The time is  %@!",
      name,dateStr];

      result = [PluginResultresultWithStatus:PGCommandStatus_OK
      messageAsString:returnStr];
          jsString = [result toSuccessCallbackString:callbackId ];

      [selfwriteJavascript:jsString];
      }
      @end`

      只是创造了。h 和。m 文件并将文件放在插件文件夹中是不够的。我们需要向 PhoneGap 框架注册我们的 SimplePlugin。在 Supporting Files 文件夹中的 PhoneGap.plist 文件中添加一个条目就可以做到这一点。

      这显示在图 9–10 中。

      images

      图 9-10。 注册 PhoneGap 插件

      声明插件的 JavaScript 部分

      iPhone 插件的 JavaScript 部分和你见过的 Android 不同。

      这主要分两步完成

      1. 声明一个名为 SimplePlugin 的 JavaScript 类,并向其添加一个方法,在我们的示例中为“hello”。在 hello 函数中,我们将 JavaScript 参数映射到目标插件类和方法。
      2. 第二部分是为 SimplePlugin 创建一个方法 install,通过调用 phonegap . add constructor(simple plugin . install)注册 JavaScript 插件;

      我们先来关注一下插件的 hello 功能。注意,我们在插件内部调用 PhoneGap.exec 函数。

      继 PhoneGap.exec 的签名之后

      PhoneGap.exec(<<successCallback>>,<<failureCallback>>,<<Plugin Name>>,<<Action Name>>,<<Arguments Array>>)

      注意我们如何将 hello 函数的第一个参数“name”作为参数数组的一部分传递。successCallback 和 errorCallback 作为 PhoneGap.exec 函数的第一个和第二个参数。插件类和方法名作为第三和第四个参数。

      SimplePlugin.prototype.hello = function(name,successCallback, errorCallback) {     PhoneGap.exec(         successCallback,         errorCallback, "SimplePlugin",         "hello",         [name]); };

      JavaScript 部分的完整代码如下所示。

      `if (!PhoneGap.hasResource("simpleplugin")) {
          PhoneGap.addResource("simpleplugin");

      /**
           * @returns instance of powermanagement
           */

      function SimplePlugin() {};

      /**
           *
           * @param name Given the name, successCallBack gets the string "Hello ! The
      time is

      /**
           * Register the plug-in with PhoneGap
           */
          SimplePlugin.install = function () {
              if (!window.plugins) window.plugins = {};

      window.plugins.SimplePlugin = new SimplePlugin();

      return window.plugins.SimplePlugin;
          };

      PhoneGap.addConstructor(SimplePlugin.install);
      }`

      调用插件

      为了测试插件,我们将创建一个 PhoneGap 应用,并从那里调用插件。这部分和安卓的一模一样。

      你需要遵循这些步骤

      1. 包括 PhoneGap 1.1.0 js 文件。
      2. 包括 simpleplugin.js 文件。
      3. 注册按钮点击调用插件。
      4. 注册成功和失败回调以显示结果。

      index.html 的完整源代码如下:

      `


              PhoneGap
              
              
              
          


              


                  Simple Plugin Demo
              


              
                  
                      
                       
                               
                      
                      
                  
                  
                      
                  
              

                          Enter Name
                      

                          
                          
                      

                          
                              Output:
                          

                      

                          

                          

                      

                          
                      

          

      `

      当您运行 PhoneGap 示例时,您将看到如图 Figure 9–11 所示的应用。

      images

      图 9–11。??【PhoneGap】插件输出

      共享 iPhone PhoneGap 插件

      您需要共享以下文件来共享插件。

      1. SimplePlugin.h
      2. 简单插头.m
      3. 简单插头.js

      将上述文件列表添加到插件文档中。还要记录如何从 JavaScript 调用插件。

      为 BlackBerry 扩展 PhoneGap

      与 Android 类似,PhoneGap 针对 BlackBerry 的插件有两个部分,分别位于 PhoneGap 框架(桥)的两侧。我们有一个原生部分(一个类扩展 PhoneGap 的插件)和一个使用 PhoneGap 的 JavaScript 框架的 JavaScript 文件。

      我们假设您使用的是高于 1.5 版的 BlackBerry WebWorks SDK 版本。

      我们假设 BlackBerry WebWorks SDK 安装在 C:\BBWP 上,我们安装了 Java 1.6 SDK 和 Ant,并且在 path 中。我们还假设我们的开发目录是 D:\PhoneGap-Plugin,我们在 D:\ PhoneGap-Plugin \ PhoneGap-1 . 1 . 0 目录中有 PhoneGap SDK。参考第三章回忆 BlackBerry PhoneGap 开发的系统要求。

      创建和测试 BlackBerry 插件的步骤如下:

      1. 创建插件 Java 文件,并将其转储到 PhoneGap SDK 的框架中。
      2. 创建 BlackBerry PhoneGap 项目来测试插件。
      3. 然后编译 BlackBerry PhoneGap 项目来编译插件 Java,您将它转储到 PhoneGap SDK 的框架文件夹中。如果出现编译错误,您需要删除您在步骤 2 中创建的项目,修复 Java 文件,并从步骤 2 开始重复。
      4. 当插件 Java 文件编译无误时,您就可以转储 JavaScript 插件文件并编写 HTML 页面来使用该插件。

      声明插件的本机部分

      BlackBerry 插件类与 Android 插件类非常相似。唯一不同的是 BlackBerry 插件类使用 PhoneGap 1.1.0 框架;因此,有几个不同之处。

      以下是 BlackBerry 插件类的框架:

      `package com.phonegap.plugins;

      import com.phonegap.api.Plugin;
      import com.phonegap.api.PluginResult;

      import java.util.Date;
      import com.phonegap.json4j.JSONArray;

      public class HelloWorldPlugin extends Plugin {

      private static final String ACTION_HELLO="hello";

      /**
           * Executes the requested action and returns a PluginResult.      *
           * @param action     The action to execute.
           * @paramcallbackIdThe callback ID to be invoked upon action completion.
           * @paramargsJSONArry of arguments for the action.
           * @return           A PluginResult object with a status and message.
           */
          public PluginResult execute(String action, JSONArray data, String callbackId) {
                      return null;
          }

      /**
           * Called when the plug-in is paused.
           */
          public void onPause() {

      }

      /**
           * Called when the plug-in is resumed.
           */
          public void onResume() {

      }

      /**
           * Called when the plug-in is destroyed.
           */
          public void onDestroy() {

      }
      }`

      注意,我们不是将这个插件类转储到我们的项目区域,而是转储到 PhoneGap SDK 区域。Figure 9–12 中的屏幕截图显示了我们复制这个插件类的位置。您可能需要创建插件文件夹。

      images

      图 9–12。?? 插件的原生部分

      这里的主要问题是,在继续之前,您需要在上面的目录中有一个适当的 Java 插件类(它可以编译)。我们会引导你度过这一关。

      下一步是创建 BlackBerry WebWorks PhoneGap 项目。

      $>D: $>cd d:\PhoneGap-plugin\phonegap-1.1.0 $>ant create -Dproject.path=D:\PhoneGap-Plugin\BB-Plugin-Test

      这将向您显示图 9–13 中所示的目录。

      images

      图 9–13。 PhoneGap 黑莓项目结构

      现在让我们确保我们的插件类是否编译。

      $>cd D:\PhoneGap-Plugin\BB-Plugin-Test $>ant build

      如果上述步骤在 HelloWorldPlugin 中显示了一些编译错误,您需要

      1. 修复那些编译错误。
      2. 删除位于 D:\ PhoneGap-Plugin \ b B- Plugin-Test 的项目。
      3. 使用 Ant create-D project . path = D:\ PhoneGap-Plugin \ b B- Plugin-Test 重新创建项目。
      4. 使用“ant build”检查编译。

      现在,您已经解决了编译空白 Java 插件类的问题,让我们在其中放一些代码。

      下面是插件类的完整代码(这与 Android 插件非常相似)。我们公开一个名为“hello”的动作,并期望一个名为“name”的参数假设有人用“Rohit”这个名字叫出了“hello”这个动作,我们会返回“Hello Rohit!时间是。”

      `package com.phonegap.plugins;

      import com.phonegap.api.Plugin;
      import com.phonegap.api.PluginResult;

      import java.util.Date;
      import com.phonegap.json4j.JSONArray;

      public class HelloWorldPlugin extends Plugin {

      private static final String ACTION_HELLO="hello";

      /**
           * Executes the requested action and returns a PluginResult.
           *
           * @param action     The action to execute.
           * @paramcallbackIdThe callback ID to be invoked upon action completion.
           * @paramargsJSONArry of arguments for the action.
           * @return           A PluginResult object with a status and message.
           */
      public PluginResult execute(String action, JSONArray data, String callbackId) {
              PluginResult pluginResult=null;
          if (ACTION_HELLO.equals(action)) {

      String name;
                               try {
                                      name = data.getString(0);
                                      String result = "Hello " + name
                                                  + "! The time is "
                                                + (new Date()).toString();
                                      pluginResult =
                                                 new PluginResult(PluginResult.Status.OK,
      result);
                                      returnpluginResult;
                              } catch (Exception e) {
                                      pluginResult =                                             new
      PluginResult(PluginResult.Status.JSONEXCEPTION,  
                                                      "missing argument name");
                              }

      } else {
                              pluginResult =
                                    new PluginResult(PluginResult.Status.INVALIDACTION,
                                                      "Allowed actions is hello");
                  }
              return pluginResult;
          }

      /**
           * Called when the plug-in is paused.
           */
          public void onPause() {

      }

      /**
           * Called when the plug-in is resumed.
           */
          public void onResume() {

      }

      /**
           * Called when the plug-in is destroyed.
           */
          public void onDestroy() {

      }
      }`

      请注意,您必须在 PhoneGap 的框架中再次转储修改后的 HelloWorldPlugin.java,如图 9–13 所示。您还必须使用 Ant create-D project . path = D:\ PhoneGap-Plugin \ b B- Plugin-Test 删除并重新创建项目,以测试插件。

      声明插件的 JavaScript 部分

      同样,插件的 JavaScript 部分与插件的 Android JavaScript 部分非常相似。在这种情况下,我们在函数声明中声明所有内容,并调用它。

      (function () {     var HelloWorld = function () {             return {                 hello: function (message, successCallback, errorCallback) {                     PhoneGap.exec(successCallback, errorCallback, 'HelloWorldPlugin', 'hello', [message]);                 }             }         }; `    PhoneGap.addConstructor(function () {
              // add the plug-in to window.plugins
              PhoneGap.addPlugin('simpleplugin', new HelloWorld());

      // register the plug-in on the native side
              phonegap.PluginManager.addPlugin('HelloWorldPlugin', 'com.phonegap.plugins.HelloWorldPlugin');
          });
      })();`

      第一步是创建一个名为 HelloWorld 的 JavaScript 对象,并在其中声明一个名为“hello”的函数。该函数在内部调用 PhoneGap 注册的服务,该服务又调用实际的本地类。

      现在我们有了这个对象,它将从我们的 HTML 中被调用,我们需要将这个对象注册为 PhoneGap JavaScript 插件。我们还需要将服务名“helloworldplugin”映射到类“com . phonegap . plugins . hello world plugin”,所有这些都是在 PhoneGap.addConstructor()调用中完成的。

      我们使用 PhoneGap.addPlugin()将“simpleplugin”名称映射到 JavaScript 插件对象。这将插件公开为 windows.plugins.simpleplugin。

      然后我们用 phonegap。PluginManager.addPlugin()将服务名映射到实际的 Java 类。

      这就完成了我们创建插件的 JavaScript 部分。我们将把这个 JavaScript 放在项目的 www 目录中。

      调用插件

      为了调用插件,我们修改了项目 www 目录中的 index.html 文件。

      这与我们之前为 Android 和 iPhone 所做的非常相似。

      下面是用于调用我们的插件的代码片段:

      window.plugins.simpleplugin.hello(     document.getElementById("name").value,     //success callback     function (message) {         document.getElementById("output").innerHTML = message;     },     //failure callback     function () {         log("Call to plugin failed");     } );

      正如我们前面所做的,我们提供名称;在这种情况下,名称来自输入类型文本元素。然后我们提供一个成功回调和一个失败回调。在成功回调中,我们在 id 为“output”的 div 中设置返回值。

      以下是 index.html 页面的完整代码:

      `


              
              
              
              
              
              
          


              


                  Simple Plugin Demo


              
                  
                      
                      
                  
                  
                      
                      
                  
                  
                      
                  
              

                          Enter Name
                      

                          
                          
                      

                          
                              Output:
                          

                      

                          

                          

                      

                          
                      

              

                  ...
              

          

      `

      最后一步是运行 WebWorks BlackBerry 项目。转到命令提示符,转到项目目录,并运行以下命令:

      $>ant build load-simulator

      这将打开黑莓模拟器,你可以看到我们的应用在里面运行。在文本框中输入一个值,然后点击按钮。您将看到如图图 9–14 所示的结果。

      images

      图 9–14。??【PhoneGap】插件输出

      共享 BlackBerry PhoneGap 插件

      要共享插件,您需要发布两个文件

      1. helloworldplugin.java
      2. helloworld.js

      将上述内容添加到关于如何从 JavaScript 调用插件的文档中。

      结论

      虽然 JavaScript 对于开发跨移动应用来说是一种快速而灵活的语言,但在实现复杂的处理和后台工作时,JavaScript 有某些固有的局限性。有时有必要使用本机代码来执行繁重的工作。

      PhoneGap 的架构允许我们扩展其插件,为我们的 PhoneGap 应用引入本机代码。

    posted @   绝不原创的飞龙  阅读(113)  评论(0编辑  收藏  举报
    相关博文:
    阅读排行:
    · DeepSeek 开源周回顾「GitHub 热点速览」
    · 物流快递公司核心技术能力-地址解析分单基础技术分享
    · .NET 10首个预览版发布:重大改进与新特性概览!
    · AI与.NET技术实操系列(二):开始使用ML.NET
    · 单线程的Redis速度为什么快?
    点击右上角即可分享
    微信分享提示