Phonegap 应用程序开发

作者:Mike Diehl

您听过多少次“有个应用程序可以做到那个”?但有时,实际上并没有“可以做到那个的应用程序”,或者现有的应用程序不符合您的需求。作为 Linux 用户,我们倾向于解决自己的难题,如果这意味着我们需要编写一些代码来做到这一点,那就这样吧。然而,编写在安卓手机或平板电脑上运行的代码有一定的学习曲线,在苹果产品上则更糟。幸运的是,Phonegap 提供了一种简单的方法来为安卓、iPhone、WebOS、Blackberry 和 Windows Phone 等创建独立的应用程序。您只需要精通 HTML、JavaScript 和 CSS,就可以为目前使用的大多数智能手机开发原生应用程序。而且,相同的代码库可以在任何 Web 浏览器上运行(存在明显的局限性)。

为安卓开发原生代码相对容易。您需要学习使用安卓基于 XML 的屏幕布局机制,并且您需要学习 Java。对于 iPhone,您需要学习 Objective C。如果您想为 Windows Phone 开发,您还需要学习 C#。相反,您可以简单地使用 Phonegap 并维护一个 HTML/JavaScript/CSS 的单一代码库。这绝对是“不费脑筋”的选择。

在我深入探讨之前,我需要澄清一个潜在的混淆来源。Phonegap 最初是由一家名为 Nitobi 的公司开发的,该公司后来被 Adobe 收购。2011 年,Nitobi/Adobe 将 Phonegap 代码库捐赠给了 Apache 基金会。由于这项贡献,他们需要确保知识产权不受商标歧义的影响,因此他们将 Phonegap 项目重命名为 Cordova。Apache 基金会正在进行从 Phonegap 到 Cordova 的迁移,因此我在此处将此项目称为 Cordova。

在安卓上开始使用 Cordova 并不困难。冒着重复其他地方有详细记录的材料的风险,我将简要概述所涉及的过程。首先,您必须安装安卓 SDK,这是一个可以从安卓网站免费下载并且文档非常完善的软件开发工具包。安卓 SDK 与 Eclipse IDE 集成,因此您需要安装一个相当新的 Eclipse 版本。SDK 文档将引导您完成整个过程,从下载软件到构建和运行示例应用程序。SDK 允许您在模拟器中或在真正的安卓设备上运行您的程序(如果您有的话)。

安装 Cordova 也相当简单且文档齐全。我在整个过程中遇到的唯一困难是我不太熟悉 Eclipse,并且有些磕磕绊绊。Cordova 安装过程的最终步骤是构建和运行示例 Cordova 应用程序。示例应用程序演示了 Cordova 的大部分 API,值得一看。

我发现创建一个新的 Cordova 项目的过程有点笨拙。该过程首先涉及创建一个新的安卓项目,然后对一个 Java 程序进行两行修改,将十几行 XML 粘贴到另一个文件中,嗯,您明白了。所有更改都很有道理,但似乎容易出错。最后,我决定复制示例项目并将其精简到最基本的需求。我推荐这种方法;它对我很有效。

一个 Cordova 应用程序有三个主要部分。有一个特定于架构的二进制部分,它实际上直接与设备的硬件通信。然后是一个基于 Java 的抽象层,它设置应用程序的运行时环境并为您的应用程序提供 JavaScript API 以供使用。第三部分是您的 HTML/JavaScript/CSS 代码,这是您通常唯一需要关心的部分。所有这些部分在构建时链接在一起,形成目标设备的本机二进制可执行文件。

Cordova JavaScript API 允许您的程序访问主机设备的许多传感器。这意味着您的应用程序可以轻松访问设备的 GPS、加速度计、指南针、麦克风和扬声器。API 通过允许访问设备的联系人数据库、其文件系统和本机 SQLite 数据库来提供持久性数据存储。

让我们看一些代码。

为了便于说明,我开发了一个简单的应用程序。该应用程序旨在演示三个主要功能:访问设备的 GPS 传感器、访问用户的联系人以及能够对远程 Web 服务进行 Ajax 调用。

创建此应用程序所需的 HTML 非常简单。请参见列表 1。

列表 1. 示例应用程序的 HTML

<html>
<head>
<h3>Sample Application</h3>
<meta name="viewport" content="width=320; user-scalable=no" />
<meta http-equiv="Content-type" content="text/html; charset=utf-8">
<link rel="stylesheet" href="./master.css" type="text/css" 
 ↪media="screen" title="no title">

<script type="text/javascript" charset="utf-8"
 ↪src="cordova-1.9.0.js"></script>
<script type="text/javascript" charset="utf-8" 
 ↪src="main.js"></script>
</head>

<body style="{background: beige;}">
<img id="picture" height="100px" width="200px" border="0"
 ↪src="https://linuxjournal.cn/files/linuxjournal.com/
