Java 9で強化されたデスクトップ環境関連のAPIをJavaFXで使ってみる

このエントリは JavaFX Advent Calendar 2016 の 18 日目のエントリです。前日は id:nodamushi さんによる「 JavaFX9からPlatformに追加されるAPIについて 」でした。

はじめに

今回のエントリは JDK9 に新たに取り込まれる "JEP 272: Platform-Specific Desktop Features" を JavaFX から触ってみるというものです。

実は JDK9 では AWT/Swing/Java2D にかなり手が入ります。次のように多くの JEP が 9 には取り込まれます。

利用者に見えない内部的な強化や HiDPI 対応のような流石に対応しないとまずいものが中心ですが、その中でも JEP 272 はアプリケーション開発者側から見ても大きな機能追加になります。

まずはこの JEP 272 について紹介し、そしてこれが JavaFX からどの程度利用できるか調べてみた結果について述べていきたいと思います。

JEP 272 について

JEP 272 は "Platform-Specific Desktop Features" の名の通り、デスクトップ環境特有の機能を Java からも利用できるようにするというものです。それもプラットフォーム固有の機能も積極的に利用するというものです。

ご存じの通り Java のデスクトップ GUI アプリケーションでは、そのアプリケーションを実行するデスクトップ環境が提供している機能を利用することは中々難しかったりします。クロスプラットフォームアプリケーションの宿命かも知れません。

ですが、過去に Java 6 でそのための API が追加されたことがありました。

  • 特定のファイル・タイプに関連付けられたデフォルト・アプリケーションと対話する機能を提供する java.awt.Desktop クラス。
    • デスクトップ環境で設定されたデフォルトブラウザやエディタ、メーラなどを Java 側から起動することができます。
  • デスクトップ環境のシステムトレイにアクセスし、アプリケーション独自のトレイアイコン、メニューを追加できる java.awt.SystemTray クラス。

システムトレイを使ったりすると、よりネイティブアプリケーションっぽくなりますよね。ですが、その後の各 OS の進化に伴い、Java から使えないデスクトップ環境の機能がどんどん増えてきました。

例えば、Windows のタスクバー、Mac の Dock では次のようにプログレスバーを表示することができるようになっています。

f:id:aoe-tk:20161218003523p:plain:w1000 f:id:aoe-tk:20161218003543p:plain:w1000

バッジも表示できるようになっていますね。

f:id:aoe-tk:20161218003757p:plainf:id:aoe-tk:20161218003806p:plain:h94

でもこれらの機能には Java からアクセスすることができません。

また、Mac 特有の話ですが、Mac で実行するアプリケーションでは、メニューバーにアプリケーション名のメニューが通常のメニューの左に表示されます。アプリケーションの About ダイアログや環境設定ダイアログはここから開けるようにするよう Mac 上で動くアプリケーションは統一されています。でも、Mac 特有の話なので、Java アプリケーションはここにアクセスすることができません。

f:id:aoe-tk:20161218004452p:plain:w250

JEP 272 はこういったデスクトップ環境が提供する機能へ Java アプリケーションでもアクセスできるようにするものなのです。

提供されている機能を列挙します。

  • デスクトップ環境が起こすイベント (スリープ、サインアウト、フォアグラウンド/バックグラウンドの切り替えなど) に対してイベントリスナやイベントハンドラを登録する。
  • タスクバーへのアクセス。プログレス表示やバッジ表示、コンテキストメニューの追加を行える。
  • Mac 特有のアプリケーションメニューへのアクセス。

API レベルでは次のような追加になります。

  • java.awt.desktop パッケージの追加
    • デスクトップ環境で発生するイベントに対応した各種イベントクラスや、それに対するイベントリスナ、ハンドラが定義されている。
  • java.awt.Desktop クラスにメソッド追加
    • デスクストップ環境で発生する各種イベントに対して、イベントリスナの追加、ハンドラの登録を行うメソッドが追加されている。
  • java.awt.Taskbar クラスの新規追加
    • タスクバー (Mac での Dock) を操作するためのメソッドが定義されている。

この APIAWT の API です。でもこのエントリは JavaFX Advent Calendar のエントリです! なので、JavaFX からこれら機能を利用できるかを調べてみることにしましょう。

JavaFX から JEP 272 を利用する

それでは JavaFX から試してみることにします。ソースコードの全体は gist にアップしています。

https://gist.github.com/aoetk/7d5cc13e64239d1233e6dd879fed682e

Desktop クラスの利用

java.awt.Desktop クラスを使うと、デスクトップ環境で発生する様々なイベントに対して応答することができるようになります。まずはこのクラスのインスタンスを取得してみます。

if (Desktop.isDesktopSupported()) {
    Desktop desktop = Desktop.getDesktop();
    addAppEvents(desktop);
    setSystemMenuHandler(desktop);
} else {
    System.out.println("デスクトップはサポートされていません.");
}

実行している環境が Desktop クラスをサポートしているかを確認するメソッドがあるので、どの環境でも動かせるよう、必ずこれでチェックするようにしましょう。static メソッドである getDesktop() メソッドを使ってインスタンスを取得します。

デスクトップ環境で発生するイベントに対して応答できるようにしてみます。イベントが発生するとその旨を ListView に表示するようにします。スクリーンのスリープを例に取ると次のようになります。

// (中略)
@FXML
ListView<String> displayList;

private ObservableList<String> eventList = FXCollections.observableArrayList();
// (中略)

private void addAppEvents(Desktop desktop) {
    // (中略)
    if (desktop.isSupported(Desktop.Action.APP_EVENT_SCREEN_SLEEP)) {
        desktop.addAppEventListener(new ScreenSleepListener() {
            @Override
            public void screenAboutToSleep(ScreenSleepEvent screenSleepEvent) {
                addMessage("画面がスリープしようとしています.");
            }

            @Override
            public void screenAwoke(ScreenSleepEvent screenSleepEvent) {
                addMessage("画面がスリープから復帰しました.");
            }
        });
    } else {
        System.out.println("ScreenSleepEventはサポートされていません.");
    }
    // (中略)
}

// (中略)
private void addMessage(String msg) {
    Platform.runLater(() -> eventList.add(msg));
}

Desktop#addAppEventListner() メソッドを使って各種イベントリスナを登録します。画面スリープに対応するイベントは ScreenSleepEvent になります。イベント別にサポート有無をチェック可能なので、チェックするようにしましょう。

