Androidアプリ開発用のMVCフレームワーク 「Android-MVC」 ver0.3をリリース
AndroidのMVCフレームワーク,「Android-MVC framework」がバージョンアップした。
本ツールは,生産的なAndroidアプリ開発を支援するための,Java製のオープンソース・フレームワーク。
ver0.2がリリースされたのは2012/03/23である。
このたび 7月30日付で,バージョン0.3をGoogle Code上に公開した。
上の画面キャプチャでわかる通り,本フレームワークを利用したサンプルとして,GPSロガーのようなマップアプリが同梱されている。
プロジェクトのトップページから,全ソースコードをダウンロード可能。
AndroidのMVCフレームワーク - 「Android-MVC」
http://code.google.com/p/android-mvc-...
今回の ver0.3 の特徴は,下記のページで詳しく解説されている。
ver0.3の新機能と改良点
http://code.google.com/p/android-mvc-...
上記の解説ページの内容を,本エントリに引用する。
(※ここからは,現時点でのGoogleCode上の記述の引用となる。最新の情報はGoogleCodeを参照のこと。)
ver0.3の特徴:概観
これまで,本フレームワークは,下記のような進歩を遂げてきた。
- ver0.1:AndroidアプリにおけるMVCアーキテクチャの骨組みを示した。
- ver0.2:MVCアーキテクチャの構造を強化し,レイヤ間の連携を容易にした。
そして今回,ver0.3では,「実用性」を向上させる事に主眼を置いたリリースとなった。
- ver0.3:サンプルアプリ充実,および他ツールの特徴を導入。実用性を向上させた。
各レイヤごとに,機能面での主な改良点を取り上げる。
View層
- UIを,4通りの方法で実装可能になった。HTML5やjQuery Mobile,またJS+CSSなど既存のWeb制作技術を使った画面構築をサポート。
- MapViewの扱いが極めて容易になった。現在地の追跡や,マップ上のアイコン描画をサポート。
- Viewのアニメーションの連続実行を可能にするようなライブラリを提供。
Controller層
- サービス(バッチ)で,定期的にタスクを実行するような常駐型の仕組みを容易に実現可能になった。
- 端末ブート時に起動するサービスも容易に実現可能になった。
- 複数の非同期タスクを逐次的に連続実行する際,イベント駆動型のタスクも実行できるようになった。
- GPSで現在地を取得する処理を容易に実現可能になった。位置情報の変換ユーティリティ付き。
- コントロールフローをより簡潔にカスタマイズ可能に。ダイアログの文言や,BLを介さない画面遷移など。
Model層
- ORMで,論理エンティティと物理エンティティの相互変換をスマートに実行するための方式を整備した。
- RDBの初期状態の定義を簡潔にした。カラムに論理名コメントを付与可能になり,初期データ投入も統合。
- SELECT時にLIMITとOFFSETを指定可能にした。
これらの特徴は,同梱のサンプルアプリを動作させて確認することができる。
下記では,これらの特徴を,サンプルコードを引用しつつ詳細に説明する。
ver0.3の特徴:詳細
ver0.2の時に倣って,V→C→Mの順に見てゆく。
View層:
■ UIを,4通りの方法で実装可能になった。HTML5やjQuery Mobile,またJS+CSSなど既存のWeb制作技術を使った画面構築をサポート。
もともと,従来のレイアウトXMLによる画面描画方法に加えて,UIBuilderクラスを使った手軽なUI構築が可能な造りとなっていた。
この方法は,画面のプロトタイプ構築などの際に威力を発揮することだろう。
今回ver0.3では,他のAndroidアプリ開発フレームワークの特徴を導入した。
即ち,HTML5等を使ったUI構築である。
素のHTML4と,JavaScriptとJavaを連携させる仕組みのサンプルコードは,下記を参照。
SampleHtmlActivity.java
http://code.google.com/p/android-mvc-framework/source/browse/tags/20120730_ver0.3/src/com/android_mvc/sample_project/activities/func_html/SampleHtmlActivity.java
HTML5と,jQuery Mobileを使ったUI構築のサンプルコードは,下記のアクティビティを参照。
SampleJQueryMobileActivity.java
http://code.google.com/p/android-mvc-framework/source/browse/tags/20120730_ver0.3/src/com/android_mvc/sample_project/activities/func_html/SampleJQueryMobileActivity.java
本フレームワークのユーザは,用途に応じて,自分の好きなUI実装方法を選んでよい,というわけだ。
これらの機能を実現するためには,下記のエントリが参考になった。
たった2ファイルで,HTML+JS製のネイティブAndroidアプリを作る手順 (動作するサンプルコード付き。WebViewの活用方法)
http://language-and-engineering.hatenablog.jp/entry/20120710/CreateAndroidAppByHtml5JavaScript
jQuery Mobile と HTML5 で、Androidのネイティブアプリを作成する手順
http://language-and-engineering.hatenablog.jp/entry/20120717/CreateAndroidNat...
AndroidやiOSの「ハイブリッドアプリ」で,JavaScriptとネイティブ・コードが連携する仕組みを図解 (おまけ:HTML側で施すべき,クロスプラットフォーム対策)
http://language-and-engineering.hatenablog.jp/entry/20120713/p1
Javaで,匿名クラス内で定義したpublicメソッドの警告が消せず困った話 (静的なJavaと,動的なJavaScriptを連携させるDSLを作りたい)
http://language-and-engineering.hatenablog.jp/entry/20120728/AnonymousClassWarnsOnBridgingBetweenJavaAndJS
■ MapViewの扱いが極めて容易になった。現在地の追跡や,マップ上のアイコン描画をサポート。
MapViewを拡張したMMapViewの作成により,マップアプリの開発工数が劇的に削減される。
マップ上では,自分の現在地を自動的に追跡させる事もできるし,任意の座標にアイコンを描画する事も容易である。
下記が該当するサンプルコード。
SampleMapActivity.java
http://code.google.com/p/android-mvc-framework/source/browse/tags/20120730_ver0.3/src/com/android_mvc/sample_project/activities/func_map/SampleMapActivity.java
Mapを利用するためには,Eclipse上でプロジェクトを新規作成する際に,ターゲットとしてGoogle APIを含むようなセットアップが必要である。
また,strings.xmlの中に,自分の取得したGoogle Maps APIのキーを記述しておくことが必要だ。
これらの機能を実現するためには,下記のエントリが参考になった。
Androidアプリで,Google Maps API+GPS+Geocoderを使って,現在地の地図と地名を表示させよう
http://language-and-engineering.hatenablog.jp/entry/20110828/p1
Androidアプリで,Google Mapsの地図上にアイコン画像を配置し,そのTapイベントに反応するサンプルコード
http://language-and-engineering.hatenablog.jp/entry/20110907/p1
■ Viewのアニメーションの連続実行を可能にするようなライブラリを提供。
AndroidのアニメーションAPIには,「連続実行がしづらい」という欠点があった。
しかし,それを解消するような便利なライブラリが提供された。
AnimationDescriptionを列挙するだけで,時系列にアニメーションが実行されるのである。
SampleAnimationActivity.java
http://code.google.com/p/android-mvc-framework/source/browse/tags/20120730_ver0.3/src/com/android_mvc/sample_project/activities/func_visual/SampleAnimationActivity.java
この機能を実現するためには,下記のエントリが役立った。
Androidで,複数のAnimationを「順番に」実行するためのライブラリ (XMLを使わずに「連続した動きの変化」を指定し,逐次実行するDSL)
http://language-and-engineering.hatenablog.jp/entry/20120416/AndroidAnimationSetSequentialDSL
Androidで,「ビットマップのピクセル操作」をリアルタイムに実行するサンプルコード
http://language-and-engineering.hatenablog.jp/entry/20120626/AndroidManipulateBitmapPixels
ただし,本機能には限界もある。利用時には,1個のViewに対する操作にとどめられたい。
Controller層:
■ サービス(バッチ)で,定期的にタスクを実行するような常駐型の仕組みを容易に実現可能になった。
処理の実行間隔と,処理の実行内容さえ指定すれば,もうそれで常駐型のサービスの出来上がりである。
SamplePeriodicService.java
http://code.google.com/p/android-mvc-framework/source/browse/tags/20120730_ver0.3/src/com/android_mvc/sample_project/bat/SamplePeriodicService.java
■ 端末ブート時に起動するサービスも容易に実現可能になった。
マニフェストXML中にも指定が必要だが,ブート時に行なうべきことの記述は極めて少なくて済むようになっている。
OnBootReceiver.java
http://code.google.com/p/android-mvc-framework/source/browse/tags/20120730_ver0.3/src/com/android_mvc/sample_project/bat/OnBootReceiver.java
なお,これらサービス関連の機能を実現するにあたり,下記のエントリが役立った。
Androidで,自動起動する常駐型サービスのサンプルコード (アプリの裏側で定期的にバッチ処理)
http://language-and-engineering.hatenablog.jp/entry/20120724/AndroidAutoStartingResidentServiceBatch
■ 複数の非同期タスクを逐次的に連続実行する際,イベント駆動型のタスクも実行できるようになった。
この部分は,本フレームワークの全ての要とも言える。
非同期で複雑なマルチスレッドの連続した処理を,まるでシングルスレッドでもあるかのように,逐次的に記述できるのだ。
今までは,AsyncTasksRunnerに渡されるSequentialAsynkTaskクラスは,ただ単に「重い処理を別スレッドで逐次的に実行するための手段」でしかなかった。
しかし,今回からはここにイベント駆動型の「SequentialEventTask」が加わった。
SequentialEventTask.java
http://code.google.com/p/android-mvc-framework/source/browse/tags/20120730_ver0.3/src/com/android_mvc/framework/task/SequentialEventTask.java
特定のイベントの発火を待つような場合,逐次的な処理はいったん途切れることになる。
イベントリスナをimplementしつつ,本イベントタスクをextendすれば,逐次的に取り扱い可能なイベントクラスが作成可能になる。
つまり,イベントの処理結果を次の非同期タスクに容易に渡す事ができる,ということだ。
■ GPSで現在地を取得する処理を容易に実現可能になった。位置情報の変換ユーティリティ付き。
GPS情報の取得タスクは,前項で言及したイベントタスクとして実装されている。
Android中で取り扱われる位置情報オブジェクトの種類はさまざまなので,それらを相互に変換するユーティリティも付属している。
GetMyLocationEventTask.java
http://code.google.com/p/android-mvc-framework/source/browse/tags/20120730_ver0.3/src/com/android_mvc/framework/gps/GetMyLocationEventTask.java
LocationUtil.java
http://code.google.com/p/android-mvc-framework/source/browse/tags/20120730_ver0.3/src/com/android_mvc/framework/gps/LocationUtil.java
■ コントロールフローをより簡潔にカスタマイズ可能に。ダイアログの文言や,BLを介さない画面遷移など。
コントローラ上で,BLを介さない画面遷移を非常に簡潔に記述できるようになった。
サンプルコードを記載する。
/** * TOP画面からの遷移時 */ public static void submit(TopActivity activity, String button_type) { // 送られてきたボタンタイプに応じて,遷移先を振り分ける。 // extra付きの遷移を実行 if( "EDIT_DB".equals(button_type) ) { Router.goWithData(activity, DBEditActivity.class, "DB編集画面へ", new Intent().putExtra("hoge", "Intentで値を渡すテスト").putExtra("fuga", 1) ); } else { // extraのない遷移を実行 Router.goByRoutingTable(activity, button_type, new RoutingTable() .map("VIEW_DB", DBListActivity.class, "DB一覧画面へ") .map("TAB_SAMPLE", SampleTabHostActivity.class, "タブ画面へ") .map("MAP_SAMPLE", SampleMapActivity.class, "マップ画面へ") .map("HTML_SAMPLE", SampleHtmlActivity.class, "HTMLのサンプル画面へ") .map("JQUERY_SAMPLE", SampleJQueryMobileActivity.class, "jQuery Mobileのサンプル画面へ") .map("ANIM_SAMPLE", SampleAnimationActivity.class, "アニメーションのサンプル画面へ") ); } /* NOTE: 下記のように書くのと同じ。 if( "VIEW_DB".equals(button_type) ) { // 一覧画面へ Router.go(activity, DBListActivity.class); } if( "TAB_SAMPLE".equals(button_type) ) { // タブ画面へ Router.go(activity, SampleTabHostActivity.class); } if( "MAP_SAMPLE".equals(button_type) ) { // マップ画面へ Router.go(activity, SampleMapActivity.class); } if( "HTML_SAMPLE".equals(button_type) ) { // HTMLのサンプル画面へ Router.go(activity, SampleHtmlActivity.class); } if( "JQUERY_SAMPLE".equals(button_type) ) { // jQuery Mobileのサンプル画面へ Router.go(activity, SampleJQueryMobileActivity.class); } if( "ANIM_SAMPLE".equals(button_type) ) { // アニメーションのサンプル画面へ Router.go(activity, SampleAnimationActivity.class); } */ }
要は,RoutingTableの利用可能局面が増えたのである。
MainController.java
http://code.google.com/p/android-mvc-framework/source/browse/tags/20120730_ver0.3/src/com/android_mvc/sample_project/controller/MainController.java
また,ControlFlowDetailが流れる間のダイアログの文言も設定可能になった。
Model層
■ ORMで,論理エンティティと物理エンティティの相互変換をスマートに実行するための方式を整備した。
Javaの世界の論理エンティティを,SQLiteの世界の物理エンティティと相互変換させるための仕組みが強化された。
下記のサンプルコートで,「LPUtil」というユーティリティが果たしている役目に注目されたい。
Friend.java
// ----- LP変換(Logical <-> Physical) ----- /** * DBの格納値から論理エンティティを構成 */ @Override public Friend logicalFromPhysical(Cursor c) { setId(c.getLong(0)); setName(c.getString(1)); setAge( c.getInt(2) ); setFavorite_flag( LPUtil.decodeIntegerToBoolean( c.getInt(3) ) ); return this; } /** * 自身をDBに新規登録可能なデータ型に変換して返す */ @Override protected ContentValues toPhysicalEntity(ContentValues values) { // entityをContentValueに変換 if( getId() != null) { values.put("id", getId()); } values.put("name", getName()); values.put("age", getAge()); values.put("favorite_flag", LPUtil.encodeBooleanToInteger( getFavorite_flag() ) ); return values; }
LocationLog.java
// ----- LP変換(Logical <-> Physical) ----- /** * DBの格納値から論理エンティティを構成 */ @Override public LocationLog logicalFromPhysical(Cursor c) { setId(c.getLong(0)); setRecorded_at( LPUtil.decodeTextToCalendar(c.getString(1)) ); setLatitude( c.getDouble(2) ); setLongitude( c.getDouble(3) ); setGeo_str( c.getString(4) ); return this; } /** * 自身をDBに新規登録可能なデータ型に変換して返す */ @Override protected ContentValues toPhysicalEntity(ContentValues values) { // entityをContentValueに変換 if( getId() != null) { values.put("id", getId()); } values.put("latitude", getLatitude()); values.put("longitude", getLongitude()); values.put("recorded_at", LPUtil.encodeCalendarToText( getRecorded_at() )); values.put("geo_str", getGeo_str()); return values; }
こういった機構が準備されているおかげで,フレームワークのユーザは,Javaの便利なオブジェクトと,SQLiteの少ない型制限との間でギャップに苦しまないで済む。
そのギャップは,フレームワークが埋めてくれるからだ。変換ミスも起きない。
なにしろ,SQLiteには,BooleanもDatetimeも存在しないから。
かわりにintegerとtextで代用する必要があるのだ。
そこを透過的にサポートしてくれるツールがあると心強い。
BaseLPUtil.java
http://code.google.com/p/android-mvc-framework/source/browse/tags/20120730_ver0.3/src/com/android_mvc/framework/db/entity/BaseLPUtil.java
■ RDBの初期状態の定義を簡潔にした。カラムに論理名コメントを付与可能になり,初期データ投入も統合。
これは,スキーマ定義クラスを見ればわかって頂ける事と思う。
SchemaDefinition.java
http://code.google.com/p/android-mvc-framework/source/browse/tags/20120730_ver0.3/src/com/android_mvc/sample_project/db/schema/SchemaDefinition.java
■ SELECT時にLIMITとOFFSETを指定可能にした。
下記のようなコードが,DAOの基底クラスで標準装備されている。
/** * idが最新の1件を取得する。 */ public T findNewestOne(DBHelper helper, Class<T> entity_class) { List<T> records = new Finder<T>(helper) .where("id > 0") .orderBy("id DESC") .offset(1) .limit(1) .findAll(entity_class) ; if( records.size() > 0 ) { return records.get(0); } else { return null; } }
サンプルアプリの仕様
ver0.3に同梱されているサンプルの仕様を述べる。
外部仕様:
- TOP画面から,DBを扱うサンプル,GPS系のサンプル,UI系のサンプル等に遷移できる。
- TOP画面で「サービスを起動」すると,現在位置の記録が始まる。
- GoogleMap表示画面において,取得される位置情報には2パターンある。
- 「現在地を追跡」のトグルボタンがONである場合,マップ自体がGPS通信を行ない,現在地を取得する。取得された情報は,マップの中心地点を移動させるために利用される。
- 現在地を取得するためのサービスが起動している場合,マップとは別に,サービス自身がGPS通信を行なう。取得された情報は,マップ上に現在地のアイコンを表示するために利用される。
- 端末の電源を再起動すると,起動時にサービスが自動起動する。アプリのTOP画面でサービスの常駐をOFFにすれば,サービスは停止する。
- 端末がブートするたびにサービスが起動するのが邪魔な場合は,サービスが自動起動しないようにマニフェストXML等を書き換えるか,サンプルアプリをアンインストールすること。
内部仕様:
- AppSettingsにおいて,デバッグモードの指定があるため,アプリ起動のたびにDBを初期化するようになっている。DBに登録された情報を消さずに取っておきたい場合は,デバッグモードをOFFにすること。
- プロジェクトがGoogleMapを利用しない場合,Googleの提供するマップ系APIを参照しているパッケージはEclipse上でエラーになる。利用しないなら,削除して構わない。
結び
以上が,ver0.3における改良点のサマリである。
今回の目玉を要約すると,
- HTML5 を含む,セレクタブルなUI実装方法。
- MapとGPSが簡単に使えるようになった。
- 常駐サービスのひな型。
という事になる。
加えて,M・V・Cの全レイヤについて,細かな点で改良が図られ,実際のアプリケーション開発に役立つ仕掛けが詰め込まれているのが分かるはずだ。
本フレームワークは,「スタンダードな設計パターンや開発プロセス」を提唱しつつも,ユーザに対して方式を選択する余地を残している。
ユーザの現実のニーズに応えつつも,ユーザに新たな発想法を提供しようと試みているのである。
ver1.0出現の日は,そう遠くない。
(ここまで引用)
では以上をもって,「Android-MVC」バージョン0.3のリリース報告とさせて頂く。