↪ufiles/logo-lj.jpg">
<br>
<p>
You are here:<br>
<input name="lon", id="lon" size="15"> Longitude, <br>
<input name="lat", id="lat" size="15"> Latitude.<br>
<p>
<input id="id" name="id"><br>
<input id="name" name="name"><br>
<input id="phone" name="phone"><br>
<input id="email" name="email"><br>
<button onclick="previous_contact();">Previous</button>
<button onclick="next_contact();">Next</button>
<p>
<hr>
"<span id="quote">Linux Rocks!</span>"<br>
<span id="error"></span><br>
<hr>
</body>
</html>

<head> 部分的内容主要是样板代码。请注意,您导入了 cordova.js,然后是您的 main.js JavaScript 文件,并且顺序很重要。在 <body> 中,您会找到一个从远程服务器引入的图形。然后您会看到当前 GPS 坐标的输入字段。接下来,您有一些表单字段,其中将包含来自手机联系人目录的信息,然后是“上一页”和“下一页”按钮,允许用户滚动浏览他们的联系人。最后,有两个 <span>,程序将使用它们来显示来自远程网站的俏皮评论和可能需要显示的任何错误消息。图 1 显示了该页面在浏览器中的外观。

图 1. 在浏览器中运行的示例应用程序

列表 2 显示了使其全部工作的 JavaScript 代码。

列表 2. 示例应用程序的 JavaScript 代码

1 var mobile = 1;
2 var contacts;
3 var current_contact = 0;
4
5 function init () {
6
7 if (mobile == 1) {
8 navigator.contacts.find(["*"], store_contacts);
9 }
10
11 update();
12 window.setInterval(update, 1000);
13 }
14
15 function update () {
16 var req;
17
18 if (mobile == 1) {
19 navigator.geolocation.getCurrentPosition
↪(set_location,location_error);
20 } else {
21 document.getElementById("lat").value = 
 ↪Math.floor(Math.random()*46)
22 document.getElementById("lon").value = 
 ↪Math.floor(Math.random()*46)
23 }
24
25 req = new XMLHttpRequest();
26
27 if (!req) {
28 alert("Ajax failed!");
29 return false;
30 }
31
32 req.open("GET", "http://example.com/test.html", true);
33 req.onreadystatechange = set_quote;
34 req.send(null);
35
36 return true;
37 }
38
39 function store_contacts (c) {
40 contacts = c;
41 display_contact();
42 return true;
43 }
44
45 function previous_contact () {
46 current_contact = current_contact - 1;
47 if (current_contact < 0) { current_contact = 0; }
48 display_contact();
49 return true;
50 }
51
52 function next_contact () {
53 current_contact = current_contact + 1;
54 if (current_contact > (contacts.length-1)) { current_contact =
 ↪contacts.length-1; }
55 display_contact();
56 return true;
57 }
58
59 function display_contact () {
60 document.getElementById("id").value = " ";
61 document.getElementById("name").value = " ";
62 document.getElementById("phone").value = " ";
63 document.getElementById("email").value = " ";
64
65 document.getElementById("id").value = 
 ↪contacts[current_contact].id;
66 document.getElementById("name").value =
 ↪contacts[current_contact].displayName;
67 document.getElementById("phone").value =
 ↪contacts[current_contact].phoneNumbers[0].value;
68 document.getElementById("email").value =
 ↪contacts[current_contact].emails[0].value;
69
70 return true;
71 }
72
73 function set_location (p) {
74 document.getElementById("lat").value = p.coords.latitude;
75 document.getElementById("lon").value = p.coords.longitude;
76 return true;
77 }
78
79 function location_error (e) {
80 document.getElementById("error").innerHTML = e.message;
81 return true;
82 }
83
84 function set_quote (p) {
85 if (!p) { return 1; }
86 if ((p.status) && (p.status > 299)) { return 1; }
87 document.getElementById("quote").innerHTML = this.responseText;
88 return true;
89 }
90
91 if (mobile == 1) {
92 document.addEventListener("deviceready", init, false);
93 } else {
94 window.onload = init;
95 }

第 1 行是一个简单的布尔标志,用于确定脚本是在移动设备还是 Web 浏览器上运行。将此变量设置为 0 允许我在 Firefox 中运行和调试程序,在那里我拥有所有我习惯使用的 HTML、DOM 和 JavaScript 开发工具。将此变量设置为 1 将程序的目标定为移动设备,在那里我可以调试程序的 Cordova 特定方面,因为我知道我的 JavaScript 可能是正确的。

第 91-95 行安排在 DOM 加载后以及 Cordova 初始化例程运行后调用 JavaScript init() 函数。这些行还指出了 Cordova 开发的一些怪异之处。首先,无法自动检测程序是在浏览器中还是在智能手机上运行。这就是我设置该变量的原因,如前所述。此外,Cordova 创建了自己的事件,当它准备好开始 JavaScript 执行时会触发该事件;您不能像通常那样使用 window.onload,因为此事件可能会在 Cordova 准备就绪之前触发。无论哪种方式,init() 函数都将在适当的时间被调用。

