:Tips  Android で JSON を使おう 〜 前編〜

ListView で一覧表示



 本ブログ「 :Tips TextView を使いこなそう」で TextView の表現方法をいろいろ見てきました。せっかくなので TextView を活用した「何か」を題材に記事を書きたいな、と考え行き着いたのが『 ListView を用いた(何がしかの)情報の一覧表示』というテーマ。

いや、本当は「もう少し派手なやつを」とも考えたのですが、アニメーションやら何やらとやってはみたのですが、派手さこそあれ「これ何に使うの?」という根本的な疑問は払拭できず・・・”却下”と相成りました。その点、 ListView の一覧表示というのは使い手が豊富で、利用頻度も高い優れた表現方法だと思います。特にタッチパネルのスマートフォンでは主要な操作体系の1つになっています。



まずは下準備から・・・

「一覧表示」というからには、まず表示する「情報」を予め揃えねば始まりません。もちろん、自分で用意するという方法もありますが結構面倒くさいものです。ましてや多少の「見た目」を求めるには画像は必須。だからといって、いつまでも「 R.drawable.icon (ドロイド君)」のお世話にばかりなっているのも能がありません(実は後々お世話になるのですが・・・)そこで思いついたのが何某かの Web Service API を利用するというアイデア。別の意味で面倒な気もしますが、とりあえずやってみようと思います。



どの Web Service API を使うか・・・

結論から先に言うと楽天ウェブサービスにしました。特に理由はありません。
唯一、接点がるあるとすれば私が「イーグルス」のファンだということくらいでしょうかw ま、実際のところ、たまたま最初にクリックしたのがそこだったというだけの話なんですが、画像が使えるということが確認できたので即決定です(注意・利用にはデベロッパー登録が必要です。)



データフォーマットを選択する・・・

楽天ウェブサービス」では2つのデータフォーマットに対応しています。「 REST 」と「 JSON 」です。
・「 REST 」は単なる XML 形式のデータフォーマット。
・「 JSON 」は JSON 形式のデータフォーマット。
今まで JSON 形式のデータフォーマットを扱ったことが無いので、せっかくなので試してみよう、ということでJSONに決定。「ドロイド君」の次は「ジェイソン君」の登場です。なにか危険な香りがします・・・。



JSON Data Format



 JSON Data Format とは・・・といっても説明するだけの知識もありません。ですので、 JSONなんて怖くない!- Think IT - さんとかネットで各々調べてみてください。しかし、 JSON 形式データの利用側として知っておかなければならない知識は余りないものと思います。「オブジェクト」と「配列」と「データ」の3つを憶えれば少なくとも「楽天ウェブサービス」を利用する上では問題ありません。「オブジェクト」というと大袈裟な感じですが、単なる” { ”と” } ”で囲われた範囲を「一固まり(オブジェクト)」と命名してるだけですし、「配列」といっても” [ ”と” ] ”で囲われた範囲を指して命名しているだけです。それ以外に特にコレといった決まりも在りません。ですから、各 Web Service 毎に「どういった構造になっているのか」は異なっており、その構造を知るには実際のデータをダウンロード、解析、し理解する以外にありません。逆に言えば、それが全てだともいえます。

では、早速ダウンロードして解析してみましょう。



楽天ウェブサービス」から JSON 形式データを取得し解析する



どのような情報が欲しいのか・・・

楽天ウェブサービス」と一言でいっても扱っている商品は多様であり、それに対応する Web API も様々です。今回は無難なところで「書籍の売り上げ TOP 10 」の情報を取得して表示することとします。使用する API 楽天ブックス書籍検索API (version:2010-03-18) を利用します。

■リクエストURL
http://api.rakuten.co.jp/rws/3.0/json?[parameter]=[value]...
JSONP形式は、JSON形式で入力パラメーターにcallBackを指定することで出力されます。
フィールド名title, author, publisherName, sortに対応する[value]はUTF-8でURLエンコードされている必要があります。(リクエストURL全体をエンコードするのではなく、[value]部分を個別にエンコードしてください。)

このリクエストURLの [parameter]=[value] に「入力パラメーター」として示されている「フィールド名」「型」を参考にして其々指定していきます。エンコードに関して注意書きが在りますが、 Android はデフォルトのエンコードUTF-8 ですので特に気にする必要はないでしょう。



