ラベル json の投稿を表示しています。 すべての投稿を表示
ラベル json の投稿を表示しています。 すべての投稿を表示

2012/04/01

AndroidでJSONICライブラリを使ってみる

このブログを作成するシステムはgdataライブラリを利用して、いくつかの手製スクリプトで構成されています。

Subversionで履歴を管理していたのですが、実際にはほとんど使えないデータだったので履歴を切り離してGitに移行してしまいました。 まぁcommitしてきたログがあるので移行しても良かったのですが、気分的にはすっきりしていい感じです。

さて、新しい仕事を始めてからは.Net FrameworkやらAndroidやらiPhoneなんかのプログラミングを始めていますが、今回はJSONICというJavaでJSONデータを扱うライブラリについてです。

AndroidでのJSONデータの扱い

さて、Androidではorg.jsonパッケージがついてきて最低限のJSONデータを取り扱う環境があります。

送信用にJavaのデータ構造をJSONにする際には、特に問題なく扱えると思います。 反対に外部から受け取ったデータを扱う際にはローレベル過ぎて扱いが少しだけ面倒です。

今回はorg.jsonパッケージの上にライブラリを構築するのではなく、JSONICというJava言語用のJSONライブラリを利用する事にしました。

作成した成果物はjsonic4androidとしてGithubで公開しています。

JSONICを扱うメリット

基本的には無理に使う必要はありません。

自作の郵便番号検索システムから受け取ったJSONデータをJavaBeansとして扱うために使用しています。

この部分をorg.jsonパッケージを利用して取り出そうとして、面倒だったのは次のような点です。

  • UTF-8にエンコードするため、BufferedReaderを使い全データを変換する必要があった
  • 配列の中に各データのレコードを持っているため、要素名を指定してアクセスする処理が冗長で、JavaBeansにマッピングする必要があった
  • org.jsonパッケージを利用する自作変換クラスが安定して動作しなかった (これは自分の技量の問題です)

JSONのデータ構造は単純ですが項目数が多いため、各要素にアクセスするコードは繰り返しが多く、JavaBeansに変換してから扱うのがベストに思えたのですが、その変換コードを書くのがさらに面倒だったというのが実情です。

エントリ数が10以下で配列の中にさらにJSONデータ構造を持つような場合でなければ、org.jsonパッケージをそのまま使うのがお勧めです。

Android端末向けのプログラミングではライブラリを利用する事も作業量を減らす上では大切ですが、アプリ全体のパッケージサイズの圧縮が必要だったり、インタフェースを使わないなどのいくつかのプログラミング上のテクニックが存在します。

そういった事との兼ね合いで常にベストな選択をしてください。

パフォーマンス

jsonicでBufferedInputStreamからJavaBeansオブジェクト(JPostalBeanインスタンス)を生成した場合と、自前でInputStreamを開いてUTF-8に変換した文字列からJavaBeansオブジェクトを返すメソッドを作成して変換時間を計測して比較してみました。

jsonicを使ったコード


is = urlconn.getInputStream();
bis = new BufferedInputStream(is);
long beginTime = new Date().getTime();
ret = JSON.decode(bis, JPostalBean.class);
long endTime = new Date().getTime();

自前の変換メソッドを使ったコード (reqTextの生成時間は計測していません)


is = urlconn.getInputStream();
br = new BufferedReader(new java.io.InputStreamReader(is,"UTF-8"));
StringBuilder reqText = new StringBuilder();
...
long beginTime = new Date().getTime();
ret = parseJsonStream(reqText.toString());
long endTime = new Date().getTime();

合計で170件のデータを含むデータを処理しましたが、jsonicを利用した場合は約10%ほど高速でした。 自前ライブラリの場合には、BufferedReaderを使いUTF-8文字列を取得するところは省いていますが、それを含めると2倍近く処理速度の差が確認できました。

jsonicを使ったから特に処理が遅くなるという事ではなさそうなので、全体のバランスの中で選択すれば良いのかなと思います。

パッケージサイズの増加量

郵便番号検索アプリで作成したAPKファイルで比較するために、jsonicに依存するコードをコメントアウトしてjsonic4androidライブラリプロジェクトへのリンクを削除したAPKファイルを作成してみました。

$ ls -l JPostalSearch.*.apk
-rw-r--r-- 1 yasu yasu 469461 2012-04-01 10:38 /home/yasu/JPostalSearch.16e.without_jsonic.apk

