seraphyの日記

日記というよりは過去を振り返るときのための単なる備忘録

Javaアプリケーションの起動時にスプラッシュをつける

JavaSE6以降ではJavaアプリケーションの起動時にスプラッシュスクリーンを表示できるオプションが加わった。
これはアプリケーションのクラスがロードし終わるまえにJava実行ファイル側が表示してくれるものであるため、可能な限り早い段階で画面に表示される。
Javaアプリケーション、とくにSwingなどのGUI画面をもつアプリケーションは多数のクラスを読み込む必要があるため、それ自身が、いくら数キロバイトという小さなアプリケーションであったとしても起動するまで数秒は要すという欠点がある。
(起動してしまえば、けっこう速い。)
この遅さは「あれ、操作まちがえたかな??」とか「もしかして、このアプリ、壊れている??」とか不安になるくらいには十分な時間があり、可能であればスプラッシュを表示して「読み込み中なんだ」とユーザに知らせるのは、ほとんど義務といってよいくらいである。


このスプラッシュに対応するには画像ファイルを1つ用意するだけでよい。


これをjava起動オプションに指定するだけでコードは一切書く必要はない。
(というよりコード上からスプラッシュを表示させることはできない。)
(コードからは、表示されているスプラッシュスクリーンに対してプログレスバーをつけたりなどの操作はできる。)

javaコマンドラインから起動する場合
java -splash:画像ファイル -cp クラスパス ...

残念な事にJDK1.6以外でjavaコマンドを実行すると、-splash:は不明なオプションだと言われてこけてしまうので、バージョンについてはチェックする必要がある。(コマンドから実行する場合、.batや.shからJAVA_HOMEを設定するであろうから、そのあたりはチェック可能とは思われる。)

実行可能jarの場合

マニフェストに「SplashScreen-Image」というキーで画像ファイルを指定する。
たとえば、AntでJar生成時に設定するマニフェストを以下のようにすればよい。

    <jar basedir="work" destfile="${jarName}">
        <manifest>
            <attribute name="Main-Class" value="macjava.Main"/>
            <attribute name="SplashScreen-Image" value="macjava/splash.png"/>
        </manifest>
    </jar>

SplashScreen-Imageで指定する画像ファイルはjar内のものを指定することができる。

JavaSE6以前であれば認識できないオプションであるため何も起きないが、アプリケーションそのものが動かなくなるということはないようだ。

Mac OS Xのアプリケーションバンドルでスプラッシュを表示する場合

これについては後述。

JAVAアプリをMac OS X用にApplication Bundle化する

昨日の続き。


Mac OS Xには実行ファイルや、それに付随するリソースファイルなどをフォルダにまとめて、それらの複雑な階層構造を、あたかも1個のファイルであるかのように見せかける「バンドル」ファイルというものがある。
たいていのアプリケーションは1つ以上のリソースをもっているので、/Applicationsフォルダの下にいるアプリケーションのうち、「*.app」というファイルは実体はバンドルである。
この実体はフォルダであるはずのものをFinderからダブルクリックすると、フォルダの中に設定されているinfo.plistファイルの設定に従いバイナリが起動する、というものである。
これはターミナルから見れば、属性付きのただのディレクトリである。
Finderからも「パッケージの内容を表示」とすると、実体のフォルダの内容が確認・変更できる。

アプリケーションバンドルにするメリット

実行可能Jarでもリソースをjarの中にいれ、外部ライブラリも一緒に閉じ込めておけば1個のファイルにでき、こちらは文字通り本当に一個のファイルになるのだが、それでも、JAVAアプリケーションをバンドル化すると、いくつかのメリットがある。

  • DockアイコンやFinderで見た場合のアイコンを設定できる。(Leopardより前のMac OS XでもDockアイコンに対応できる。)
  • Dockやスクリーンメニューに表示するアプリケーション名を設定できる。
  • 起動時のJavaVMへのオプションを指定できる。(実行可能jarには、この機能がない!!)
    • 最大・最小メモリの指定ができる
    • 起動時にシステムプロパティを設定することができる
  • アプリケーションに関連する様々なファイルやディレクトリを一元管理できる。
    • 依存するライブラリjarを入れておいて起動時にクラスパスを通す事ができる。
    • 使用する画像や音声ファイルなども入れておける
    • その他、ファイルならなんでも入れておける。
  • /Applicationsフォルダにいれても違和感ない。

