求知 文章 文库 Lib 视频 iPerson 课程 认证 咨询 工具 讲座 Modeler   Code  
会员   
 
  
 
 
     
   
分享到
在 Android 上使用 XML 和 JSON
 

作者:Frank Ableson,发布于2012-10-31,来源:IBM

 

简介:

这个两部分文章系列探索了在 Android 平台上处理 Internet 上两种最常见的数据格式 — XML 和 JavaScript Object Notation (JSON) — 的技术。第一部分介绍了 XML 和 JSON 的基础概念,并展示了如何构建一个 Android 应用程序,分析和显示了这两种格式的 Twitter 状态更新 feed。

目录:

第 1 部分: 在 Android 应用程序上研究 JSON 和 XML 益处

第 2 部分: 交付混合了 JSON 的 Android 应用程序

第 1 部分: 在 Android 应用程序上研究 JSON 和 XML 益处

移动设备和平台吹嘘每个新版本都有更多特性和功能,并且仅仅数月重要通告就从主流移动设备提供商中分离出来了。标题多数是关于 UI 特性(例如先进的多点触摸功能和 Adobe? Flash? 技术)以及硬件提升(例如,处理器速度和存储容量)。但是至关重要的仍然是 “内容为王”。内容 — 或者,一般来说,是数据 — 在应用程序、服务器、移动设备和用户之间不停地被交换。智能手机,如 Apple 的 iPhone 和 Google 的 Android 只是标价过高且表现不佳的移动电话,而且也不能很好的工作 。

考虑到社会网络非比寻常的成功,例如:Facebook、LinkedIn 和 Twitter。单从特征和功能的角度来说,这些平台很大程度上都是单调的,之所以流行是因为会员和网站访问者可以从其公布的内容获益。而且,其内容逐渐可以通过移动设备访问。

这篇文章主要说明 Android 平台上 XML 和 JSON 数据交换格式的使用。对于一个 Twitter 帐户,示例应用程序数据源是一个状态更新 feed。Feed 数据可以以 XML 和 JSON 格式从 Twitter 上获取。正如您所看到的,操作数据的程序设计方法在这两种格式中明显不同。

建议您安装 Android SDK version 1.5 或最新版以及 Eclipse 来运行本文附带的 示例代码。为了了解更多关于设置环境的知识,访问 Android Developers 网站。使用一个活动的帐户跟着示例练习是很重要的,但不是必须的。相关链接见 参考资料。

首先我们来简要的看一下这两个数据格式,从 XML 开始。如果您对 XML 和 JSON 已经很熟悉了,就可以跳转到 应用机会:Twitter feeds 在 Android 上开始运行。

XML:一个老朋友

事实上,近几年来为企业、web 或移动市场编程的人都遇到过 XML,几乎您见到的每个地方都有。

一个 XML 文档有一个可辨认的结构:一系列可以随意包含属性和子元素的元素。每个有效的 XML 文件第一行都有这样一个声明:<?xml version="1.0" encoding="utf-8"?>。后面的内容根据应用程序而定。XML 的好处就在于它的自述性。

XML 模式

尽管 XML 文档是自述的,但也必须遵循一定的规则和指导方针,这就需要 XML 模式,它是一个描述特定 XML 文件结构的文档。此类结构通常是冗长且复杂的。(值得争议的是,XML 对 IT 领域最糟糕的贡献是,当高度描述的数据结构这一理念成为时尚时导致了数据爆炸,且这种情况由于过去十年中磁盘存储技术成本大幅降低而加剧。)

当这些大且复杂的文件变得更规范时,对程序员和分析员来说手工处理通常是不可能完成的。为了解决这个问题,XML 编辑器和验证工具可以在文件和相关的任务管理方面给予帮助。例如,文档和遗留格式转换。

除了一般的文本数据,XML 过去常用于存储二进制数据,通过一个称为 CDATA 的特殊标记集合实现。一个 XML 文档中的 CDATA 标记可能含有各种数据,包括其他标记文本,假设该文本本身不含有 CDATA。

通过使用 XML 作为一个结构来执行请求/响应查询,这并不是 API 使用该功能的常用方式。响应数据经常包含一个包含在 CDATA 标记中的 XML 结构。例如,一个 API 调用可能使用一个姓氏 Mott 来请求一个客户记录。数据找到时,封装到一个 XML 结构中并将其置于响应元素中,如 清单 1 所示:

清单 1. 将数据封装到 XML 结构中并将其置于响应元素中

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<request>
<query>
<lastname>Mott</lastname>
<maxhits>100</maxhits>
</query>
</request>

