在Android應(yīng)用程序中使用Internet數(shù)據(jù)
Android 應(yīng)用程序必須訪問位于 Internet 上的數(shù)據(jù),而 Internet 數(shù)據(jù)可以有幾種不同的格式。本文將介紹在 Android 應(yīng)用程序中如何使用三種數(shù)據(jù)格式:
XML
JSON 首先開發(fā)一個 Web 服務(wù),將 CSV 數(shù)據(jù)轉(zhuǎn)換成 XML、JSON 和 protocol-buffers 格式。然后構(gòu)建一個樣例 Android 應(yīng)用程序,可以從 Web 服務(wù)中以任何一種格式提取數(shù)據(jù)并將其解析并顯示給用戶。
Google 的 protocol buffers
要進(jìn)行本文中的練習(xí),您需要最新的 Android SDK(參見 參考資料)和 Android 2.2 平臺。SDK 還要求您安裝一個 Java™ 開發(fā)包(JDK);本文中使用了 JDK 1.6.0_17。您不需要有 Android 物理設(shè)備;所有代碼都將在 SDK 的 Android 仿真器中運行。本文并沒有教您如何進(jìn)行 Android 開發(fā),因此建議您熟悉 Android 編程。當(dāng)然,只憑借 Java 編程語言的知識也可以完成本文的學(xué)習(xí)。
您還需要一個 Java web 應(yīng)用程序服務(wù)器來運行 Android 應(yīng)用程序使用的 Web 服務(wù)。此外,也可以將服務(wù)器端代碼部署到 Google App Engine。參見 下載 部分獲得完整的源代碼。
Day Trader 應(yīng)用程序
您將開發(fā)一個簡單的 Android 應(yīng)用程序,叫做 Day Trader。Day Trader 允許用戶輸入一個或更多的股票代碼并獲取其所代表股票的最新價格信息。用戶可以指定數(shù)據(jù)使用的格式:XML、JSON 或 protocol buffers。實際的 Android 應(yīng)用程序通常不會提供此選擇,但是通過實現(xiàn)此功能,您可以了解如何讓您的應(yīng)用程序處理每一種格式。圖 1 展示了 Day Trader 用戶界面:
圖 1. 運行中的 Day Trader 應(yīng)用程序
文本框及其旁邊的 Add Stock 按鈕允許用戶輸入感興趣的每支股票的代碼。用戶按下 Download Stock Data 按鈕后,會從服務(wù)器請求所有這些股票的數(shù)據(jù),在應(yīng)用程序中解析并顯示在屏幕上。默認(rèn)情況下,獲取的是 XML 數(shù)據(jù)。通過菜單,您可以在 XML、JSON 或 protocol buffers 數(shù)據(jù)格式間切換。
清單1顯示用于創(chuàng)建 圖 1 中所示 UI 的布局 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>
上述中的大部分代碼都簡單明了。可以看到幾個小部件創(chuàng)建了 圖 1 所示的輸入和按鈕。還會看到一個 ListView,Android 小部件中真正的瑞士軍刀。此 ListView 將用從服務(wù)器下載的股票數(shù)據(jù)填充。清單 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 設(shè)置了 清單 1 中 XML 文件的布局,它將幾個事件處理程序連接起來。首先,對于 Add Stock 按鈕而言,代碼讀取文本框中的代碼并將其添加到 symList TextView 中,用逗號分隔每個代碼。接下來,對于 Download 按鈕而言,處理程序從 symList TextView 中讀取數(shù)據(jù),然后 —基于 mode 變量— 使用三個不同的類之一從服務(wù)器下載數(shù)據(jù)。菜單設(shè)置 mode 變量的值;這個代碼不是很重要,因此我在 清單 2 中省略了它。在了解各種數(shù)據(jù)下載/解析類之前,我先為您展示一下服務(wù)器如何提供此數(shù)據(jù)。提供股票數(shù)據(jù)
應(yīng)用程序服務(wù)器需要能夠做兩件事。第一,它必須獲取股票代碼列表并檢索它們的數(shù)據(jù)。然后,它需要接受一個格式參數(shù)并基于該格式編碼數(shù)據(jù)。對于 XML 和 JSON 格式而言,該服務(wù)器將返回作為文本的串行化的股票數(shù)據(jù)。對于 protocol buffers 而言,它必須發(fā)送二進(jìn)制數(shù)據(jù)。 清單 3 顯示了處理這些步驟的 servlet:
#p#
清單 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 請求。它讀入股票的值和格式請求參數(shù)。然后調(diào)用 getStocks() 方法。該方法調(diào)用 Yahoo! Finance 獲取股票數(shù)據(jù)。Yahoo! 只支持 CSV 格式的數(shù)據(jù),因此 getStocks() 方法將其解析到一個 Stock 對象列表。清單 4 展示了這個簡單的數(shù)據(jù)結(jié)構(gòu):
清單 4. 股票數(shù)據(jù)結(jié)構(gòu)
- 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 — 和幾個便捷的方法,以便將其自己轉(zhuǎn)換成 XML 字符串或 JSON 字符串。它提供了一個工具方法,用于將 Stock 對象列表轉(zhuǎn)換成 XML 或 JSON?;氐?清單 3,根據(jù)格式請求參數(shù),Stock 對象列表被轉(zhuǎn)換成 XML 或 JSON 字符串并被發(fā)送回客戶端。
XML 和 JSON 用例非常類似和直接。對于 protocol buffers,您必須生成 protocol buffers 格式的代碼讀寫對象。為此,您需要使用 protocol buffers 規(guī)范格式定義數(shù)據(jù)結(jié)構(gòu)。清單 5 展示了一個示例:
#p#
清單 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),它與語言無關(guān),因此可以將其與各種語言一起使用。在本例中,運行 protocol buffers 編譯器(protoc)將 清單 5 中的代碼編譯成要用于客戶端和服務(wù)器的 Java 類。有關(guān)將 protocol buffers 消息編譯成 Java 類的詳細(xì)信息,請參閱 Protocol Buffers Developer Guide(參見 參考資料)。
在 清單 3 中,一個名為 toProtoBuf() 的方法將 Stock 對象列表轉(zhuǎn)換成一個 Portfolio 消息。清單 6 展示了該方法的實現(xiàn):
清單 6. 創(chuàng)建組合消息
- 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 類。只需構(gòu)建來自每個 Stock 對象的 Quote,然后將其添加到 清單 3 中返回到 servlet 的 Portfolio 對象即可。在 清單 3 中,servlet 直接打開到客戶端的流并使用生成的代碼編寫到流的二進(jìn)制協(xié)議 buffers 數(shù)據(jù)。
現(xiàn)在,您了解了服務(wù)器如何創(chuàng)建要發(fā)送到 Android 應(yīng)用程序的數(shù)據(jù)。接下來將學(xué)習(xí)應(yīng)用程序如何解析此數(shù)據(jù)。
清單 2 中的主 Activity 需要使用服務(wù)器可以發(fā)送的各種格式的數(shù)據(jù)。它還需要請求適當(dāng)格式的數(shù)據(jù)并且數(shù)據(jù)一旦解析,就用它來填充其 ListView。因此,無論數(shù)據(jù)格式是什么,大部分功能都是通用的。
首先,創(chuàng)建一個抽象的基類,封裝此通用功能,如 清單 7 所示:
清單 7. 數(shù)據(jù)解析器基類
- 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。這是一個常用的用于異步操作的類。它抽象出線程和處理程序的創(chuàng)建,用于請求主 UI 線程。它是基于其輸入和輸出數(shù)據(jù)類型參數(shù)化的。對于所有解析器而言,輸入總是一樣的:股票代碼字符串。 輸出也是一樣的:Stock 對象數(shù)組?;惈@取 format,這是一個指定了要使用的數(shù)據(jù)格式的字符串。然后提供一個方法,發(fā)出適當(dāng)?shù)?HTTP 請求并返回一個流響應(yīng)。最后,它覆蓋 AsyncTask 的 onPostExecute() 方法并使用從解析器返回的數(shù)據(jù)為 Activity 的 ListView 創(chuàng)建一個 Adapter。
現(xiàn)在看到三個解析器的功能是通用的。我將為您展示更具體的解析代碼,從 XML 解析器開始。
#p#
用 SAX 解析 XML
Android SDK 提供了幾種使用 XML 的方式,包括標(biāo)準(zhǔn) DOM 和 SAX。 對于一些對內(nèi)存密集型情況,可以使用 SDK 的 pull-parser。大部分時候,SAX 是最快的方式。Android 包括一些便捷的 API 使得使用 SAX 更輕松。清單 8 顯示了 Day Trader 應(yīng)用程序的 XML 解析器
清單 8. XML 解析器實現(xiàn)
- 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() 方法中,該方法創(chuàng)建一個 ContentHandler。如果熟悉 SAX 解析, 會知道 ContentHandler 通過響應(yīng) SAX 解析器觸發(fā)的各種事件創(chuàng)建解析數(shù)據(jù)。newHandler() 方法使用 Android 便捷 API 指定使用事件處理程序的 ContentHandler。代碼只是偵聽在解析器遇到各種標(biāo)記時觸發(fā)的事件,然后選取數(shù)據(jù),放到 Stock 對象列表中。 創(chuàng)建 ContentHandler 后,調(diào)用 Xml.parse() 方法來解析基類提供的 InputStream 并返回 Stock 對象數(shù)組。這是快速解析 XML 的方法,但是 —即使使用 Android 提供的便捷 API— 它也是非常冗長的。
使用 JSON
XML 是 Android 上的一等公民,鑒于依賴于 XML 的 Web 服務(wù)的數(shù)量,這是個好事。很多服務(wù)還支持另一個流行格式 JSON。它通常比 XML 簡潔一些,但也是人們可讀的,使得它更易于使用,并且可以更輕松地將其用于調(diào)試使用它的應(yīng)用程序。Android 包括一個 JSON 解析器。(您可以從 JSON.org 網(wǎng)站獲得該解析器,只是要去除幾個手機不需要的類)。 清單 9 顯示了使用中的解析器:
清單 9. JSON 解析器實現(xiàn)
- 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 解析器是多么簡單。您將來自服務(wù)器的流轉(zhuǎn)換成傳遞給 JSON 解析器的字符串。您遍歷對象圖并創(chuàng)建 Stock 對象數(shù)組。如果使用過 XML DOM 解析,這看起來很類似,因為編程模型幾乎一樣。
像 DOM 一樣,JSON 解析器可以用于內(nèi)存密集型應(yīng)用。在 清單 9 中,所有來自服務(wù)器的數(shù)據(jù)都表示為字符串,然后作為 JSONObject,最后作為 Stock 對象數(shù)組。換句話說,同一數(shù)據(jù)通過三種不同的方式表示。可以看到,對于大量數(shù)據(jù)而言,這可能是個問題。當(dāng)然,一旦到達(dá)方法末尾,這三種數(shù)據(jù)表示方式中的兩種都會落在范圍之外,被垃圾回收器回收。但是,只是觸發(fā)更頻繁的垃圾回收可能會對用戶體驗帶來負(fù)面影響,造成處理速度下降。如果內(nèi)存效率和性能很重要,使用 protocol buffers 的解析器可能是個較好的選擇。使用 protocol buffers 處理二進(jìn)制
Protocol buffers 是一個由 Google 開發(fā)的與語言無關(guān)的數(shù)據(jù)串行化格式,旨在比 XML 更快地通過網(wǎng)絡(luò)傳送數(shù)據(jù)。它是 Google 用于服務(wù)器對服務(wù)器調(diào)用的事實 標(biāo)準(zhǔn)。Google 將該格式及其用于 C++、Java 和 Python 編程語言的綁定工具以開源方式提供。
在 清單 3 和 清單 6 中看到 protocol buffers 是二進(jìn)制格式。如您所料,這使得數(shù)據(jù)很簡潔。如果在客戶端和服務(wù)器端啟用 gzip 壓縮,在使用 XML 和 JSON 時通常也可以得到類似的消息大小,但是 protocol buffers 仍然有一些大小上的優(yōu)勢。它還是一種可以迅速解析的格式。最后,它提供了一個相當(dāng)簡單的 API。 清單 10 顯示了一個示例解析器實現(xiàn):
清單 10. Protocol buffers 解析器實現(xiàn)
- 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 類。這與服務(wù)器使用的 helper 類相同??梢跃幾g它一次,然后在服務(wù)器和客戶端共享它。 這樣,您可以更輕松地直接從服務(wù)器的流讀取數(shù)據(jù)并將其轉(zhuǎn)換成 Stock 對象數(shù)組。這種簡單編程也具有非常出色的性能?,F(xiàn)在看一下此性能與 XML 和 JSON 的比較。
比較性能通常涉及某種微基準(zhǔn)測試,此類基準(zhǔn)測試很容易產(chǎn)生偏見或無意間得到不正確的結(jié)果。即使以公平方式設(shè)計微基準(zhǔn)測試,很多隨機因素也會對結(jié)果產(chǎn)生影響。盡管有這些問題,我還是要使用這樣的微基準(zhǔn)測試來比較 XML(大約 1300 ms)、JSON(大約 1150 ms)和 protocol buffers(大約 750 ms)。基準(zhǔn)測試向服務(wù)器發(fā)送了一個關(guān)于 200 個股票的請求并測量了從發(fā)出請求到用于創(chuàng)建 ListView 的 Adapter 的數(shù)據(jù)準(zhǔn)備就緒所需的時間量。對每個數(shù)據(jù)格式在兩個設(shè)備上進(jìn)行 50 次這樣的操作:一個 Motorola Droid 和一個 HTC Evo,兩個都通過 3G 網(wǎng)絡(luò)。 圖 2 顯示了結(jié)果:
圖 2. 比較數(shù)據(jù)格式速度
圖 2 顯示出,在此基準(zhǔn)測試中 protocol buffers(大約 750 ms)比 XML (大約 1300 ms)幾乎快兩倍。很多因素影響著數(shù)據(jù)通過網(wǎng)絡(luò)和被手持設(shè)備處理的性能。一個明顯的因素是通過網(wǎng)絡(luò)的數(shù)據(jù)量。二進(jìn)制格式的 protocol buffers 比文本格式的 XML 和 JSON 在通過網(wǎng)絡(luò)時小得多。然而,文本格式可以使用 gzip 進(jìn)行有效地壓縮,這是 Web 服務(wù)器和 Android 設(shè)備都支持的標(biāo)準(zhǔn)技術(shù)。圖 3 顯示了在打開和關(guān)閉 gzip 時通過網(wǎng)絡(luò)的數(shù)據(jù)大?。?/p>
圖 3. 不同格式的數(shù)據(jù)大小
圖 3 應(yīng)該增加了您對 XML 和 JSON 之類的文本內(nèi)容的壓縮效果的喜愛(更不用說 Web 格式、HTML、JavaScript 和 CSS 了)。protocol buffers 數(shù)據(jù)(大約 6KB)比原始 XML(大約 17.5KB)或 JSON(大約 13.5KB)數(shù)據(jù)小得多。但是一旦進(jìn)行了壓縮, JSON 和 XML(都是大約 3KB)實際上比 protocol buffers 小很多了。在本例中,它們都接近于 protocol-buffers 編碼消息大小的一半了。
回到 圖 2,速度的不同顯然不能由通過網(wǎng)絡(luò)的消息大小解釋。protocol-buffers 消息比 XML 或 JSON 編碼的消息大,但是通過使用 protocol buffers,您仍然能夠削減半秒鐘的用戶等待時間。這是否意味著應(yīng)該在 Android 應(yīng)用程序中使用 protocol buffers 呢?這樣的決定很少是固定的。如果要發(fā)送的數(shù)據(jù)量很小,則三種格式間的差異也不大。對于大量數(shù)據(jù)而言,protocol buffers 可能會有所不同。但是,像這樣精心設(shè)計的基準(zhǔn)測試無法替代對您自己的應(yīng)用程序的測試。
結(jié)束語
本文介紹了如何使用 Internet 上流行的兩種數(shù)據(jù)格式 XML 和 JSON 的方方面面。還講到了第三種可能性,protocol buffers。像軟件工程中的其他內(nèi)容一樣,選擇技術(shù)主要就是權(quán)衡利弊。當(dāng)您為一個局限的環(huán)境(比如 Android)開發(fā)時,這些決定的結(jié)果往往被放大了。我希望您現(xiàn)在擁有的關(guān)于這些后果的額外知識能夠幫助您創(chuàng)建出色的 Android 應(yīng)用程序。
【編輯推薦】
Android應(yīng)用程序開發(fā)環(huán)境的搭建