今回使うフィールド名&値は・・・

フィールド名(=parameter) 必須 値(=value)
developerId String
"?????"
operation String
"BooksBookSearch"
version String
"2010-03-18"
size
int

(*1)
0
hits
int
10
sort String
"sales"

注)developerId は実際とは異なります。各々、取得したIDを用いてください。

上記の6つの「フィールド名&値」のセットが、”今回の”「書籍の売り上げ TOP 10 」の情報を引き出す為に必要な最低限の [parameter]=[value] のセットになります。



まずはPCのブラウザで取得してみる・・・

取り合えず、どの様なデータが返ってくるのかPCのブラウザを使って取得してみます。

http://api.rakuten.co.jp/rws/3.0/json?developerId=<?????>&operation=BooksBookSearch&version=2010-03-18&size=0&hits=3&sort=sales

注) <?????> にはデベロッパー登録で得たIDが入ります。

パラメーター「 hits 」の値が10では情報が多すぎるので、3にしてみました。返ってくるデータは以下の通りです(デベロッパーIDは伏せています)

{"Body":{"BooksBookSearch":{"Items":{"Item":[{"contentsKana":"コノ ジダイ ノ ナ オ シロヒゲ ト ヨブ","limitedFlag":0,"authorKana":"オダ,エイイチロウ","booksGenreID":"001001001008","author":"尾田栄一郎","subTitle":"","seriesNameKana":"ジャンプ コミックス","title":"ONE PIECE(巻58)","subTitleKana":"","publisherName":"集英社","itemCaption":"","listPrice":"","isbn":"9784088700458","largeImageUrl":"http://thumbnail.image.rakuten.co.jp/@0_mall/book/cabinet/0887/08870045.jpg?_ex=200x200","mediumImageUrl":"http://thumbnail.image.rakuten.co.jp/@0_mall/book/cabinet/0887/08870045.jpg?_ex=120x120","titleKana":"ワン ピース","availability":"1","postageFlag":0,"salesDate":"2010-06-01","contents":"この時代の名を“白ひげ”と呼ぶ","smallImageUrl":"http://thumbnail.image.rakuten.co.jp/@0_mall/book/cabinet/0887/08870045.jpg?_ex=64x64","itemPrice":420,"size":"コミック","affiliateUrl":"","seriesName":"ジャンプ・コミックス","reviewCount":596,"reviewAverage":4.88,"discountRate":0,"itemUrl":"http://books.rakuten.co.jp/rb/6482061/"},{"contentsKana":"","limitedFlag":0,"authorKana":"ニノミヤ,トモコ","booksGenreID":"001001002005","author":"二ノ宮知子","subTitle":"","seriesNameKana":"コウダンシャ コミックス キス","title":"のだめカンタービレ(#24(アンコールオペラ編))","subTitleKana":"","publisherName":"講談社","itemCaption":"","listPrice":"","isbn":"9784063407952","largeImageUrl":"http://thumbnail.image.rakuten.co.jp/@0_mall/book/cabinet/0634/06340795.jpg?_ex=200x200","mediumImageUrl":"http://thumbnail.image.rakuten.co.jp/@0_mall/book/cabinet/0634/06340795.jpg?_ex=120x120","titleKana":"ノダメ カンタービレ","availability":"1","postageFlag":0,"salesDate":"2010-04-01","contents":"","smallImageUrl":"http://thumbnail.image.rakuten.co.jp/@0_mall/book/cabinet/0634/06340795.jpg?_ex=64x64","itemPrice":440,"size":"コミック","affiliateUrl":"","seriesName":"講談社コミックスkiss","reviewCount":506,"reviewAverage":4.45,"discountRate":0,"itemUrl":"http://books.rakuten.co.jp/rb/6418314/"},{"contentsKana":"","limitedFlag":0,"authorKana":"タニタ","booksGenreID":"001010011001","author":"タニタ","subTitle":"500kcalのまんぷく定食","seriesNameKana":"","title":"体脂肪計タニタの社員食堂","subTitleKana":"ゴヒャッキロカロリー ノ マンプク テイショク","publisherName":"大和書房","itemCaption":"カロリーダウンの調理のコツ。オーブントースターで油分カット。かみ応えでまんぷく感。薬味で味わいアップ。しっかりだしで塩分ダウン。肉も魚も野菜もたっぷりおいしさ・ボリュームそのまま。人気社員食堂の定食レシピ31日分。","listPrice":"","isbn":"9784479920250","largeImageUrl":"http://thumbnail.image.rakuten.co.jp/@0_mall/book/cabinet/4799/47992025.jpg?_ex=200x200","mediumImageUrl":"http://thumbnail.image.rakuten.co.jp/@0_mall/book/cabinet/4799/47992025.jpg?_ex=120x120","titleKana":"タイシボウケイ タニタ ノ シャイン ショクドウ","availability":"1","postageFlag":0,"salesDate":"2010-02-01","contents":"","smallImageUrl":"http://thumbnail.image.rakuten.co.jp/@0_mall/book/cabinet/4799/47992025.jpg?_ex=64x64","itemPrice":1200,"size":"単行本","affiliateUrl":"","seriesName":"","reviewCount":694,"reviewAverage":4.33,"discountRate":0,"itemUrl":"http://books.rakuten.co.jp/rb/6333205/"}]},"pageCount":100,"hits":3,"last":3,"count":751821,"page":1,"carrier":0,"first":1}},"Header":{"Status":"Success","Args":{"Arg":{"apiVersion":{"content":true,"value":"30"},"operation":{"content":true,"value":"BooksBookSearch"},"hits":{"content":true,"value":"3"},"sort":{"content":true,"value":"sales"},"developerId":{"content":true,"value":"?????????"},"User-Agent":{"content":true,"value":"Mozilla/4.0 (compatible; MSIE 7.0; Windows NT 5.1; Trident/4.0; GTB6.5)"},"size":{"content":true,"value":"0"},"version":{"content":true,"value":"2010-03-18"}}},"StatusMsg":""}}

