Android Make Days in 明星和楽が開催されました

11月 14th, 2011

 2011年11月11日から12日にかけて、Androidイベント、「Android Make Days in 明星和楽」が開催されました。

 東京ではAndroid Bazaar & Conference(通称ABC)が定期的に開催されているのですが、福岡でもAndroidのでっかいイベントやろうぜ!ってことで日本Androidの会福岡支部が中心となり、今回開催されることとなりました。僕も裏方で色々お手伝いさせていただいたので、その様子を前日から少し紹介しようと思います。


会場設営の傍ら、Android Make Daysのステッカーを作ったのが届いたのでみんなで切り分け。(業者にやってもらうと高いんす。。。)


会場のチェックと配置をしました。椅子並べるの地味に大変。。。翌日のタスクを多少残してとりあえずそれっぽい配置になったので前日の作業終了。(が、当日になってみるとなんだかんだと細かいタスクが発生して余裕なんてありませんでしたw)


当日の受付の場所。googleさんから頂いたムックとガチャピンのうちわ(これうちわなんですw)が目立つw 前日に切ったステッカーも置いてます。


Arduino体験講座のスペース。arduinoを5台+設定完了しているPCを用意しました。実は開催前ぎりぎりにATNDを立てちゃったので、参加してくれる方がいらっしゃるかどきどきものだったんですがなんだかんだで集まって参加した方はすごい満足そうだったのでほっとしています。


ブースの一角、 日本Androidの会福岡支部で出していたロボットです。福岡はハード方面に強い方が多いみたいで、こういった物理的に動くものの展示が多かったように思います。


各ブースの準備中。皆さんやはり慣れてらっしゃるのか、さくさく準備してました。スマートフォンの展示が結構多かった印象です。


JuJuさんのブースで展示されていたArduinoベースのLED制御基板。某東の方の動画が流れてておー、って思って内容聞いたらこれまた技術的にハイレベルすぎてびっくり。


唯一最初から最後まで聞けた @R246 さんの講演。「ぷんぷくり~ん!!」には爆笑でしたw

 このほか、24時間耐久アプリ作成コンテストだったり、Arduino体験講座の場所でゲリラ的LTをやってみたり、レッドブルのおねーさんがサンプリングでレッドブルを配ってたりとかなり色々詰め込まれたイベントになりました。

 裏方で動きまわってたのであまり講演など聞けてないんですが、やはりこういう場をセッティングすることは大事だなと感じます。人との繋がりもそうですが、参加する側、運営する側共にヤル気が湧きます。 そういう空気を共有できたのが今回のAndroid Make Daysを通しての一番のメリットかなと思います。

内容に関してはUstreamに大体記録されていますので、以下を参照してください。
各講演も見ていただきたいですが、個人的にオススメなのはコンテストのアプリ発表です。これは技術が分からない人が見ても面白いと思うので、是非見ていただきたいです。
→ コンテストアプリ発表
※最大3セクション同時に講演があったので、3つにチャンネルが分かれています。

http://www.ustream.tv/channel/androidmakedays
http://www.ustream.tv/channel/androidmakedays1
http://www.ustream.tv/channel/androidmakedays2

taga Android, 勉強会

アニメーション画像の使用方法

10月 20th, 2011

アニメGIFに悩まされたので調べた内容をメモがわりに書いておきます。
Androidでよろしくアニメーションを表示する方法がいくつかあります。

1.FLASHを使用する

 最初っから画像じゃなくなっていますが、一番楽なのはこの方法だと思います。
WebViewにFlashファイルをつっこめば実現可能です。ただ、2.2以前の端末や2.2でもFlashが使えない端末もあるので全端末に対応させるというのはこの方法では無理です。
参考:Android 2.2だからといって『Flash Player 10.1』が動くわけではない

	WebView webView = (WebView) findViewById(R.id.top);
	WebSettings settings = webView.getSettings();
	settings.setPluginsEnabled(true);
	settings.setJavaScriptEnabled(true);
	settings.setAllowFileAccess(true);
	settings.setBuiltInZoomControls(false);
	webView.setHorizontalScrollBarEnabled(false);
	webView.setVerticalScrollBarEnabled(false);
	webView.setVerticalScrollbarOverlay(true);

	webView.setOnTouchListener(new OnTouchListener() {
		@Override
		public boolean onTouch(View arg0, MotionEvent arg1) {
			return true;
		}
	});
	String html = "<head><style>* {margin:0;padding:0}</style></head><body><div><object  width=\"100%\" height=\"100%\"> ";
	html += "<param name=\"movie\" value=\"file:///hogehoge.swf\">" ;
	html += "<embed src=\"file:///hogehoge.swf\" width=\"100%\" height=\"100%\">";
	html += "</embed> </object></div><body>";
	String mimeType = "text/html";
	String encoding = "utf-8";
	webView.loadDataWithBaseURL("null", html, mimeType, encoding, "");
2.MovieクラスまたはGifViewを使う

 MovieクラスでアニメGIFを動かすには下記ページが詳しいです。
(※若干コードが間違ってるのでご注意を。)
Android OS Beginning – GIF Animation in Android
しかし、Movieクラスではバグのため、offset値を指定しているアニメGIFでは動かないようです。
参照: 明日の鍵 – AndroidでGIFアニメーションを動かすんだもんね