Pure Java上記に対応しようと思ったら、Web Startとか、その類いのデプロイ方法を考えなければならない。



Windowsの場合は「Launch4j」「exewrap」などのオープンソースJavaランチャにするという選択肢もある。
http://launch4j.sourceforge.net/
http://www.ne.jp/asahi/web/ryo/exewrap/

実行可能Jarをアプリケーションバンドルに変換する

Jarファイルをアプリケーションバンドルに変換するには「Jar Bundler.app」というツールを使う。
使い方は至って簡単である。

Mainクラスとアイコン、JVMバージョンの指定


  1. Main Classは、Chooseで実行可能jarファイルを選択すれば自動的に設定される。
  2. Custom Iconは、Choose Iconで*.icnsファイルを指定する。
  3. JVM Versionの選択肢が1.5+以降しかないような場合、手入力で「1.4+」と入れることができる。
  4. Use Macintosh Menu Barはスクリーンメニューのことだと思われるが、入れても入れなくても効果がない?

アイコンファイルは、「Icon Composer.app」を使って作成するか、あるいは「iConvert」のようなウェブ上のサービスを使ってPNG等からicnsファイルを得ることができる。
http://www.iconverticons.com/

関連するjarファイル等の指定

Application Bundleに含めるファイルと、クラスパスを通すファイルを設定する。
前画面で実行可能Jarを選択している場合は、それが自動的に設定されている。
追加のライブラリがあれば、Addition Files and Resourcesでjarファイルを選択すれば、自動的にクラスパスにも追加される。


クラスパスの指定で使える変数として「$JAVAROOT」「$APP_PACKAGE」がある。
$JAVAROOT」はアプリケーションバンドルのフォルダ階層においてJavaディレクトリのある場所「Contents/Resources/Java」を指す。(ここにjarファイルがコピーされる。)
$APP_PACKAGE」はアプリケーションバンドルのルートディレクトリである。

バンドル名、バージョン、ヒープサイズ、システムプロパティ等の指定

このタブでVMOptionsなどの指定を行う。

  1. Heap Max/Min sizeで使用するヒープサイズの設定を行う。
  2. Version/Short Versionでバージョンを指定する。(Mac OS XデフォルトのAboutボックスや、Finderの「情報を見る」でバージョン情報が出てくるので。)
  3. Development Regionは「Japan」にしておく。
  4. Bundle Nameはバンドル名(アプリケーション名)になるが、この段階で日本語をいれると壊れるので、あとでinfo.plistを手作業で修正する。
  5. Set Working Directory to ... は、$JAVAROOTをカレントディレクトリにしたい場合にチェックをいれる。なにも指定しなければ$APP_PACKAGEがカレントディレクトリとなる。
  6. Additional Propertiesは、Java起動時に指定するシステムプロパティを、ここで設定できる。(上記例では「apple.laf.useScreenMenuBar=true」を設定している。)

Typeは「APPL」でアプリケーションバンドル固定とし、Signatureは「????」のままとする。(これはADCでコードをもらっている場合に指定するものらしい。大抵のアプリは????のままになっているようだ。)

アプリケーションバンドルを生成する。

「Create Application ...」ボタンで出力先のアプリケーションバンドル名を指定すれば生成される。
たとえば「MacJava」とすれば「MacJava.app」が生成される。
できあがった「MacJava.app」はダブルクリックすれば起動できる。

手動/もしくは自動でアプリケーションバンドルを作る

Jar Bundlerは、すでにあるjarファイルをもとにアプリーションバンドルを作る手軽な方法であるが、このツールを使わないと作れないというわけではない。
バンドルファイルの作り方は公式ページに記述されている。
http://developer.apple.com/mac/library/documentation/Java/Conceptual/Java14Development/03-JavaDeployment/JavaDeployment.html

