Android アプリケーションでインターネット・データを使用する

XML、JSON、およびプロトコル・バッファーのデータを構文解析する

Android アプリケーションの多くは、さまざまなフォーマットで提供されるインターネット上のデータを操作しなければなりません。この記事では、よく使われている 2 つのデータ・フォーマット、XML と JSON (JavaScript Object Notation)、そしてプロトコル・バッファーという Google の特異なフォーマットを扱う Android アプリケーションを作成します。この 3 つそれぞれのフォーマットに関するパフォーマンスとコーディングのトレードオフについて学んでください。

Michael Galpin, Software architect, eBay

Michael Galpin's photoMichael Galpin は eBay のアーキテクトであり、developerWorks に頻繁に寄稿しています。彼は JavaOne、EclipseCon、AjaxWorld など、さまざまな技術カンファレンスで講演を行っています。彼が次に取り組もうとしていることを知るには、Twitter で @michaelg をフォローしてください。



2010年 6月 29日

よく使われる頭字語

  • Ajax: Asynchronous JavaScript + XML
  • API: Application Programming Interface
  • CSV: Comma-Separated Values
  • CSS: Cascading Stylesheet
  • DOM: Document Object Model
  • HTML: HyperText Markup Language
  • HTTP: HyperText Transfer Protocol
  • IDL: Interface Description Language
  • JSON: JavaScript Object Notation
  • SAX: Simple API for XML
  • SDK: Software Developer Kit
  • UI: User Interface
  • URL: Uniform Resource Locator
  • XML: Extensible Markup Language
  • 3G: Third-generation of mobile phone technology standards

Android アプリケーションはインターネット上に置かれたデータにアクセスしなければならないことがよくありますが、インターネット・データを構造化する方法はさまざまにあります。この記事では、Android アプリケーションで次の 3 つのデータ・フォーマットを扱う方法を説明します。

  • XML
  • JSON
  • Google のプロトコル・バッファー

記事ではまず初めに、CSV データを XML、JSON、プロトコル・バッファーの各フォーマットに変換する Web サービスを開発します。次に、この 3 つのどのフォーマットのデータでも Web サービスからプルし、構文解析してユーザーに表示するサンプル Android アプリケーションを作成します。

この記事の演習に従うためには、最新の Android SDK (「参考文献」を参照) と Android 2.2 プラットフォームが必要です。また、SDK を使用するには JDK (Java™ Development Kit) がインストールされていることも必要条件となります (この記事では JDK 1.6.0_17 を使用しました)。実際の Android 機器は必要ありません。コードはすべて、SDK の Android エミュレーターで実行します。この記事では Android の開発自体については説明しません。そのため、Android のプログラミングに精通していることが望ましいですが、Java プログラミング言語の知識があれば、演習にはついていけるはずです。

サンプル Android アプリケーションが使用する Web サービスを実行するための Java Web アプリケーション・サーバーも必要ですが、代わりにサーバー・サイドのコードを Google App Engine にデプロイするのでも構いません。完全なソース・コードを入手するには、「ダウンロード」を参照してください。

Day Trader アプリケーション

この記事で開発するのは、Day Trader という名前の単純な Android アプリケーションです。Day Trader では、ユーザーが 1 つ以上の株の銘柄記号を入力すると、その記号が表す株の最新株価情報を取得することができます。データに使用するフォーマットは、ユーザーが XML、JSON、プロトコル・バッファーのいずれかを指定することができます。このような選択肢は、実際の Android アプリケーションが一般に提供するものではありませんが、この選択肢を実装することによって、サンプル・アプリケーションがそれぞれのフォーマットをどのように扱うのかを説明します。図 1 に、Day Trader のユーザー・インターフェースを示します。

図 1. 実行中の Day Trader アプリケーション
実行中の Day Trader アプリケーションのスクリーン・キャプチャー。画面には、AAPL、IBM、MSFT、GOOG の株価情報が表示されています。

テキスト・ボックスとその横にある「Add Stock (株の追加)」ボタンを使用して、ユーザーは興味のある株それぞれの銘柄記号を入力することができます。ユーザーが「Download Stock Data (株価データのダウンロード)」ボタンを押すと、入力した株のデータがサーバーから取得され、アプリケーション内で構文解析されて画面に表示されます。デフォルトでは、データは XML として取得されますが、メニューを使ってデータ・フォーマットを XML、JSON、またはプロトコル・バッファーに切り替えられるようになっています。