<?xml version="1.0" encoding="UTF-8" standalone="yes"?>
<response>
<returncode>200</returncode>
<query>
<lastname>Mott</lastname>
<hits>1</hits>
</query>
<data>
<![CDATA[
<contact>
<firstname>Troy</firstname>
<lastname>Mott</lastname>
<age>not telling</age>
</contact>

</data>
</response>

<div id="chart" style="width:360px;height:200px"/>
</body>
</html> 

工作空间中的 XML

今天,XML 是默认的、预定数据格式。尽管同一数据的其他格式也可用,但基于 XML 结构的可用性进行计划是一种最安全的方式。

Enterprise Resource Planning (ERP) 包频繁使用 XML 进行数据到任务的导入和导出。Internet 新闻网站经常将数据提供为 Really Simple Syndication (RSS) — 具有新闻阅读软件可以处理的预定义格式的 XML 文档。甚至文字处理应用程序(例如 OpenOffice.org 和 Microsoft? Office)也使用 XML。

现在的 Microsoft Office 文件是 PKZIP 兼容的,含有多个 XML 文档。 每个 XML 在第一行都有这个常见声明。正如您在 清单 2 所看到的,阅读这些属性可能有点难:

清单 2. 每个文件第一行的常见声明

 <?xml version="1.0" encoding="UTF-8" standalone="yes"?>
 <w:document xmlns:ve="http://schemas.openxmlformats.org/markup-compatibility/2006"
 xmlns:o="urn:schemas-microsoft-com:office:office"
 xmlns:r="http://schemas.openxmlformats.org/officeDocument/2006/relationships"
 xmlns:m="http://schemas.openxmlformats.org/officeDocument/2006/math"
 xmlns:v="urn:schemas-microsoft-com:vml"
 xmlns:wp="http://schemas.openxmlformats.org/drawingml/2006/wordprocessingDrawing"
 xmlns:w10="urn:schemas-microsoft-com:office:word"
 xmlns:w="http://schemas.openxmlformats.org/wordprocessingml/2006/main"
 xmlns:wne="http://schemas.microsoft.com/office/word/2006/wordml">
 <w:body><w:p w:rsidR="00B6337C" w:rsidRDefault="00663F0E"><w:r>
 <w:t xml:space="preserve">This is a sample </w:t></w:r><w:r
 w:rsidRPr="006906EA"><w:rPr><w:i/></w:rPr><w:t>Microsoft 
 Word document</w:t></w:r><w:r><w:t xml:space="preserve"> used
 to </w:t></w:r><w:r w:rsidRPr="006906EA"><w:rPr><w:b/>
 <w:u w:val="single"/></w:rPr><w:t>demonstrate</w:t></w:r>
 <w:r><w:t xml:space="preserve"> some XML topics.</w:t></w:r>
 </w:p><w:p w:rsidR="00B14B2A" w:rsidRDefault="00B14B2A"/><w:p 
 w:rsidR="00B14B2A"w:rsidRDefault="00B14B2A"><w:r><w:rPr>
 <w:noProof/></w:rPr><w:drawing><wp:inline distT="0" distB="0" 
 distL="0" distR="0"><wp:extent cx="3276600" cy="3838575"/><wp:effectExtent
 l="19050" t="0" r="0" b="0"/><wp:docPr id="1" name="Picture 0"
 descr="frankableson.jpg"/><wp:cNvGraphicFramePr><a:graphicFrameLocks
 xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"
 noChangeAspect="1"/></wp:cNvGraphicFramePr><a:graphic
 xmlns:a="http://schemas.openxmlformats.org/drawingml/2006/main"><a:graphicData
 uri="http://schemas.openxmlformats.org/drawingml/2006/picture"><pic:pic
 xmlns:pic="http://schemas.openxmlformats.org/drawingml/2006/picture">
 <pic:nvPicPr><pic:cNvPrid="0"name="frankableson.jpg"/><pic:cNvPicPr/>
 </pic:nvPicPr><pic:blipFill><a:blip r:embed="rId4"
 cstate="print"/><a:stretch><a:fillRect/></a:stretch>
 </pic:blipFill><pic:spPr><a:xfrm><a:off x="0" y="0"/>
 <a:ext cx="3276600" cy="3838575"/></a:xfrm><a:prstGeom
 prst="rect"><a:avLst/></a:prstGeom></pic:spPr></pic:pic>
 </a:graphicData></a:graphic></wp:inline></w:drawing>
 </w:r></w:p><w:p w:rsidR="00663F0E" w:rsidRDefault="00663F0E"/>
 <w:p w:rsidR="00CC16CE" w:rsidRDefault="00CC16CE"/><w:sectPr 
 w:rsidR="00CC16CE" w:rsidSect="00B6337C"><w:pgSz w:w="12240" w:h="15840"/>
 <w:pgMar w:top="1440" w:right="1440" w:bottom="1440" w:left="1440" w:header="720" 
 w:footer="720" w:gutter="0"/><w:cols w:space="720"/><w:docGrid
 w:linePitch="360"/></w:sectPr></w:body></w:document> 

XML 是自述的,但是并不意味着标记一定是容易理解的。这个虚构的示例展示了多个名称空间的使用,多个名称使得理解 XML 文件更有挑战性,如果不使用专用工具就很难理解。

XML 随处可见,但是对于 Android 程序员来说并不是一个明智的选择,特别是,如果数据结构成为与 XML 结构伴随着的数据爆炸的牺牲品。像 Android 这样通常在蜂窝数据网络(cellular data network)上工作的资源受限平台不能存储和解析大量 XML 数据。然而,如果特殊编程任务要求文本和二进制数据交换,那么 XML 是一个可靠的选择。

现在,让我们看一看数据交换的另一个格式:JSON。

JSON:网络的新成员

越来越多的 Internet API 供应商提供 JSON 作为一种数据格式。JSON 在 Ajax (Asynchronous JavaScript and XML) 网页编程社区中享有盛名。Ajax 技术使 web 页面可以动态地更新,只更新所选区域的数据而不用更新整个页面。由于较少数据被传送 — 更重要的是,因为较少的数据被解析且显示在浏览器窗口 — 使用 AJAX 的应用程序比起传统 web 应用程序能够提供一个更好的用户体验。事实上,一个结构良好的 Ajax 应用程序可与智能或富客户端应用程序相抗衡。

当 Ajax 应用程序与 web 服务器交换数据时,经常需要某类数据的刷新,但不需要格式化。通常认为一个 web 服务器提供预格式化(preformatted)HTML 是一个糟糕的实践。相反的,一个格式良好的应用程序应该将数据内容发送到浏览器并应用一个 Cascading Style Sheets (CSS) 文件来产生视觉效果,比如颜色和特殊字体。

假设应用程序想要请求 Mr. Mott.(这是我们虚构的)的联系记录,应用程序返回浏览器的数据元素不止一个。它是如何包装的呢?在 清单 1 示例中,您可以使用一个简单的请求/响应结构。这已经足够了;然而它要求您解析来自服务器的每个响应、以某种结构(DOM)存储数据,然后更新网页内容。

还有一种选择,您可以从服务器上返回一些 JavaScript,并用它来直接处理。以下是一个虚拟应用程序的样例响应,对 Mott 查询(http://<yourserver/app/searchcontact?Mott)的响应。这个响应是一个 JavaScript 对象字符串表示 — 即 JSON 字符串(为了适应本文页宽在这分成两行):

[{"firstname":"Troy","lastname":"Mott","age":"don't ask!"},{"firstname":"Apple seed",
   "lastname":"Mott's","age":"99"}]

XML 以其冗长而著名,而 JSON 也因其难度而闻名。JSON 对象其构造是一个键 :值 对,对象元素之间用逗号隔开,每个对象被包含在一对大括号 {} 内。一组对象数组包含在一对方括号中。这是将数据行从一个数据库发送到一个对象数组的一种常见方法。其中每个数组元素对应数据库中的一行,每个对象属性代表数据的一列。

清单 3 显示了一个在 HTML 页面中使用这类对象的示例。为简单起见,不包括服务器通信;相反,JSON 数据作为一个字符串变量 serverresponse 提供。

清单 3. 在 HTML 页面中使用一个 JSON 对象

<html>
<head>
<script language="JavaScript">
var serverresponse = "[{\"firstname\":\"Troy\",\"lastname\":\"Mott\",\"age\":\"don't
ask!\"},{\"firstname\":\"Apple seed\",\"lastname\":\"Mott's\",\"age\":\"99\"}]";
function updatepage()
{
    var contacts = eval(serverresponse );
    var i;
    var s = "Search Results:<br />";
    for (i=0;i<contacts.length;i++)
    {
        s = s + contacts[i].firstname + " " + contacts[i].lastname + "'s age is ... " 
+ contacts[i].age + "<br />";
    }
    document.getElementById("target").innerHTML = s;
}
</script>
</head>
<body>
<button onclick="updatepage();">Search for Mott</button><br />
<span id="target"> </span>
</body>
</html>

注意,这个示例使用 JavaScript 函数 eval() 将字符串转换成一个 JavaScript 数组。JSON 库可以为执行该步骤提供更快更安全的方法,清单 3 中的方法并不是最佳实践。在这只是提供了一个 JSON 对象,如何用于 Ajax 应用程序的背景:JSON 结构可被客户端代码交换、解析和操作。

总的来说,JSON 是:

一种数据交换格式。

JavaScript 对象作为字符串编码的一种方式。

仅限于文本和数值。二进制值是明确不允许的。JSON 没有 CDATA 对应模式。

在数据可读性费用方面、就数据大小而言比 XML 更经济。

越来越多的 API 供应商将其作为一种选择,例如 Twitter。

在 清单 3 中,客户端是一个运行客户端脚本的 web 浏览器。返回本文主题,接下来将研究在 Android 应用程序中 XML 和 JSON 的使用。

应用机会:Twitter feeds

Twitter 现在已经变成了一种国际力量,它提供的更新包罗万象:从什么人正在吃早饭,他们的孩子的球队在棒球比赛中的表现这类琐事,到封闭国家中的政治动乱的街头新闻,器官移植实时报道等严肃话题。

要获取一些 XML 和 JSON 文档以用于本文附带的 样例代码,最简单的方法就是通过 URL http://twitter.com/statuses/user_timeline/userid.format,其中 userid 是您的 Twitter 用户 ID,format 是您要的格式:XML 或 JSON。

您也可以直接在您的 Twitte 页面上找到此页的链接,如 图 1 所示。在那里可以看到您的 Twitter 用户 ID。

图 1. 您 Twitter 页面上的 feed 页链接

完整的 feed 文件相当冗长,下面两个清单只显示 feed 的第一项(来自我的 Twitter 帐户)。清单 4 包含以下 XML 片段:

清单 4. XML 片段

<?xml version="1.0" encoding="UTF-8"?>
<statuses type="array">
<status>
  <created_at>Thu Apr 29 05:25:29 +0000 2010</created_at>
  <id>13052369631</id>
  <text>Wrapping up new article on JSON for Android
 programmers...</text>
  <source><a href="http://www.linkedin.com/"rel="nofollow">
   LinkedIn</a></source>
  <truncated>false</truncated>
  <in_reply_to_status_id/>
  <in_reply_to_user_id/>
  <favorited>false</favorited>
  <in_reply_to_screen_name/>
  <user>
    <id>15221439</id>
    <name>fableson</name>
    <screen_name>fableson</screen_name>
    <location>Byram Township, NJ</location>
    <description/>

<profile_image_url>http://a3.twimg.com/profile_images/260492935
/bookcover_normal.jpg</profile_image_url>
    <url>http://msiservices.com</url>
    <protected>false</protected>
    <followers_count>52</followers_count>
    <profile_background_color>9ae4e8
    <profile_text_color>000000</profile_text_color>
    <profile_link_color>0000ff</profile_link_color>
    <profile_sidebar_fill_color>e0ff92
</profile_sidebar_fill_color>
    <profile_sidebar_border_color>87bc44
</profile_sidebar_border_color>
    <friends_count>10</friends_count>
    <created_at>Tue Jun 24 17:04:11 +0000 2008</created_at>
    <favourites_count>0</favourites_count>
    <utc_offset>-18000</utc_offset>
    <time_zone>Eastern Time (US & Canada)</time_zone>

   <profile_background_image_url>http://s.twimg.com/a/1272044617/
images/themes/theme1/bg.png</profile_background_image_url>
   
<profile_background_tile>false</profile_background_tile>
    <notifications>false</notifications>
    <geo_enabled>false</geo_enabled>

    <verified>false</verified>
    <following>false</following>
    <statuses_count>91</statuses_count>
    <lang>en</lang>
    <contributors_enabled>false</contributors_enabled>
  </user>
  <geo/>
  <coordinates/>
  <place/>
  <contributors/>
</status>
</statuses> 

清单 5 是同一数据,这次是以 JSON 格式显示:

清单 5. JSON 格式的 feed 数据

[
{"in_reply_to_status_id":null,
"favorited":false,
"created_at":"Thu Apr 29 05:25:29 +0000 2010",
"in_reply_to_screen_name":null,
"geo":null,
"source":"<a href=\"http://www.linkedin.com/\" rel=\"nofollow\
          ">LinkedIn</a>",
"contributors":null,
"place":null,
"truncated":false,
"coordinates":null,
"user":
{
    "friends_count":10,
    "description":"",
    "lang":"en",
    "statuses_count":91,
    "time_zone":"Eastern Time (US & Canada)",
    "profile_link_color":"0000ff",
    "favourites_count":0,
    "created_at":"Tue Jun 24 17:04:11 +0000 2008",
    "contributors_enabled":false,
    "profile_sidebar_fill_color":"e0ff92",
    "following":null,
    "geo_enabled":false,
    "profile_background_image_url":"http://s.twimg.com/a/1272044617/images/themes
/theme1/bg.png",
    "profile_image_url":"http://a3.twimg.com/profile_images/260492935
/bookcover_normal.jpg",
    "notifications":null,
    "profile_sidebar_border_color":"87bc44",
    "url":"http://msiservices.com",
    "verified":false,
    "profile_background_tile":false,
    "screen_name":"fableson",
    "protected":false,
    "location":"Byram Township, NJ",
    "profile_background_color":"9ae4e8",
    "name":"fableson",
    "followers_count":52,
    "id":15221439,
    "utc_offset":-18000,
    "profile_text_color":"000000"
},
"in_reply_to_user_id":null,
"id":13052369631,
"text":"Wrapping up new article on JSON for Android programmers..."}
] 

注意,在两个清单中除了状态更新外还有非常多附加数据。您只需要注意日期/时间,何时发布公告以及公告的内容。接下来,将向您展示解析该数据的 Android 应用程序的相关部分。整个项目见 下载 部分。

XMLvsJSON 应用程序

Android 应用程序很简单。其中包含 XML 和 JSON 数据资料的全部副本。用户可以任选其一进行分析。 图 2 显示 Eclipse 中项目文件的结构。(查看 图 2 的文本版本。)

图 2. Eclipse 项目的文件结构

图 3 显示选择解析选项之前的应用程序 UI:

图 3. 选择解析选项之前的应用程序 UI

应用程序 UI 有两个按钮,Parse XML 和 Parse JSON,接着是默认文本。清单 6 包含该 UI 的布局,在项目的 res/layout 文件夹中的 main.xml 中可以找到:

清单 6. UI 的布局

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="horizontal"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:gravity="center_horizontal">

<Button android:layout_width="wrap_content" android:layout_height="wrap_content"
 android:id="@+id/btnXML" android:text="Parse XML"></Button>
<Button android:layout_width="wrap_content" android:layout_height="wrap_content"
 android:id="@+id/btnJSON" android:text="Parse JSON file"></Button>
</LinearLayout>

<ScrollView xmlns:android="http://schemas.android.com/apk/res/android" 
android:id="@+id/ScrollView01" android:layout_width="fill_parent"
android:layout_height="wrap_content">

<TextView  
    android:layout_width="fill_parent" 
    android:layout_height="wrap_content" 
    android:text="default text" 
    android:layout_gravity="center_horizontal"
    android:id="@+id/txtData" 
    />

</ScrollView>
</LinearLayout>

Parse XML 和 Parse JSON file 按钮是在 ScrollView 之上定义的,ScrollView 中含有一个 TextView 控件。这里的意思是想要用户可以滚动结果数据。

注意多个 LinearLayout 结构的使用。第一个是垂直对齐,其中既含有一个带有水平结构的 LinearLayout 和一个 ScrollView。内层 LinearLayout 含有两个 Button 小部件。这个布局很冗长,一般在 onCreate() 方法中调用,见 清单 7:

清单 7. onCreate() 方法

    Button btnXML;
    Button btnJSON;
    TextView tvData;
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);


        tvData = (TextView) findViewById(R.id.txtData);
        btnXML = (Button) findViewById(R.id.btnXML);
        btnXML.setOnClickListener(new Button.OnClickListener()
        {
            public void onClick(View v)
            { 
                examineXMLFile();
            }
        });


        btnJSON = (Button) findViewById(R.id.btnJSON);
        btnJSON.setOnClickListener(new Button.OnClickListener()
        {
            public void onClick(View v)
            {
                examineJSONFile();
            }
        });

    } 

examineXMLFile() 方法控制 XML 解析。

XML 解析

XML 数据解析通常是使用一个 SAX 风格的解析器完成的。对于这类解析器,您可以建立一个 InputSource 指向源 XML 数据,并在文档 “运行” 时提供一个接收某些事件的处理程序。清单 8 展示 examineXMLFile() 方法,执行以下任务:

使用原始资料中的 XML 文件建立 InputSource

创建一个 SAXParser,关联处理程序 twitterFeedHandler(见 清单 9)

调用解析器,在 TextView 小部件中显示结果,在布局文件中作为 R.id.txtData 识别,在代码中作为 tvData 引用

如果有错误,也显示在 TextView 中

清单 8. examineXMLFIle() 方法

void examineXMLFile()
    {
        try {
            InputSource is = new InputSource(getResources()
.openRawResource(R.raw.xmltwitter));
            // create the factory
            SAXParserFactory factory = SAXParserFactory.newInstance();
            // create a parser
            SAXParser parser = factory.newSAXParser();
            // create the reader (scanner)
            XMLReader xmlreader = parser.getXMLReader();
            // instantiate our handler
            twitterFeedHandler tfh = new twitterFeedHandler();

            // assign our handler
            xmlreader.setContentHandler(tfh);
            // perform the synchronous parse
            xmlreader.parse(is);
            // should be done... let's display our results
            tvData.setText(tfh.getResults());
        }
        catch (Exception e) {
            tvData.setText(e.getMessage());
        }
    } 

虽然 examineXMLFile() 设置好了一切,但应用程序角度来看,真正的解析工作实际是在处理程序中进行的,该处理程序是在 twitterFeedHandler.java 文件中实现的。实现 DefaultHandler 接口的类见 清单 9:

清单 9. twitterFeedHandler 类

public class twitterFeedHandler extends DefaultHandler {

    StringBuilder sb = null;
    String ret = "";
    boolean bStore = false;
    int howMany = 0;

    twitterFeedHandler() {
    }

    String getResults()
    {
        return "XML parsed data.\nThere are [" + howMany + "] status updates\n\n" + ret;
    }
    @Override

    public void startDocument() throws SAXException {
        // initialize "list"
    }

    @Override
    public void endDocument() throws SAXException {

    }

    @Override
    public void startElement(String namespaceURI, String localName, String qName, 
Attributes atts) throws SAXException {

        try {
            if (localName.equals("status")) {
                this.sb = new StringBuilder("");
                bStore = true;
            }
            if (localName.equals("user")) {
                bStore = false;
            }
            if (localName.equals("text")) {
                this.sb = new StringBuilder("");
            }
            if (localName.equals("created_at")) {
                this.sb = new StringBuilder("");
            }
        } catch (Exception ee) {

            Log.d("error in startElement", ee.getStackTrace().toString());
        }
    }

    @Override

    public void endElement(String namespaceURI, String localName, String qName)
throws SAXException {

        if (bStore) {
            if (localName.equals("created_at")) {

                ret += "Date: " + sb.toString() + "\n"; 
                sb = new StringBuilder("");
                return;

            }

            if (localName.equals("user")) {
                bStore = true;
            }

            if (localName.equals("text")) {

                ret += "Post: " + sb.toString() + "\n\n";
                sb = new StringBuilder("");
                return;

            }


        }
        if (localName.equals("status")) {
            howMany++;
            bStore = false;
        }
    }

    @Override

    public void characters(char ch[], int start, int length) {

        if (bStore) {
            String theString = new String(ch, start, length);

            this.sb.append(theString);
        }
    }

} 

清单 9 中有一些值得注意的细节,首先考虑到的是 SAX 解析器是一个基于事件的解析器,这就意味着使用 SAX 进行解析时要建立真实文件。在文档开始和结束、标记开始和结束、发现数据时,事件被触发。这意味着您必须定义一个数据结构来保留感兴趣的数据、抛弃余下的。

注意,使用 StringBuilder 和附加数据是因为一个特定数据在 InputSource 上能通过多个读取来进行处理。千万不要以为所有数据都在给定的调用 characters() 方法中提供。

应用程序将数据收集到一个简单的格式化字符串中。而另一个示例中则是把这些实体放在一个集合类或数据库中,特别是如果有大量的操作都发生在解析之后。

getResults() 方法是该类特有的,用来收集这类数据的集中表示法,并将其提供给应用程序,这不是 DefaultHandler 接口的一部分。

图 4 展示了已解析的 XML 数据。(查看 图 4 的文本版本。)

图 4. 已解析的 XML 数据

尽管从构建、管理和导航结果结构方面来说,使用 SAX 解析器解析 XML 数据并不容易,但其主要优势是快速和极大地减少了解析过程中和解析之后的 RAM 需求数量。

现在看一下 Android 解析 JSON 数据的方法。

JSON 解析

在用户选择 JSON 按钮时解析 JSON 数据就开始了。调用 examineJSONFile() 方法,如 清单 10 所示。不需要额外的处理类。因为所有解析和文档管理都在 Android 提供的库中进行,所有 JSON 相关的代码都包含在这个方法中。

清单 10. 调用 examineJSONfile() 方法

void examineJSONFile()
    {
        try
        {
            String x = "";
            InputStream is = this.getResources().openRawResource(R.raw.jsontwitter);
            byte [] buffer = new byte[is.available()];
            while (is.read(buffer) != -1);
            String jsontext = new String(buffer);
            JSONArray entries = new JSONArray(jsontext);

            x = "JSON parsed.\nThere are [" + entries.length() + "]\n\n";

            int i;
            for (i=0;i<entries.length();i++)
            {
                JSONObject post = entries.getJSONObject(i);
                x += "------------\n";
                x += "Date:" + post.getString("created_at") + "\n";
                x += "Post:" + post.getString("text") + "\n\n";
            }
            tvData.setText(x);
        }
        catch (Exception je)
        {
            tvData.setText("Error w/file: " + je.getMessage());
        }
    }

像之前显示的 XML 例程一样,代码是从原始资源文件夹的文件中读取的。数据被整体读到内存中,转换成一个 java.lang.String,然后解析成 JSONArray。要注意的是,正如此例所示,一个特殊的字符串能直接解析成一个数组,或者也可以直接解析成一个 JSONObject。因为 Twitter 数据是一个对象数组,整体解析成一个数组然后按顺序逐个访问对象是明智的选择。

该方法的流程简单明了,一旦数据被解析,代码建立一个类似于 XML 解析器处理程序方法的字符串表示。有趣的是数据将由您来管理;您不需要建立额外内存结构来容纳数据,同样地,应用程序预先就知道在 JSONArray 中有多少个条目(在这个例子中是 20 个)

尽管 JSON 解析在编程上简单得多,但并不是没有代价的。它要增加内存消耗,在处理之前整体读取数据流并进行存储。相反的,SAX XML 方法仅使用相关数据。另外,如果解析特殊 JSON 对象的内存是足够的,则这个方法将很受应用程序欢迎,特别是,几乎不需要使用 DOM 的应用程序。

结束语

本文在 Android 应用程序背景下介绍了 XML 和 JSON 数据交换格式。比起 JSON 方法,XML 方法更快、内存受限更小 — 但是以增加复杂性为代价。在第二部分,我将介绍一些结合使用 JSON 方法的先进技术,例如,基于 WebKit 的 WebView 小部件,并为 Android 应用程序定制动态应用程序逻辑。

第 2 部分: 交付混合了 JSON 的 Android 应用程序

要运行本文的 示例代码,应该安装 Android SDK 1.5 或更晚版本和 Eclipse。要进一步学习如何设置环境,请访问 Android Developers 网站。参见 参考资料 中的链接。

简介

难以想象还有哪种技术比移动电话更流行。大量的平台在销售和心理份额方面争占此行业的顶级市场。设备是高级的工程样品,真正让它们流行起来的动力在于这些平台上可用的大量应用程序带来的用户体验。具体来说,iPhone 和 Android 平台是最新的设备,点燃了人们的消费欲望。

对移动用户可用的大部分应用程序是由移动开发人员通过各平台供应商提供的核心 SDK 编写的。移动设备的流行离不开大量天才的 web 技术程序员,过去十年间,他们在 Web 上大获成功,现在又开创了一种新型的应用程序 — 混合应用程序,即既使用 web 浏览器接口又使用本地移动组件的应用程序。针对 iPhone 和 Android 都存在混合应用程序,不过本文的重点放在 Android 混合应用程序及 JavaScript 和 JSON 的使用上。

混合应用程序是用 Android 的 WebView 控件中的 WebKit 引擎构建的。这是一个用户界面小部件,它向 Android 程序员提供 WebKit 的功能。此控件可用于在应用程序中呈现远程 web 页面,以便为开发人员和用户等提供熟悉的用户界面体验,以及在本地 Android 应用程序中利用强大且灵活的 JavaScript 环境。

混合应用程序通常利用 WebView 小部件来为用户界面元素充分利用 WebKit 的优势,但是混合应用程序不仅仅是简单地在小部件中显示一些 HTML。混合应用程序是多才多艺的 — Android SDK 中包含的广泛功能加上 HTML、CSS 和 JavaScript 等 web 技术,使得混合应用程序的功能不可限量。为了实际了解混合应用程序的概念,本文将介绍一个名为 AndroidJSON 的样例应用程序,它实现了 Activity、WebView 和 JSON 之间的很多交互,以交换数据。该应用程序演示了 Activity 和 WebView 宿主的 HTML 及 JavaScript 之间的很多交互,主要特性是一个 JavaScript 计算器。

首先,我们来看直接在 Android 应用程序中嵌入一个 WebKit 引擎。

嵌入在 Android 中的 JavaScript 计算器

大部分基于 SDK 的 Android 应用程序都包含 Activity 类的一个或多个实现。Activity 类本质上是一个屏幕或页面,其中包含由应用程序用户体验的用户界面元素。

Activity 显示一组由程序员定义的用户界面元素,比如按钮、标签、文本输入框、单选按钮列表,等等。所有预期的条目都可在 Android SDK 中找到。除了这些用户界面元素之外,还有一个特殊的小部件,就是 WebView。

JavaScript 计算器演示了 Activity 的 Java 环境和 WebView 的 JavaScript 环境之间相辅相成的关系。应用程序不仅仅是要求 WebView 显示 HTML 内容 — 它实际上是连接 Java 环境,以向 JavaScript 环境提供功能,这样可以将两者紧密地集成在一起,从而带来独特的用户体验。一旦两个环境连接起来,就可以 JSON 形式交换数据,以交付各种特性,本文将全面解释这些特性。我们首先来看 JavaScript 计算器如何利用 WebView 小部件。

在深入应用程序是如何构造的细节之前,先花点时间回顾一下应用程序的各种特性。图 1 展示了应用程序屏幕。

图 1. 展示正在工作的 JavaScript 计算器

在名为 AndroidJSON 的样例本地 Android 应用程序中,屏幕是用 Activity 组件定义的。它在屏幕的上半部分包含传统的用户界面元素,比如一个 TextView(静态标签)、一个 EditText(文本框,用户在这里输入公式)和三个按钮(即 Simple、Complex 和 Make Red)。Activity 也具有 WebView 控件的单个实例,用于显示屏幕的下半部分。

WebView 显示一个与 Android 应用程序打包在一起的 HTML 文件 (index.html),不过您也可以从 Internet 单独下载此文件。该 web 页面包含标题、一些样例文本、计算结果和六个执行各种功能的按钮(Log Info、Log Error、Dynamic、How Many Calls、History 和 Kill This App)。

这个项目中最有趣的文件是 AndroidJSON.java(Android 应用程序代码)、index.html(web 页面)和 main.xml(一个 UI 布局文件,后面将会介绍)。参见 下载 部分到这些文件的链接。

首先,来看 Activity 中三个按钮的功能:

Simple

Simple 按钮导致 EditText 的内容被作为数学表达式进行计算。注意,EditText 的内容或者说公式,在 JavaScript 中被传递到 WebView 控件并进行计算。

Complex

Complex 按钮将 JSON 对象发送到 WebView 进行计算。这被认为复杂,是因为对象随后在 JavaScript 代码中被解释并以数学方式被操纵。该按钮在两个功能之间交替,一个功能是将一个整数数组的元素相加,另一个功能是将这个整个数组的元素相乘。

Make Red

这第三个按钮在此主要是出于好玩。选中时,该按钮向嵌入的 WebView 内容应用一种样式,将包含在 <body> 标记中的文本元素变成红色。

现在来看 index.html 文件中的函数,该文件由嵌入的 WebView 控件在运行时启用。

Log Info

该按钮调用 Android 应用程序中的一个回调函数,以将数据项写到 Info 分类下的应用程序日志中。

Error Info

该按钮调用 Android 应用程序中的一个回调函数,以将数据项写到 Error 分类下的应用程序日志中。

Dynamic

该按钮调用 Android 应用程序中的一个回调函数,以检索一段代表有效 JavaScript 代码的文本。此代码被带回 WebView 中并执行,演示了应用程序两端之间的交互。注意,此方法存在安全隐患,因为它盲目信任 JavaScript eval 函数。但是,我们这里将重点放在基本的示例应用程序上,而不是介绍完善的生产性应用程序。

How many calls

每调用一次回调函数,计数器就会增 1。 该按钮只是显示计数器。

History

每调用一次 JavaScript 函数,一个表示函数名的字符串就会被添加到 JavaScript 数组。当 history 按钮被调用时,此数组将被转换成 JSON 并传递到 Android 应用程序的本地部分。数组被改造为 Java 代码中的一个对象,并枚举写到日志中的每个数组元素。

Kill This App

该按钮是此应用程序的又一个只是出于好玩的特性。该按钮调用一个会通过调用 finish() 而终止 Android 活动的回调函数。
跟很多不完善的应用程序一样,此 Android 应用程序也使用了内置在 Android 中的日志功能。本文中展示的一些屏幕截图来自 Eclipse 中的 Dalvik Debug Monitor Service (DDMS) 视图,其中 LogCat 窗口是可见的。要获得更多关于如何使用 Android 开发工具的信息,请参考 参考资料 中的链接。

刚才解释了应用程序的函数,现在来看用户界面是如何构造的。

设置用户界面

为该应用程序创建用户界面要调用前面介绍过的三个文件。首先是布局文件 main.xml,如 清单 1 所示。

清单 1. main.xml,用户界面布局文件

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
    <TextView android:layout_width="fill_parent"  
android:layout_height="wrap_content" android:text="@string/title" />
    <EditText android:id="@+id/formula" android:layout_width="fill_parent" 
android:layout_height="wrap_content" android:text="" android:visible="False" />
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:orientation="horizontal"
        android:layout_width="fill_parent"
        android:layout_height="wrap_content">
        <Button android:text="Simple" android:id="@+id/btnSimple" 
android:layout_width="wrap_content" android:layout_height="wrap_content">
</Button>
        <Button android:text="Complex" android:id="@+id/btnComplex"
 android:layout_width="wrap_content" android:layout_height="wrap_content">
</Button>
        <Button android:text="Make Red" android:id="@+id/btnRed" 
android:layout_width="wrap_content" android:layout_height="wrap_content">
</Button>    
    </LinearLayout>
    <WebView android:layout_width="fill_parent" android:layout_height="fill_parent"
 android:id="@+id/calculator" android:layout_weight="1" />
</LinearLayout> 

在 清单 1 中,布局包含各种用户界面元素。注意,android:id 属性使得应用程序可以引用布局中的特定小部件。例如,WebView 包含 calculator 的一个 id;但是 TextView 不包含 id,因为它的值在应用程序的整个生命期内是不变的。

AndroidJSON.java 中的 onCreate() 方法负责搭建布局,如 清单 2 所示。

清单 2. 设置用户界面

public class AndroidJSON extends Activity {
    private final String tag = "AndroidJSON";
    private WebView browser = null;
    private int flipflop = 0;

    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);


        final EditText formula = (EditText) this.findViewById(R.id.formula);
        final Button btnSimple = (Button) this.findViewById(R.id.btnSimple);
        final Button btnComplex = (Button) this.findViewById(R.id.btnComplex);
        final Button btnRed = (Button) this.findViewById(R.id.btnRed);
    // remaining code removed for brevity - shown in next listings
} 