コレに対してどんなアニメGifでも動くようにtomorrowkeyさんによって改良されたGifViewというのがあります。
参照:明日の鍵 – AndroidでGIFアニメーションが動いたんだもんね!!

こちらを使うとどんなアニメGIFでも動きます。が、Movieクラスに比べると動作が重いです。Movieクラスで動くアニメGIFの場合はそちらを使用した方がいいでしょう。

3.別々の画像を用意しパラパラ漫画のようにレイアウトする

 そもそもアニメGIFを用いずに、自前で別々の画像を用意してそれをパラパラと切り替える方法です。下記サンプルではディレイタイムは設定してないので、端末のスペックによって左右されると思います。

public class AnimationImageView extends ImageView {

	private static ArrayList<Integer> frames = new ArrayList<Integer>();
	private int frameIndex = 0;
	static {
		frames.add(R.drawable.anim_01);
		frames.add(R.drawable.anim_02);
		frames.add(R.drawable.anim_03);
		frames.add(R.drawable.anim_04);
		frames.add(R.drawable.anim_05);
		frames.add(R.drawable.anim_06);
		frames.add(R.drawable.anim_07);
		frames.add(R.drawable.anim_08);
		frames.add(R.drawable.anim_09);
		frames.add(R.drawable.anim_10);
	}
	public AnimationPngView(Context context, AttributeSet attrs) {
		super(context, attrs);
		this.setImageResource(frames.get(this.frameIndex++));
	}

	@Override
	protected void onDraw(Canvas canvas) {
		super.onDraw(canvas);
		if (this.frameIndex >= frames.size()) {
			this.frameIndex = 0;
		}
		this.setImageResource(frames.get(this.frameIndex++));
	}
}

taga Android , ,

ドラッグ可能なViewを作るには

9月 24th, 2011

 要件として、とある画像などをドラッグさせたいという話はよく出ると思います。これを実現する方法は主に二通りあるようで、OnTouchListenerを使う方法と、GestureDetectorを使う方法のようです。

OnTouchListenerを使う方法の場合、Viewにリスナー登録して、onTouchメソッドに処理を記述します。

	private int currentX;
	private int currentY;
	private int offsetX;
	private int offsetY;

	public DraggableImageView(Context context) {
		//ソースコードから生成する場合
		super(context);
		this.setOnTouchListener(touchListener);
	}

	public DraggableImageView(Context context, AttributeSet attrs) {
		//layoutファイルに記載した場合に呼ばれるコンストラクタ
		super(context, attrs);
		this.setOnTouchListener(touchListener);
	}

	private OnTouchListener touchListener = new OnTouchListener() {
		@Override
		public boolean onTouch(View v, MotionEvent event) {
			_gestureDetector.onTouchEvent(event);

			int x = (int) event.getRawX();
			int y = (int) event.getRawY();

			if (event.getAction() == MotionEvent.ACTION_MOVE) {

				int diffX = offsetX - x;
				int diffY = offsetY - y;

				currentX -= diffX;
				currentY -= diffY;
				v.layout(currentX, currentY, currentX + v.getWidth(),
						currentY + v.getHeight());

				offsetX = x;
				offsetY = y;
			} else if (event.getAction() == MotionEvent.ACTION_DOWN) {
				offsetX = x;
				offsetY = y;
			} else if (event.getAction() == MotionEvent.ACTION_UP) {
				//何もしない
			}
			return true;
		}
	};

GestureDetectorはタッチだけでなく、ロングプレスやダブルタップなども検知できるため、複雑なジェスチャーを検知したい場合はこちらを使用するのがいいと思いますが、実装時には6個のメソッドをオーバーライドする必要があるので、単純な操作であれば、OnTouchListenerを使用する方が楽です。

参考:GestureDetector | Android Developers

	@Override
	public boolean onTouchEvent(MotionEvent event) {
		touchDetector.onTouchEvent(event);
		return true;
	}

	private GestureDetector touchDetector =
			new GestureDetector(DraggableImageView.this.getContext(), new OnGestureListener(){

		@Override
		public boolean onDown(MotionEvent e) {
			// TODO Auto-generated method stub
			offsetX = (int) e.getRawX();
			offsetY = (int) e.getRawY();
			return true;
		}

		@Override
		public void onShowPress(MotionEvent e) {
			// TODO Auto-generated method stub

		}

		@Override
		public boolean onSingleTapUp(MotionEvent e) {
			// TODO Auto-generated method stub
			return false;
		}

		@Override
		public boolean onScroll(MotionEvent e1, MotionEvent e2,
				float distanceX, float distanceY) {
			int x = (int) e2.getRawX();
			int y = (int) e2.getRawY();
			int diffX = offsetX - x;
			int diffY = offsetY - y;

			currentX -= diffX;
			currentY -= diffY;
			DraggableImageView.this.layout(currentX, currentY,
					currentX + DraggableImageView.this.getWidth(),
					currentY + DraggableImageView.this.getHeight());

			offsetX = x;
			offsetY = y;
			return true;
		}

		@Override
		public void onLongPress(MotionEvent e) {
			// TODO Auto-generated method stub
		}

		@Override
		public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
				float velocityY) {
			// TODO Auto-generated method stub
			return false;
		}

	});