JavaFX からの利用に当たって注意点があります。それはイベントリスナの処理は AWT のイベントディスパッチスレッドで実行されるということです。このスレッドは JavaFX のアプリケーションスレッドとは別スレッドです。従って、 Platform.runLater() メソッドに処理をくるむ必要があります (addMessage() メソッドの実装に注目) 。

この他にも AppForegroundEvent (フォアグランド/バックグラウンドの変化に反応するイベント) 、 AppHiddenEvent (Mac 特有の「アプリケーションを隠す」に反応するイベント) 、SystemSleepEvent (システムのスリープに反応するイベント) 、 UserSessionEvent (ログインユーザのスイッチに反応するイベント) に対するリスナをセットして実行してみました。

Mac では全てのイベントに対応しています。アプリケーション起動後、「アプリケーションを隠して復帰 -> スクリーンをスリープして復帰 -> システムをスリープして復帰 -> ユーザスイッチをして復帰」を行った結果を示します。

f:id:aoe-tk:20161218020017p:plain:w532

バックグラウンドに回ったことを検知できるのはいいですね。そのタイミングで処理を停止してリソースの消費を防いだりするようなことができますね。

Windows 環境では残念ながら SystemSleepEventUserSessionEvent にしかサポートしていませんでした。システムをスリープ、復帰させた結果を示します。

f:id:aoe-tk:20161218020602p:plain:w301

Mac のアプリケーションメニューの利用

次に Mac のアプリケーションメニューにアクセスしています。これも Desktop クラスに対してハンドラを登録する形でカスタマイズします。まずは About メニューから。

if (desktop.isSupported(Desktop.Action.APP_ABOUT)) {
    // 現時点ではJavaFXでは何も起きない
    desktop.setAboutHandler(aboutEvent -> {
        Alert alert = new Alert(Alert.AlertType.INFORMATION);
        alert.setContentText("オリジナルのAboutダイアログです.");
        alert.setHeaderText("設定");
        alert.show();
    });
} else {
    System.out.println("Aboutメニューはサポートされていません.");
}

Desktop#setAboutHandler() メソッドを使い、About メニューがクリックされた時の応答処理を登録します。オリジナルのダイアログを出そうとしたのですが...JavaFX ではそもそも About メニューの追加がされませんでした。( ;∀;)

ちなみに Swing アプリケーションで試したときはうまく動作しました。

次に設定メニューです。こちらは Desktop#setPreferencesHandler() メソッドを使い、設定メニューがクリックされた時の応答処理を登録します。

if (desktop.isSupported(Desktop.Action.APP_PREFERENCES)) {
    // 現時点ではJavaFXではエラーが起きる
    desktop.setPreferencesHandler(preferencesEvent -> {
        Alert alert = new Alert(Alert.AlertType.INFORMATION);
        alert.setContentText("オリジナルの設定ダイアログです.");
        alert.setHeaderText("設定");
        alert.show();
    });
}

ですが、このコードを実行すると Cocoa 側から次のエラーメッセージが返ってきました...。

2016-12-17 21:21:08.978 java[1903:815004] *** -[__NSArrayM insertObject:atIndex:]: object cannot be nil
2016-12-17 21:21:08.984 java[1903:815004] (
    0   CoreFoundation                      0x00007fff88a2c452 __exceptionPreprocess + 178
    1   libobjc.A.dylib                     0x00007fff89405f7e objc_exception_throw + 48
    2   CoreFoundation                      0x00007fff88942a40 checkForCloseTag + 0
    3   AppKit                              0x00007fff8e19f8ce -[NSMenu insertItem:atIndex:] + 521
    4   libawt_lwawt.dylib                  0x00000001353c156f addMenuItem + 174
    5   libawt_lwawt.dylib                  0x00000001353c13f0 -[ApplicationDelegate _updatePreferencesMenu:enabled:] + 195
    6   JavaNativeFoundation                0x000000013536fd60 +[JNFRunLoop _performCopiedBlock:] + 17
(以下略)
)

AWT のフレームが生成されていることを前提に処理を進めているように推測されます。これはちょっと残念。なお、Swing で試したときはちゃんと動作しました。

というわけで、結論としては JavaFX では現状 Mac のアプリケーションメニューの利用はできないということになります。

Taskbar の利用

次にタスクバー (Dock) の利用を試してみることにします。バッジの表示とプログレス表示を試してみます。

if (Taskbar.isTaskbarSupported()) {
    Taskbar taskbar = Taskbar.getTaskbar();
    if (taskbar.isSupported(Taskbar.Feature.ICON_BADGE_NUMBER)) {
        taskbar.setIconBadge("10");
    } else {
        System.out.println("タスクバーのアイコンバッジへの数値登録はサポートされていません.");
    }
    addAction(taskbar);
} else {
    System.out.println("タスクバーはサポートされていません.");
}

タスクバーへのアクセスは Taskbar クラスを通して行います。インスタンスは static メソッドである Taskbar.getTaskbar() メソッドを使って取得します。

アイコンバッジのセットには Taskbar#setIconBadge() メソッドを使います。Mac 環境で試すと次のようにバッジの表示に成功しました!

f:id:aoe-tk:20161218174009p:plain:w423

Windows 環境は Taskbar クラスの利用そのものは可能だったものの、アイコンバッジの設定はサポートされていませんでした。まあ、Windows でのタスクバーアイコンへのバッジ登録が可能になったのは Anniversary Update からですしね。

次にプログレス表示を試してみます。次のように、JavaFX アプリ側のプログレス表示と、タスクバー側のプログレス表示を同時に行うようにしてみました。 AnimationTimer を使って表示しています。

AnimationTimer timer = new AnimationTimer() {
    private long startTime;

    @Override
    public void handle(long currentTime) {
        long elapsedTime = currentTime - startTime;
        if (elapsedTime > PROGRESS_TIME) {
            stop();
            progress.setProgress(1.0);
            startButton.setDisable(false);
        } else {
            double rateForProgressBar = Long.valueOf(elapsedTime).doubleValue() / PROGRESS_TIME;
            progress.setProgress(rateForProgressBar);
            int rateForTaskBar = (int) (rateForProgressBar * 100);
            if (taskbar.isSupported(Taskbar.Feature.PROGRESS_VALUE)) {
                taskbar.setProgressValue(rateForTaskBar);
            }
        }
    }

    @Override
    public void start() {
        startTime = System.nanoTime();
        progress.setProgress(0);
        if (taskbar.isSupported(Taskbar.Feature.PROGRESS_VALUE)) {
            taskbar.setProgressValue(0);
        } else {
            System.out.println("タスクバーのプログレス表示はサポートされていません.");
        }
        super.start();
    }
};