無事に取得できました。



構造を解析する・・・

素のままでは、構造を解析するにも一苦労ですwこういった場合にはこちらを利用しましょう。「 Online JSON Parser 」

注)デベロッパーIDは上記のオンラインソフトでの解析前に削除or変更しておいたほうが無難です。

object{
	"Body": object{
		"BooksBookSearch": object{
			"Items": object{
				"Item": array[
					0 object{}, 
					1 object{}, 
					2 object{}
				]
			}
		}
	}
	"Header": object{
		"Args": object{
			"Arg": object{
				"apiVersion": object{},
				"operation": object{},
				"hits": object{},
				"sort": object{},
				"developerId": object{},
				"User-Agent": object{},
				"size": object{},
				"version": object{}
			}
		}
	}
}

上記のコードは「楽天ウェブサービス」から返された JSON 形式データから「データ」要素を除き、「オブジェクト」と「配列」だけを改行を加えツリー状に示したものです。Root オブジェクトから"Body"オブジェクトと"Header"オブジェクトに分かれますが、重要なのは"Body"オブジェクトから下の部分であり"Header"オブジェクト以下は無視してかまいません。ここから分かることは、"Body"という名のオブジェクトは1つの"BooksBookSerch"という名のオブジェクトを持ち、"BooksBookSerch"という名のオブジェクトは1つの"Items"という名のオブジェクトを持ち、"Items"という名のオブジェクトは"Item"という名の配列を1つ持ち、"Item"という名の配列は”複数”(この場合は3つ)のオブジェクトを持っている、という関係です。そして、"Item"配列配下の複数のオブジェクトが持っている「データ」が一冊の本(Book)の商品情報になります。



Android で「楽天ウェブサービス」の JSON 形式データを取得する・・・



 ここからは、具体的に AndroidJSON 形式データをどう扱っていくのか、を見ていきます。



どのパッケージで通信するか・・・

Android で HTTP 通信をする場合、大まかに2つのアプローチがあります。 java.net パッケージを使う方法と org.apache.http.* パッケージを使う方法です(実はもう1つ android.net.http パッケージというズバリなモノもあるのですが AndroidHttpClient という HTTP クライアントクラスが API Level.8 ( Android 2.2 ) であり、現状では実用的とは言えません。私はバージョンを調べもせずに使おうとし、引数の UserAgent をどうしたものかと、あーだこーだ小一時間潰してしまいました・・・)。どちらを使っても構わないのですが、 Android 的には org.apache.http.* をベースに通信用パッケージを構築し始めているようなので(ということは逆になくなる可能性も高い!?)勉強も兼ねてこちらを使うことにします。