GestureDetectorでの実装内容です。ドラッグしたいだけならOnTouchListenerの方がやはりいいですね。

 ちなみにダブルタップを検知したい場合はGestureDetector.OnDoubleTapListenerをGestureDetectorに食わせて使用します。

参考: GestureDetector.OnDoubleTapListener | Android Developers

taga Android ,

【Corona Meetup in 博多】に参加してきました

9月 19th, 2011

先日CoronaのMeetupに参加してきた際、じゃんけん大会でTシャツもらっちゃったので、お礼がてらMeetupの内容をまとめておこうと思います。

Corona Meetup in 博多
http://atnd.org/events/18829

Coronaというのは、アンスカ社が提供しているiOS,Android向けアプリを開発できる開発支援ツールです。主に画面遷移の少ない一画面ゲームなどがCoronaの得意とする部分で、物理演算が簡単に使え、luaというスクリプト言語で記述します。以下の紹介動画を見てもらうとなんとなくメリットが見えると思います。

参考:Corona Game Edition – Physics in 5 Lines
参考:Corona公式サイト

■@ymmtny さんのcoronaの紹介とアンスカ社に行ってきた時の様子について

coronaSDKは2009年に誕生、その後がんばってきたけど、鳴かず飛ばずの時期が続いたが、14歳の少年が作ったBubbleBallというアプリが売れた今年の1月がきっかけで有名に。

参考:米14歳が初めて開発したiPhoneアプリがランキング1位に

coronaSDKと同様にiOS,Android向けの開発支援ツールとしてunity3dやtitaniumなどがあるが、普及率は今のところ三つ巴状態。

Coronaで作られたアプリでは「Dolphin Tale」というアプリがおもしろいよ!

参考:‘Dolphin Tale: Fling a Fish’ for iPhone and Android

Kwik!っていうPhotoShopのプラグインでCoronaベースのアプリが作れたりもする。(有料っぽい)

アンスカ社はアメリカにあって、近くにゴルフ場やちょっとした飛行機の離着陸場などもあったりするようなところにある。創業者は元Adobeメンバーらしい。近くのカフェなどでも無線はフリーで使えてかなり快適だとか。

参考:Ansca Mobile (アンスカ モバイル) とは?

■田中さんのcorona(というか、LevelHelper/SpriteHelperというcorona開発支援ツール)を実際に使ってみての話

・coronaを使ってはまったこと。

  • androidとiphoneの実機のパフォーマンスに差がある。おそらくcoronaの履きだすコードによってパフォーマンス差がでちゃってる
  • coronaにおける画面遷移のシーケンスを判定する仕組みがない。仕方ないのでグローバル変数で管理。
  • luaは日本語文字列操作に対応してない。バイト列としてしかみれないので、「あいう」の文字列長を取ると9が返って来たりする。仕方がないので文字列長の計算はSQLiteのsubstrに任せた。