通过调用 setContentView() 搭建布局。注意,通过调用 findViewById() 方法设置用户界面元素。每次保存 main.xml 文件时会自动产生 R.java 文件。包含 android:id 属性的布局元素变成 R.id 类中的值,如 清单 3 所示。

清单 3. R.java

/* AUTO-GENERATED FILE.  DO NOT MODIFY.
 *
 * This class was automatically generated by the
 * aapt tool from the resource data it found.  It
 * should not be modified by hand.
 */

package com.msi.androidjson;

public final class R {
    public static final class attr {
    }
    public static final class drawable {
        public static final int icon=0x7f020000;
    }
    public static final class id {
        public static final int btnComplex=0x7f050002;
        public static final int btnRed=0x7f050003;
        public static final int btnSimple=0x7f050001;
        public static final int calculator=0x7f050004;
        public static final int formula=0x7f050000;
    }
    public static final class layout {
        public static final int main=0x7f030000;
    }
    public static final class string {
        public static final int app_name=0x7f040001;
        public static final int title=0x7f040000;
    }
}

本文稍后还会详细介绍 Button 设置代码,现在将重点放在 WebView 控件或小部件的设置上。尽管 Button 和其他用户界面元素相当直观,但是 WebView 还是得稍微费点功夫。但是不必担心 — 它也不是那么难,具体来说重点就是使用常用的剪切粘贴技术!来看一下 清单 4 中的代码段,它还是来自 AndroidJSON.java 中的 onCreate() 方法。