リスト 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 に示された入力とボタンを作成する複数のウィジェットがあります。さらに、まさに Android ウィジェットのスイス・アーミー・ナイフとも言える ListView もあります。この 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 Stock Data (株価データのダウンロード)」ボタンのイベント・ハンドラーは、symList TextView からデータを読み取った後、mode 変数を基に、3 つのクラスのいずれかを使用してサーバーからデータをダウンロードします。mode 変数の値はメニューが設定しますが、これは至って単純なコードなのでリスト 2 には記載していません。さまざまなデータ・ダウンロード/構文解析クラスについて検討する前に、株価データをサーバーがどのように提供するかを説明します。


株価データを提供する方法

このサンプル・アプリケーション用のサーバーでは、2 つのことができなければなりません。1 つは、銘柄記号のリストを受け取り、それぞれの銘柄記号ごとのデータを取得することです。もう 1 つは、フォーマット・パラメーターを受け取り、パラメーターが指定するフォーマットに基づいてデータをエンコードするという操作です。フォーマットが XML または JSON の場合、サーバーは株価データをテキストとしてシリアライズしてから返します。一方、フォーマットがプロトコル・バッファーの場合には、サーバーがバイナリー・データを送信しなければなりません。リスト 3 に、以上の操作に対処するサーブレットを記載します。

リスト 3. Stock Broker サーブレット
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;
    }
}

上記は、HTTP GET リクエストをサポートするだけの単純な Java サーブレットです。このサーブレットはリクエストから株の銘柄記号の値とフォーマット要求パラメーターを読み取り、getStocks() メソッドを呼び出します。するとこのメソッドが、株価データを取得するために Yahoo! Finance を呼び出します。Yahoo! がサポートする株価データのフォーマットは CSV だけなので、getStocks() メソッドはデータを Stock オブジェクトのリストに構文解析します。このオブジェクトは、リスト 4 のとおり、単純なデータ構造です。

リスト 4. Stock データ構造
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 という 3 つのプロパティーと、オブジェクト自体を XML 文字列または JSON 文字列のいずれかに変換するためのコンビニエンス・メソッドがあります。さらに、Stock オブジェクトのリストを XML または JSON に変換するユーティリティー・メソッドもあります。リスト 3 をもう一度見ると、フォーマット要求パラメーターに応じて Stock オブジェクトのリストが XML 文字列または JSON 文字列に変換されてから、クライアントに送信されるようになっています。

XML を扱う場合と JSON を扱う場合はかなり似ていて、どちらも単純明快です。一方、プロトコル・バッファーの場合には、プロトコル・バッファー・フォーマットでコードを読み取るオブジェクトと書き込むオブジェクトを生成しなければなりません。それには、プロトコル・バッファーが指定するフォーマットでデータ構造を定義する必要があります。リスト 5 に、一例を記載します。

リスト 5. 株の場合のプロトコル・バッファー・メッセージ
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;
}

プロトコル・バッファー・メッセージのフォーマットは IDL (Interface Description Language) と似ています。つまり、言語に依存しないように作成されているため、さまざまな言語で使用することができます。このサンプル・アプリケーションでは、プロトコル・バッファー・コンパイラー (protoc) を実行して、リスト 5 のコードをクライアントとサーバーの両方で使用する Java クラスにコンパイルします。プロトコル・バッファー・メッセージを Java クラスにコンパイルする方法については、Protocol Buffers Developer Guide で詳細を調べてください (「参考文献」を参照)。

リスト 3 では、toProtoBuf() というメソッドが Stock オブジェクトのリストを Portfolio メッセージに変換していました。このメソッドの実装をリスト 6 に記載します。

リスト 6. Portfolio メッセージの作成
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 のサーブレットに返される Portfolio オブジェクトに追加しているだけです。リスト 3 のサーブレットは、ストリームをクライアントに直接公開し、生成されたコードを使ってバイナリー・フォーマットのプロトコル・バッファー・データをストリームに書き込みます。

サーバーがどのようにして 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 リクエストを行ってストリーミング・レスポンスを返すメソッドを呼び出します。そして最後に、AsyncTaskonPostExecute() メソッドをオーバーライドし、パーサーから返されたデータを使って ActivityListView のための Adapter を作成します。

3 つすべてのパーサーに共通する機能を説明したところで、ここからはそれぞれに特有の構文解析コードについて説明します。最初に取り上げるのは、XML パーサーです。