・LevelHelper/SpriteHelperなどのライブラリについて

  • coronaは物理エンジンが簡単に利用できて、跳ねっ返りのためのshapeを結構簡単にかけるし、視覚的に見れる。(ライブラリを使えば。
  • scenesっていう画面遷移ライブラリがあったり、levelhelper/SpriteHelper っていうシーンを構成するためのツールがあったりするよ。
  • このツールはパスが絶対パス固定なので、うっかりディレクトリ移動させるとひどいことになるよー。
  • coronaのアニメーションのさせ方はFlashでいうトゥイーンと同じ。こっからここまでこのくらいの時間をかけて移動、という感じ。

・coronaに対する不満について

  • 外部バイナリとリンクできない
  • in-ap Purchase(Android)との連携できない
  • カメラとの連携ができない
  • androidのリソースデータの持ち方が固定すぎるのが辛い

■Q&A

Q:coronaは3Dサポートしてますか?
A:してないです。

Q:androidのintentとか扱えますか?
A:できないです。coronaは主にゲーム向け。インテントとかは飛ばせない。マルチタッチでの処理とかは自分でごりごり書かないといけなかったりもする。(おそらくtouchdownとかtouchupみたいなのは取れるんだろうと思う)

Q:androidって解像度違う端末ばかりですけど、対応はやっぱり面倒ですか?
A:面倒です。ただ、相対的な位置指定もできるので多少はなんとかできます。

Q:フォントは何が使えますか?
A:端末に組み込まれているフォントです。自前でフォントを作ってもOKです。

Q:参考になるような動画ってありますか?
A:Dr. Hernandezさんのyoutube動画とか見ておくと結構いい感じです。
http://blog.anscamobile.com/2011/02/corona-sdk-tutorial-make-a-game-in-40-minutes/

■CoronaTシャツを巡ってのじゃんけん大会
うっかり勝っちゃいましたw Tシャツあざーっす!

■参加してみて
 coronaは以前から耳にはしてたんですが、ゲーム用のツールで、扱いが難しそうというイメージでした。
 ただ、今回参加して話を聞いてみると、coronaのライブラリやツール類が大分出始めてきているようで、田中さんの紹介されたLevelHelper/SpriteHelperを使うとかなり用意にアプリが作れそうな気がします。何より物理演算や加速度センサー、傾きセンサーあたりを使える点が大きいです。あのAngry Birdなども物理演算を基礎に据えたゲームですし、スマートフォン向けの気軽にできるゲームを作る上では非常に魅力的に思えます。
 気になる点としては、coronaは最終的にはライセンス購入が必須になることです。このためか、coronaのライブラリなども有料のものが多いようですが、ロイヤルティーフリーなのはありがたいです。

参考:Corona (ライセンス料について)

■lua言語のどうでもいい話
 RagnarokっていうMMOなネットゲームで、ホムンクルスというキャラのAIの挙動をコントロールさせるためにユーザにコーディングさせるというある意味すごいゲームがあるんですが、その際の言語がluaでした。
興味がある方は以下を参考にどうぞ。

ホムンクルスAIマニュアル

taga Android, ライブラリ, 環境 , ,

facebook認証をrestfbを使って試してみた

9月 5th, 2011

 facebook認証を試してみたので、まとめてみました。
 facebookのデータを扱うAPIはGraph APIというようで、公開指定されているフィールドはaccess_tokenが無くても取ってこれます。限定公開しているようなデータはOAuth認証する必要がありますが、facebook認証はOAuth2.0のため、access_token取得時にどの情報を扱えるのかというscopeの指定ができたり、OAuth1.0?では非常にめんどくさかったaccess_token取得フローもかなりシンプルになって、扱いやすくなっているのが利点があります。
 今回はrestfbというjavaのライブラリがあったので、それを使って実装してみました。ただ、restfbはaccess_token取得の部分はサポートしていないので、その部分だけ自前実装になります。

■準備
restfb(http://restfb.com/)をダウンロードして展開し、restfb-1.6.6.jarをassetsにいれてBuildPathに追加します。

■情報源
Graph API
http://developers.facebook.com/docs/reference/api/

Facebookの認証方法(access_tokenの取得)について
http://developers.facebook.com/docs/authentication/

permissions(俗に言うscopeの話)
http://developers.facebook.com/docs/reference/api/permissions/

■access tokenの事前準備(自前アプリケーションの申請)

  1. https://developers.facebook.com/apps にアクセス。
  2. 右上の「Create New App」を選択
  3. AppNameに適当に文字列を入れ、チェックボックスにチェックを入れて「続行」。
  4. セキュリティチェックの文字列を入力して送信。
    (携帯メールか、クレジットカードをアカウントに登録しておかないとアプリ作成できないので注意)
  5. 作成後、「WebSite」にURLを入力して、「変更を保存」。このURLを【REDIRECT_URL】とします。
  6. App ID/API Keyとアプリの秘訣をメモ。それぞれ【APP_ID】、【APP_SECRET】とします。

■access_tokenの取得の流れ

  1. 以下のURLにリクエストする。
    “https://www.facebook.com/dialog/oauth?client_id=” + APP_ID + “&redirect_uri=” + REDIRECT_URL + “&scope=user_birthday,user_location,user_hometown”
    ここでscopeパラメータに与える値は、上記permissionsの項目から必要なものをカンマ区切りで指定します。
  2. 認証を求める画面が表示されるのでユーザが許可ボタンを押下すると、以下のURLにリダイレクトされます。
    REDIRECT_URL + “?code=【code】”
  3. 2で受け取ったcodeを使って以下のURLにリクエストします。
    “https://graph.facebook.com/oauth/access_token?client_id=” + APP_ID + “&redirect_uri=” + REDIRECT_URL + “&client_secret=” + APP_SECRET + “&code=” + code;
  4. 3のリクエストに対するレスポンスで、以下のようなデータが返却されます。
    access_token=【ACCESS_TOKEN】&expires=【EXPIRES】

■Facebookのデータ取得
自分のプロフィールデータは、restfbを使うと以下のように取得できます。

DefaultFacebookClient client = new DefaultFacebookClient(ACCESS_TOKEN);
User user = client.fetchObject("me", User.class);
TextView textView = (TextView)findViewById(R.id.main_text);
StringBuffer sb = new StringBuffer();
sb.append("user name: " + user.getName() + "\n");
sb.append("user location: " + user.getLocation() + "\n");
textView.setText(sb.toString());

このように、Userクラスにマッピングしてくれます。

■サンプルコード
 2画面で構成してます。まず、button(R.id.main_button_facebook_login)を押したときにWebViewで認証画面を表示し、リダイレクトしたときに、【REDIRECT_URL】から始まるURLであればWebViewを終了してcodeを取り出します。そのあと、graphAPIに対してaccess_tokenをリクエストして保持してます。
その後button2(R.id.main_button_get_facebook_data)が押されたときに取得したaccess_tokenをrestfbに渡して情報を出力している、という流れです。
 UIスレッドで通信してたりするので行儀悪いですが、処理の流れ自体はこのサンプルでつかめると思います。Layoutファイルなどはコードから推測できるレベルなので書いてません。

FbTest.java

public class FbTest extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);
        Button button = (Button)findViewById(R.id.main_button_facebook_login);
        button.setOnClickListener(new Button.OnClickListener() {
        	public void onClick(android.view.View arg0) {
        		Intent intent = new Intent(FbTest.this, FacebookAuthorize.class);
        		int REQUEST_CODE = 101;
        		FbTest.this.startActivityForResult(intent, REQUEST_CODE);
        	}
        });

        Button button2 = (Button)findViewById(R.id.main_button_get_facebook_data);
        button2.setOnClickListener(new Button.OnClickListener() {
        	public void onClick(android.view.View arg0) {
                DefaultFacebookClient client = new DefaultFacebookClient(ACCESS_TOKEN);
                User user = client.fetchObject("me", User.class);
                TextView textView = (TextView)findViewById(R.id.main_text);
                StringBuffer sb = new StringBuffer();
                sb.append("user name: " + user.getName() + "\n");
                sb.append("user location: " + user.getLocation() + "\n");
                textView.setText(sb.toString());
        	}
        });
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    	// TODO Auto-generated method stub
    	super.onActivityResult(requestCode, resultCode, data);

    	// ハードキーで戻ってきた場合など。
    	if (data == null) {
    		return;
    	}
    	String code = data.getExtras().getString("code");
    	Log.d("TEST", "result code: " + code);
    	String requestURL = "https://graph.facebook.com/oauth/access_token?client_id=" +
    			APP_ID + "&redirect_uri=" + REDIRECT_URL + "&client_secret=" + APP_SECRET + "&code=" + code;

    	//HttpAccessは自前で作ってるクラスですが、単純にリクエストしてbodyをInputStreamで返してるだけです。
    	HttpAccess access = new HttpAccess();
    	try {
			InputStream is = access.getContent(requestURL);
			String result = HttpAccess.inputStreemToString(is, "UTF-8");
			Log.d("TEST", "result: " + result);
			String[] results = result.split("&");

			for (String str: results) {
				if (str.startsWith("access_token=")) {
					ACCESS_TOKEN = str.replace("access_token=", "");
				}
			}
			Log.d("TEST", "ACCESS_TOKEN: " + ACCESS_TOKEN);
		} catch (ClientProtocolException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
    }
}

