在 Android 应用程序中使用 Internet 数据


原文:http://www.ibm.com/developerworks/cn/xml/x-dataAndroid/index.html

Android 应用程序必须访问位于 Internet 上的数据,而 Internet 数据可以有几种不同的格式。本文将介绍在 Android 应用程序中如何使用三种数据格式:

  • XML
  • JSON
  • Google 的 protocol buffers

首先开发一个 Web 服务,将 CSV 数据转换成 XML、JSON 和 protocol-buffers 格式。然后构建一个样例 Android 应用程序,可以从 Web 服务中以任何一种格式提取数据并将其解析并显示给用户。

要进行本文中的练习,您需要最新的 Android SDK(参见参考资料)和 Android 2.2 平台。SDK 还要求您安装一个 Java™ 开发包(JDK);本文中使用了 JDK 1.6.0_17。您不需要有 Android 物理设备;所有代码都将在 SDK 的 Android 仿真器中运行。本文并没有教您如何进行 Android 开发,因此建议您熟悉 Android 编程。当然,只凭借 Java 编程语言的知识也可以完成本文的学习。

您还需要一个 Java web 应用程序服务器来运行 Android 应用程序使用的 Web 服务。此外,也可以将服务器端代码部署到 Google App Engine。参见下载部分获得完整的源代码。

Day Trader 应用程序

您将开发一个简单的 Android 应用程序,叫做 Day Trader。Day Trader 允许用户输入一个或更多的股票代码并获取其所代表股票的最新价格信息。用户可以指定数据使用的格式:XML、JSON 或 protocol buffers。实际的 Android 应用程序通常不会提供此选择,但是通过实现此功能,您可以了解如何让您的应用程序处理每一种格式。图 1展示了 Day Trader 用户界面:

图 1. 运行中的 Day Trader 应用程序
在 Android 应用程序中使用 Internet 数据_第1张图片

文本框及其旁边的Add Stock按钮允许用户输入感兴趣的每支股票的代码。用户按下Download Stock Data按钮后,会从服务器请求所有这些股票的数据,在应用程序中解析并显示在屏幕上。默认情况下,获取的是 XML 数据。通过菜单,您可以在 XML、JSON 或 protocol buffers 数据格式间切换。

清单 1显示用于创建图 1中所示 UI 的布局 XML:

清单 1. Day Trader 布局 XML

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 <? 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 android:orientation = "horizontal" android:layout_width = "fill_parent" android:layout_height = "wrap_content" > < EditText android:id = "@+id/symbol" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:width = "120dip" /> < Button android:id = "@+id/addBtn" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:text = "@string/addBtnLbl" /> </ LinearLayout > < LinearLayout android:orientation = "horizontal" android:layout_width = "fill_parent" android:layout_height = "wrap_content" > < TextView android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:id = "@+id/symList" /> < Button android:id = "@+id/dlBtn" android:layout_width = "wrap_content" android:layout_height = "wrap_content" android:text = "@string/dlBtnLbl" /> </ LinearLayout > < ListView android:id = "@android:id/list" android:layout_height = "fill_parent" android:layout_width = "fill_parent" android:layout_weight = "1" /> </ LinearLayout >

清单 1中的大部分代码都简单明了。可以看到几个小部件创建了图 1所示的输入和按钮。还会看到一个ListView,Android 小部件中真正的瑞士军刀。此ListView将用从服务器下载的股票数据填充。清单 2显示了控制该视图的Activity

