シスアーキ in はてな

シスアーキ(自称)の技術ブログ

続・AndroidでJavaFXを動かしてみたよ!

JavaFXアドベントカレンダーの23日目です!
qiita.com

みなさ〜ん、今日もJavaFXですか〜!!

JavaFXは僕の好きな技術*1ですが、イマイチ現場で使われている感がありません。その理由はずばり、スマートフォンタブレットなどのモバイル端末への対応の遅れだと感じております。Javaと言ったら「Write once, run anywhere」!やっぱり、モバイル端末上でも動いてほしい!!
去年、
AndroidでJavaFXを動かしてみたよ! - シスアーキ in はてな
という記事でJavaFXアドベントカレンダーに投稿しましたが、今回もその続編で行きたいと思います!

JavaFXPorts とは

JavaFXPorts はGluonでメンテナンスされているOSSです。Gluonといえば、Scene Builderのバイナリを配布してくれていることで有名ですね。JavaFXPortsを用いることで、JavaFXをモバイル端末や組み込みハードウェアで実行させることができます。JavaFXPortsのコンセプトは次の図に一番あらわれています。
f:id:kozake:20151223101709p:plain

使ってみよう!

では、JavaFXPortsを使ってみましょう!JavaFXPortsをAndroidで使う上で必要な環境は次の通りです。

必要環境

Hello World

JavaFXPortsのサンプルが用意されているので、bitbucketからクローンを作成します。

hg clone https://bitbucket.org/javafxports/samples

samplesというプロジェクトが出来ましたね!
Androidの場合はandroid用の定義が必要です。jfxmobileに以下の定義を追加します。androidSdkに、自分の環境のAndroid SDKのパスを定義してください。

jfxmobile {
    ios {
        forceLinkClasses = [ 'org.javafxports.**.*' ]
    }
    // ここにandroidの環境を定義
    android {
        applicationPackage = 
            "org.javafxports.${project.name.toLowerCase()}"
        androidSdk = '/Applications/adt-bundle-mac-x86/sdk'
    }
}

Android端末をUSB接続して*2、クローンしたプロジェクトのディレクトリで以下のコマンドを実行します。

./gradlew HelloWorld:androidInstall

「HelloWorld」というアプリが出来ましたね!起動してみると、
f:id:kozake:20151223111626p:plain
はい、起動できました!!

なにが起きたの?

JavaFXPortsにはJavaFXMobileプラグインが用意されています。そのプラグインを用いることで、AppleのAppStoreやAndroid Play Storeにアップロードできるパッケージが作成できます。JavaFXPortsを動作させるにはJavaFXSDKが必要となるのですが、それもこのプラグインがダウンロードして勝手にインストールしてくれます。androidInstallはJavaFXMobileプラグインが用意するタスクの1つで、その実行のなかでSDKのダウンロードから様々なことをやってくれたんですね*3

Androidでラムダっちゃ♡

では、HelloWorldのソースを見てみましょう!
ソースは、

  • /samples/HelloWorld/src/main/java/org/javafxports/helloworld/Main.java

にあります。

public class Main extends Application {

    @Override
    public void start(Stage stage) throws Exception {
        Rectangle2D visualBounds =
                Screen.getPrimary().getVisualBounds();
        double width = visualBounds.getWidth();
        double height = visualBounds.getHeight();

        Label label = new Label("Click the button.");
        label.setTranslateY(30);

        Button button = new Button("Hello JavaFXPorts");
        button.setOnAction(e ->
                label.setText("You clicked the button!"));

        Rectangle rectangle =
                new Rectangle(width - 20, height - 20);
        rectangle.setFill(Color.LIGHTBLUE);
        rectangle.setArcHeight(6);
        rectangle.setArcWidth(6);

        StackPane stackPane = new StackPane();
        stackPane.getChildren().addAll(
                rectangle, button, label);

        Scene scene = new Scene
                (stackPane,
                visualBounds.getWidth(),
                visualBounds.getHeight());

        stage.setScene(scene);
        stage.show();
    }
}

お気づきいただけたでしょうか...

        button.setOnAction(e ->
                label.setText("You clicked the button!"));

ラムダ!そう、ラムダです。なんとAndroidでラムダが使えている!なんで?Dalvik VMJava SE 7ランタイムみたいなVM*4じゃなかったの!?はい、JavaFXPortsを使うと、ラムダが使えるんですね。なぜかというと、RetroLambdaというツー ルで、ラムダ式が(バイトコード・レベルで)変換できるからなのです。RetroLambdaはクラス・ファイルを調査して、す べ ての invokedynamic呼出しを、Java SE 7で サポートされるinvoke呼出しに置き換えてくれます。

やったぜ、父さん!これでStreamも使い放題だね♡!

Streamを使ってみる。

        button.setOnAction(e ->
                label.setText(Stream.of("Hello", "Lambda")
                        .collect(Collectors.joining(" "))));

と書き換えて、HelloWorld:androidInstallタスクをターン*5!よし、インストールできたぜ!
ボタンをポチっとな!

はい、落ちました。。orz

java.lang.NoClassDefFoundError: Failed resolution of: Ljava/util/stream/Stream;
at org.javafxports.helloworld.Main.lambda$start$0(Main.java:31)
at org.javafxports.helloworld.Main.access$lambda$0(Main.java)
       :
       :

AndroidおよびiOSJava ランタイム環境は、現時点ではJava 8 固有のAPIをサポートしていません。ですので、Java 8固有のAPIを利用できないのです。これでは、ラムダの実力半減ですね。。

そこでGS Collectionsですよ!

GS Collectionsとは、ゴールドマン・サックスの開発したオープンソースコレクションフレームワークです。GS CollectionsはStream APIも使え、さらに便利な機能を備えています。なんとJava 5からサポートされています!詳しくはこちらをご覧下さい。現在は、Eclipse Collectionsに移管中みたいなので、GS Collectionsを用いてみます!
12/24 に Eclipse Collectionsが移管されました!!(((o(*゚▽゚*)o)))。
詳細はこちら

dependenciesにgs-collectionsを加えて、

subprojects {
            :
            :
    dependencies {
        compile 'com.goldmansachs:gs-collections:7.0.0'
    }
}

GS Collectionsを使った形に修正!

        button.setOnAction(e ->
                label.setText(
                        Lists.immutable.of("Hello", "Lambda")
                            .makeString(" ")));

ボタンをポチっ(恐る恐る

f:id:kozake:20151223124705p:plain

おお!動きましたね!!

javafxmobile-plugin-ensembleを動かす!

また、面白いサンプルとしてはjavafxmobile-plugin-ensembleがあります。こちらを動かしてみましょう!

hg clone https://bitbucket.org/javafxports/javafxmobile-plugin-ensemble

先ほどと同様にjfxmobileのandroidSdkに自分の環境のAndroid SDKのパスを定義してからインストール。

./gradlew androidInstall

f:id:kozake:20151223143648p:plain

おお!EnsembleがAndroidで動いている!!

まとめ

どうだったでしょうか。なかなか去年から比べてよくなっているようにおもいます。
これなら業務にも使えそうですね!え、なに?テキストに日本語が入力できない?バックキーがきかない?そんなの運用でカバーすればいいんですよ*6

*1:出来るとは言ってない

*2:開発者向けオプションでUSBデバッグを有効にしておいてくださいね

*3:すごいよ、Gradle!

*4:言い回しには気を使っております、はい

*5:かっこよくエンターキーを押そうな!

*6:つらい