清单 4. 设置 WebView 小部件

        // connect to our browser so we can manipulate it
        browser = (WebView) findViewById(R.id.calculator);

        // set a webview client to override the default functionality
        browser.setWebViewClient(new wvClient());

        // get settings so we can config our WebView instance
        WebSettings settings = browser.getSettings();

        // JavaScript?  Of course!
        settings.setJavaScriptEnabled(true);

        // clear cache
        browser.clearCache(true);

        // this is necessary for "alert()" to work
        browser.setWebChromeClient(new WebChromeClient());

        // add our custom functionality to the javascript environment
        browser.addJavascriptInterface(new CalculatorHandler(), "calc");

        // uncomment this if you want to use the webview as an invisible calculator!
        //browser.setVisibility(View.INVISIBLE);

        // load a page to get things started
        browser.loadUrl("file:///android_asset/index.html");

        // allows the control to receive focus
        // on some versions of Android the webview doesn't handle input focus properly
        // this seems to make things work with Android 2.1, but not 2.2
       // browser.requestFocusFromTouch(); 

注意,在 清单 4 中,您将一个名为 browser 的 Activity 范围变量捆绑到了 WebView 控件。WebView 是一个相当复杂的类,可高度定制。例如,您需要设置几个类,以得到与 web 浏览器相关的预期函数。这是程序员必须投入一定精力来得到一些有用函数的地方之一。但是,此定制是没有限制的。对于此应用程序的目的来说,WebView 控件已经进行了最低限度的部署。

