Android上でRubyアプリケーションが開発できるRubotoについて書いてみる

 ちょっと前にRubotoの記事を某雑誌に掲載するということで、色々準備していたのだけれども、Titanium Mobileとか、他にも魅力的な開発環境がたくさん出てきたので、結局Rubotoの記事を掲載するのはお流れに。とはいうものの、Ruboto自体は今後も発展するだろうし、国内のブログ記事を見渡しても、Rubotoの記事は殆ど無くて、あってもHello Worldを書いた程度だったりするので、ここにまとめておくことにします。
 雑誌に載せるレベルの量は書ききれないですが、雰囲気でも掴んでいただけると幸いです。
 
1.Rubotoについて

 Rubotoの紹介自体は、ちょっと調べてくれば出てくるので割愛しますが、参考リンクとしてここを挙げておきます。

 http://octoba.net/archives/20100305-ruboto-irb-android-427.html

 あまり語られることもありませんが、RubyRails)の世界では有名なEngineYard社がバックアップしているプロジェクトで、JRuby開発者のCharles Nutter氏などが参画しています。Titanium程の盛り上がりはありませんが、今も活発に開発が続いています。

 http://www.engineyard.com/open-source
 http://groups.google.com/group/rubotoGoogle Group)
 
2.Rubotoの特徴

 RubotoはAndroid上でRuby言語を使って開発が出来る環境です。
 Rubotoにはruboto-irbとruboto-coreというものがあります。

 ruboto-irbrubyで開発したことがある人なら、パッと想像がつくかと思いますが、irbという対話式のインターフェースを持ったアプリケーションです。ruboto-irb上でプログラムを入力すると、即座に結果が返ってくるというシンプルなアプリケーションです。Android上で直接アプリ開発をしたり、動作確認をしたいときは、ruboto-irbを使います。

 一方ruboto-coreは、直接rubyファイルを解釈して、単体のアプリケーションとして動作させるエンジンとして動作します。そのため、開発はAndroidの外のPCで行い、付属のantビルドスクリプトを動作させることで、apkファイルを生成します。

 RubotoはJRubyを内蔵し、Javaで実装されたRubyインタプリタによりRubyプログラムを解釈します。
 そのため、RubyプログラムからJavaのライブラリを呼ぶことも出来ます。もちろんAndroidAPIを呼び出すことも可能です。
 ただし、少し凝ったことをするためにはRuboto側から開発する場合であっても、Anrdoid開発のルールを理解する必要があります。物によってはそのままJavaで書いてしまったほうが早く実現できることもあると思います。

 またパフォーマンスですが、JRubyはCで書かれたRubyよりも早く動作することもある言語なのですが、JRubyの初期化は遅く、ruboto-irbを起動したときのJrubyの初期化は非常に遅い物となっています。これは思いついたことをすぐに実現したいというモバイルアプリケーションを開発するには、致命的です。今後改善により初期化が早くならなければ、普及は難しいと思います。
 
3.RubotoによるHello World

 手っ取り早くRubotoでアプリケーションを動作させたいのであれば、ruboto-irbに付属しているtoast.rbが参考になると思います。

#displays a 'Toast'
import 'android.widget.Toast'

Toast.makeText($activity, "Hello Ruboto!", Toast::LENGTH_LONG).show

 1行目のimport文はいきなりJavaの呼び出しです。
 android.widget.Toastクラスを呼び出せるようにインポートします。

 http://developer.android.com/reference/android/widget/Toast.html

 呼び出すとToastクラスが直接使えるようになります。
 4行目のmakeTextはToastクラスのインスタンスを直接生成します。
 最後のshowでToast#showメソッドが呼ばれて、表示を行います。

 $activityグローバル変数は、rubotoアプリで利用出来る変数で、$activityはRubotoで表示するアクティビティのインスタンス変数が入っています。このような形で、Rubotoが管理する変数が用意されており、JavaAndroid APIに直接渡すことができます。
 
4.RubotoによるGPSアプリケーション開発

 ここまでは、いくつかのブログでも紹介されていますが、ここから更に踏み込んで、少し複雑なアプリケーションを作りたいと思います。折角Androidのアプリを作るので、GPSを使ったアプリケーションを作りたいと思います。

 本当はruboto-coreを使って、単体で動作するアプリケーションの手順を書きたいのですが、ruboto-coreの環境構築なども説明が必要なので、今回はruboto-irb上で動作するものを作りたいと思います。

 下記のサンプルでは、GPSから位置情報を得て、その情報を記録し位置情報をGoogle Mapに引数として渡し、Google Maps上で移動経路を表示するというアプリケーションです。

 画面にはボタンが3つ表示され、Create Tableで得たデータを表形式で表示し、Create Routing Pathを使うことでGoogle Mapでの表示を行います。またClear Location Tableを押すことで移動経路情報を削除します。

f:id:Kirika:20110327222103j:image

require 'ruboto.rb'
require 'pathname'
import "android.widget.Toast"
import "android.webkit.WebView"
import "android.content.Context"

ruboto_import "org.ruboto.callbacks.RubotoLocationListener"
ruboto_import_widgets :TextView, :LinearLayout, :Button, :TableLayout, :TableRow