タスクバーのプログレス表示は Taskbar#setProgressValue() メソッドを使います。0 から 100 の間の数値をセットします。JavaFXプログレスバーは 0 から 1 と違うんですよね...。

Mac で試したところ、次のように Dock 側に進捗を表示できるようになりました!

f:id:aoe-tk:20161218175830g:plain

Windows は残念ながら未サポートでした。何でや、Windows7 の時からサポートしてたのに!

まとめ

というわけで JEP 272 の機能を JavaFX で試してみました。分かったことをまとめると次の通りです。

  • JavaFX からも JEP 272 の機能は一部を除き利用可能。
    • ただし、イベントリスナの処理は AWT のスレッドで実行される点に注意。
  • Mac のアプリケーションメニューにアクセスする機能は JavaFX からは使えない。
  • フル機能が使えるのは Mac だけ。Windows では一部の機能しかサポートしていない。
    • Linux では Ubuntu の Unity 環境でのみ使えるようです。今回は時間がなくて試せませんでした。

Mac から優先して実装されているのは理由があります。かつて Mac 環境向けの JavaApple 自身が開発、提供しており、こういた Mac 環境特有の機能にアクセスするために EAWT という API を提供していました。

Java 7 以降、Mac 向けの Java は OpenJDK で開発されるようになりますが、この EAWT は 7、8 にはバンドルが続いていました。ですが 9 からは提供をやめることになり、代替機能を Java 側で用意することになりました。それがこの JEP 272 であるということです。

ともあれ、よりネイティブアプリケーションっぽく振る舞えるような機能が実装されたのは嬉しいことです。今後のアップデートで Mac 以外の環境向けにも実装が進んでいくことでしょう。JavaFX 向けには 10 から同等機能の実装が予定されていますが、前倒しで実装とかされないかなあ。 *1

明日は誕生日枠の id:yumix_h さんの予定です。

*1:8 の時も u40 でかなりの機能追加が入ったことがあったので

Lavie Direct HZを購入しました

携帯の PC 端末として新たに NECLavie Direct HZ を購入しました。

それまで携帯 PC 端末として使っていたのは 2 年半前に購入した VAIO Tap 11 です。それが次のような理由で色々辛くなってきたので、新しい端末を探していました。

  • 軽量、高解像度で、スペック的にはまずまず満足していたが、肝心の品質が悪かった。
    • 電源を入れても起動しないトラブルが頻発 (マザーボード交換やファームウェアアップデートを繰り返すことで多少改善はした) 。
      • 勉強会とかに持っていっても、電源を入れても立ち上がらずに何度困ったことか...。
      • 他にも電源周りのトラブルも多く、キーボードの蓋をしてもスリープにならないことがあった。
    • 使用中にフリーズや BSOD もしばしば発生。
  • 無線通信する分離式のキーボードは打鍵の取りこぼしが多く、ラップトップと比べるとやはり辛い。
    • トラックパッドの操作性が非常に悪かったのもフラストレーションが溜まった。
  • やはり 2 in 1 という形態は中途半端だった。
    • 大は小を兼ねるというか、PC があれば基本的にタブレットがなくても問題ない場面が多い。
    • 携帯電話の方がファブレット化が進み、十分にタブレット的な役目を果たせるようになった。

そんな折、NEC のオンラインショップで 1 年前のハイエンドモデルがアウトレットで安く売られていたのをたまたま見掛けたので、やや衝動的にポチってしまいました。

この機種は「世界一軽量なノート PC」が売りのシリーズで、その中でも一番ハイエンドのモデルであったので、昨年の製品とは言え、次のように中々優れたスペックです。

  • Core i7
  • ディスプレイは 13 インチで解像度は 2560 x 1440 の高精細液晶
    • 映り込みのないノングレア。
    • Retina MBP 13 インチと同じ解像度です。
  • メモリ 8GB
  • ストレージ 512GB
    • 薄型ラップトップでストレージ 512GB となると極端に値段が上がるものが多いので、これはお得でした。
  • 重量なんと 779g!

とにかく軽いです。次の写真のように片手で楽々持てちゃいます。これに比べると MBA ですらもズシリと感じるようになります。

f:id:aoe-tk:20161015175617j:plain

以下、ちょっと触ってみた感想をば。

  • 良かった点
    • とにかく軽いので鞄に入れても全く苦にならず、どこにでも持っていきたいと思えます。
    • ディスプレイが非常に高精細で素晴らしい。最近の携帯電話と同様、全くドットが見えないレベルです。
      • あの MS ゴシックフォントも印刷した場合と同じようなレベルで滑らかに見えます *1
      • デフォルトではスケーリングが 200% に設定されていますが、これでは 98dpi 換算で 1280 x 720 相当と、作業領域として少し手狭です。自分は 175% に設定しました。少し字が小さくてもいいという人は 150% でもいいかもしれません。
    • ストレージ 512GB は安心感があります。
    • メーカー製 PC にしては初期インストールされているアプリが少ないです。
    • Windows PC はトラックパッドが小さいものが多いのですが、この機種は大きめで使い易いです。
    • 薄型端末にしてはインターフェースがしっかり揃っており、USB が 2 つの他に HDMI ポート、SD カードスロットもちゃんとあります。
  • イマイチな点
    • 軽量化のため、素材はプラスチッキーで質感としてはどうしても劣ります。
      • 「ぺらぺらした ThinkPad」といった感じでしょうか *2
    • キーボードの打鍵感は最低ラインはクリアしているというレベル。素材についてもしばらく使っていると表面がツルテカになりそう。
    • トラックパッドは MacBook のようにパッドとボタンが一体化したタイプですが、パッドの押し込みがちょっと固いです。
      • この部分については MacBook を超えるものを見たことが無いですねえ。

とまあこんな感じです。もし同じような機種を買おうと思っている人がいたらご参考に。とりあえず外出時のお供として 3 年くらいこれで戦えそうです。

*1:もっともそれでも美しいフォントではないですけど...

