简介: 本文是共两部分的系列文章 “Android 和 iPhone 浏览器之战” 的第 2 部分,主要关注为 iPhone 和 Android 开发基于浏览器的应用程序。在第 1 部分中,我们介绍了 iPhone 和 Android 中的浏览器的核心引擎 WebKit。在本文中,我们将更进一步,构建一个运行在 iPhone 和 Android 浏览器之上的网络管理应用程序。该应用程序同时展示了浏览器的本地 SQL 存储和 Ajax,后者是帮助移动浏览器获得丰富应用程序体验的关键技术。此外,此应用程序还利用了流行的 jQuery JavaScript 库。
开发具有丰富特性的移动 Web 应用程序在过去一直面临着重重困难,但是这一局面正在迅速改变。网速不够快的问题仍然没有完全解决,但是随着 3G 技术开始广泛应用于移动电话,这一问题已经得到了极大地改善。同样,用户界面也有了很大的改进,iPhone 是这一领域的领先者,而 Android 则通过创新的设计以及能够实现最佳 HTML 和 CSS 呈现的强大的 WebKit 引擎获得了发展动力。用户使用蹩脚的移动 UI 再也无法出色地完成工作。
和桌面浏览器体验一样,当将本地数据库存储用于浏览器后,移动 Web 应用程序也经历了一次复兴。另一项助力下一代移动 Web 应用程序的技术是 Ajax。Ajax 描述了这样一种实践:使用 JavaScript 调用服务器端页面以通过 HTTP 检索特定数据元素,这个过程不需要检索和重新呈现全部 Web 页面。由于 Ajax 以异步方式工作,因此移动 Web 应用程序在特性功能方面得到了巨大的改进。
在本文中,我们将主要针对 iPhone 和 Android。为了方便开发,我们还将在桌面中测试 Safari 内的应用程序。由于 Safari 同样基于 WebKit,因此它是一个理想的开发平台,它加速了开发并提供了出色的调试帮助,这要归功于十分有用的 WebKit Inspector 应用程序。
在开始研究代码细节之前,让我们先来了解一下这个应用程序的目标。我们希望通过这个网络监控/管理应用程序完成哪些事呢?
如果您曾经管理过一个面向客户的 Web 站点 — 内部和外部 — 那么您很可能收到过一个表示 Web 站点停止运转的通知。这条通知可能来自一个自动的主动监视工具,有时甚至是您不愿意看到的形式,比如来自客户的电子邮件或电话,表示 “网站停止运行。请尽快检查并给予回复。”
当遇到此类问题时您的第一反应是什么?您会打开浏览器并尝试加载主页。也许宕机与某个位置的连接性有关。有时会发生这种情况,并且这样做也是有收获的,避免您花费力气来寻找根本不存在的问题。
当我们排除了基本的客户机连接性问题后,诊断的下一步是尝试收集一些关键的细节,比如文件系统资源、可用内存、各种连接性、最新的错误消息,等等。执行这些操作通常需要通过 Remote Desktop 或 SSH 会话访问服务器本身。但是,如果您碰巧不能使用桌面计算机,那该怎么办?
收到表示 “站点停止运转” 的通知绝对不是件好玩的事。它经常会在不合时宜的时间发生,比如当您不在办公室并且没办法使用传统 Internet 连接时。这常常令您一筹莫展,不知该如何是好。随着时间的推移,您了解到某个特定站点出现故障的原因无非就那么几个。问题可能是由于一个外部资源(数据库 或第三方支付处理程序)不可用;或者文件传输失败,Web 站点的部分呈现包含了不恰当或过时的信息。不管出于哪些问题,在诊断问题的初始阶段,一些关键的统计数据会有帮助。这些统计数据或性能指标因站点而异。而 我们将在本文探讨的应用程序的目标就是构建一种可以解决此类问题的工具。
本文讨论的应用程序旨在帮助您在办公室以外的环境下诊断 Web 站点问题。如果您有一台 iPhone 或 Android 设备,那么您已经获得了一些动力。我们可以利用这些强大平台的功能来帮助管理我们的 Web 站点。
此应用程序的设计动机是尽可能地独立于服务器数据库 — 换言之,我们希望此应用程序可以在无需对第三方服务创建帐户的情况下运行。我们准备这样构建应用程序:用户只需下载一次 Web 页面,之后就可以在其浏览器中本地运行它。当然,用户可以根据需要对页面设置书签并执行刷新,以获取随后添加到应用程序中的任何新特性。然而,所有数据都 被本地存储在移动设备中的 SQL 数据库中。
虽然将这类数据存储到服务器等位置确实有其优势,但是针对多名用户管理和存储数据的功能不属于本文讨论范围。我们关注的主要问题是利用本地存储和 Ajax 构建一个实用的、有用的应用程序。我们最后将提出一些用于扩展应用程序的逻辑步骤。
此应用程序的代码可以分为两个不同的部分。我们具有运行在移动设备上的代码,其中包括:
- index.html 文件,这是应用程序的 shell。
- 名为 netmon.js 的 JavaScript 文件,其中包含应用程序的大部分功能。
- 另一个名为 json2.js 的 JavaScript 文件,包含与 JSON 相关的例程。
- 多个 CSS 文件,包含样式信息。回忆一下第 1 部分,许多样式信息包含在一个主要的 CSS 文件中。然而,我们使用特定于设备的 CSS 文件来帮助在一个特定平台上重新定义应用程序的感观。
- 我们包含 jquery.js 库文件来实现 DOM 操作和 Ajax 查询。
我们还包含运行在服务器上的代码,并且这些代码对于每一个希望管理的站点都是不同的。这些代码的细节在其实现方面存在差异,但是产生的内容始 终是相同的:一个 JavaScript Object Notation (JSON) 对象,其中包含了一些特定的属性,包括属性名,即由名称/值对组成的数组。这个 JSON 对象中的数据描述了如何在应用程序的浏览器中呈现它。此外,名称/值对包含针对每个站点的关键操作数据,因此提供了快速了解情况并帮助支持人员正确地确定 和解决问题所必需的信息。
我们首先了解此应用程序的数据模型。当对数据模型有所了解后,我们将查看与数据交互的代码,从而构建实际的应用程序。
当通过浏览器将数据存储在移动设备上时,实际上数据被存储在一个 HTML 5 数据库,或一个可通过浏览器访问的 SQL 数据库存储中。基于浏览器的 SQL 数据的规范仍然在不断演变,具体细节仍然在整理当中。然而,就实践而言,我们现在已经可以将它用于 iPhone、Android 和其他支持 WebKit 的环境中。这个数据库功能实际上就是一个与底层 SQLite 实现交互的 JavaScript 接口。我们的数据库只包含一个表:tbl_resources。
该表模拟了应用程序在运行时使用的数据 — 实际上就是一个 JSON 对象。每个对象包含以下内容:
- 站点名。
- 站点主页的 URL。
- 站点的关键统计数据 URL;我们称之为 ping URL。
- 站点的当前状态:OK 或 BAD。
- 站点的当前摘要,这是一段简短的文本,描述了站点的当前状态。比如,“数据库当前停止运行”。
- 一个名称/值对数组,包含特定于站点的细节,用于描述站点的当前运行状态。注意,即使站点没有停止运转,这些值仍然非常重要。一个特定的元素可能包含有可以帮助了解即将发生的问题的数据。
清单 1 是一个表示某个站点的示例 JSON 对象。
清单 1. 表示某个站点的 JSON 对象
|
数据库的作用是长时间持久化数据,但这是通过包含这些对象的数组实现的,而不是不断地引用数据库。这个策略的实现大大简化了 JavaScript 内的操作,并且最小化了数据进出数据库的次数。然而,对于具有大量数据元素的应用程序,直接使用数据库可能更具优势,或者很可能使用某种 “分页” 模式,其中每次从数据库获取大量元素,而一个 JavaScript 数组将包含元素的一个 “窗口”,表示全部数据项的其中一个子集。
图 1 包含了数据库结构的屏幕快照,其中包含一些记录。可以通过 Web Inspector 查看数据库,该工具是 Safari/WebKit 浏览器平台的一部分。这也是为什么 WebKit 开发具有强大功能的原因之一。我们将在桌面上查看 Web Inspector。所有代码在 iPhone 和 Android 上都表现良好。
注意:Android V2.0 是这些代码的目标。Android 中设置的 WebKit 特性随着版本的发行而日趋成熟。
图 1. 数据库结构的屏幕快照,其中包含一些记录
现在我们已经了解了数据元素的大致样子,那么如何使用我们的应用程序管理它们呢?
应用程序的默认 UI 是一个列表,其中列出了正在管理中的站点,这些站点根据其状态值排序。状态为 not OK 的站点被列在了前面,如图 2 所示。
图 2. 状态为 not OK 的站点
我们看到有三个站点处于管理中。目前,有两个站点显示出现问题。如果一个站点处于良好的状态,那意味着它的状态属性为 OK,那么我们将不会显示摘要字段,并且将以黑色文本显示。如果状态为 BAD,我们将在站点名称旁边显示摘要,并且 CSS 文件中名为 BAD 的样式将指出呈现属性 — 本例中为红色文本。有关更多细节,参考文件 netmon.css;此应用程序的完整源代码可以从 下载 部分获得。
通过单击某个条目,就可以隐藏或显示有关该条目的细节。如 图 2 所示,每个条目都有三个可用链接,其中为主页和 ping URL 位置,然后是包含了有关站点细节的部分,即表示该站点的状态的名称/值对列表。
在本例中,我们看到此站点的名称为 ibm demo 2,其摘要为 “No more coffee?”,这当然不算什么紧迫的技术事件,但是为我们提供了一个有趣的示例。跳到该条目的 Details 部分,我们看到这个服务器条件下隐藏的关键统计数据为:The Coffee Pot is empty。
我们可以点击主页链接,这将启动一个新的浏览器窗口。其次,我们可以通过进入 Refresh 链接刷新数据。我们稍后将查看 Refresh 步骤。
最后,我们通过选择 Remove 链接删除这个条目。一个简单的 window.confirm()
查询要求我们确认是否希望执行这个不可恢复的任务。
要创建该列表,需要详细了解两个文件。第一个文件为 index.html,如 清单 2 所示,第二个文件为 netmon.js,如 清单 3 所示。在本文中,我们同时还将探查一些代码片段,您可以从 下载 部分获得完整的源代码。
我们仍然指定移动 WebKit viewport meta 来帮助指导浏览器按照我们希望的方式呈现页面。我们还使用一些可选的 JavaScript 来指定一个针对特定设备的 CSS 文件。
新内容是包括了 JS 文件和一些 “逻辑” JavaScript 文件。该文件补充了一个新的 HTML div
和 ID entryform
,其中包含直接从应用程序内添加新条目所需的元素。不需要加载不同的 HTML 页面,这是完成此任务的传统方法。回想一下,我们的设计目标之一就是我们的应用程序不依赖额外的服务器工具在所监视站点以外进行数据操作或存储。清单 2 展示了 index.html 中包含的语句。
清单 2. index.html
|
数据从我们的本地数据库获取并显示在一个服务器条目列表中。要获得该列表,我们打开数据库并发出一个 SQL 查询,取回数据库表中的所有数据项。
这类应用程序常用的一个技巧就是动态创建表。记住:没有 DBA 可以帮助我们。我们的应用程序必须独力完成全部工作。清单 3 包含了 netmon.js 的源代码:该文件实现了许多项,包括:
netmonResource
对象的数据类型构造函数的定义。- 一组函数,用于帮助应用程序实现与数据库和将要监视的 Web 站点的交互。这是在 netmon 名称空间中实现的,其中包括:
dbHandle
— 用于管理到数据库的连接的函数。initialize
— 用于创建/打开数据库并检索正在管理的服务器列表的函数。清单 3 包含有这个函数,它尝试打开数据库(如果尚未打开的话)并查询数据库。如果数据库表没有给出,该函数将发起创建表的过程。createResourcesTable
— 创建所需数据库表并创建用于演示目的的单个默认条目的函数。addEntry
— 将新条目插入到数据库表的函数。deleteEntry
— 从数据库表删除条目的函数。refreshEntry
— 通过使用 Ajax 调用检索条目的 ping URL,从而刷新条目的函数。Resources
— 包含数据库条目的 “缓存” 的数组。某些其他功能作用于这个数组,而不是直接作用于数据库。Render
— 返回 HTML 字符串的函数,这些字符串根据我们显示每个服务器条目的规则进行格式化。如果出现新的格式化想法,可以使用这个函数以及任何必要的 CSS 样式实现。
清单 3. netmon.js
|
名称空间 netmon
包含实现站点所需的大量逻辑,通过 index.html 文件中的 helper 函数进行了增强。让我们看看如何将一个新条目添加到数据库中。
应用程序管理用户需要的一个或多个站点的列表。对于每个站点,我们最初将收集并存储以下内容:
- 站点名
- 主页 URL
- ping URL
为此,我们创建了一个简单的表单,包含三个字符、字段标签和几个按钮,如图 3 所示。
图 3. 简单表单
此表单内容实际被包含在如 清单 2 所示的同一个 index.html 页面中。通过使用一些 jQuery,我们可以在默认列表视图和这个 add-new-server 表单之间切换。站点条目列表被包含在 HTML div
中,其 ID 为 mainContent
。表单被包含在 HTML div
中,其 ID 为 entryform
,后者在最开始被隐藏。在任何时候,这两个 div
元素的可见性都是相互排斥的。要切换它们的可见性,我们将对 addNewEntry
函数中所示的每一个这些元素调用 jQuery 方法 toggle
,如清单 4 所示。
清单 4.
addNewEntry
函数
|
当生成一个新的服务器条目后,我们需要将其保存到数据库并刷新服务器列表,以反映列表中新增加的条目。用户可以通过选择 Save 按钮来发起此操作,该按钮将调用 index.html 中实现的 saveEntry
JavaScript 函数。这个函数创建一个名为 netmonResource
的新 JavaScript 对象。我们将检查所有字段是否都已正确填充,然后将其保存到数据库中。我们通过调用 netmon.addEntry
保存新条目。此后,通过再次对两个主要的 div
元素调用 toggle
来返回到条目列表。注意:我们并没有实现完整的正则表达式来验证 URL,这其实是一种好的做法。
接下来我们将讨论 清单 3:netmon.js。
要在 JavaScript 中使用 SQL 数据库接口,需要两个基本步骤。第一,如果数据库尚未打开的话,我们需要打开它。在这个应用程序中,我们在初始化函数中打开了数据库。要在数据库中创建新记录,让我们看一看 addEntry
函数中的代码,如 清单 5 所示。通过调用 <databasehandle>.transaction(function (tx) {tx.executeSql()});
发起了一个 SQL 事务。在我们的例子中,数据库句柄为 netmon.dbHandle
。
executeSql
函数接受 4 个参数:
- 一个 SQL 语句,其中的任何参数,比如字段值,都通过一个占位符
?
表示。根据函数的第二个参数中包含的顺序位置,每个?
被替换为一个值。 - 一组值,用于根据第一个参数中的 SQL 语句替换
?
占位符。 - 一个回调函数,在语句成功执行时调用。这个回调函数的参数包含一个事务句柄和一个结果集。
- 一个回调函数,当语句出现错误时调用。该回调函数的参数包括一个事务句柄和一个错误对象。
清单 5 展示了函数 netmon.addentry
。
清单 5.
netmon.addentry
函数
|
我们现在具备了添加条目的能力。从列表中删除条目也十分简单。参考 netmon.deleteEntry
函数,看看如何从数据库中删除条目。
现在让我们看看如何使用 Ajax 刷新条目。
如前所述,Ajax 能够在不刷新整个 Web 页面的情况下检索服务器端内容。这是通过浏览器使用的不同底层技术实现的。然而,我们已经采用这种方法来依赖 JavaScript 库 jQuery 将这些细节隐藏起来。本文虽然关注的是 iPhone 和 Android 浏览器开发,但是通过这种方式,利用 JavaScript 库变得非常普遍并可接受 — 甚至受到鼓励。如果您了解到 jQuery 如何简化工作的话,您也会同意这一点的。
netmon.refreshEntry
函数包含 Ajax 代码,如清单 6 所示,以及一些使用生成的数据更新数据库的代码。
清单 6.
netmon.refreshEntry
函数
|
要从服务器获取数据,只需调用 $.get()
。第一个参数是 URL,第二个参数是一个函数,将在完成调用后调用。在我们的例子中,通过使用前面提到的数据库技巧,我们使用新检索的数据更新数据库(参见 清单 3:netmon.refreshEntry
)。
jQuery 还提供了工具,为将参数传递给 Web 页面提供 “表单编码的数据”,并提供了有关期望返回的数据类型的暗示。参见 参考资料,获得有关 jQuery 的 Ajax 函数的介绍。根据您的应用程序需求,还可以实现额外的粒度。
注意,jQuery 还提供了一种直接访问 JSON 对象的方法。然而,我们选择使用 “通用的” Ajax 函数,这样就可以普遍应用于您可能希望构建的其他应用程序。
当从服务器接收回数据后,我们希望将其转换为 JavaScript 对象以访问其属性。我们可以使用名为 eval
的 JavaScript 函数完成这个任务:var object = eval(<json text>);
。
此时,我们很好地处理了客户端功能。现在还剩下一项内容需要讨论:服务器端页面,负责生成我们的应用程序要对之进行处理的 JSON。
我们已经介绍了我们的应用程序期望的 JSON 结构,但是如何生成这个结构?简单来说,这取决于您。如果您希望使用 .NET 技术,则可以生成一个 .aspx 页面来动态生成数据。或者,可以使用一个定期调度的 shell 脚本来生成一个文本文件,您只需检索这个文件来找出最近更新过的状态。底线是应用程序必须能够根据一个 URL 获取一个 JSON 对象,但是执行生成 JSON 的任务完全取决于您。
清单 7 展示的样例 PHP 文件生成了一个文件列表,并有选择性地查看名为 error.txt 的文件。如果该文件不为空,那么我们认为出现了某种问题并将构建 JSON 对象来表示一个 BAD 状态值,然后使用 error.txt 文件的内容来更新 summary 属性。
清单 7. 样例 PHP 文件
|
这些代码生成如清单 8 所示的 JSON 数据,假设给出了一个错误条件。
清单 8. 清单 4 生成的 JSON 数据
|
在本例中,我们提供了生成数据的日期,文件的当前计数,以及由一组文件及其大小组成的列表。注意,根据您的具体位置,服务器上的时区可能有所不同。
由于 error.txt 的大小不为 0,因此我们将读取该文件的内容并将它分配给 summary 属性。就是这样 — 一个非常基本的监视脚本。
在生成 JSON 的过程中,您可能会发现从基于浏览器的应用程序中解析代码会出现问题。如果是这种情况,您可能需要使用一个 JSON 验证工具(参见 参考资料)。图 4 展示了在桌面浏览器中呈现的此条目。
图 4. JSON 验证工具
图 5 展示了在 iPhone 中运行的应用程序。
图 5. 运行在 iPhone 上的应用程序
图 6 展示了运行在 Android 上的应用程序,其中的服务器列表稍有不同。
图 6. 运行在 Android 上的应用程序
应用程序现在差不多已经全部完成。总是可以添加一些新的内容,因此在下面的小节中,我们来看一看可以将哪些内容留作练习。
总是可以实现一些新的想法来完善这个应用程序。下面列出了一些有趣的内容,可以添加到应用程序中:
- 编辑您的条目 — 目前,您只能 Add New 或 Delete。
- 实现一些正则表达式来适当地验证表单字段条目。
- 从应用程序中直接通过电子邮件向同事发送信息。
- 将概要文件存储在服务器中,这样就可以在设备之间转移文件。
- 使用 PhoneGap 或 Appcelerator 之类的工具将此应用程序合并到一个本地应用程序。这使您可以在 AppStore 上出售应用程序。
描述 | 名字 | 大小 | 下载方法 |
---|---|---|---|
第 2 部分的源代码 | os-androidiphone2-browserwars2.zip | 31KB | HTTP |