WebViewClient 提供用于捕获各种事件的钩子,这些事件包括页面加载开始和结束、表单重新提交、键盘截取以及程序员喜欢跟踪并操纵的很多其他事件。类似地,您需要 WebChromeClient 的一个实例,用于允许诸如非常有用的 alert() JavaScript 函数之类的函数。使用 WebSettings 来为控件启用 JavaScript。

要导致 WebView 控件导航到一个页面,可以采用几种不同的方式。在这个应用程序中,您采用 loadurl() 方法,带有到打包为项目资产的 index.html 文件的全限定路径。要获得更多关于设置 WebView 控件的信息,请在线查看 android.webkit 包的文档(参见 参考资料)。名为 index.html 的文件直接从应用程序随带的资源加载到 Webview 控件中。注意 图 2 中资源下面的 assets 文件夹。此文件夹是存储混合应用程序中使用的 html 文件的理想位置。(查看 图 2 的文本版本。)

图 2. Eclipse 中的项目

处理 WebView 最重要且有趣的方面是下一步:将 WebView 的 JavaScript 环境连接到 Android Activity 代码。

连接 JavaScript 接口

下一步是启用 Activity 中的 Java 代码,以与 WebView 管理的 HTML 文件中的 JavaScript 代码交互。这是通过调用 addJavascriptInterface() 方法完成的,如 清单 4 所示。