*2:今の NEC PC は Lenovo の傘下に入っていることも影響しているんですかね

JJUG CCC 2016 Springに参加しての感想

2016/05/21 に開催された JJUG CCC 2016 Spring に参加してきました。ちょっと遅くなりましたが参加した各セッションについて感想を簡単にまとめました。

参加したセッション

基調講演2: Raspberry Pi with Java

Java エバンジェリストである Stephen Chin さんによるキーノートセッション。Stephen さんは Night Hacking Tour というのを行っていて、世界の色んな国をバイクで駆け巡り、Java を使った面白い勉強会を行っていますが、今回日本でそれを行っています。既に岡山や大阪などを回っていますが、今回この JJUG CCC で東京版を行うことになったというものでした。

内容は Raspberry Pi を使って、ファミコンのゲームコンソール (筐体の形はゲームボーイアドバンス風) を作るというもの。ソフトウェア、ハードウェアの両面から様々なハッキングを行ったという内容でとても楽しい内容でした。

ソフトウェア面では Raspberry Pi 上の JavaJavaNES エミュレータ halfnes を使用した点が目を引きました。オリジナルは Swing 製でデスクトップ向けでしたが、これを Raspberry Pi 向けに GUI 部分を JavaFX で作り直したとのこと! X を使わず直接フレームバッファに書き込む JavaFX の利点をアピールしていました。 *1

ボタンの数に対する GPIO の不足を左・右ボタンは同時に押さない点に着目して回避したり、3D プリンタでのヒンジの作成で様々な試行錯誤したりなど、ハードウェア面でのハックの話も面白かったです。

こう言っちゃあれかもしれませんが、Oracle にもまだこういう遊び心たっぷりな方がエバンジェリストとして残っていて良かったなあと思いました。

E-3 Spring Boot で Boot した後に作る Web アプリケーション基盤

エムスリーの吉田さんによる、Spring Boot で構築した Web アプリケーション開発について、開発環境を構築した後の開発方針についてどうしていったかについてのお話でした。

Spring Boot では開発環境構築のお膳立てをしてくれますが、その後は Spring Framework を用いたアプリケーション開発になっていきます。そこで例外処理やトランザクション設計、URL 設計、バッチ処理、設定ファイル、ログ、セキュリティといった、共通的に考慮しないといけないことをどう設計したかを説明してもらいました。

バッチ設計のところなど、所々自分の考えとは違うところはあったものの、参考になる情報が多かったです。実は近々自分も Spring Framework を久々に使うことになりそうなので、Spring については浦島状態になっている自分にはとても助かる内容でした。

AB-4 Introduction to JShell: The Java REPL Tool

Kulla プロジェクトのコミッタになった bitter_fox さん自身による JShell の解説です。今年から就活する点をちょっとアピールしてましたw

JShell でやれることを全体的にとても分かり易く説明していました。他の言語では大体あったものですが、ようやく Java にも来てくれて嬉しいです。今まで自分自身は IntelliJ の Groovy コンソールを使ってこの用途を満たしていましたが。 *2

少し驚いたのが、JShell の開発者に JavaFX Script コンパイラの作者が入っていた点です。JavaFX Script が無くなった後、こういうことをしていたのかあ。

I-5 JavaデスクトッププログラムをふつーのWindowsプログラムのように配布・実行する方法とPCの動きが重くならないよう気を付けること

高橋さんによる、Windows 環境へ javapackager を用いてパッケージングした JavaFX アプリケーションを配布する際に工夫したことを説明するというセッションです。

このセッションで取り上げられていた javapackager については手前味噌ながら自分のブログエントリでも紹介しています。

aoe-tk.hatenablog.com

この javapackager、中々良くできているのですが、上書きインストールができない、インストールディレクトリの指定をさせることができない、など細かい点で足りないところが目に付きます。そこを Wix Toolset 側の設定に手を入れることで乗り切ったという話がとても参考になりました。

javapackager の存在については驚いた人が多かったようで、セッションの最後に沢山質問が出てきたのが印象的でした。やはりこういうものを求めていた人が多かったんでしょうねえ。*3 その割に知られていないというのが残念です。もっと Oracle はこのツールの存在をアピールした方がいいですよー。

M-6_1 十徳ナイフとしてのGradle

grimrose さんによる Gradle についてのショートセッションです。何と立ち見状態になっていて、床にべたっと座って聞く形になっちゃいました。Gradle 本当に注目を浴びてますねえ。

https://nbviewer.jupyter.org/github/grimrose/JJUG-CCC-2016-Spring/tree/master/Gradle%20as%20Army%20Knife.ipynb

内容としては Gradle 徹底入門を補完するような内容になっていて、Gradle のタスクランナーとしての側面に着目し、日常のタスク処理に Gradle を活用するためのお話になっていました。特にスクリプト実行環境があまり強力ではない Windows 環境での活用を推してましたねえ。

ホットな話題である、Kotlin Gradle についても少し触れましたが、「うーん、これ嬉しいですか?」と聞いたところで場が爆笑で包まれたのが印象的でした。 *4

I-6_2 OpenJDK コミュニティに参加してみよう

OpenJDK のコミッタである久保田さんによる、OpenJDK コミュニティに参加するための最初の一歩について説明するというセッションでした。

OpenJDK のような巨大なプロジェクトになると、参加するのはとても敷居が高い印象がありますが、やはりパッチを送るのが一番良いとのことでした。ML へパッチを送るくらいならば、OCA にサインすればできるようになるようです。

多数あるリポジトリの歩き方、パッチや意見の送り先となる ML の雰囲気など、OpenJDK プロジェクトに参加されている方ならではの話が満載でとても面白かったです。

GH-7 Java Puzzlers

最後はさくらばさん、てらださんのコンビによる、Java Puzzlers のセッションでした。

Java Puzzlers と言えば、Joshua Bloch さんと Neal Gafter さんによる JavaOne の名物セッションでしたが、今や前者は Google 、後者は Microsoft に籍を移しているため *5 、行われなくなっちゃいました。

日本版はさくらばさんとてらださんコンビになりましたが、意図的なのか自然にそうなったのか、てらださんがボケ、さくらばさんがツッコミというスタイルでの進行になっていましたw