SAX による XML の構文解析

Android SDK には、標準的な DOM と SAX を含め、XML を処理する手段がいくつか用意されています。メモリーを駆使するような場合には SDK のプル・パーサーを使用することができますが、大抵は SAX が最も手っ取り早い手段となります。さらに、Android には SAX を簡単に使えるようにするコンビニエンス API が組み込まれています。リスト 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 のコードの大部分は、ContentHandler を作成する newHandler() メソッドの中に含まれています。SAX による構文解析に慣れているとしたら、ContentHandler は SAX パーサーが起動した各種イベントにアクセスすることによって、構文解析されたデータを作成することはご存知のはずです。newHandler() メソッドは Android のコンビニエンス API を利用して、イベント・ハンドラーを使って ContentHandler を指定します。このコードは単に、パーサーがさまざまなタブを検出したときに起動するイベントをリッスンし、Stock オブジェクトのリストに挿入するデータを抽出しているだけです。ContentHandler が作成されると、Xml.parse() が呼び出されます。このメソッドが、基底クラスによって提供された InputStream を構文解析し、Stock オブジェクトの配列を返します。XML を構文解析するには、これが素早い方法ですが、Android が提供するコンビニエンス API を使用していても、かなり冗長なコードとなってしまいます。


JSON の使用

Android では、XML が第一級市民となっています。いかに多くの Web サービスが XML に依存しているかを考えると、このことは Android にとってメリットになります。Web サービスの多くは、よく使用されているもう 1 つのフォーマット、JSON もサポートしています。JSON は一般に XML よりも簡潔ですが、それでも人間が理解できるため、JSON を使用したアプリケーションは操作するにも、デバッグするにも簡単です。Android には JSON パーサーが組み込まれています (このパーサーは、モバイルには必要のないクラスが取り除かれている点を除き、JSON.org の Web サイトから入手できるパーサーと同じです)。リスト 9 では、このパーサーを使用しています。

リスト 9. JSON パーサーの実装
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 オブジェクトの配列として表現されます。つまり、まったく同じデータが 3 通りの方法で表現されるということです。大量のデータを扱うとなると、このことが問題になるのは目に見えています。もちろんメソッドの終わりに達すれば、データの 3 つの表現のうちの 2 つはスコープから外されるので、ガーベッジ・コレクターによって回収することができます。けれどもガーベッジ・コレクションをトリガーする回数を増やすだけでは、速度が不安定に遅くなることから、ユーザー・エクスペリエンスに悪影響を及ぼすはずです。メモリー効率とパフォーマンスが重要事項であれば、プロトコル・バッファーを使用するパーサーを選ぶほうが賢明です。


プロトコル・バッファーによるバイナリーの使用

プロトコル・バッファーは、ネットワークでのデータ送信を XML よりも短時間で行えるように Google が開発した、言語に依存しないデータ・シリアライズ・フォーマットです。Google でのサーバー間の呼び出しでは、プロトコル・バッファーがデファクト・スタンダードとなっています。Google はこのフォーマットと、C++、Java、および Python プログラミング言語とのバインディング・ツールをオープンソースとして公開しています。

リスト 3リスト 6 に示されているように、プロトコル・バッファーはバイナリー・フォーマットです。ご想像のとおり、バイナリー・フォーマットはデータを極めてコンパクトにします。XML と JSON でも、クライアントとサーバーの両方で gzip 圧縮を有効にすることができれば、同じ程度のメッセージ・サイズにすることは可能ですが、プロトコル・バッファーには他にもサイズに関する利点があります。その上、非常に素早く構文解析できるフォーマットであるという利点もあります。さらに、プロトコル・バッファーが提供する API は至って単純です。リスト 10 に、サンプル・パーサーの実装を記載します。

リスト 10. プロトコル・バッファー・パーサーの実装
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 と同じく、ヘルパー・クラスを使用していますが、この場合のヘルパー・クラスは、プロトコル・バッファー・コンパイラーによって生成されます。サーバーも同じヘルパー・クラスを使用するので、一度コンパイルすれば、サーバーとクライアントの両方で共有することができます。したがって、サーバーからのストリームを直接読み取って Stock オブジェクトの配列に変換するという操作をより簡単に行うことができます。この単純なプログラミングはさらに、優れたパフォーマンス特性ももたらします。次のセクションでは、プロトコル・バッファーのパフォーマンスを XML および JSON と比較してみます。


パフォーマンスの比較