サーバから JSON 形式データを取り出す・・・

最初に URI 文字列を作ります。ベタ書きでも良いのですが、 android.net.Uri.Builder というそれ用のクラスがあるので、それを使います。


Class Overview :
An absolute hierarchical URI reference follows the pattern: ://?#

上記引用文は Uri.Builder クラスの Class Overview に書かれているものです。<scheme>,<authority>,<absolute path>,<query>,<fragment>,それぞれに対応したメソッドが scheme(String), authority(String), path(String), query(String) or appendQueryParameter(String, String), fragment(String) と揃っているので特に問題はないでしょう。

それでは、実際のコードで URI の文字列を生成する過程を見ていきましょう。

String scheme = "http";
String authority = "api.rakuten.co.jp";
String path = "/rws/3.0/json";

String developerId = "?????????";
String operation = "BooksBookSearch";
String version = "2010-03-18";
String size = "0";
String hits = "10";
String sort = "sales";

Uri.Builder uriBuilder = new Uri.Builder();

uriBuilder.scheme(scheme);
uriBuilder.authority(authority);
uriBuilder.path(path);
uriBuilder.appendQueryParameter("developerId", developerId);
uriBuilder.appendQueryParameter("operation", operation);
uriBuilder.appendQueryParameter("version", version);
uriBuilder.appendQueryParameter("size", size);
uriBuilder.appendQueryParameter("hits", hits);
uriBuilder.appendQueryParameter("sort", sort);

String uri = uriBuilder.toString();

特に問題はないと思います。最期の String uri = uriBuilder.toString() で得た文字列をログなどに表示して確認してみてください。この URI を HTTP GET で渡すと JSON 形式データが返ってくるのは既にブラウザで確認済みです。早速、 Android 上で同様の処理をしてデータを受け取ってみましょう。

HTTP サーバと通信をするのに用いるクラスは「 HTTP クライアント 」役の org.apache.http.impl.client.DefaultHttpClient 、「 HTTP リクエスト 」役の org.apache.http.client.methods.HttpGet 、「 HTTP レスポンス 」役のインターフェイス org.apache.http.HttpResponse 等を用います。

DefaultHttpClient のコンストラクタは・・・

public DefaultHttpClient () 

HttpGet のコンストラクタは・・・

public HttpGet (String uri) 

を使います。この引数に先程生成した URI 文字列を渡します。

注) HttpGet のもう1つのコンストラクタに public HttpGet (URI uri) というのがあるのですが、本来ならこちらを使いたいところなのですが何故か使えないようです。

まず最初は、 HTTP クライアントの生成です。

HttpClient httpClient = new DefaultHttpClient();
HttpParams params = httpClient.getParams();
HttpConnectionParams.setConnectionTimeout(params, 1000);
HttpConnectionParams.setSoTimeout(params, 1000);

引数なしのコンストラクタで HTTP クライアントを生成。次からの3行の処理はタイムアウトの設定をしています。この設定をせずともデフォルトの設定値で処理される(多分・・・)ので省いても構わないと思います、が念のため。

次は、HTTP リクエストの生成です。

HttpUriRequest httpRequest = new HttpGet(uri);

コンストラクタ引数の uri は先程 Uri.Builder で作った文字列です。

次は、サーバにリクエストを投げてレスポンスを受け取ります。

HttpResponse httpResponse = null;
        
try {
    httpResponse = httpClient.execute(httpRequest);
}
catch (ClientProtocolException e) {
    //例外処理
}
catch (IOException e){
    //例外処理
}

HttpClient#execute() メソッドの引数には先程生成した HTTP リクエストのインスタンスを渡します。このメソッドを実行することでサーバとの通信が開かれ、戻り値に HTTP レスポンスのインスタンスが返ってきます。

次に接続されたサーバからの入力ストリームを介して JSON 形式データを読み出します。

String json = null;