内容としては基本的な演算子やクラス、Java8 で加わったラムダ式のコーナーケースについて問うものでした。選択肢式だったこともあり、間違った理解で回答したものもいくつかあったのですが、終わってみたら全問正解は私一人という結果になっちゃいました。(^^ゞ

全問正解ということで、さくらばさん作「Java SE 7/8 速攻入門」を頂いちゃいました。ありがとうございました。

最後に

こういう技術者が集まる場に参加するのは恐らく昨年の CCC Spring 以来だったと思いますが、やはり他のエンジニアの方々と交流すると色々な刺激が得られていいですね。

非常に規模が大きくなって驚いたのですが、これを運営するのも大変だったと思います。JJUG 運営の皆様、本当にご苦労様でした。

*1:セッションの後の休憩時間で harfnes のソースも覗いてみましたが、色々興味深かったです。

*2:自分の周囲を見ていると、Java で REPL 使いたい、というときは Groovy 使う派と Scala 使う派に分かれているように見受けられます。

*3:このセッションの影響か、私の javapackager についてのブログエントリへもアクセス、ブックマークが増えているような。

*4:でも、今後 Gradle は Kotlin ファーストで行くらしいですね。

*5:特に前者が効いとりますな (^^;;

JavaFX Maven PluginのWebサイトが復活して便利になっていた

JavaFXMaven で開発する際に便利なプラグインとして JavaFX Maven Plugin というものがあります。これは javapackager を使ったビルド、パッケージング作業を Maven 経由で実行できるようにするプラグインです。

蓮沼さんの以下のブログエントリでも紹介されています。

www.coppermine.jp

NetBeans にも JavaFX 開発向けの Maven プロジェクトを作成するためのウィザードがあるのですが、こちらは Exec Maven Plugin を使って javapackager のコマンド実行をラップしただけのものです。パッケージングについても、依存ライブラリの JAR を全て解凍してから 1 つの JAR にパッケージングするようになっており、今ひとつという印象です。

この JavaFX Maven Plugin ですが、ドメインを維持することができなくなったのか、ドキュメントを載せていた Web サイトが途中で消滅してしまいました。 GitHub リポジトリの README でも Web Archive の方 にリンクが貼られているという状態ですw

ところが最近になって github.io 上に新たに Web サイトが作られるようになり、中々便利な作りになっていました。 (GitHub のコミットログを見ていると、メンテナが最初の作者とは別の人になったようで、そのことも影響している?)

http://javafx-maven-plugin.github.io/

以下のキャプチャを見てもらうと分かりますが、作りたいプロジェクトの内容を画面上でぽちぽち選択していくと、右側に pom.xml に記述すべき内容が吐き出されるようになっています。これを自分のプロジェクトの pom.xml にコピーすればいいわけです。最近こういうの増えましたねえ。

f:id:aoe-tk:20160417210337g:plain

出力を JAR にするかネイティブパッケージにするか、ビルドは Maven JavaFX Plugin のゴールを直接叩くのか、それとも Maven ライフサイクルの中で実行するのか、などといった選択肢が用意されています。

以上、ちょっと嬉しかったのでお知らせまで。

Mac上のOpenJDK/OracleJDKはApple実装モジュールへの依存が残っている

はじめに

Mac OS X 向けの Java 実装はかつて Apple 自身が開発、提供していました。ですがそれは 6 で終了し、7 以降は Apple も OpenJDK に参加、OpenJDK 上で OS X 向け実装も開発されるようになり、Oralce から Mac OS X 向け JDK/JRE を提供されるようになっています。

ところが、全てが OpenJDK に移ったわけではなく、一部 Apple が実装、メンテナンスしているモジュールへの依存が残っていることを知り、ちょっと驚いたのでその内容についてまとめてみました。

Yosemite以降の新Look & FeelへのSwingの対応について

これに気付いたきっかけは、Mac OS X の UI デザインが Yosemite で一新されたことです。

JavaGUI ツールキットの 1 つである Swing は、OS の UI ツールキットが提供するコントロールを直接利用せず、Java2D を使って自分でコントロールを描画します。OS の UI ツールキットに似せた見た目にする、システム Look & Feel もありますが、これは自分で描画して似せているだけです。

さて、Mac OS X では Yosemite になって Look & Feel が一新されましたが、Swing については Swing 側が対応しない限り見た目は前のままのはずです。実際、次のスクリーンショットのように Mavericks 以前の Look & Feel のままでした。
f:id:aoe-tk:20160214010144p:plain

最近、家で使っているマシンを新しく Mac mini に変えました。これには El Capitan がプリインストールされているのですが、こちらでは Swing の Look & Feel が Yosemite 以降のそれに変更されていたのです。
f:id:aoe-tk:20160214010230p:plain

Java のアップデートで対応したのかな?と思って、別にある Yosemite 環境のマシンについても Java を最新版にしました。ところが、JRE のバージョンが全く同じなのに、こちらでは古い Look & Feel のままなのです (上 2 つのスクリーンショットJava バージョン表記を確認してください) 。

これは一体どういうことなのだろうと不思議に思って、調べてみました。すると、OpenJDK の ML に次のようなスレッドがあるのを見つけました。

http://mail.openjdk.java.net/pipermail/swing-dev/2015-September/004855.html

そこでは次のような説明がありました。

The Aqua look and feel is based on the JRS library, which is provided by Apple. As far as I know the Apple provide the new "modern style controls" in the new version of JRS in OS X v10.11. You can ask additional information on java-dev mailing list at apple.com [1]

Mac OS X の Aqua 風 Look & Feel については Apple が提供している JRS と呼ばれるライブラリによって提供されているとのことです。そして OS X 10.11 (つまり El Capitan) では新しい JRS が提供され、その JRS では新しい Look & Feel に対応しているため、El Capitan でだけ Swing の見た目がアップデートされたということになります。

JRSとは何か?

JRS の正式名称は JavaRuntimeSupport になります。実体は以下の場所にありました。

/System/Library/Frameworks/JavaVM.framework/Versions/A/Frameworks/JavaRuntimeSupport.framework/Versions/A/JavaRuntimeSupport

この JRS は OpenJDK には含まれておらず、ソースコードApple によってメンテナンス、提供されているとのことです。次の Apple の開発者向け ML にその点を説明するスレッドがありました。

http://lists.apple.com/archives/java-dev/2015/Dec/msg00007.html

これには驚きました。最初に述べたように Java 7 以降の Mac 上の Java 開発は OpenJDK に移行したはずです。それなのに、Apple 側から OS アップデートを通して提供されるモジュールがなければ変わらない部分があったということです。UI の Look & Feel に関わる部分であるため、意匠権絡みなどで移せない事情でもあったのでしょうか?

今後はどうなる?

というわけで Mac 環境における OpenJDK (Oracle JDK) には Apple 実装モジュールへの依存が残っていることが分かりましたが、少し不安なところがあります。というのも、Apple は旧 Apple Java 6 のサポートは El Capitan を最後にすると宣言しているからです。

applech2.com

そのため、JRS のような Apple 提供モジュールの更新も今後行われなくなる可能性があります。OpenJDK 側でもこの状況については Issue が上がっていますが、さてどうなることやら...。

[JDK-8024281] Mac OS X: stop relying on Apple's JavaVM Frameworks - Java Bug System
https://bugs.openjdk.java.net/browse/JDK-8024281

おまけ

Apple Java 6 は El Capitan が最後のサポートバージョンになるとのことですが、その El Capitan 向けインストーラApple から提供されています。 *1

ダウンロード - Java for OS X 2015-001
https://support.apple.com/kb/DL1572?locale=ja_JP&viewlocale=ja_JP

このインストーラYosemite 以前のバージョンにも使うことができます。しかもこのインストーラでインストールされる Java 6 には、El Capitan 向けの JRS がバックポートされています。よって、Yosemite にこのインストーラを使って Java 6 をインストールすると、次のように Yosemite でも Swing の Look & Feel が新デザインに変わります!
f:id:aoe-tk:20160214015307p:plain

*1:El Capitan ではこのバージョンで導入された rootless の関係で、以前のインストーラが使えなかったようです

javapackagerの紹介

このエントリは JavaFX Advent Calendar 2015 の 19 日目のエントリです。前日は id:yumix_h さんによる「 JavaFXで画面解像度を調べてみる 」でした。

今回は JDK に付属しているツールである javapackager について紹介します。このツール、私が見る限り公式のドキュメント以外では断片的な解説しか無い (主にネイティブパッケージの解説などでしか登場しない) ように見受けられるので、ここでこのツールができること全般について紹介したいと思います。

アプリケーション配布を巡る環境の変化

まず、javapackager のようなツールが登場した背景について触れたいと思います。これにはアプリケーション配布を巡る環境の変化が大きく関わっていると考えています。

既にご存じの通り、Java は "Write Once, Run Anywhere." を目指して作られたものであり、Java で実装、ビルドしたアプリケーションはどの環境でもそのまま動くことが期待されています。

この Java アプリケーションの配布ですが、元々はどのプラットフォームにも JRE がインストールされていることを前提とした考え方でした。クライアントアプリケーションの配布は実行可能 JAR の形態で行うことが普通です。サーバサイド Java では JRE の上にさらに Java EE コンテナのレイヤを敷き、アプリケーションは WAR もしくは EAR として配布しますね。

ところが、最近はこのように予めランタイムを別途インストールしておき、その上にアプリケーションをデプロイするスタイルはあまり好まれなくなっているように見受けられます。必要なものは1つのパッケージに全て含まれており、箱から出したらすぐ使えるような形態 (これを「自己完結型パッケージ」と呼びます) が好まれるようになっています。

これは OS ベンダ提供のアプリケーションストアという配布形態の登場が大きく関係していると考えています。アプリケーションストアから配布するアプリケーションはサンドボックス化されており、自己完結型であることが望まれています。エンドユーザとしても、アプリケーションストアからワンクリックでインストールが可能なスタイルに慣れてしまうと、わざわざランタイムをインストールするのは煩わしく感じられるようになるでしょう。特にデフォルトでは JavaFlash などがインストールされておらず、アプリケーションストアの利用率が高い Mac 環境ではその傾向が強いように思われます。

そしてもう一つの環境の変化はセキュリティです。どの OS、プラットフォームでも以下のようにセキュリティ対策が強化され、以前のように何も考えずに「野良アプリケーション」をほいほいインストールできなくなっています。

  • Java Web StartJava Applet では未署名のアプリケーションの実行はブロックされるようになりました。
  • Windows では未署名アプリケーションをダウンロードする際にはブラウザが警告を出します。
  • Mac では Gatekeeper という仕組みが導入され、デフォルトの状態では Mac App Storeからインストールしたアプリケーション、もしくはAppleデベロッパ登録し、そのデベロッパ ID で署名したアプリケーションのみが起動可能になっています。
  • FirefoxGoogle Chrome といった Web ブラウザの拡張機能でさえも、ブラウザベンダが運営する配布サイトからのみのインストールに制限されるようになりました。

JDK に同梱されている javapackager はこのようなクライアントアプリケーション配布を巡る環境の変化に対応できるようになっています。

javapackager の概要

javapackager は JDK に付属しており、Java アプリケーションのパッケージング、デプロイメントのためのツールです。

元々は javafxpackager という名前で、その名の通り JavaFX アプリケーションをパッケージングするための専用ツールとして JavaFX SDK に同梱されていました。JDK7 update6 以降は JDK に同梱されるようになり、Java アプリケーション全般に利用可能なツールになりました。JDK8 update20 以降は javapackager に名を変え、名実共に Java SE のためのツールに昇格 (?) しています。

javapackager が提供する機能は次のようなものになります。

  • 実行可能 JAR のビルド
  • 配布用パッケージの生成
    • Java Web StartJava Applet 向けバンドルの生成
    • 自己完結型パッケージの生成
      • インストール先の OS に合わせたネイティブインストーラの生成
      • JRE も含めたパッケージを生成し、OS 側にランタイムの事前インストールを要求しない
  • 配布プラットフォームに合わせたアプリケーションの署名
  • アプリケーションストア向けパッケージの生成

以降では上に挙げた javapackager の各機能について簡単に説明していきます。

なお、この javapackager の使い方を含む、クライアント Java アプリケーションのデプロイメント全般について詳細に解説した Oracle 公式のドキュメントがあります。日本語化もされています。詳細についてはこちらを参照してもらうとして、ここではつかみの部分を解説することにします。

Java Platform, Standard Editionデプロイメント・ガイド
http://docs.oracle.com/javase/jp/8/docs/technotes/guides/deploy/

javapackager 提供機能の簡単な説明

基本的な使い方

javapackager はコマンドラインツールです。javac や jar コマンドなどと同じディレクトリにインストールされているはずなので、これらコマンドへのパスが通っていれば使うことができます。また、Ant 向けタスクも用意されているのですが、ここではコマンドラインとしての使い方に限定して説明します。Ant での利用方法については上記ドキュメントを参照してください *1

次のような使い方をします。

$ javapackager コマンド [オプション]

javapackager に続けて実行したいタスクに応じたコマンドを指定するのが基本です。コマンド別にオプションがあり、必要に応じて指定します。コマンドは次の 5 種類があります。

コマンド 実行するタスク
-createbss CSS ファイルをバイナリ形式に変換する。
-createjar 実行可能 JAR を生成する。
-deploy デプロイ可能なパッケージを生成する。
-makeall -createjar と -deploy の両方を実行し、実行しているプラットフォームで生成可能な全ての形式のアーカイブを生成する。
-signjar JAR ファイルを署名する。

実行可能 JAR のビルド

まずは基本となる実行可能 JAR のビルドです。通常の jar コマンドが提供する機能に加え、次のような JavaFX 固有の機能を提供しています。

  • プリローダアプリケーションの指定
  • CSS のバイナリ化

1番目にあるプリローダとは、JavaFX アプリケーション本体が起動するまでの間に表示する小さなアプリケーションのことです。主にネットワークからアプリケーションをダウンロードする Java Web StartApplet で使われます。

2番目は CSS ファイルをバイナリ変換するというもので、CSS ファイルが巨大な場合などに、ファイルのパース速度を上げる目的で使用されます *2

-createjar コマンドを使って本機能を利用します。以下にコマンドの実行方法を示します。

$ javapackager -createjar -nocss2bin -appclass アプリケーションクラス名 -srcdir JARに含めるファイルのディレクトリ -outdir JARの出力ディレクトリ -outfile JARファイル名 -preloader プリローダクラス名

-nocss2bin オプションを指定すると、CSS ファイルのバイナリ化が実行されなくなります。未指定の場合だと CSS ファイルのバイナリ化が勝手に実行されてしまうので注意が必要です。 -appclass オプションで指定するのは main メソッドを含むクラスの名前です。マニフェストファイルは勝手に作ってくれますが、自分で追加の属性を指定したい場合は -manifestAttrs オプションに続けて "名前=値,名前=値,..." の形式で指定します。

配布用パッケージの生成

これが javapackager のメイン機能となります。アプリケーション JAR ファイルを準備した状態で -deploy コマンドを使って実行しますが、オプションの指定次第で様々なことができます。

まずは基本的な使い方を示します。以下に示すコマンドを実行すると、Java Web StartApplet 向けに JNLP ファイル及び Applet を実行する HTML ファイルが出力されます。

$ javapackager -deploy -outdir 出力ディレクトリ -outfile 出力ファイル名 -srcdir JARのあるディレクトリ -srcfiles 対象となるJARファイル名 -appclass アプリケーションクラス名 -name アプリケーション名称 -title アプリケーションタイトル

このコマンドを実行すると -name オプションで指定した名前の HTML、JNLP ファイルが出力されます。-title オプションは JNLP ファイル内の <title> タグに反映されます。また、これから説明する自己完結型パッケージの生成でも意味を持ちます。他にもオプションはあるのですが、詳細は javapackager コマンドのリファレンス ( Windows 向けMac、Linux 向け ) を参照してください。

ここでさらに -native オプションを指定すると、自己完結型パッケージを生成することが可能になります。インストール先の OS 向けのネイティブインストーラを生成し、アプリケーション専用の JRE も含めたインストールイメージを作成します。これにより、インストール先の OS に JRE の事前インストールを要求せず、また OS に予めインストールされている public JRE の影響も受けません。これならば「アプリケーションの検証が終わっていないので、JRE のアップグレードはしないでください (> <)」みたいなかっこ悪いことも言わずに済みますね。:)

-native オプションに続けて、次のバンドルタイプを指定することができます。

タイプ 説明
all 無指定の場合はこれが選ばれる。installer、image の両方を指定した場合と同じ結果になる。
installer コマンドを実行している OS が対応している全てのインストーラを生成する。
image アプリケーションインストールディレクトリの内容を展開する。Mac の場合は .app ディレクトリを作り、その下に展開する。
exe Windows の .exe インストーラを生成する。Inno Setup のバージョン 5 以上がインストールされている必要がある。
msi Windows の .msi インストーラを生成する。WiX Toolset のバージョン 3.8 以上がインストールされている必要がある。
dmg MacDMG パッケージ (ドラッグ&ドロップ形式のインストーラ) を生成する。
pkg Mac の PKG インストーラを生成する。
mac.appStore Mac App Store 用のパッケージを生成する。
rpm RPM パッケージを生成する。
deb Debian パッケージを生成する。

指定できるタイプはコマンドを実行する OS で利用可能なパッケージに限定されます。Windows では exe や msi を指定できますが、dmg や pkg、rpmdeb は指定できません。Mac App Store 向けのバンドルタイプもありますね!

-native オプションを使って、自己完結型パッケージを生成する場合、パッケージ固有のオプションを -Bオプション名=値 の形式で指定することができます。ここでは、WindowsMac 向けの代表的なオプションを紹介します。

Windows 向けパッケージ生成オプション

Windows の場合、-native exe もしくは -native msi オプションでインストーラを生成しますが、さらに次のようなオプションを指定することで、カスタマイズを行うことができます。

オプション 説明
-BappVersion=バージョン文字列 アプリケーションのバージョンを指定します。アプリケーションのプロパティで確認できるバージョンになります。
-Bicon=icoファイルのパス アプリケーションのアイコンファイル (.ico ファイル) を指定します。パスは -srcdir で指定するディレクトリからの相対パスになります。
-Bcopyright=コピーライト文字列 アプリケーションのコピーライト文字列を指定します。
-BlicenseFile=ファイルパス exe の場合にのみ有効で、インストーラに使用許諾契約を表示したい場合、そのファイルへのパスを指定します。
-BmenuHint=boolean インストール後、スタートメニューにショートカットを追加したい場合は true を指定します。
-BshortcutHint=boolean インストール後、デスクトップにショートカットを追加したい場合は true を指定します。
-BsystemWide=boolean アプリケーションをユーザローカルにインストールするか、システムレベルにインストールするかを選択します。
-Bwin.menuGroup=グループ名称 スタートメニューにショートカットを追加する場合、スタートメニューにグループを追加した上でその下にショートカットを作ります。そのグループ名を指定します。
-Bvendor=任意文字列 アプリケーションを提供する組織や個人名などを指定します。

結構色んな指定ができることが分かるでしょう。少し重要なのが -BsystemWide オプションです。true にした場合はシステムレベルでインストールされ、いわゆる Program Files ディレクトリ以下にイントールされるようになります。つまり、インストールに管理者権限が必要となります。false の場合はユーザローカルにインストールされます (インストールディレクトリは %LOCALAPPDATA%) 。この場合は管理者権限は不要です。

生成されたインストーラに対して、SignTool を使って署名することもできます。

以下に、実際のコマンド実行例を示します。自分が開発している Social Bookmark Viewer FX をパッケージ化したときのコマンドです。

> javapackager -deploy -native exe -outdir target -outfile SocialBookmarkViewer -srcdir target -srcfiles social-bookmark-viewer-fx-0.1-SNAPSHOT.jar -appclass aoetk.bookmarkviewer.MainApp -name "SocialBookmarkViewer" -title "Social Bookmark Viewer" -BappVersion=0.1 -BsystemWide=true -Bwin.menuGroup="Social Bookmark Viewer"

これを実行すると、次のような出力が得られます。
f:id:aoe-tk:20151219164935p:plain

生成されたインストーラを実行すると、次のようなおなじみのアプリケーションインストーラが立ち上がることになります。
f:id:aoe-tk:20151219165022p:plain

インストールが完了すると、スタートメニューにも登録されました!
f:id:aoe-tk:20151219165035p:plain

インストールディレクトリの下は次のようになっています。exe を実行して起動します。runtime ディレクトリの下には JRE のライブラリが入っています *3
f:id:aoe-tk:20151219165052p:plain

Mac 向けパッケージ生成オプション

Mac の場合は -native dmg-native pkg オプションを使いますが、Windows の場合と異なり、インストーラを生成するために特別なアプリケーションをインストールする必要が無いので、単純に -native だけを指定しちゃっても問題ありません。Mac 向けのカスタマイズオプションを示します。

オプション 説明
-BappVersion=バージョン文字列 アプリケーションのバージョンを指定します。アプリケーションのプロパティで確認できるバージョンになります。
-Bicon=icnsファイルのパス アプリケーションのアイコンファイル (.icns ファイル) を指定します。パスは -srcdir で指定するディレクトリからの相対パスになります。
-Bcopyright=コピーライト文字列 アプリケーションのコピーライト文字列を指定します。
-BlicenseFile=ファイルパス pkg の場合にのみ有効で、インストーラに使用許諾契約を表示したい場合、そのファイルへのパスを指定します。
-BsystemWide=boolean アプリケーションをユーザローカルにインストールするか、システムレベルにインストールするかを選択します。
-Bmac.CFBundleName=名称 アプリケーションメニューバーに表示するアプリケーション名称を -name オプションで指定した名称とは別の名称にしたい場合に指定します。
-Bmac.signing-key-developer-id-app=署名キー名 dmg の場合のオプション。Gatekeeper 向け署名を行いたい場合に指定します。キーがインストールされている場合はデフォルトでそれが使用されるようです。
-Bmac.signing-key-developer-id-installer=署名キー名 pkg の場合のオプション。Gatekeeper 向け署名を行いたい場合に指定します。キーがインストールされている場合はデフォルトでそれが使用されるようです。

Mac の場合、-BsystemWide は true にすると /Applications ディレクトリにインストールされます。Mac の場合はここにアプリケーションをインストールするのが一般的であるため、無指定の場合は true 扱いになります。false にした場合はユーザの Desktop ディレクトリがターゲットになります。

また、Mac 向けにはアプリケーションを "Gatekeeper Ready" にするための対応が入っていることが分かりますね。

Mac 向けのコマンド実行例も同様に示しておきますね。

$ javapackager -deploy -native -outdir target -outfile SocialBookmarkViewer -srcdir target -srcfiles social-bookmark-viewer-fx-0.1-SNAPSHOT.jar -appclass aoetk.bookmarkviewer.MainApp -name "SocialBookmarkViewer" -title "Social Bookmark Viewer" -BappVersion=0.1

これを実行すると、次のような出力が得られます。
f:id:aoe-tk:20151219021419p:plain

生成された DMG ファイルを起動すると、次のようなおなじみのインストール画面が出てきます。
f:id:aoe-tk:20151219021613p:plain

まとめ

このエントリでは javapackager の使い方について掴みの部分の紹介を行いました。最近のクライアントアプリケーション配布を巡る環境の変化に対応した、重要なツールであることを分かってもらえたらと思います。

このツールはクライアントアプリケーションだけでは無く、サーバアプリケーションでも使えると思います。最近はサーバアプリケーションもコンテナにデプロイするのでは無く、単一 JAR に全てをパッケージングしてデプロイする方法も好まれるようになってきました。javapackager を使えば、ランタイムごとパッケージングできるので、より配布が容易になるのではないでしょうか。

javapackager は今回紹介した内容の他にもまだ色々できることがあります (Mac App Store 向けパッケージの作成とか) 。詳しくは上に挙げた Oracle 公式のドキュメント読んでもらえればと。

明日は @ さんの予定です。

*1:というか、この公式ドキュメントでは Ant タスクで利用する方法ばかり説明されています

*2:バイナリ変換すると、ファイルの拡張子が ".css" から ".bss" に変わります。コード側もそれに合わせる必要があります。

*3:昔は JDK を丸ごと放り込むという豪快な感じになっていましたが、最近は結構スリムアップしました。JDK9 の Jigsaw が入るともっと効率よくなるでしょう。

離婚しました

はい、標題の通りです。昨年結婚してからわずか1年とちょっとなのですが、つい先日結婚生活を終了することになりました。ブログに書くかどうか迷いましたが、昨年結婚報告を書いていたので、こちらでも報告することにしました。

色んな方面から多くのお祝いやお心遣いの言葉を頂いたにも関わらず、このような残念な結果になってしまい、誠に申し訳ありません。

理由についてはここでは語らないことにします。ただ、相当に悩んだ上での決断であり、決断に対しては後悔していません。

これからまた人生を再スタートすることになります。このような情けない結果になってしまいましたが、どうかこれからもよろしくお願いします。