フォルダ構造とファイルを格納する


  1. フォルダ名の拡張子を.appとする。
  2. Contentsというフォルダをつくる。
  3. Info.plistファイルをつくる。(中身はxml形式。ここに各種設定がある。「Property List Editor.app」で編集可能。)
  4. MacOSというフォルダの下にJavaApplicationStub実行ファイルを/System/Library/Frameworks/JavaVM.framework/Versions/Current/Resources/MacOS/からコピーする。(実行権限も付ける。)
  5. PkgInfoを作る。中身は「APPL????」で固定。
  6. Resourcesフォルダをつくる。
  7. アイコンファイルは、この直下におく。
  8. Javaフォルダをつくる。
  9. この下にjarファイルをおく。
  10. .appフォルダにバンドル属性をつける。

バンドル属性は「setFile -a B フォルダパス」のコマンドで設定できる。(解除する場合は、小文字のbで指定する。)


アプリケーションバンドルは実体は、ただのフォルダであるから、Javaアプリケーションからみてクラスパスとjarファイルの位置等の指定はフォルダの場合とかわりない。
フォルダに対するクラスパスを指定したい場合は、Info.plistのClassPathキーに対してフォルダも追加する。
よって、外部リソースへのアクセスは、これまでどおり「Class#getResource()」などの方法でアクセスすることが可能である。

カレントディレクトリの位置指定もできるので、カレントディレクトリ相対でファイル位置を想定している場合でも、そのままの方法でアクセスできる。

Info.plistを編集する。

各種設定情報は「Info.plist」ファイルの中に格納されている。
中身はxml形式であり、各種Appleのドキュメントでもxmlの断片で示されることが多々あるが、デフォルトのエディタである「プロパティリストエディタ」を使うと多少は編集しやすいかもしれない。
さきほどのJar Bundlerで生成したものは、以下のような内容である。

ここからはJar Bundlerでは指定することができなかった他のパラメータも編集できる。
また、Jar Bundlerツールでは日本語を使うと壊れてしまうことがあるが、プロパティリストエディタからなら日本語の編集も問題ない。


Javaディクショナリ下では前述の「$JAVAROOT」「$APP_PACKAGE」の変数を使うことができる。
この中のVMOptionsキーおよびClassPathキーは「Value Type」を「Array」することで、複数の値をリストで指定することができる。


なお、デスクトップアプリケーションで64bitの恩恵はまずないと思うが、Leopard以降で、且つ、JDK1.5以上の指定であれば64bit JavaVMでの起動オプションも指定できる(らしい)。
http://developer.apple.com/java/javaleopard.html

<key>JVMArchs</key>
<array>
  <string>x86_64</string>
</array> 
<key>VMOptions.x86_64</key>
<string>-Xms2g -Xmx8g</string>

また、特に指定がない場合は標準で32ビットで動作する。

http://developer.apple.com/mac/library/releasenotes/Java/JavaLeopardRN/ResolvedIssues/ResolvedIssues.html

Radar #5226690
Bundled applications cannot run in 64-bit mode


Description:
Bundled Java applications deployed on 64-bit Intel machines cannot be forced to use a particular architecture by clicking a checkbox in the Get Info panel for the application.


Resolution:
If you want your bundled Java application to run in 64-bit you need to make the following changes to the Java dictionary in the Info.plist file. -- Set the value of "JVMVersion" to 1.5* or 1.5+. -- Add an array element named "JVMArchs". Each of the elements must be of type String, and specify the order of preference for architectures. The available architectures of the machine running the application are compared against this list, and the first one that matches is chosen. For example, JVMArchs x86_64 i386 ppc would choose 64-bit Intel if available, then i386. If the application is run on Power PC hardware, it will run as it did before Mac OS X 10.5. If you specify a list and none of your choices could not be satisfied, the application will not launch.

以上でアプリケーションバンドルの出来上がり

上記の手順でアプリケーションバンドルとして認識されるので、Jar Bundler.appで一度作成した構造をもとに次回以降は必要な場所だけ差し替え、あるいはInfo.plistを編集すれば良く、
あらためてJar Bundlerで生成し直す必要はない。

Antでアプリケーションバンドルを作れるようにしてみる。