通常、パフォーマンスを比較するには、ある種のマイクロベンチマークが必要になりますが、このようなベンチマークはバイアスがかかりがちであったり、思いがけないところで正確さを欠きやすいことで有名です。マイクロベンチマークが公正に設計されているとしても、さまざまな無作為の要因によって結果の信憑性に疑いが出てくることがあります。以上の注意事項はあるものの、私はそのようなベンチマークを使用して、XML (約 1300 ms)、JSON (約 1150 ms)、プロトコル・バッファー (約 750 ms) のパフォーマンスを比較しました。このベンチマークは、200 の株に対するリクエストをサーバーに送信し、リクエストが送信されてから ListViewAdapter を作成するためのデータが使用できる状態になるまでの時間を測定するというものです。各データ・フォーマットについて、この操作を 2 台の機器で 50 回繰り返しました。使用した機器は Motorola Droid と HTC Evo で、いずれも 3G ネットワークに接続します。図 2 に、このベンチマークの結果を示します。

図 2. データ・フォーマットの速度の比較
データ・フォーマットの速度を比較する棒グラフ。XML、JSON、およびプロトコル・バッファーの速度が比較されています。

図 2 のグラフに示されているように、このマイクロベンチマークでは、プロトコル・バッファーの速度 (約 750 ms) が XML の速度 (約1300 ms) の約 2 倍となっています。データをネットワーク上に送信して携帯機器で処理する場合のパフォーマンスには、多くの要素が影響してきます。1 つの明らかな要素は、ネットワーク上に送信されるデータの量です。この点に関して、プロトコル・バッファーはバイナリー・フォーマットであることから、ネットワーク上に送信するには XML や JSON などのテキスト・フォーマットよりも遥かにデータ・サイズが小さくなります。その一方、テキスト・フォーマットは gzip を使用して効率的に圧縮することができます。gzip は、Web サーバーと Android 機器が両方ともサポートしている標準技術です。図 3 に、gzip を有効にした場合、無効にした場合の送信データのサイズを示します。

図 3. フォーマット別のデータ・サイズ
フォーマット別にデータ・サイズを比較する棒グラフ。XML および JSON (圧縮されていない状態または圧縮された状態) がプロトコル・バッファーと比較されています。

図 3 を見ると、(HTML、JavaScript、CSS などの Web フォーマットは言うまでもなく) XML や JSON のようなテキスト・コンテンツでの圧縮の効果に対する認識が高まるはずです。プロトコル・バッファーのデータ (約 6KB) は、圧縮されていない状態の XML のデータ (約 17.5KB) や JSON のデータ (約 13.5KB) と比べるとかなりサイズが下回っていますが、圧縮後のサイズとなると、JSON と XML (両方とも 3KB) のほうがプロトコル・バッファーよりも小さくなります。この例の場合、この 2 つのサイズは、プロトコル・バッファーでエンコードされたメッセージの半分近くです。

図 2 をもう一度見てみると、速度の違いはネットワーク上に送信されるメッセージのサイズでは確かに説明しきれません。プロトコル・バッファー・メッセージは XML または JSON でエンコードされたメッセージよりも大きいにも関わらず、プロトコル・バッファーを使用することによって、ユーザーがデータを目にするまでの時間を 0.5 秒短縮することができます。これは、Android アプリケーションにはプロトコル・バッファーを使用するべきだということを意味しているのでしょうか?もちろん、データ・フォーマットに関する結論はそれほど単純に出せるものではありません。送信するデータの量が少なければ、3 つのフォーマットによる速度の違いは微々たるものです。大量のデータであれば、おそらくプロトコル・バッファーに差が出てくると思いますが、この記事で使用した類の不自然なベンチマークの結果を、実際にアプリケーションをテストした結果の代用にすることはできません。


まとめ

この記事では、インターネットでよく使われている 2 つのデータ・フォーマット、XML と JSON を操作する際のノウハウを説明しました。また、3 つ目の可能性として、プロトコル・バッファーについても取り上げました。ソフトウェア・エンジニアリングの常として、どの技術を選択するかは、トレードオフによって決まります。特に Android などの制約された環境で使用するアプリケーションを開発する場合には、使用することにした技術の長所と短所がますます際立ってくることがよくあります。この記事の結果から得られた新たな知識が、皆さんが素晴らしい Android アプリケーションを作成する手助けになることを願っています。


ダウンロード

内容ファイル名サイズ
Article source codedaytrader.stockbroker.zip4.81MB

