JavaFXがApplicationThread以外からのUIの更新に厳しくなってた対策
JavaFXはJavaでGUIのアプリケーションを作るのに便利です。
Java8とともにJavaFX8がリリースされたので、以前にJavaFX2.2で作成したアプリをJava8(JavaFX8)で置き換える作業を現在行っています。
わりと簡単にできるだろうと楽観していたのですが、いきなり困った事がありました。
ApplicationThread以外からはUIを変更できない。
表題の通りです。どこかで聞いたことがあるようなないような...
Androidでありますね。これ。Androidの場合は"UIThread"って名称だったと思いますけど、ここでは同じようなものだと考えればいいです。
わかってたのなら今更言うなやし。と思われるかもしれませんが、
これ、JavaFX2.2までは可能だったんです。
(稀にApplicationThread以外から更新ダメってエラーが出る部分がありましたけど、むしろそのケースを潰してた方が楽なレベルでした)
ですが、JavaFX8からはほぼ完全に(?)ApplicationThread以外からのUI更新は駄目っぽいです。
実際にそのコード例を書いてみます。
JavaFX2.2では問題ないが、JavaFX8では問題のあるコード
Service<?> service = new Service<Void>() { @Override protected Task<Void> createTask() { return new Task<Void>() { @Override protected Void call() throws Exception { File file = new FileChooser().showOpenDialog(owner); System.out.println(file); return null; } }; } }; service.start();
service.start(); から実行されているので、別スレッドでの処理になっています。
そして、new FileChooser().showOpenDialog(owner); の部分で(JavaFX2.2では)ファイルチューザが表示されるはずです。が、JavaFX8では表示されません。
これをJavaFX8で対応するためにServiceを使わないように書き換えるのは大変です。
なので、以下の方法で対応しました。
JavaFX8のために対策をしたコード
class FxUtils { public static <V> V getFromApplicationThread(Callable<? extends V> callable) throws Exception { if (Platform.isFxApplicationThread()) { return callable.call(); } RunnableFuture<V> future = new FutureTask(callable); Platform.runLater(future); return future.get(); } } Service<?> service = new Service<Void>() { @Override protected Task<Void> createTask() { return new Task<Void>() { @Override protected Void call() throws Exception { File file = FxUtils.getFromApplicationThread(() -> { return new FileChooser().showOpenDialog(owner); }); System.out.println(file); return null; } }; } }; service.start();
違いは、FxUtils.getFromApplicationThread() を呼んでいる部分です。
ApplicationThread で実行しないといけない処理だけをラムダ式にして渡しています。
このメソッドでは"現在"の Thread を調べて、ApplicationThread ならばそのまま実行。そうでなければ Platform.runLater() に FutureTask を渡します。そうすると ApplicatoinThread で実行してもらえます。あとは結果が返るのを待つだけです。(FutureTask: この場合、 future.get() はラムダ式が値を返すまで待ちます。戻り値はその値になります。)
まとめ
未調査な部分もあります
今回のコードようにはっきりと ApplicationThread 以外からUIを更新していることがわかっていれば対策はできますが、ApplicationThread 以外から Property を変更して、それが UI に影響がでるような Bind(callback) をしてるとなるとどうなんだろうなとか...
まだ調べてません。