$activity.start_ruboto_activity "$test" do
  $data = nil
  pathname = Pathname.new("data")
  if pathname.exist?
    pathname.open("rb") do |f|
      $data = Marshal.load(f)
    end
  end
  $data = Array.new if $data.nil?

  setTitle "This is the Title"
  setup_content do
    linear_layout :orientation => LinearLayout::VERTICAL do
      button :text => "Create Table", :width => :wrap_content
      button :text => "Create Routing Path", :width => :wrap_content
      button :text => "Clear Location Table", :width => :wrap_content
    end
  end
  handle_click do |view|
    if view.getText == "Create Table"
      self.start_ruboto_activity "$table" do
        setup_content do
          table_layout do
            $data.each do |d|
              table_row do
                text_view :text => d[0].to_s, :padding => [3,3,3,3]
                text_view :text => d[1].to_s, :padding => [3,3,3,3]
              end
            end
          end  
        end
      end
    elsif view.getText == "Create Routing Path"
      self.start_ruboto_activity "$map" do
        map_path = "http://maps.google.com/maps/api/staticmap?size=320x480&sensor=true&" +
        "path=color:0x0000ff|weight:5|" + $data.map{|d| "#{d[0]},#{d[1]}"}.join("|")
        webview = Java::AndroidWebkit::WebView.new(self)
        webview.loadUrl(map_path)
        setContentView(webview,
           ViewGroup::LayoutParams.new(
          ViewGroup::LayoutParams::FILL_PARENT,
          ViewGroup::LayoutParams::WRAP_CONTENT))
      end
    elsif view.getText == "Clear Location Table"
      $data = Array.new
    end
  end

  @location_listener = RubotoLocationListener.new.handle_location_changed do |location|
    $data.push([location.getLatitude, location.getLongitude])
    pathname = Pathname.new("data")
    pathname.open("wb") do |f|
      Marshal.dump($data, f)
    end
  end

  @manager = getSystemService(Context::LOCATION_SERVICE)
  @manager.requestLocationUpdates("gps", 0, 0, @location_listener) if @manager.present?
end
  • 初期化処理

 まずruboto.rbをrequireします。ruboto.rbはrubotoの提供するヘルパークラスで、Androidアプリケーションを作る際の色々な決まりごとを簡単に書くためのメソッドを提供してくれます。
 その他アプリケーション内で使用するクラスなどを行頭でまとめてimportしておきます。

  • GPSの呼出処理(+リスナーメソッドの実装)

 次にruboto_importを使って、RubotoLocationListenerをインポートします。
 Androidでリスナークラスを使うようなアプリケーションを作ったことがあるなら分かりますが、GPSなどの外部から得られる情報をアプリケーション内で受け取るためにはリスナークラスを実装する必要があります。
 RubotoではAndroidで利用する汎用的なリスナークラス1つ1つに対して、Rubotoのヘルパークラスが用意されており、リスナークラスの実装メソッドであるonXXXXXという名前のメソッドがすべて、handle_XXXXXという名前でアクセスすることが出来ます。
 上記のサンプルアプリでは@location_listener = RubotoLocationListener.new.handle_location_changedという部分がそれに当たります。

 Listenerの種類はrubotoが用意してくれているもの以外は自前で用意する必要がありますが、rubotoがデフォルトで用意してくれているListenerだけでも、下記のURLで示すだけの量があり、Androidの基本的なアプリケーションであれば、自作の必要がありません。

https://github.com/ruboto/ruboto-irb/tree/master/src/org/ruboto/callbacks

 LocationListenerクラスのヘルパーを利用し、onLocationChangedを使ってGPSの位置情報が変更されたときの動作を記述しています。位置座標を記録したArrayクラスに位置座標を追加し、dataという名前のファイルに出力するように書いています。

 最後にgetSystemServiceにてLOCATION_SERVICEを呼び出して、requestLocationUpdatesで、位置情報を更新するように書いています。

  • UI周りの実装

 rubotoの画面はすべて$activity.start_ruboto_activityメソッドのブロックに記述します。

 start_ruboto_activityの中のそれぞれのメソッドについて説明します。

    • setup_content

 setup_contentは初期化処理を行うメソッドです。このサンプルでは、画面の構成を行うlinear_layoutを呼び出して、画面にボタンを作成する処理を行っています。また、普通のAndroidアプリケーションと同じようにレイアウトXMLを利用することも出来ます。

    • handle_click

 handle_clickは先程のGPSの項でも書きましたが、JavaのonClickメソッドを呼び出すためのRuby側のハンドラです。このサンプルでは各ボタンをクリックしたときの処理について記載しています。

 GPSで取得したデータを引数にGoogle Mapsのアドレスを使って、Google Mapsに移動経路を表示しています。AnrdoidにはWebViewというブラウザをビューとして利用する機能があるので、それを利用しました。import文でandroid.webkit.WebViewを呼び出すことで利用できます。

 こんな感じで呼び出したGoogle Map上の移動経路は以下のように表示されます。
f:id:Kirika:20110327222104j:image
 
5.まとめ
 こんな感じでJavaに比べて、短いコード量でAndroidアプリケーションを作成できるのがRubotoの強みです。とはいうものの、実際には分からないことはrubotoのソースコードを読まないといけなかったり、rubotoでうまく動かない場合に初めにJavaで組み立てて、rubotoの上でどう動かせばよいかを考えないといけなかったり、と課題が多くあります。
 まだrubyAndroid開発を行うというのは実用的ではありませんが、いずれAndroid開発の一手段となることを望みながら、この記事を締めくくりたいと思います。

 また、今回は諸事情により流れてしまいましたが、実際雑誌の記事になるときには、開発環境の構築方法、開発の進め方、よりrubotoの実装に突っ込んだ話なども書いていく予定でした。もし、今からでもどこかの雑誌で紹介してもらえるのであれば、喜んで書きますので、その際にはTwitter(@Kirika_K2)もしくはkirika.k2(gmail)にご連絡ください。