该函数的参数是一个 Java 类的实例和一个名称空间标识符。例如,对于这个应用程序,您定义一个 calc 名称空间,并实现名为 CalculatorHandler 的类中的代码,如 清单 5 所示。

清单 5. CalculatorHandler 实现

// Javascript handler
    final class CalculatorHandler
    {
        private int iterations = 0;
        // write to LogCat (Info)
        public void Info(String str) {
            iterations++;
            Log.i("Calc",str);
        }
        // write to LogCat (Error)
        public void Error(String str) {
            iterations++;
            Log.e("Calc",str);
        } 
        // sample to retrieve a custom - written function with the details provided 
        // by the Android native application code
        public String GetSomeFunction()
        {
            iterations++;
            return "var q = 6;function dynamicFunc(v) { return v + q; }";
        }
        // Kill the app        
        public void EndApp() {
            iterations++;
            finish();
        }
        public void setAnswer(String a)
        {
            iterations++;
            Log.i(tag,"Answer [" + a + "]");
        }
        public int getIterations()
        {
            return iterations;
        }
        public void SendHistory(String s)
        {
            Log.i("Calc","SendHistory" + s);
            try {
                JSONArray ja = new JSONArray(s);
                for (int i=0;i<ja.length();i++) {
                    Log.i("Calc","History entry #" + (i+1) + " is [" + ja.getString(i) 
+ "]");
                }
            } catch (Exception ee) {
                Log.e("Calc",ee.getMessage());
            }
        }
    }