FacebookAuthorize.java

public class FacebookAuthorize extends Activity {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.facebooklogin);
        WebView webView = (WebView)findViewById(R.id.facebooklogin_webview);
        webView.setWebViewClient(new WebViewClient(){
        	private ProgressDialog pd;

        	@Override
        	public void onPageFinished(WebView view, String url) {
        		super.onPageFinished(view, url);
                this.dismissDialog();
        	}

        	@Override
        	public void onPageStarted(WebView view, String url, Bitmap favicon) {
        		super.onPageStarted(view, url, favicon);
        		this.dismissDialog();
            	pd = new ProgressDialog(FacebookAuthorize.this);
            	pd.setProgressStyle(ProgressDialog.STYLE_SPINNER);
            	pd.setMessage("ロード中...");
            	pd.show();

        		Log.d("onPageFinished", url);
        		if(url != null && url.contains(REDIRECT_URL + "?code=")){
        			// URLパラメータを分解する。
        			String code = url.split("\\?code=")[1];

        			Intent intent = getIntent();
        			intent.putExtra("code", code);

//        			 元のActivityに戻す。
        			setResult(Activity.RESULT_OK, intent);
        			finish();
                    this.dismissDialog();
        		}

        	}

            private void dismissDialog() {
                if (pd != null) {
                    pd.dismiss();
                    pd = null;
                }
            }
        });

        // 認証ページを表示。
        webView.loadUrl("https://www.facebook.com/dialog/oauth?client_id=" + APP_ID +
				"&redirect_uri=" + REDIRECT_URL +
				"&scope=user_birthday,user_location,user_hometown");
        Log.d("TEST","FacebookAuthorize#onCreate");
    }
}

■facebookのAPIは結構使用変更が多い
らしいので、開発者ブログをチェックしておくと、幸せになれるかもしれません。
http://developers.facebook.com/blog/

taga Misc.

Jacksonを使ってみた

8月 21st, 2011

jsonのパーサでいいのないかなーと調べてみたところ、jsonicとjacksonの2つが有名で、速度的にはjacksonの方がデータの構成によらず一定の速度が出るという情報があったのでjacksonを使ってみました。

まず、Androidで使うための準備。
Jacksonhttp://jackson.codehaus.org/
まず、上記ページから【Download】→1.8.5のDeployable jarsの【core-asl】, 【mapper-asl】の2つをダウンロード。
ダウンロードした2つのjarをassetsに入れて、プロジェクトのProperties→Java Build Path→Librariesから、2つのjarを追加。
これで準備完了です。

【jsonから単一POJOへ変換】
まず、変換先のPOJOは以下。

public class Data {
	private int id;
	private String message;
	private Date createdAt;

	public Date getCreatedAt() {
		return createdAt;
	}
	public void setCreatedAt(Date createdAt) {
		this.createdAt = createdAt;
	}
	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getMessage() {
		return message;
	}
	public void setMessage(String message) {
		this.message = message;
	}
}