jsonic4androidを含む、通常のAPKファイルは次のようになります。

$ ls -l JPostalSearch.*.apk
-rw-r--r-- 1 yasu yasu 499148 2012-04-01 10:45 /home/yasu/JPostalSearch.16e.apk

およそ30KB程度の増加量になり、配布について大きな障害ではなくなります。

jsonic4androidが対応するSDK(API)バージョン

作成しているアプリはAndroid 1.6 から(minSdkVersion="4")対応するようにエミュレータで動作を確認しています。

実機で確認しているのはAndroid 2.3.3 (SdkVersion="8")、及び、Android 3.2 (SdkVersion="13") です。

変更内容は次のセクションを参照してください。

jsonic4android - Eclipseプロジェクト形式での配布

今回扱ったJSONICのバージョンは1.2.10です。当初は1.2.9でしたがバージョンアップしたため、Githubのjsonic4androidも追随しました。

Githubに登録してあるのはEclipseに組み込む事を前提としたインポート可能なパッケージ形式です。

GithubのプロジェクトページのWikiにアクセスすると、画像で使い方が並んでいます。 gitでcloneしてから「Existing Projects into Workspace」を選ぶだけで、Eclipseの中で利用する事ができます。

jsonic4androidとして変更点

Eclipseパッケージとして組み込めるような形式になっている他の変更は次のような点です。

SDKのバージョンでいうと4、いわゆるAndroid 1.6以降で扱えるように、主にString::isEmptyメソッドを使わないように修正しています。

jsonicとproguardとの関係

リリースする際には署名をしますが、ここで通常はProguardを使う事になります。

開発環境ではうまく動くのに、署名したAPKファイルを配布すると問題になる場合がある原因の一つにはProguardがあります。

Proguardでの課題はテンプレートプログラミングを行なうためのGenericsとの相性です。

Proguardはパッケージ名やメソッドなどを変更してしまいますが、Java Reflection APIを扱うJSONICからはメソッドの名称が変更されてしまうと変数にアクセスする事ができなくなってしまいます。

鉤括弧でクラスを指定するGenericsの形式を使う場合には、明示的にproguard.cfgでそのオブジェクトを対象外にする必要があります。

今回はJPostalBeanクラスをjsonicのdecodeメソッドで処理しているため、proguard.cfgに次のような記述を追加しました。

proguard.cfgに追加したJSONICで処理するJavaBeans用の記述

-keep class net.yadiary.android.jpostal.beans.JPostalBean {
  <init>(...);
  *;
}

さいごに

JSONはクライアントとサーバーが1対1に対応するような軽量Webサービスを利用する場合の選択肢としては最有力だと思います。

SOAPでなければいけない場合は、ステートフルなセッションを行ない、メッセージボディを途中で分割して別々のサーバーに送信するSOPA対応Proxyを使用しなければいけない、限られた環境に限定されるでしょう。

Androidのようなスマートフォンで外部とのメッセージをやりとりする場合には自然に使う事になるJSONですが、アクセスするためにJSONICのようなデコーダとJavaBeansのデータ構造を利用しないと、JSONのデータ構造が変化する度にコードを追加、修正する手間が発生する事になります。

選択肢は沢山あるので、うまくライブラリを利用して見通しの良いコードを書くようにしたいものです。

2010/12/08

データ処理用のJSONアプリを作って、FlexBoxを使ってみた

jQueryプラグインのFlexBoxを使ってみた結果のまとめです。

Fairwayが提供するサンプル、デモへのリンクはたくさんあったんですが、FlexBoxを使った具体例はなかなかみつかりませんでした。

jQueryのプラグイン紹介ページからダウンロードできるバージョンは少し古いので、Fairwayのリンク先から CodePlexのFlexBoxページに飛んで、0.9.6をダウンロードして使っています。

背景

CouchDBの検証用にDTIのVPSサービスを利用して、インターネットに実サーバを借りたのでCouchDB、その他もろもろをインストールしてアプリケーションを作る準備をしていました。

ネタはOpenLDAPの時と同じで、 郵便番号の検索システムです。

CouchDBを使うのが目的なので、単純に番号から住所が分かっても、それほどおもしろくなさそう。 それに、似たようなシステムはたくさんあるから、これを元ネタにいろいろ検索できたり、新しい方法を試すのがおもしろいんじゃないかと思い機能を追加しています。