在 JavaScript 环境中,通过 window.calc.methodname 语法访问 CalculatorHandler 的方法。例如,CalculatorHandler 实现一个名为 Info() 的方法,后者接受一个字符串参数并将之写到应用程序日志中。要从 JavaScript 环境访问此方法,可使用类似这样的语法:window.calc.Info("write this string to the application log!");。

基本了解了如何从 JavaScript 代码调用 Java 代码之后,我们再来看 清单 6 中的 index.html 文件,看各种方法是如何被调用的。

清单 6. WebView 控件中呈现(和执行)的 index.html

<html>
<head>
<meta name="viewport" content="width=device-width,initial-scale=0.25,
    user-scalable=yes" />
<title>Android to JavaScript with JSON</title>
</head>
<script language="JavaScript">
var cmdHistory = new Array();
function startup() {
    try {
        window.calc.Info("Starting up....");
        cmdHistory[cmdHistory.length] = "startup";
    } catch (ee) {

    }
}
function PerformSimpleCalculation(formula) {
    try {
        cmdHistory[cmdHistory.length] = "PerformSimpleCalculation";
        var answer = eval(String(formula));
        document.getElementById('data').value = answer;
        window.calc.setAnswer(answer);
    }    catch (ee)     {
        window.calc.Error(ee);
    }
}
function PerformComplexCalculation(andmethod) {
    try    {
        /*
         * argument to this function is a single object with 2 "members or properties"
         * operation: this is a string naming what we want the function to do.
         * array of arguments: this is an array of integers
         * 
         */
        //alert(andmethod.operation);
        //alert(andmethod.arguments.length);
        if (andmethod.operation == "addarray") {
            cmdHistory[cmdHistory.length] = "PerformCompleCalculation-addarray";
            var i;
            var result = 0;
            for (i=0;i<andmethod.arguments.length;i++) {
                result += andmethod.arguments[i];
            }
            document.getElementById('data').value = result;
            window.calc.setAnswer(result);
        }
        if (andmethod.operation == "multarray") {
            cmdHistory[cmdHistory.length] = "PerformCompleCalculation-multarray";
            var i;
            var result = 1;
            for (i=0;i<andmethod.arguments.length;i++) {
                result *= andmethod.arguments[i];
            }
            document.getElementById('data').value = result;
            window.calc.setAnswer(result);            
        }
    }    catch (ee)    {
        window.calc.Error(ee);
    }
}
function dynamicfunction()
{
    try {
        cmdHistory[cmdHistory.length] = "PerformCompleCalculation-dynamic";
        eval(String(window.calc.GetSomeFunction()));
        var result = dynamicFunc(parseInt(document.getElementById('data').value));
        document.getElementById('data').value = result;
    }catch (ee) {
        alert(ee);
    }
}
</script>
<body >
<center>
<h3>Running in Web View :)</h3>
this is some sample text here <br />
<input type="text" id="data" value="starting value"><br />
<button onclick="window.calc.Info(document.getElementById('data').value);">Log
 Info</button>  
<button onclick="window.calc.Error(document.getElementById('data').value);">Log
 Error</button><br />
<button onclick="dynamicfunction();">Dynamic</button>
<button onclick="alert(String(window.calc.getIterations()));">How 
    Many Calls</button>
<button onclick="window.calc.SendHistory(JSON.stringify(cmdHistory));">
    History</button>
<button onclick="if (window.confirm('End App?')) window.calc.EndApp();">Kill This
 App</button><br />
</center>
</body>
</html> 