if (httpResponse != null && httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK){
    HttpEntity httpEntity = httpResponse.getEntity();
    try {
        json = EntityUtils.toString(httpEntity);
    }
    catch (ParseException e) {
        //例外処理
    }
    catch (IOException e) {
        //例外処理
    }
    finally {
        try {
            httpEntity.consumeContent();
        }
        catch (IOException e) {
            //例外処理
        }
    }
}

先頭で宣言されている文字列変数 json はサーバから受け取った JSON 形式データを格納するためのものです。次に、サーバとの接続が無事に行われたのかどうかを条件に分岐処理をします。 HTTP レスポンスインスタンスが null でないこと。且つ、 HTTP レスポンスのステータスコードが「 200 (無事に通信接続が完了した事を示す)」であることが条件です。条件が整ったら HTTP レスポンスからエンティティを取り出します。 HttpResponse#getEntity() がそれです。戻り値は org.apache.http.HttpEntity のインスタンスです。このエンティティにはサーバからの入力ストリームが格納されています。本来でしたら、この入力ストリームを取り出し、ストリームから順次データを読み出すという処理が必要なのですが、その処理を自動でやってくれるユーティリティが存在します。それが org.apache.http.util.EntityUtils の toString() メソッドです。引数に HttpEntity のインスタンスを渡すと勝手に入力ストリームから読み出し、文字列を返してくれます(便利ですねぇ)最期に finally で HttpEntity#consumeContent() を実行して HttpEntity のリソースを開放させ終了します。これで、json には JSON 形式のデータが格納されました。本当でしたら、条件分岐のもう1つ「接続が不調に終わった」場合の処理も書かなければならないのでしょうが、今回は割愛します。

最期に・・・

httpClient.getConnectionManager().shutdown();

と、 HTTP クライアントも終わらせ全ての処理は終了です。

「楽天ウェブサービス」から JSON 形式データを取得し TextView に表示するサンプルコードです。

public class RakutenRanking extends Activity {
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        
        String scheme = "http";
        String authority = "api.rakuten.co.jp";
        String path = "/rws/3.0/json";
        
        String developerId = "????????????";
        String operation = "BooksBookSearch";
        String version = "2010-03-18";
        String size = "0";
        String hits = "10";
        String sort = "sales";
        
        Uri.Builder uriBuilder = new Uri.Builder();
        
        uriBuilder.scheme(scheme);
        uriBuilder.authority(authority);
        uriBuilder.path(path);
        uriBuilder.appendQueryParameter("developerId", developerId);
        uriBuilder.appendQueryParameter("operation", operation);
        uriBuilder.appendQueryParameter("version", version);
        uriBuilder.appendQueryParameter("size", size);
        uriBuilder.appendQueryParameter("hits", hits);
        uriBuilder.appendQueryParameter("sort", sort);
        
        String uri = uriBuilder.toString();
        
        HttpClient httpClient = new DefaultHttpClient();
        HttpParams params = httpClient.getParams();
        HttpConnectionParams.setConnectionTimeout(params, 1000);
        HttpConnectionParams.setSoTimeout(params, 1000);
        
        HttpUriRequest httpRequest = new HttpGet(uri);
        
        HttpResponse httpResponse = null;
        
        try {
            httpResponse = httpClient.execute(httpRequest);
        }
        catch (ClientProtocolException e) {
            //例外処理
        }
        catch (IOException e){
            //例外処理
        }
        
        String json = null;
        
        if (httpResponse != null && httpResponse.getStatusLine().getStatusCode() == HttpStatus.SC_OK){
            HttpEntity httpEntity = httpResponse.getEntity();
            try {
                json = EntityUtils.toString(httpEntity);
            }
            catch (ParseException e) {
                //例外処理
            }
            catch (IOException e) {
                //例外処理
            }
            finally {
                try {
                    httpEntity.consumeContent();
                }
                catch (IOException e) {
                    //例外処理
                }
            }
        }
        
        httpClient.getConnectionManager().shutdown();
        
        TextView textView = new TextView(this);
        textView.setText(json);
        ScrollView scrollView = new ScrollView(this);
        scrollView.addView(textView);
        setContentView(scrollView, new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT));
    }
}



実行してみます・・・


JSON 形式データが表示されました。





次回は・・・

この続きとして JSON 形式データの Android 上での扱い方をなどを書きます。