本当は全文検索システムにデータを入れて、探せばいいと思うんですけどね。 それでも整理された項目について条件を絞り込む、こういうシステムもおもしろいと思います。

今回はその中で都道府県名欄への入力をサポートする仕組みを使った時の作業メモです。

他の欄にも応用できそうですが、まずは47都道府県の漢字、カタカナに空文字を加えた95通りを検索させています。

検索リスト候補の表示

JavaScriptが動く環境でどれくらい入力をサポートできるものか試したいと思い、jQueryベースのリストボックスを探して、みつかったのがFairwayの FlexBoxでした。

サンプルをみた感じはちゃんと動きそうですが、検索をしても実例の紹介はあまりみつからず、裏側のデータを取得する部分の仕組みについてのガイドがない状態でした。

とりあえず使うだけなら、JSON的な内容のファイルを準備するだけで、動きそうだと思ったのが間違いでした…。 そんなに大変じゃなかったですけどね。

まずはテキストファイルを元に静的なリストを表示する

Fairwayのサイトにある FlexBoxのデモページの先頭には、"json.txt"というファイルを使って動かす例が載っています。

よくよく試すと分かりますが、これは検索結果を絞り込む機能はありません。 単純に入力した文字が強調されるだけです。最初はこれに気がつかず、json.txtファイルを準備しました。

静的検索用のjson.txtデータを作成する

最初は適当に都道府県データをRubyでHashに入れて、.to_jsonメソッドで変換しました。

rubyと郵便番号データを使った都道府県リストを生成するスクリプト

#!/usr/local/bin/ruby
# -*- coding: utf-8 -*-
##
require 'csv'
require 'json'
entry = Hash.new

tmp = Hash.new
id = 1
CSV.open(ARGV[0], 'r').each do |row|
  ## prepare output format
  pref = row[6]
  pref_kana = row[3]

  tmp[pref] = 0
  tmp[pref_kana] = 0
end

res = Hash.new
res["results"] = [{"id"=>"0","name"=>""}]
id = 1

tmp.keys.each do |pref|
  res["results"] << {"id" => id.to_s, "name" => pref}
  id += 1
end

print res.to_json

引数にUTF-8に変換した郵便番号のCSVファイルを指定して実行すると、画面に結果が出力されるので、適当にリダイレクトしてjson.txtファイルを作成します。 出力結果はおもしろくないので省略。

$ ./show_all_pref_list4flexbox.rb ../initdb/ken_all.utf8.csv

このjson.txtファイルを検索ページと同じディレクトリに配置して、検索ページの中にJavaScriptを埋め込みます。

これは動きますが、100近い候補が単純にリスト表示されるのは、まったく便利ではありません。うれしくもないし、楽しくもない。

最初はこれで絞り込みもできると思ったんですけどね、そんなに甘くはありませんでした。

動的に検索結果を絞り込む検索ボックスにする

Example 2:から先のデモは、/flexbox/results.aspx からJSON形式の検索結果をもらって表示している例があります。

問題は検索に使っているプロトコルが書かれていないところです。

ブラウザから/flexbox/results.aspxにアクセスしてみるとエラーになってしまいます。

とりあえず準備したjson.txtの内容を返信するCGIアプリを作成してみました。

json.txtの内容をそのまま返すCGI用bashスクリプト

#!/bin/bash

BASEDIR="$(dirname $0)"

echo "Content-type: application/json"
echo ""

cat "${BASEDIR}/pref_json.txt"

結局はこのスクリプトをポイントするログをみて、 q, p, s, contentTypeをキーにしていることが分かりました。それを元に検索をしてみると、stackoverflowのサイトに「 jQuery FlexBox: how to retrieve user's query & process submitted form?」などがひっかかります。

ここまでわかれば実際に動いているアプリケーションを観察することができるようになります。

$ curl 'http://www.fairwaytech.com/flexbox/results.aspx?q=&s=10&p=10'
{"results":[{"id":90,"name":"taxonomy"},{"id":91,"name":"tectonic"},{"id":92,"name":"tempestuous"},{"id":93,"name":"thermodynamics"},{"id":94,"name":"totalitarian"},{"id":95,"name":"unctuous"},{"id":96,"name":"usurp"},{"id":97,"name":"vacuous"},{"id":98,"name":"vehement"},{"id":99,"name":"vortex"}],"total":"105"}