清单 2. Day Trader 主活动

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 public class Main extends ListActivity { private int mode = XML; // default @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); final EditText input = (EditText) findViewById(R.id.symbol); final TextView symbolsList = (TextView) findViewById(R.id.symList); final Button addButton = (Button) findViewById(R.id.addBtn); final Button dlButton = (Button) findViewById(R.id.dlBtn); addButton.setOnClickListener( new OnClickListener(){ public void onClick(View v) { String newSymbol = input.getText().toString(); if (symbolsList.getText() == null || symbolsList.getText().length() == 0){ symbolsList.setText(newSymbol); } else { StringBuilder sb = new StringBuilder(symbolsList.getText()); sb.append( "," ); sb.append(newSymbol); symbolsList.setText(sb.toString()); } input.setText( "" ); } }); dlButton.setOnClickListener( new OnClickListener(){ public void onClick(View v) { String symList = symbolsList.getText().toString(); String[] symbols = symList.split( "," ); symbolsList.setText( "" ); switch (mode){ case JSON : new StockJsonParser().execute(symbols); break ; case PROTOBUF : new StockProtoBufParser().execute(symbols); break ; default : new StockXmlParser().execute(symbols); break ; } } }); } }

Activity设置了清单 1中 XML 文件的布局,它将几个事件处理程序连接起来。首先,对于Add Stock按钮而言,代码读取文本框中的代码并将其添加到symList TextView中,用逗号分隔每个代码。接下来,对于Download按钮而言,处理程序从symList TextView中读取数据,然后 —基于mode变量— 使用三个不同的类之一从服务器下载数据。菜单设置mode变量的值;这个代码不是很重要,因此我在清单 2中省略了它。在了解各种数据下载/解析类之前,我先为您展示一下服务器如何提供此数据。

提供股票数据

应用程序服务器需要能够做两件事。第一,它必须获取股票代码列表并检索它们的数据。然后,它需要接受一个格式参数并基于该格式编码数据。对于 XML 和 JSON 格式而言,该服务器将返回作为文本的串行化的股票数据。对于 protocol buffers 而言,它必须发送二进制数据。清单 3显示了处理这些步骤的 servlet:

清单 3. Stock Broker servlet

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 public class StockBrokerServlet extends HttpServlet { public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { String[] symbols = request.getParameterValues( "stock" ); List<Stock> stocks = getStocks(symbols); String format = request.getParameter( "format" ); String data = "" ; if (format == null || format.equalsIgnoreCase( "xml" )){ data = Stock.toXml(stocks); response.setContentType( "text/xml" ); } else if (format.equalsIgnoreCase( "json" )){ data = Stock.toJson(stocks); response.setContentType( "application/json" ); } else if (format.equalsIgnoreCase( "protobuf" )){ Portfolio p = Stock.toProtoBuf(stocks); response.setContentType( "application/octet-stream" ); response.setContentLength(p.getSerializedSize()); p.writeTo(response.getOutputStream()); response.flushBuffer(); return ; } response.setContentLength(data.length()); response.getWriter().print(data); response.flushBuffer(); response.getWriter().close(); } public List<Stock> getStocks(String... symbols) throws IOException{ StringBuilder sb = new StringBuilder(); for (String symbol : symbols){ sb.append(symbol); sb.append( '+' ); } sb.deleteCharAt(sb.length() - 1); String urlStr = "http://finance.yahoo.com/d/quotes.csv?f=sb2n&s=" + sb.toString(); URL url = new URL(urlStr); HttpURLConnection conn = (HttpURLConnection) url.openConnection(); BufferedReader reader = new BufferedReader( new InputStreamReader(conn.getInputStream())); String quote = reader.readLine(); List<Stock> stocks = new ArrayList<Stock>(symbols.length); while (quote != null ){ String[] values = quote.split( "," ); Stock s = new Stock(values[0], values[2], Double.parseDouble(values[1])); stocks.add(s); quote = reader.readLine(); } return stocks; } }

这是一个简单的 Java servlet,只支持 HTTPGET请求。它读入股票的值和格式请求参数。然后调用getStocks()方法。该方法调用 Yahoo! Finance 获取股票数据。Yahoo! 只支持 CSV 格式的数据,因此getStocks()方法将其解析到一个Stock对象列表。清单 4展示了这个简单的数据结构:

清单 4. 股票数据结构

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 public class Stock { private final String symbol; private final String name; private final double price; //getters and setters omitted public String toXml(){ return "<stock><symbol>" + symbol + "</symbol><name><![CDATA[" + name + "]]></name><price>" + price + "</price></stock>" ; } public String toJson(){ return "{ 'stock' : { 'symbol' : " +symbol + ", 'name':" + name + ", 'price': '" + price + "'}}" ; } public static String toXml(List<Stock> stocks){ StringBuilder xml = new StringBuilder( "<stocks>" ); for (Stock s : stocks){ xml.append(s.toXml()); } xml.append( "</stocks>" ); return xml.toString(); } public static String toJson(List<Stock> stocks){ StringBuilder json = new StringBuilder( "{'stocks' : [" ); for (Stock s : stocks){ json.append(s.toJson()); json.append( ',' ); } json.deleteCharAt(json.length() - 1); json.append( "]}" ); return json.toString(); } }

每个Stock都有三个属性—symbolnameprice— 和几个便捷的方法,以便将其自己转换成 XML 字符串或 JSON 字符串。它提供了一个工具方法,用于将Stock对象列表转换成 XML 或 JSON。回到清单 3,根据格式请求参数,Stock对象列表被转换成 XML 或 JSON 字符串并被发送回客户端。

XML 和 JSON 用例非常类似和直接。对于 protocol buffers,您必须生成 protocol buffers 格式的代码读写对象。为此,您需要使用 protocol buffers 规范格式定义数据结构。清单 5展示了一个示例:

清单 5. 股票的 Protocol buffers 消息

?
1 2 3 4 5 6 7 8 9 10 11 12 13 package stocks; option java_package = "org.developerworks.stocks" ; message Quote{ required string symbol = 1; required string name = 2; required double price = 3; } message Portfolio{ repeated Quote quote = 1; }

protocol buffers 消息格式类似于接口描述语言 (IDL),它与语言无关,因此可以将其与各种语言一起使用。在本例中,运行 protocol buffers 编译器(protoc)将清单 5中的代码编译成要用于客户端和服务器的 Java 类。有关将 protocol buffers 消息编译成 Java 类的详细信息,请参阅 Protocol Buffers Developer Guide(参见参考资料)。

在清单 3中,一个名为toProtoBuf()的方法将Stock对象列表转换成一个Portfolio消息。清单 6展示了该方法的实现:

清单 6. 创建组合消息

?
1 2 3 4 5 6 7 8 9 10 11 12 13 public static Stocks.Portfolio toProtoBuf(List<Stock> stocks){ List<Stocks.Quote> quotes = new ArrayList<Stocks.Quote>(stocks.size()); for (Stock s : stocks){ Quote q = Quote.newBuilder() .setName(s.name) .setSymbol(s.symbol) .setPrice(s.price) .build(); quotes.add(q); } return Portfolio.newBuilder().addAllQuote(quotes).build(); }

清单 6中的代码使用了从清单 5中的消息生成的代码 —QuotePortfolio类。只需构建来自每个Stock对象的Quote,然后将其添加到清单 3中返回到 servlet 的Portfolio对象即可。在清单 3中,servlet 直接打开到客户端的流并使用生成的代码编写到流的二进制协议 buffers 数据。

现在,您了解了服务器如何创建要发送到 Android 应用程序的数据。接下来将学习应用程序如何解析此数据。

使用数据格式

清单 2中的主Activity需要使用服务器可以发送的各种格式的数据。它还需要请求适当格式的数据并且数据一旦解析,就用它来填充其ListView。因此,无论数据格式是什么,大部分功能都是通用的。

首先,创建一个抽象的基类,封装此通用功能,如清单 7所示:

清单 7. 数据解析器基类

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 abstract class BaseStockParser extends AsyncTask<String, Integer, Stock[]>{ String urlStr = "http://protostocks.appspot.com/stockbroker?format=" ; protected BaseStockParser(String format){ urlStr += format; } private String makeUrlString(String... symbols) { StringBuilder sb = new StringBuilder(urlStr); for ( int i=0;i<symbols.length;i++){ sb.append( "&stock=" ); sb.append(symbols[i]); } return sb.toString(); } protected InputStream getData(String[] symbols) throws Exception{ HttpClient client = new DefaultHttpClient(); HttpGet request = new HttpGet( new URI(makeUrlString(symbols))); HttpResponse response = client.execute(request); return response.getEntity().getContent(); } @Override protected void onPostExecute(Stock[] stocks){ ArrayAdapter<Stock> adapter = new ArrayAdapter<Stock>(Main. this , R.layout.stock, stocks ); setListAdapter(adapter); } }

清单 7中的基类扩展了android.os.AsyncTask。这是一个常用的用于异步操作的类。它抽象出线程和处理程序的创建,用于请求主 UI 线程。它是基于其输入和输出数据类型参数化的。对于所有解析器而言,输入总是一样的:股票代码字符串。 输出也是一样的:Stock对象数组。基类获取format,这是一个指定了要使用的数据格式的字符串。然后提供一个方法,发出适当的 HTTP 请求并返回一个流响应。最后,它覆盖AsyncTaskonPostExecute()方法并使用从解析器返回的数据为ActivityListView创建一个Adapter

现在看到三个解析器的功能是通用的。我将为您展示更具体的解析代码,从 XML 解析器开始。

用 SAX 解析 XML

Android SDK 提供了几种使用 XML 的方式,包括标准 DOM 和 SAX。 对于一些对内存密集型情况,可以使用 SDK 的 pull-parser。大部分时候,SAX 是最快的方式。Android 包括一些便捷的 API 使得使用 SAX 更轻松。清单 8显示了 Day Trader 应用程序的 XML 解析器:

清单 8. XML 解析器实现

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 private class StockXmlParser extends BaseStockParser{ public StockXmlParser(){ super( "xml" ); } @Override protected Stock[] doInBackground(String... symbols) { ArrayList<Stock> stocks = new ArrayList<Stock>(symbols.length); try { ContentHandler handler = newHandler(stocks); Xml.parse(getData(symbols), Xml.Encoding.UTF_8, handler); } catch (Exception e){ Log.e( "DayTrader" , "Exception getting XML data" , e); } Stock[] array = new Stock[symbols.length]; return stocks.toArray(array); } private ContentHandler newHandler(final ArrayList<Stock> stocks){ RootElement root = new RootElement( "stocks" ); Element stock = root.getChild( "stock" ); final Stock currentStock = new Stock(); stock.setEndElementListener( new EndElementListener(){ public void end() { stocks.add((Stock) currentStock.clone()); } } ); stock.getChild( "name" ).setEndTextElementListener( new EndTextElementListener(){ public void end(String body) { currentStock.setName(body); } } ); stock.getChild( "symbol" ).setEndTextElementListener( new EndTextElementListener(){ public void end(String body) { currentStock.setSymbol(body); } } ); stock.getChild( "price" ).setEndTextElementListener( new EndTextElementListener(){ public void end(String body) { currentStock.setPrice(Double.parseDouble(body)); } } ); return root.getContentHandler(); } }

清单 8中的大部分代码都在newHandler()方法中,该方法创建一个ContentHandler。如果熟悉 SAX 解析, 会知道ContentHandler通过响应 SAX 解析器触发的各种事件创建解析数据。newHandler()方法使用 Android 便捷 API 指定使用事件处理程序的ContentHandler。代码只是侦听在解析器遇到各种标记时触发的事件,然后选取数据,放到Stock对象列表中。 创建ContentHandler后,调用Xml.parse()方法来解析基类提供的InputStream并返回Stock对象数组。这是快速解析 XML 的方法,但是 —即使使用 Android 提供的便捷 API— 它也是非常冗长的。

使用 JSON

XML 是 Android 上的一等公民,鉴于依赖于 XML 的 Web 服务的数量,这是个好事。很多服务还支持另一个流行格式 JSON。它通常比 XML 简洁一些,但也是人们可读的,使得它更易于使用,并且可以更轻松地将其用于调试使用它的应用程序。Android 包括一个 JSON 解析器。(您可以从 JSON.org 网站获得该解析器,只是要去除几个手机不需要的类)。清单 9显示了使用中的解析器:

清单 9. JSON 解析器实现

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 private class StockJsonParser extends BaseStockParser{ public StockJsonParser(){ super( "json" ); } @Override protected Stock[] doInBackground(String... symbols) { Stock[] stocks = new Stock[symbols.length]; try { StringBuilder json = new StringBuilder(); BufferedReader reader = new BufferedReader( new InputStreamReader(getData(symbols))); String line = reader.readLine(); while (line != null ){ json.append(line); line = reader.readLine(); } JSONObject jsonObj = new JSONObject(json.toString()); JSONArray stockArray = jsonObj.getJSONArray( "stocks" ); for ( int i=0;i<stocks.length;i++){ JSONObject object = stockArray.getJSONObject(i).getJSONObject( "stock" ); stocks[i] = new Stock( object .getString( "symbol" ), object .getString( "name" ), object .getDouble( "price" )); } } catch (Exception e){ Log.e( "DayTrader" , "Exception getting JSON data" , e); } return stocks; } }

可以看到在 Android 中使用 JSON 解析器是多么简单。您将来自服务器的流转换成传递给 JSON 解析器的字符串。您遍历对象图并创建Stock对象数组。如果使用过 XML DOM 解析,这看起来很类似,因为编程模型几乎一样。

像 DOM 一样,JSON 解析器可以用于内存密集型应用。在清单 9中,所有来自服务器的数据都表示为字符串,然后作为JSONObject,最后作为Stock对象数组。换句话说,同一数据通过三种不同的方式表示。可以看到,对于大量数据而言,这可能是个问题。当然,一旦到达方法末尾,这三种数据表示方式中的两种都会落在范围之外,被垃圾回收器回收。但是,只是触发更频繁的垃圾回收可能会对用户体验带来负面影响,造成处理速度下降。如果内存效率和性能很重要,使用 protocol buffers 的解析器可能是个较好的选择。

使用 protocol buffers 处理二进制

Protocol buffers是一个由 Google 开发的与语言无关的数据串行化格式,旨在比 XML 更快地通过网络传送数据。它是 Google 用于服务器对服务器调用的事实标准。Google 将该格式及其用于 C++、Java 和 Python 编程语言的绑定工具以开源方式提供。

在清单 3和清单 6中看到 protocol buffers 是二进制格式。如您所料,这使得数据很简洁。如果在客户端和服务器端启用 gzip 压缩,在使用 XML 和 JSON 时通常也可以得到类似的消息大小,但是 protocol buffers 仍然有一些大小上的优势。它还是一种可以迅速解析的格式。最后,它提供了一个相当简单的 API。清单 10显示了一个示例解析器实现:

清单 10. Protocol buffers 解析器实现

?
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 private class StockProtoBufParser extends BaseStockParser{ public StockProtoBufParser(){ super( "protobuf" ); } @Override protected Stock[] doInBackground(String... symbols) { Stock[] stocks = new Stock[symbols.length]; try { Stocks.Portfolio portfolio = Stocks.Portfolio.parseFrom(getData(symbols)); for

更多相关文章

  1. android中使用sqlite、复制assets下的数据库到SD卡、支持大于1M
  2. 2015Android设备、系统、分辨率最新统计数据
  3. 2010-03-02 传智播客—Android(四)数据存储之五网络
  4. 22、从头学Android之Android的数据存储--SQLite
  5. Android入门篇五:使用全局变量在Activity之间传递数据
  6. 第十二章:Android数据存储(下)

随机推荐

  1. ASP.Net Core中关于WebApi几种版本控制对
  2. 详解.Net Core使用Socket与树莓派进行通
  3. C#实现表格隔行换色
  4. .NET Core中遇到的一些坑的图文详解
  5. .net core使用Redis发布订阅方法介绍
  6. C#中关于List<T>的并集与交集以及差集解
  7. C#中发送邮件的实现方法详解
  8. C# 数组作为参数传递出现的问题解决
  9. C#中引用类型之特例string的详细介绍
  10. C#实现Json序列化删除null值的方法实例