RoboVMまとめ
1年くらいRoboVM使ってきたので、このへんで得られた知見をまとめときます。
RoboVMに関するオフィシャルな情報は http://docs.robovm.com/ を参照してください。
RoboVMとは
詳細は http://robovm.com/ を見てください。
JVMのバイトコードをネイティブコードにコンパイルするコンパイラおよびランタイムです。
Mac OS X, iOS, Linux 向けにビルドすることができます。
Windowsはサポートされていません。また、iOS向けにビルドするにはMacが必要です。
実際はiOSアプリをJava(およびJVMバイトコードにコンパイルできる言語)で開発するためのツール、といった位置づけになっていますが、単にコマンドラインツールを作ったりすることもできます。
Androidとのクロスプラットフォーム開発を行いやすくするため、ランタイムにはAndroidで標準で利用できるライブラリ(ただしAndroidプラットフォームに依存しないもの)と同等のものが含まれています。
しくみ
大まかな仕組みについては若干古い内容ですが以下がまとまってて良いです。
RoboVM 1.0 Released – Commercial Licenses & A Look under the Hood
コンパイラのコアの部分では Soot を使ってJVMバイトコードを一度Jimpleコードという中間表現に変換し、いくつかの最適化と変形を行い、LLVM IRに変換しています。
とりあえず動かす
まずは愚直に動かしてみましょう。
http://download.robovm.org/ から最新の安定版をダウンロードしてください(robovm-*.*.*.tar.gz)。
展開したディレクトリ以下の bin/robovm
が実行コマンドになります。
以下のような内容のJavaプログラムを通常通りjavaコンパイラでコンパイルした Hello.class
ファイルを用意します。
public class Hello {
public static void main(String[] argv) {
System.out.println("Hello RoboVM");
}
}
このclassファイル(ソースコードではなくバイトコード)をrobovmでネイティブコードにコンパイルして実行するには以下のように行います。
robovm -classpath . -run Hello
初回はランタイム全体のコンパイルが入るためやや時間がかかりますが、正常にコンパイルができれば Hello RoboVM
と出力されます。
とりあえずiOSシミュレータで動かす
今度はiOSアプリケーションとして、シミュレータ上で動かしてみましょう。
iOSアプリとして動かす場合、以下のようなApplecationDelegateを実装したクラスを作る必要があります。
import org.robovm.apple.coregraphics.CGRect;
import org.robovm.apple.foundation.NSAutoreleasePool;
import org.robovm.apple.uikit.NSTextAlignment;
import org.robovm.apple.uikit.UIApplication;
import org.robovm.apple.uikit.UIApplicationDelegateAdapter;
import org.robovm.apple.uikit.UIApplicationLaunchOptions;
import org.robovm.apple.uikit.UIColor;
import org.robovm.apple.uikit.UILabel;
import org.robovm.apple.uikit.UIScreen;
import org.robovm.apple.uikit.UIWindow;
public class HelloIOS extends UIApplicationDelegateAdapter {
@Override
public boolean didFinishLaunching(UIApplication application, UIApplicationLaunchOptions launchOptions) {
final UILabel label = new UILabel(new CGRect(0, 0, 200, 40));
label.setText("Hello RoboVM");
label.setTextColor(UIColor.white());
label.setTextAlignment(NSTextAlignment.Center);
UIWindow window = new UIWindow(UIScreen.getMainScreen().getBounds());
window.addSubview(label);
label.setCenter(window.getCenter());
window.makeKeyAndVisible();
return true;
}
public static void main(String[] args) {
try (NSAutoreleasePool ignored = new NSAutoreleasePool()) {
UIApplication.main(args, null, HelloIOS.class);
}
}
}
注目すべきは UILabel
, UIWindow
などCocoa Touchに含まれるクラス(通常はObjective-CやSwiftで利用するクラス)をJavaから直接利用している点です。
Cocoa Touchに含まれる各種APIをJava風に呼び出せるように、RoboVMはそれらをバインディングしたJavaクラスを提供しています。
Cocoa Touch標準のAPIはほぼすべてバインディングが用意されているので、上記のようにすぐ利用できます。
ではiOSシミュレータで動かしましょう。
まず、通常のJava classファイルとしてコンパイルします。
今回はCocoa Touchバインディングを利用しているため、展開したrobovmのディレクトリ内の lib
以下にあるいくつかのjarファイルをクラスパスに含める必要があります。
javac -cp robovm-rt.jar:robovm-objc.jar:robovm-cocoatouch.jar:. HelloIOS.java
次にコンパイルしたクラスファイルをiOSシミュレータ向けにビルドして、実行します。
引数で対象OSをiOS, CPUアーキテクチャをx86_64として指定してください。
robovm -cp robovm-objc.jar:robovm-cocoatouch.jar:. -os ios -arch x86_64 -run HelloIOS
シミュレータが起動してその中で作成したアプリケーションが実行されます。
設定ファイルを利用する
毎回コマンドラインでクラスパスなどを指定する代わりに、設定ファイルに書き出してその設定ファイルを指定することもできます。
例えば上記のシミュレータ実行時のコマンドで指定しているオプションは以下の設定ファイルと同等のものです。
<config>
<mainClass>HelloIOS</mainClass>
<os>ios</os>
<arch>x86_64</arch>
<classpath>
<classpathentry>robovm-objc.jar</classpathentry>
<classpathentry>robovm-cocoatouch.jar</classpathentry>
<classpathentry>.</classpathentry>
</classpath>
</config>
この設定ファイルは以下のようにしてrobovmコマンドで生成することができます。
robovm -cp robovm-objc.jar:robovm-cocoatouch.jar:. -os ios -arch x86_64 -dumpconfig robovm.xml HelloIOS
上記コマンドでは robovm.xml
というファイル名で設定ファイルが生成されます。
この設定ファイルを利用して実行する際は以下のように実行します。
robovm -config robovm.xml -run
Gradleプラグインを利用する
GradleからRoboVMを利用するためにプラグインが利用できます。
前述のiOSシミュレータの例をgradleプラグインを利用して実行してみましょう。
まず以下の内容で build.gradle
ファイルを作成します。
apply plugin: "java"
apply plugin: "robovm"
buildscript {
project.ext.roboVMVersion = '1.5.0'
repositories {
mavenCentral()
}
dependencies {
classpath "org.robovm:robovm-gradle-plugin:${roboVMVersion}"
}
}
sourceSets.main.java.srcDirs = ['src']
repositories {
mavenCentral()
maven { url "https://oss.sonatype.org/content/repositories/releases/" }
}
dependencies {
compile "org.robovm:robovm-rt:${roboVMVersion}"
compile "org.robovm:robovm-cocoatouch:${roboVMVersion}"
}
ここでは利用するRoboVMのバージョンを1.5.0としていますが、適宜書き換えてください。
作成した build.gradle
と前述のRoboVM設定ファイル robovm.xml
およびソースコードを以下のように配置します。
.
├── build.gradle
├── robovm.xml
└── src
└── HelloIOS.java
この状態で以下のgradleタスクを実行すればシミュレータ上での実行ができます。
gradle assemble launchIPhoneSimulator
他にも実機デプロイ、IPA作成、バイナリ作成などいろいろタスクがあるので、一度 README を見てみると良いです。
RoboVM Studioを利用する
IntelliJ IDEA Community EditionをベースにRoboVM Studioという開発環境も提供されています。
新規プロジェクト作成時のテンプレートを利用できたり、利用するプロビジョニングプロファイルの選択や、RoboVMのライセンスキーの管理などがGUIから行えます。
有償の機能
一部機能は有償で提供されています。
現状ではInterface Builderの統合とデバッガが有償の機能となっています。トライアル期間もあります。
バインディング作成(ネイティブコードとの連携)
ネイティブコードとの連携には Bro という専用のAPIを使います。
詳細は http://docs.robovm.com/advanced-topics/bro.html にあります。
https://github.com/BlueRiverInteractive/robovm-ios-bindings/blob/master/README.md も参考になります。
サードパーティライブラリを利用する際など、Objective-Cのクラスを使いたくなるときがよくあります。
ここでは Reachability を例にバインディングを作成してみましょう。
バインディングはヘッダファイル(ここでは Reachability.h
)を参考に作成していきます。
Reachability.h は以下のようになっています。(コメントとenum定義を取り除いています)
@interface Reachability : NSObject
+ (instancetype)reachabilityWithHostName:(NSString *)hostName;
+ (instancetype)reachabilityWithAddress:(const struct sockaddr_in *)hostAddress;
+ (instancetype)reachabilityForInternetConnection;
+ (instancetype)reachabilityForLocalWiFi;
- (BOOL)startNotifier;
- (void)stopNotifier;
- (NetworkStatus)currentReachabilityStatus;
- (BOOL)connectionRequired;
@end
上記に対応するバインディングは以下のようになります。
(reachabilityWithAddress:
のバインディングは sockaddr\_inPtr
がパッケージプライベートなのでここではコメントアウトしています)
@Library(Library.INTERNAL)
@NativeClass
public class Reachability extends NSObject {
@Method(selector = "reachabilityWithHostName:")
public static native Reachability createReachability(String hostName);
// @Method(selector = "reachabilityWithAddress:")
// public static native Reachability createReachability(sockaddr_inPtr hostAddr);
@Method(selector = "reachabilityForInternetConnection")
public static native Reachability createReachabilityForInternetConnection();
@Method(selector = "reachabilityForLocalWiFi")
public static native Reachability createReachabilityForLocalWiFi();
@Method
public native boolean startNotifier();
@Method
public native void stopNotifier();
@Method(selector = "currentReachabilityStatus")
public native int getCurrentReachabilityStatus();
@Method(selector = "connectionRequired")
public native boolean isConnectionRequired();
}
アノテーションをつけるだけで基本的にObjective-CのクラスをそのままJavaに翻訳するようなノリでバインディングできます。
NSObject
を継承するのも、インスタンスメソッド・クラスメソッドの扱いも、そのままJavaのクラスで同じようにやるだけです。
@Method
の引数 selector
にバインディングするメソッド名を書きますが、省略するとメソッド名と同名のセレクタのバインディングになります。
(RoboVMバインディング作成時の慣例として、Javaライクなメソッド名にする慣習があるので、ここではそれに合わせています。)
引数・戻り値の NSString
は String
として記述することが可能です。
ビルド時にリンクの指定を追加する必要もあります。リンクするライブラリは robovm.xml
で <libs>
要素で指定していきます。
前提として、ダウンロードしたReachabilityをスタティックライブラリとしてビルドして "libReachability.a" として配置されているとします。
<config>
<mainClass>HelloIOS</mainClass>
<os>ios</os>
<arch>x86_64</arch>
<classpath>
<classpathentry>robovm-objc.jar</classpathentry>
<classpathentry>robovm-cocoatouch.jar</classpathentry>
<classpathentry>.</classpathentry>
</classpath>
<libs>
<lib>libReachability.a</lib>
</libs>
</config>