init() 函数位于第 5-13 行。在第 8 行,您调用 contacts.find 方法以从设备的联系人目录中获取联系人对象数组。然后将此数组异步传递给 store_contacts()(第 39-44 行),该函数只是将数组存储在一个全局变量中。然后,init() 调用 update() 以初始化数据显示,并安排从那时起每秒调用一次 update()

update() 函数(第 15-37 行)是乐趣开始的地方。如果程序在浏览器中运行,您只需使用随机数填充经度和纬度字段。让数字像那样变化让我可以验证程序仍在运行。但是,如果程序在物理设备上运行,您可以使用 geolocation.getCurrentPosition 方法来获取真实的 GPS 坐标。如果此操作成功,则调用 set_location()。否则,将调用 location_error(),您可以显示错误消息(第 73-83 行)。我使用 getCurrentPosition 调用遇到的唯一错误是当我实际禁用 GPS 时。

第 25-36 行构成了一个几乎令人尴尬的 Ajax 调用。我已经将此代码精简到可以在 Firefox 和 Cordova 下运行的最少量代码。它不会在 IE 上运行,并且几乎没有进行任何错误检查。我不是想演示如何在 Cordova 中进行 Ajax 调用。我只是想演示您可以这样做。在这种情况下,您正在从远程服务器加载一些内容并将其放入前面讨论的 quote <div> 中。在开发过程中,我只需更改服务器上该文件的内容,以验证它在应用程序内部是否已更改。

第 44-58 行是应用程序中两个按钮的 onclick 处理程序。所有这些例程所做的只是根据需要调整数组索引加一或减一,并进行一些边界检查。最后,它们调用 display_contact() 以显示当前联系人。

display_contact() 函数(第 59-72 行)是程序中最后一个 Cordova 特定函数。在第 60-64 行中,您清空所有联系人字段,以便使用新值设置它们。我发现,如果我不首先清空它们,如果下一个记录碰巧没有给定字段的值,它们将持续到下一个记录中。在第 65-69 行中,您使用来自当前联系人记录的数据填充字段。请注意,phoneNumbers 和 e-mails 都是对象数组,并且为此目的,您只对第一个元素感兴趣。

这就是全部内容。这里没有什么 Web 开发人员不熟悉的,除了一个非常强大的 API。但是,我只是触及了这个 API 可以做什么的皮毛。图 2 显示了在我的 Droid Bionic 上运行的应用程序。

图 2. 在安卓上运行的示例应用程序

我对手头这个应用程序的一个微妙问题做了一些含糊其辞的处理,许多 JavaScript,特别是 Ajax 开发人员都遇到过这个问题。在大多数浏览器上,您的程序无法从一个域加载,然后从另一个域加载内容或代码。但是,此程序是独立的,需要从可能任意的网站加载内容。Cordova 通过“白名单”应用程序允许从中获取内容的域来处理此问题。默认情况下,所有域都被列入黑名单,因此,所有网络访问都被禁用。开发人员可以通过编辑 /res/xml/cordova.xml 并按照 <access> 标记给出的示例来将域列入白名单。这是一个安全但优雅的解决方案,可以解决潜在的棘手问题。

另一个有趣的可能是让您的应用程序从远程 Web 服务器加载其所有 HTML 和 JavaScript。通过对 ./src/{projectname}/{projectname}.java 进行简单的更改,可以轻松完成此操作。此文件只有 20 行真正的代码,并且必要的更改非常直观。

能够从远程服务器加载内容实际上使开发变得更容易。我发现在远程、公开可访问的服务器上进行开发比在我的工作站上开发更容易。这样,我可以将我的 Web 浏览器指向应用程序,并使我的所有 HTML、CSS 和 JavaScript 以我想要的方式工作。然后我在我的安卓上使应用程序完全正常运行。一旦它完全正常运行,我就将项目复制到我的工作站以进行最终构建。以这种方式进行是测试任何进行 Ajax 调用的应用程序而不会违反浏览器跨站点脚本安全策略的唯一方法。

尽管 Cordova 很棒,但我还是有一些不喜欢的地方。正如我之前提到的,无法自动检测程序是在浏览器中还是在设备上运行。此外,我发现白名单功能有点错误,但并没有以任何方式破坏我的应用程序。但是,我发现最令人沮丧的事情是当我尝试使用相机 API 时。API 没有简单地拍摄照片并返回数据或存储数据,而是实际弹出了设备的本机相机设备作为弹出窗口。这非常具有侵入性,实际上破坏了我的第一个演示应用程序。

我玩 Cordova 玩得很开心,我只是触及了它可以做什么或可以扩展做什么的皮毛。这肯定是进入智能手机应用程序开发的最简单方法。

加载 Disqus 评论