いくつかパラメータを変更すると、このWebサービスの動きがわかってきます。

  • "p"は1から始まって、0だとエラーを返す
  • "p"が適切な範囲を越えた場合、全件数分を値として返す
  • "s"は1から始まって、0だと全件数分を値として返す
  • "s"が適切な範囲を越えた場合、全件数分を値として返し、特に問題はない
  • "id"は0から始まる
  • "results"と平行に"total"が設定されていて全件数を常に含めている

"p"が適切な範囲を越えた場合には値は帰らない方が適切な気はしますが、他は妥当そうな動きです。

"total"を結果に含めて、決められた件数だけを送信するようにしないと、flexboxが"s"や"p"のパラメータを変更してURLにアクセスすることはしませんでした。

細かい動きはコードを読むしかないようですが、いままでの結果を踏まえてjson.txtを読み込みFastCGI(fcgi.rb)を使って都道府県名のリストを返すようにしました。

flexboxに指定する pref.fcgi Rubyスクリプト

#!/usr/local/bin/ruby
# -*- coding: utf-8 -*-

ENV['GEM_HOME'] = "/opt/lib"
require 'rubygems'
require 'fcgi'
require	'json'

Basedir = File.dirname($0)

pref_file = File.join(Basedir, "pref_json.txt")
json = JSON.parse(open(pref_file,"r:utf-8").read)
json_total_rows = json['results'].length

FCGI.each {|request|
  out = request.out
  out.print "Content-type: application/json\r\n\r\n"

  q = CGI.parse(request.env['QUERY_STRING'])
  page = q['p'][0].to_i
  size = q['s'][0].to_i
  query = q['q'][0]
  ## check the parsed queries
  size = json_total_rows if size == 0
  size = 1 if size < 0
  page = 1 if page < 1

  ## ok, go ahead  
  start = page * size - size
  res = {'results' => [], 'total' => "0"}
  if query == nil or query == ""
    res['results'] = json['results'][start,size] if json['results'][start,size] != nil
    res['total'] = json_total_rows.to_s
  else
    json['results'].each do |item|
      if item['name'].index(query.to_s) == 0
        res['results'] << item
      end
      res['total'] = res['results'].length.to_s
    end

    res['results'] = res['results'][start,size]
  end

  out.print res.to_json + "\n"
  request.finish
}

次に一覧を表示して、">>"ボタンで最後に飛んだところ、ちゃんと10個づつ値を取得する様子が観察できました。


2001:3ex:xxx:x:xxx:xx:xx:xxxx - - [08/Dec/2010:23:07:55 +0900] "GET /postal/pref.fcgi?q=&p=1&s=10&contentType=application%2Fjson%3B+charset%3Dutf-8 HTTP/1.1" 200 348
2001:3ex:xxx:x:xxx:xx:xx:xxxx - - [08/Dec/2010:23:07:59 +0900] "GET /postal/pref.fcgi?q=&p=10&s=10&contentType=application%2Fjson%3B+charset%3Dutf-8 HTTP/1.1" 200 212

curlを使ったコマンドラインで動きを確認する事もできます。

$ curl 'http://www.yadiary.net/postal/pref.fcgi?s=1&p=98'

まとめ

とりあえずFlexBoxを使った 郵便番号の検索システムの補完機能はちゃんと動いています。

紹介されているサイトは多かったですが、少し敷居が高いのかもしれません。 でもCouchDBとは相性が良さそうな動きをしています。

このパラメータようにCouchDBに新しいViewを定義するか、他の方法を考えてみようと思っています。

みばえに頼るのは考えもの…

もっともJavaScriptで入力をサポートする仕組みは諸刃の剣で、JavaScriptが安定して動かなければ悲惨な結果になるのは目にみえています。 そのためJavaScriptをoffにしても通常のformがvisibleになるだけで、使えるようにしています。

JavaScriptをoffにしてもCSSは有効のままな場合の方が多いのかなと思っています。

反対にCSSでdisplay: noneを設定してJavaScriptでvisibleにする使い方は、JavaScriptとCSSが同時にオフになった場合を考えると想定していないformが表われて悲惨そうです。

でもJavaScriptでDOMオブジェクトをいじる様をGoogle ChromeのInspectorでみたら、よく考えるなぁ、という気持ちになりました。