Android 应用程序中使用 Internet 数据(XML、JSON 和 protocol buffers)

【转】Android 应用程序中使用 Internet 数据(XML、JSON 和 protocol buffers)(上)
2011-03-03 14:21

简介: 您的很多 Android 应用程序都需要与 Internet 数据交互,这些数据采用各种格式。本文将带您构建一个 Android 应用程序,它可以使用两种流行数据格式 —XML 和 JavaScript Object Notation (JSON)— 以及来自 Google 的比较奇异的 protocol buffers 格式。您将了解到与每个格式相关的性能和编码利弊。

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 应用程序
 

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

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


清单 1. Day Trader 布局 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" > 
 <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 主活动
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
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,只支持 HTTP GET 请求。它读入股票的值和格式请求参数。然后调用 getStocks() 方法。该方法调用 Yahoo! Finance 获取股票数据。Yahoo! 只支持 CSV 格式的数据,因此 getStocks() 方法将其解析到一个 Stock 对象列表。清单 4 展示了这个简单的数据结构:


清单 4. 股票数据结构
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 都有三个属性— symbol、name 和 price — 和几个便捷的方法,以便将其自己转换成 XML 字符串或 JSON 字符串。它提供了一个工具方法,用于将 Stock 对象列表转换成 XML 或 JSON。回到 清单 3,根据格式请求参数,Stock 对象列表被转换成 XML 或 JSON 字符串并被发送回客户端。

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


清单 5. 股票的 Protocol buffers 消息
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 类。

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


清单 6. 创建组合消息
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 中的消息生成的代码 — Quote 和 Portfolio 类。只需构建来自每个 Stock 对象的 Quote,然后将其添加到 清单 3 中返回到 servlet 的 Portfolio 对象即可。在 清单 3 中,servlet 直接打开到客户端的流并使用生成的代码编写到流的二进制协议 buffers 数据。

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

 

使用数据格式

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

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


清单 7. 数据解析器基类
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 请求并返回一个流响应。最后,它覆盖 AsyncTask 的 onPostExecute() 方法并使用从解析器返回的数据为 Activity 的 ListView 创建一个 Adapter。

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

 

用 SAX 解析 XML

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


清单 8. XML 解析器实现
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— 它也是非常冗长的。

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值