AntのタスクとしてJarBundlerというものを公開されている方もいるので、それを使うのもよいだろうが、
http://www.informagen.com/JarBundler/

いったん、Jar Bundler.appでフォルダ構造を作ってしまったあとならば、アプリケーションバンドルの更新そのものは難しくない。
いくつかのファイルコピーや実行権限を付与し、フォルダの属性を編集するだけでよい。

<?xml version="1.0" encoding="UTF-8"?>
<project name="MacJava" default="default">
    <description>MacJavaSample</description>

    <property name="jarName" value="MacJava.jar"/>
    
    <condition property="isMacOSX">
        <and>
            <os family="mac"/>
            <os family="unix"/>
        </and>
    </condition>

    ....
    
    <target name="makeAppBundle" if="isMacOSX">
        <copy file="${jarName}" toDir="MacJava.app/Contents/Resources/Java/"/>
        <copy file="/System/Library/Frameworks/JavaVM.framework/Versions/Current/Resources/MacOS/JavaApplicationStub"
              toDir="MacJava.app/Contents/MacOS/"/>
        <exec dir="." executable="/bin/sh">
            <arg line='-c "chmod 755 MacJava.app/Contents/MacOS/JavaApplicationStub"'/>
        </exec>
        <exec dir="." executable="/bin/sh">
            <arg line='-c "/usr/bin/setFile -a B MacJava.app"'/>
        </exec>
    </target>

</project>

アプリケーションバンドルをスプラッシュ対応にする。

アプリケーションバンドル化したJavaアプリケーションでスプラッシュを表示するには、Info.plistで以下のキーを追加する必要がある。
(VMOptionsの-splash:は効かないので注意。)

<plist>
<dict>
    <key>Java</key>
    <dict>
        <key>SplashFile</key>
        <string>$APP_PACKAGE/splash.png</string>
    </dict>
</dict>
</plist>

表示する画像はjar内ではなく、アプリケションバンドル内に置く必要が有る。

http://developer.apple.com/mac/library/releasenotes/Java/JavaLeopardUpdate1RN/ResolvedIssues/ResolvedIssues.html

Radar #4561714
Bundled applications cannot display a Java 6 splash screen

Description:
New in Java 6 is the ability to display a splash screen when your application starts. From the command line, this is done with the '-splash:' argument. No such equivalent existed for bundled applications.

Resolution:
Fixed. Bundled applications can now display a splash file by adding the key 'SplashFile' with the path to the image file as the corresponding value to your bundled application's Java dictionary. This feature only applies to applications running in Java 6.

スプラッシュに対応しているのはJavaSE6以降、それよりも前のバージョンであれば単に無視される。

アプリケーションの配備・配布をどうするか?

アプリケーションバンドル化すれば、あとは/Applicationsにいれてもよいし、どこで実行しても良い。
配布する場合は「Package Maker.app」でパッケージ化するか、「ディスク ユーテリティ.app」でリードオンリーのdmgファイルのディスクイメージに格納するのが一般的のようである。
(パッケージにした場合は、それをdmgのディスクイメージに格納する。)


zipではなくdmgが使われているのはパーミッションや属性を落とさずに配布できるからだと思われるが、
ためした限りではSnow LeopardのFinderからアプリケーションバンドルを「...を圧縮」でzipにして、Pantherアーカイバで展開しても、アプリケーションバンドルとして扱ってくれるので、標準のアーカイバを使っているかぎりはバンドルをzip圧縮しても大丈夫のように見える。

dmgで配布する。

Mac OS Xは伝統的にアプリケーションはドラッグアンドドロップでインストールできるものであり、多くのアプリケーションも、そのような配備方法になっている。
アプリケーションバンドルやREADMEファイルなど、インストールに必要なファイル群を一式、Internet-Enablingなディスクイメージにしてしまえば、属性等も落ちずに単一のdmgファイルとして配布することができる。


「Internet-Enabling a Disk Image」なディスクイメージとは、バンドルではなく正真正銘の単一のファイルとして扱うことのできるdmg形式のことで、「読み込み専用」か「圧縮」ディスクが、それに該当する。
作成する場合は元フォルダをベースに「読み書き可能なdmg」を構築して、それから「読み込み専用」のdmgに変換する、という手順をとる。