仔细研究一下 清单 6 末尾的按钮处理程序。基本上,这些按钮处理程序都调用 window.calc 名称空间中的方法,这些方法在 AndroidJSON.java 中的 CalculatorHandler 类中实现。

清单 5 和 清单 6 协同工作,演示了 JavaScript 环境中初始化的和 Java 源文件中实现的代码交互。但是如何从 Activity 代码中初始化一些您想要在 WebView 中发生的动作呢?

现在应该更深入地来看 Java 代码了。

插入 JavaScript 代码

从将一个数学公式传递到 JavaScript 代码进行计算这样一个任务开始。JavaScript 最伟大(也最危险)的特性之一是 eval() 函数。eval() 函数允许字符串代码的运行时计算。在本例中,您从 EditText 控件接受一个字符串并传递到 JavaScript 环境进行计算。具体来说,我们调用 清单 6 中的 PerformSimpleCalculation() 函数。

清单 7 包含 AndroidJSON.java 中的代码,它负责处理按钮选择。

清单 7. 从 Java 调用 PerformSimpleCalculation() JavaScript 函数

  btnSimple.setOnClickListener(new OnClickListener()
  {
       public void onClick(View v) {
         Log.i(tag,"onClick Simple");
         // Perform action on click
         try
         {
            String formulaText =  formula.getText().toString();
            Log.i(tag,"Formula is [" + formulaText + "]" );
            browser.loadUrl("javascript:PerformSimpleCalculation(" + formulaText + ");");
         }
         catch (Exception e)
         {
               Log.e(tag,"Error ..." + e.getMessage());
         }
       }
  }); 

不管此方法有多少行,这里唯一要关注的是 browser.loadurl() 行,它传递一个格式字符串:javascript:<code to execute>。

此 JavaScript 代码被注入到 WebView 的当前页面并执行。这样,Java 代码就可以执行 WebView 中定义的 JavaScript 代码了。

在 Simple 例子中,传递了一个字符串。但是,当需要处理更复杂的结构时该怎么办呢?这就是 JSON 可派上用场的地方。清单 8 展示了 PerformComplexCalculation() 函数的调用,该函数参见 清单 6。

清单 8. 通过传递一个 JSON 对象调用更复杂的函数

btnComplex.setOnClickListener(new OnClickListener()
{
     public void onClick(View v) {
         Log.i(tag,"onClick Complex");
         // Perform action on click
         try
         {
             String jsonText = "";
         
             if (flipflop == 0)
             {     
                 jsonText = "{ \"operation\" : \"addarray\",\"arguments\" :
 [1,2,3,4,5,6,7,8,9,10]}";
                 flipflop = 1;
             } else {
                 jsonText = "{ \"operation\" : \"multarray\",\"arguments\" :
 [1,2,3,4,5,6,7,8,9,10]}";
                 flipflop = 0;
             }
             Log.i(tag,"jsonText is [" + jsonText + "]" );
             browser.loadUrl("javascript:PerformComplexCalculation(" + jsonText + ");");
         }
         catch (Exception e)
         {
             Log.e(tag,"Error ..." + e.getMessage());
         }
         
     }
}); 

研究一下 清单 6 中的 JavaScript 函数 PerformComplexCalculation。注意,传递进来的参数不是字符串,而是您自己创建的一个对象。

operation - 要处理的函数或过程的名称

arguments - 这是一个整数数组

对象只包含两个属性,但是完全可以更复杂,以满足更高的需求。在本例中,PerformComplexCalculation() JavaScript 函数支持两种不同的操作:addarray 和 multarray。当这些操作在调用时完成其工作时,通过调用函数 window.calc.setAnswer,将结果传递回 Java 代码。这里,您看到了 Java 和 JavaScript 代码之间的双向数据流。

在本例中,您传递了一个 JSON 对象,但是得到的一条经验是,在处理从 Java 代码返回来的 Java 字符串时,它有助于将它们转换成 JavaScript 字符串。这可以像本例中一样通过将值传递给 String 函数来做到:eval(String(formula));。

JavaScript eval() 函数使用 JavaScript 字符串。无需转换的话,eval 函数基本上不做任何事情。

对于一个稍微复杂一点的例子,鼓励您好好看一下 Dynamic 按钮在 WebView 中被选中时的代码段。

要完成代码例子,来看一下将一个字符串数组从 JavaScript 环境传递到 Java 环境。

交换 JSON 对象

示例应用程序 (index.html) 中的 JavaScript 代码将本地函数调用记录到一个名为 cmdHistory 的页面级别数组中。每次调用函数时,您都将一个新条目添加到该数组中。例如,当 dynamicfunction() 被调用时,一个新的字符串被存储:cmdHistory[cmdHistory.length] = "PerformCompleCalculation-dynamic";。

关于此方法,没有什么特别的地方;它只是一个在页面级别收集使用数据的例子。也许该数据存储在 Android 应用程序的数据库中会有用。此数据如何回到 Java 代码呢?

要发送字符串对象数组,您调用 JSON.stringify 函数,将数组作为参数传递进来。根据需要,stringify 函数可以允许定制一个复杂对象的特定属性如何被格式化。关于这是如何完成的更多信息,可以参考 json.org 中的解释(参见 参考资料)。

图 3 展示了应用程序的典型运行中解析 JSON 数组之后 Log 中的内容。

图 3. 解析从 JavaScript 发送来的 JSON 数组

本例只存储字符串数据,所以您可以简单地将之附加到一个较长的字符串后面,并调用 CalculatorHandler 中的一个简单函数,然后该函数可以将之解析出来。但是,若是应用程序想要跟踪其他数据(比如某些变量的值)或者甚至试图通过记录特定的函数调用过程来剖析代码,那么情况又是如何呢?显然,在较复杂的情景中,记录和交换对象的能力很重要。

结束语

本文演示了 Android 应用程序中的 Java 代码与 WebView 中的 JavaScript 代码之间传输数据的技术,以及利用 WebKit 开发的混合应用程序的一些比较普通的主题。混合应用程序混合了 JavaScript、JSON、回调函数、Android-SDK Java 代码以及所有当中最为重要的成份 — 想象力,以交付灵活且功能强大的移动应用程序。


 
分享到
 
 
     


android人机界面指南
Android手机开发(一)
Android手机开发(二)
Android手机开发(三)
Android手机开发(四)
iPhone消息推送机制实现探讨
手机软件测试用例设计实践
手机客户端UI测试分析
手机软件自动化测试研究报告
更多...   


Android高级移动应用程序
Android应用开发
Android系统开发
手机软件测试
嵌入式软件测试
Android软、硬、云整合


领先IT公司 android开发平台最佳实践
北京 Android开发技术进阶
某新能源领域企业 Android开发技术
某航天公司 Android、IOS应用软件开发
阿尔卡特 Linux内核驱动
艾默生 嵌入式软件架构设计
西门子 嵌入式架构设计
更多...