対してパース部分は以下。


	static String singleData = "{\"id\":10,\"message\":\"Hello World\",\"createdAt\": \"Sun Aug 21 08:26:20 +0000 2011\"}";
	public static void soroParse() {
		Log.d("TEST", "soloParse");
		ObjectMapper mapper = new ObjectMapper();
		SimpleDateFormat dateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss ZZZZZ yyyy");
		mapper.setDateFormat(dateFormat);
		try {
			Data data = mapper.readValue(singleData, Data.class);
			Log.d("TEST", data.getId() + ", " + data.getMessage() + ", " + data.getCreatedAt().toGMTString());
		} catch (JsonParseException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (JsonMappingException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
	}

変換はObjectMapper#readValueを使います。第一引数にjsonデータ、第二引数に変換先の型を指定します。
あとは、jsonのkey部分とPOJOの変数名を同一にしておけば、ObjectMapperが宜しく変換してくれます。Dateに関しては、DateFormatをmapperにセットすれば宜しくやってくれます。(何も指定しない場合はLong値を期待しているようです。)

【複数データをArrayListへ変換させる】
POJOは一緒なので、パース部分だけ。

	static String multiData = "[{\"id\":1002,\"message\":\"Hello World\",\"createdAt\": \"Sun Aug 21 08:26:20 +0000 2011\"},{\"id\":1001,\"message\":\"Hello Japan\",\"createdAt\": \"Sun Aug 21 08:26:20 +0000 2011\"}]";
	public static void parse() {
		ObjectMapper mapper = new ObjectMapper();
		SimpleDateFormat dateFormat = new SimpleDateFormat("EEE MMM dd HH:mm:ss ZZZZZ yyyy");
		mapper.setDateFormat(dateFormat);
		try {
			ArrayList<Data> dataList = mapper.readValue(multiData, new TypeReference<ArrayList<Data>>() {});
			for(Data data:dataList) {
				Log.d("TEST", data.getId() + ", " + data.getMessage() + ", " + data.getCreatedAt().toGMTString());
			}
		} catch (JsonParseException e) {
(後略)

readValueの第二引数にTypeReferenceを指定すればOKです。

【POJOがPOJOをもってる場合の変換】
まず、POJOを持ってるData2を定義します。

public class Data2 {
	private int id;
	private String message;
//	private ChildData[] children;
	private ArrayList<ChildData> children;

	public int getId() {
		return id;
	}
	public void setId(int id) {
		this.id = id;
	}
	public String getMessage() {
		return message;
	}
	public void setMessage(String message) {
		this.message = message;
	}
//	public ChildData[] getChildren() {
//		return children;
//	}
//	public void setChildren(ChildData[] children) {
//		this.children = children;
//	}
	public ArrayList<ChildData> getChildren() {
		return children;
	}
	public void setChildren(ArrayList<ChildData> children) {
		this.children = children;
	}
}

Data2が持っている子データ。

public class ChildData {
	private String name;
	private int age;

	public String getName() {
		return name;
	}
	public void setName(String name) {
		this.name = name;
	}
	public int getAge() {
		return age;
	}
	public void setAge(int age) {
		this.age = age;
	}

}

以下、パース処理です。

	static String singleData2 = "{\"id\":10,\"message\":\"Hello World\",\"children\":[{\"name\":\"jon\",\"age\":10},{\"name\":\"jeff\",\"age\":9}]}";
	public static void soroParse2() {
		Log.d("TEST", "soloParse");
		ObjectMapper mapper = new ObjectMapper();
		try {
			Data2 data = mapper.readValue(singleData2, Data2.class);
			Log.d("TEST", data.getId() + ", " + data.getMessage());
//			ChildData[] children = data.getChildren();
			ArrayList<ChildData> children = data.getChildren();
			for(ChildData child:children) {
				Log.d("TEST", child.getName() + ", " + child.getAge());
			}

		} catch (JsonParseException e) {
(後略)

配列で持つ場合とArrayListで持つ場合を試してみましたが、どちらも問題なく変換されました。
Jackson優秀ですねー。

ObjectMapperのreadValueメソッドの第一引数にはbyte[]、File、InputStream、JsonNode、JsonParser、Reader、String、URLと思いつくあたりの入力フォーマットを大体揃えちゃってるのも素晴らしい。
使い勝手いいです。

参考: ObjectMapper API

taga Android, ライブラリ ,

アプリ内課金(in-app billing)が一部端末で動作しない

4月 20th, 2011

androidアプリのアプリ内課金が使用できるようになったので、サンプルアプリを触ってみました。
しかし、どうやら一部端末で動作しないようです。(2011/04/20現在)

・In-app Billing | Android Developers
http://developer.android.com/intl/ja/guide/market/billing/index.html

・サンプルアプリのダウンロード方法
http://developer.android.com/intl/ja/guide/market/billing/billing_integrate.html#billing-download
AVD Managerから「Google Market Billing package」をダウンロードして下さい。その中に含まれています。

※テストするためにはアプリを公開できるアカウントが必要です

このサンプルアプリで、課金が実際に行えるかどうかテストしてみたところ、
NexusOneでは成功、Xperia(SO-01B)では失敗しました。
どちらもアプリ内課金の仕様条件である、マーケットアプリのバージョン2.3.4以上にしています。(どちらもv2.3.6です)

よくわからないのは、課金が使用できるかどうかをチェックできる問い合わせ(CHECK_BILLING_SUPPORTED)では問題無く「OK」が返却されている点です。
(課金処理できないならこのメソッドでOKを返すべきではないのですが・・・)

ちょっと調べてみたところ、同じ問題に出くわしていた方が何人かいました。

・In-app Billing(アプリ内課金)が動かない機種? | 日本Androidの会ML
https://groups.google.com/group/android-group-japan/browse_thread/thread/8b9789d2e83421ef/2e3bcb3e7a8aa02d?lnk=gst&q=%83A%83v%83%8A%93%E0%89%DB%8B%E0

おそらくですが、実際の課金処理はマーケットアプリにて行われていますが、
日本向けにカスタマイズされた端末ではマーケットアプリの挙動がデフォルトのものと変わってしまっているのではないかと思われます。
(XperiaもMLで報告されているIS-03もメーカーによってカスタマイズは結構されていますよね。)
よって、googleではなく、各メーカーによって修正されるべきものではないかと思いますが、
いかんせんまだ情報があまり出てきてないため、今はまだ様子見しておいた方がいいのかもしれません。

taga Android , , ,

OAuth認証のフロー

4月 3rd, 2011

TwitterのOAuth認証について、少し調べたので認証のフローを図にまとめてみました。


参考:
OAuth Core 1.0 Revision A 日本語訳

taga Misc. ,

ApiDemoのGLSurfaceViewについて調査してみました

10月 30th, 2010

 OpenGL周りの挙動がさっぱりわからなかったので、ApiDemoのGLSurfaceViewのコードを3Dモデルの定義あたりを中心に追ってみました。


まとめ(googledoc)

ステップ1、頂点を定義する。
サンプル中のint型の配列verticesは立方体の8つの頂点座標を指定している。
3つ区切りで、3次元の(x,y,z)の座標を表す。
此の時点では頂点だけが有るだけで、辺や面はまだ無いイメージ

ステップ2、頂点ごとに色を定義する
同じくint型の配列colorsで指定されている。
こちらは4つ区切りで、それぞれ(R,G,B,alpha)を表しているみたい。
verticesと対応している

ステップ3、インデックスを定義
配列indicesでインデックス(というか面?)を定義しています。
3つの数字の組が12個あり、それぞれが1つの三角形の面となります。
ここで使われている数字はステップ1のvertices配列のインデックスに対応します。
つまり、一つ目の面 0, 4, 5は頂点(-one, -one, -one)、(-one, -one, one)、(one, -one, one)の3つの頂点からなる面を表します。

この時注意すべき事は、面には裏表が有るという事です。
どちらが表で、どちらが裏かは頂点を指定する順番に寄ります。
頂点を時計回りの順番で指定したときと、反時計回りで指定したときでは表裏が逆になります。
詳しくはこちらのURLを見てみるとわかりやすいかも
http://www.komoto.org/opengl/sample10.htm

・com.exampleandroid.apis.graphics.Cube

/**
 * A vertex shaded cube.
 */
class Cube
{
    public Cube()
    {
        int one = 0x10000;
        int vertices[] = { //頂点情報。1点につきx,y,z軸の3つの情報がいるため、ここで定義してるのは8点分。
                -one, -one, -one,  //indexからみると0
                one, -one, -one,  //indexからみると1
                one,  one, -one,  //indexからみると2
                -one,  one, -one,  //indexからみると3
                -one, -one,  one,  //indexからみると4
                one, -one,  one,  //indexからみると5
                one,  one,  one,  //indexからみると6
                -one,  one,  one,  //indexからみると7
        };

        int colors[] = {  //色情報。R,G,B,A(透過率)の4情報もってて、8点分。verti順番に対応
               0,    0,    0,  one,
                one,    0,    0,  one,
                one,  one,    0,  one,
                0,  one,    0,  one,
                0,    0,  one,  one,
                one,    0,  one,  one,
                one,  one,  one,  one,
                0,  one,  one,  one,
        };

        byte indices[] = {  //vertecesで定義した頂点情報を元に図形を定義。glDrawElementsにてTRIANGLESを指定してるので、三角形で表現していく。よって3点ずつで図形を形成していく。
                0, 4, 5,    0, 5, 1,
                1, 5, 6,    1, 6, 2,
                2, 6, 7,    2, 7, 3,
                3, 7, 4,    3, 4, 0,
                4, 7, 6,    4, 6, 5,
                3, 0, 1,    3, 1, 2
        };

        // Buffers to be passed to gl*Pointer() functions
        // must be direct, i.e., they must be placed on the
        // native heap where the garbage collector cannot
        // move them.
        //
        // Buffers with multi-byte datatypes (e.g., short, int, float)
        // must have their byte order set to native order

        ByteBuffer vbb = ByteBuffer.allocateDirect(vertices.length*4);  //メモリ確保
        vbb.order(ByteOrder.nativeOrder());  //今の環境がリトルエンディアンかビッグエンディアンかを指定
        mVertexBuffer = vbb.asIntBuffer();  //intbufferですよ。
        mVertexBuffer.put(vertices);  //頂点情報ぶっこみます。
        mVertexBuffer.position(0);  //現在位置を最初に戻す。

        ByteBuffer cbb = ByteBuffer.allocateDirect(colors.length*4);  //メモリ確保。ガベコレガン無視。
        cbb.order(ByteOrder.nativeOrder());
        mColorBuffer = cbb.asIntBuffer();  //色情報
        mColorBuffer.put(colors);
        mColorBuffer.position(0);

        mIndexBuffer = ByteBuffer.allocateDirect(indices.length);
        mIndexBuffer.put(indices);  //頂点情報の順番を指定してるindex配列を渡す。
        mIndexBuffer.position(0);
    }

    public void draw(GL10 gl)
    {
        gl.glFrontFace(gl.GL_CW);  //カリング指定。こっち向いてる面(時計周り)しか描画しないよ。
        gl.glVertexPointer(3, gl.GL_FIXED, 0, mVertexBuffer);  //頂点情報を設定
        gl.glColorPointer(4, gl.GL_FIXED, 0, mColorBuffer);  //色情報を設定
        gl.glDrawElements(gl.GL_TRIANGLES, 36, gl.GL_UNSIGNED_BYTE, mIndexBuffer);  //三角形で36点を指定する。(三角形二つで四角の面を形成。キューブをあらわすのに6面いるので、3(三角形の頂点の数)*2*6=36点となる)
    }

    private IntBuffer   mVertexBuffer;
    private IntBuffer   mColorBuffer;
    private ByteBuffer  mIndexBuffer;
}

taga Android ,

maven-android-pluginを使ってアプリをビルド

10月 30th, 2010

AndroidアプリをMavenを使ってビルドしてみる。

参考資料
Android Application Development with Maven

早速使ってみる。

  1. AndroidSDKをインストールし、環境変数「ANDROID_HOME」にAndroidSDKへのパスを定義しておく。

  2. AndroidのAPI(jar)をMavenリポジトリに追加
    Maven Android SDK Deployer tool」というツールを使えばMavenのローカルリポジトリやリモートリポジトリに追加してくれるらしい。
    今回は、 ローカルリポジトリにインストールする。解凍したフォルダの直下にpom.xmlがあるので、

    mvn clean install
    

    (ちなみにmaven 3系では失敗するので、maven2.2.1以降の2系が必要。)

    mvn clean installではmaven-android-sdk-deployerがサポートしているプラットフォーム(現状3,4,7,8)をインストールしようとするが該当するバージョンのSDKがANDROID_HOMEで指定したAndroidSDKに含まれていないとBUILD ERRORになる。

    そのため、特定のバージョンに特化してインストールしたい場合は、プロファイルオプション(-P)をつけてバージョンを指定する。指定するバージョンは3=1.5、4=1.6、7=2.1、8=2.2になる。
    なので、android-7のplatformをインストールするには
    mvn clean install -P 2.1
    
    とか実行する。
    成功すると、ローカルリポジトリの
    C:\Documents and Settings\haw\.m2\repository\android\android
    直下に各バージョン毎のAndroidのjarがインストールされる。

  3. サンプルアプリをビルドしてみる。
    (事前にHelloWorldアプリを用意しておく。)
    ビルドのために↓のようなpom.xmlを定義。

    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0"
            xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
            xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
                                http://maven.apache.org/maven-v4_0_0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <groupId>jp.co.haw.android</groupId>
        <artifactId>helloworld</artifactId>
        <version>0.1</version>
        <packaging>apk</packaging>
        <name>HelloWorl</name>
    
        <dependencies>
            <dependency>
                <groupId>android</groupId>
                <artifactId>android</artifactId>
                <version>2.1_r2</version>
                <scope>provided</scope>
            </dependency>
        </dependencies>
    
        <build>
            <sourceDirectory>src</sourceDirectory>
            <plugins>
                <plugin>
                    <groupId>
                    com.jayway.maven.plugins.android.generation2
                    </groupId>
                    <artifactId>maven-android-plugin</artifactId>
                    <configuration>
                        <sdk>
                            <platform>2.1</platform>
                        </sdk>
                        <deleteConflictingFiles>
                        true
                        </deleteConflictingFiles>
                    </configuration>
                    <extensions>true</extensions>
                </plugin>
                <plugin>
                    <artifactId>maven-compiler-plugin</artifactId>
                    <configuration>
                        <source>1.5</source>
                        <target>1.5</target>
                    </configuration>
                </plugin>
            </plugins>
        </build>
    </project>
    

    このpom.xmlで定義しているのは以下の3つ。
    ・パッケージングの種類をapkとして定義
    ・Androidプラットフォームの依存性の定義
    ・Androidプラグインとコンパイラプラグインの設定

    ビルドは通常のゴールを使って↓のコマンドでOK。

    mvn clean install
    

    実行すると、targetフォルダ直下にビルドされたapkファイルが出来上がる。
    以下のコマンドだたビルド&apkファイルがAndroidにデプロイされる。(事前にエミュレータなり実機なりが接続されていて、adb installできる状態にしておく)

    mvn clean install android:deploy
    

な感じでビルドまではできた。

    azuchi Android