ユーザは、インターネットからダウンロードするか、なんらかのメディアに格納されている、このディスクイメージを開いて、その中にあるアプリケーションバンドルを自分の好きな場所にドロップして使うことになる。
(また、dmgを開くと同時に自動実行させたり、なんらかのアクションをとらせることも可能なようである。)


インストーラを使わず、ドラッグアンドドロップでマニュアルインストールする場合、インストール手順をユーザに分かりやすくするためにフォルダの背景画像とアイコン位置を調整することが一般的である。
フォルダには.app本体と、/Applicationsへのエイリアスのアイコンをならべておいて、それらの背景画像に「このファイルを、ここにドロップする!」みたいな図解をつけておく。

(フォルダに背景画像をつける機能は、dmgに限らず、どのフォルダでも設定できるFinderの標準機能である。これらの設定はWindowsでいうところのdesktop.iniに相当する、.DS_Storeファイルに記録される。)


ところが、Mac OS X 10.6 Snow Leoparddmgを作る場合、フォルダの背景画像を設定してもSnow Leopard以外では正しく見れない(というより背景が消えてしまう)という問題があるため、背景画像をつけたdmgイメージを作りたい場合はLeopard以前で作る必要がある。
また、Snow Leopardで/Applicationsへのエイリアスをつくると、なぜかサイズが大きいので、その点でもSnow Leopardでのdmg作成は不利である。

この問題については、いろいろ探してみたが、これといった解決方法はないようである。(有償のツールなら見つかったけれど。)

Package Makerでインストーラをつくる

インストーラによってアプリケーション等をインストールする場合、「Package Maker.app」を使ってパッケージ(インストーラ)を作成することができる。
機能的にはWindowsMSIに相当する印象をうける。


パッケージを使うと、

  • インストールの事前条件をチェックしたり
  • 既にインストールされているバージョンをチェックしたり
  • READMEを表示したり
  • ライセンスに同意させたり
  • インストール先を選べるようにしたり
  • インストールするコンポーネントを選択できるようにしたり
  • インストール先のパーミッションを制御したり
  • インストール後のスクリプトを実行したり
  • インストール後のリブートを要求したり

といった、こまかなインストールに必要な手順を行うことができるようになる。


たとえば、コンポーネントパーミッションは以下のように設定できる。

このようにアプリケーションバンドル内のパーミッションを制御できる。
最初に「Apply Recommendations」で「root/admin」にchownして、こののちに必要な箇所に修正を加える。
.svnファイルもみえているが、「File Filters...」で標準で「.svn」は除外になっているので、実際にインストールした先には.svnファイルは存在しない。


ただし、パッケージはMac OS Xのバージョンによりサポートしている機能が異なるので、パッケージを使うにも、それなりの対策・知識は必要になるようである。

なお、出来上がった.pkgもバンドルなので、結局、配布する場合はdmgに入れるなどする必要はある。


結論

これで、先日の日記でのMac OS X対応方法とあわせて、JavaアプリケーションをMac OS Xで実行・運用する基本的なことについては調べられたと思う。


最近はMacBookでは2GBが標準になっていて、WindowsでもVista以降は2GBを積むのが当然のようである。
かつてWindows2000/XPの512MBのマシンで動かしていた頃は「JavaVMを立ち上げるだけで64MBも消費するなんてアプリケーションとしては重すぎる」なんて思った時代もあったのだが、いまは、もう、どうでもいい感じなくらい余裕がある。
起動してしまえば、そこそこ速いし。


こうして考えてみると、マルチプラットフォームGUIアプリケーションの本命としてJavaに回帰するという線も十分ありなんじゃないか、という気がしたんだよね。
それが、今回調べる動機でした。


そして、こうやって試してみると、外観的にもネイティブなアプリに近いものを作るのも多少の手間をかければできることがわかり、実際に、これでデスクトップアプリケーションを作ることは本当にアリだという感触を得る事が出来ました。
もしかすれば、これからJavaがサーバサイドだけでなくデスクトップアプリとしても積極的に使われてゆくのかもしれません。