<?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>
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 中省略了它。在了解各种数据下载/解析类之前,我先为您展示一下服务器如何提供此数据。
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; } }
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(); } }
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; }
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(); }
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); } }
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— 它也是非常冗长的。
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; } }
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 (int i=0;i<symbols.length;i++){ stocks[i] = Stock.fromQuote(portfolio.getQuote(i)); } } catch (Exception e){ Log.e("DayTrader", "Exception getting ProtocolBuffer data", e); } return stocks; }
如 清单 3 所示,您可以使用 protocol buffers 编译器生成的 helper 类。这与服务器使用的 helper 类相同。可以编译它一次,然后在服务器和客户端共享它。 这样,您可以更轻松地直接从服务器的流读取数据并将其转换成 Stock 对象数组。这种简单编程也具有非常出色的性能。现在看一下此性能与 XML 和 JSON 的比较。