参考文献

学ぶために

  • Android Developers サイト: Android に関する最新ニュースを調べてください。また、Android API マニュアルにもアクセスできます。
  • Android で XML を扱う」(Michael Galpin 著、developerWorks、2009年6月): Android で XML を扱う際のオプション、そしてこれらのオプションを使って独自の Android アプリケーションを構築する方法を説明しています。
  • Android 開発入門」(Frank Ableson 著、developerWorks、2009年5月): この Android プラットフォームをわかりやすく紹介している記事を読んで、早速 Android を使って基本的な Android アプリケーションをコーディングしてください。
  • Protocol Buffers Developer Guide: プロトコル・バッファーを使用した開発について詳しく学んでください。
  • Introducing JSON: JSON の構文を学んでください。
  • To XML and Back: Using JSON in Android」(Frank Ableson 著、Linux Magazine、2010年3月): Android で JSON を使用する方法を詳しく説明しています。
  • Open Handset Alliance: Android を後援するグループの Web サイトにアクセスしてください。
  • Understanding SAX」(Nicholas Chase 著、developerWorks、2003年7月): このチュートリアルを読んで、SAX 構文解析のエキスパートになってください。
  • Understanding DOM」(Nicholas Chase 著、developerWorks、2007年3月): DOM 構文解析についての詳細は、このチュートリアルを読んでください。
  • この著者による他の記事 (Michael Galpin 著、developerWorks、2006年4月から現在まで): XML、HTML 5、Apache Geronimo、この記事で紹介した以外の Google API、およびその他の技術に関する記事を読んでください。
  • My developerWorks: developerWorks のエクスペリエンスを自分流にカスタマイズしてください。
  • IBM XML 認定: XML や関連技術の IBM 認定技術者になる方法について調べてください。
  • XML technical library: 広範な技術に関する記事とヒント、チュートリアル、標準、そして IBM Redbooks については、developerWorks XML ゾーンを参照してください。
  • developerWorks の Technical events and webcasts: これらのセッションで最新情報を入手してください。
  • Twitter での developerWorks: 今すぐ登録して developerWorks のツイートをフォローしてください。
  • developerWorks podcasts: ソフトウェア開発者向けの興味深いインタビューとディスカッションを聞いてください。

製品や技術を入手するために

  • Android SDK: Android SDK をダウンロードしてください。この記事の演習には、バージョン 1.5 以降を使用できます。
  • Android Open Source Project: Android のオープンソース・コードを入手してください。
  • Java Development Kit: この記事で使用した JDK 1.6.0_17 の完全なプラットフォームおよびランタイム環境をダウンロードしてください。
  • Protocol Buffers: プロトコル・バッファー・コンパイラーをダウンロードしてください。
  • IBM 製品の評価版: DB2®、Lotus®、Rational®、Tivoli®、および WebSphere® のアプリケーション開発ツールとミドルウェア製品を体験するには、評価版をダウンロードするか、IBM SOA Sandbox のオンライン試用版を試してみてください。

議論するために

コメント

developerWorks: サイン・イン

必須フィールドは(*)で示されます。


IBM ID が必要ですか?
IBM IDをお忘れですか?


パスワードをお忘れですか?
パスワードの変更

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


お客様が developerWorks に初めてサインインすると、お客様のプロフィールが作成されます。会社名を非表示とする選択を行わない限り、プロフィール内の情報(名前、国/地域や会社名)は公開され、投稿するコンテンツと一緒に表示されますが、いつでもこれらの情報を更新できます。

送信されたすべての情報は安全です。

ディスプレイ・ネームを選択してください



developerWorks に初めてサインインするとプロフィールが作成されますので、その際にディスプレイ・ネームを選択する必要があります。ディスプレイ・ネームは、お客様が developerWorks に投稿するコンテンツと一緒に表示されます。

ディスプレイ・ネームは、3文字から31文字の範囲で指定し、かつ developerWorks コミュニティーでユニークである必要があります。また、プライバシー上の理由でお客様の電子メール・アドレスは使用しないでください。

必須フィールドは(*)で示されます。

3文字から31文字の範囲で指定し

「送信する」をクリックすることにより、お客様は developerWorks のご使用条件に同意したことになります。 ご使用条件を読む

 


送信されたすべての情報は安全です。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=XML, Open source
ArticleID=502268
ArticleTitle=Android アプリケーションでインターネット・データを使用する
publish-date=06292010