アットマーク・アイティ @IT@IT情報マネジメント@IT自分戦略研究所QA@ITイベントカレンダー  
 @IT > Master of IP Network > Mobile Connection > iアプリを10Kbytesに収めるテクニック
 

DoJaによるiアプリの開発入門(最終回)
iアプリを10Kbytesに収めるテクニック

服部隆志
http://www.sinsen.org/
http://www.ngy1.1st.ne.jp/~takashi/sinsen_index.html
2001/8/30

 

今回のおもな内容
ファイルサイズを小さくするプログラムの工夫
リソースファイルの扱いによるサイズ縮小
RetroGuardを利用する
職人芸こそ携帯Javaアプリの面白さ

 iアプリのファイルサイズには、大きな制限があります。プログラム本体にあたるクラスファイルと、プログラムで使用するリソースファイルを、ZIP形式で圧縮されたJARファイルというに変換し、そのファイルサイズが10Kbytes(=10240bytes)以内という制限です。これは、ユーザーがiアプリをダウンロードする際に、「ダウンロードする時間が長い」と思わせないためといわれています。しかし、開発する方からしてみれば、「処理能力が低い」「機種依存がある」といった問題点に続き、「ファイルサイズが小さい」というのは、大きな問題です。しかも、最近になってサービスが開始されたJ-フォンやKDDIのJavaサービスの場合は、それぞれ30Kbytes、50Kbytesとiアプリに比べてファイルサイズの制限は緩やかとなっており、10Kbytesのiアプリで対抗するには並々ならぬ努力が必要となります。

 そこで、今回はJARファイルのサイズをできるだけ小さくするさまざまな方法を紹介します。

  ファイルサイズを小さくするプログラムの工夫

 クラスファイルのサイズを小さくするためには、プログラムミングの工夫が必要です。ファイルサイズを小さくするためのプログラムの基本は「非オブジェクト指向」で、より単純な機構を持つことが望まれます。

●クラスファイルを少なく

 プログラムで使用するクラスファイルを少なくすることで、JARファイルのサイズが縮小できます。Panel型のアプリケーションを作成するときは、できるだけIApplicationのみを使い、Canvas型であればIApplicationとCanvasの2つだけにまとめると有利です。

●メソッドの作成を少なく

 メソッドを作成するときには、そのメソッドが本当に必要であるかを考えます。プログラム中に一度しか使わないメソッドはファイルサイズを増やしてしまうので、メソッドの内容をプログラムに移すインライン展開などが有効です。2〜3度使用するメソッドは、それぞれインライン展開した別のバージョンを用意し、サイズを比較してみるとよいでしょう。

●変数はできるだけローカル変数で

 変数もできるだけ少なくします。特に1つのメソッド内でしか使用しない変数がある場合は、メソッド内でローカル変数として宣言した方が有利です。例えば、Canvasを使ったゲームなどではThreadを使い、run()メソッドにメインループを作成すると、run()メソッド内でしか利用しない変数がよくできます。こういった場合は、ループに入る前のメソッドの最初の方で使用する変数を作成した方が有利となります。

public void run(){

  // このメソッド内でしか利用しない変数を作成
  Graphics g = getGraphics();
  Font f = getDefaultFont();

  // メインループ
  while(true){
  ・・・・
  }
}
リスト1 ループに入る前に変数を作成

●変数を配列で置き換え

 ローカル変数にできない変数がたくさんある場合には、同じ型を持つ変数を1つの配列にまとめてしまうことで、ファイルサイズが縮小できます。ただし、配列にすると、後からの編集がとても困難になってしまうので、できるだけプログラム制作作業の終盤で行うとよいでしょう。

  int[] player = new int[5];

/* プログラム制作作業の終盤で変数を配列に置き換え、
元の記述をコメントアウトし、配列番号をメモすると便利
  int HP; // 0
  int MP; // 1
  int Power; // 2
  int Speed; // 3
  int State; // 4
*/
リスト2 変数を配列に置き換える

●例外処理をまとめる

 Javaでは、try{ }catch(Exception){ }で例外をキャッチすることができますが、このtry{ }catch(){ }を増やすとファイルサイズが大きくなってしまいます。従って、プログラム制作作業の終盤、エラーが出ないことが分かった場合には、同じメソッド内のtry{ }catch(){ }を1つにまとめてしまったり、そのメソッドより上のメソッドへ例外を投げるようにするとtry{ }catch(){ }が少なくなり、ファイルサイズを縮小することができます。

  void method(){

    try{
      /* 例外を投げる処理1 */

      /* 例外を投げる処理2 */

    }catch(Exception e){
    }

  }

  void method() throws Exception {

    /* 例外を投げる処理1 */

    /* 例外を投げる処理2 */
  }
リスト3 例外処理をまとめる

●即値を利用する

 即値とはメソッドの返り値のことで、通常は返り値の型で受け止めますが、それをそのままほかのメソッドの引数にしてやることで、余分なインスタンスを作成せずにすみます。

 例えば、Canvas型のアプリケーションで終了処理をする場合、リスト4とリスト5の2種類の方法があります。比較するとリスト5の方法がサイズを小さくできます。

 ただし、この方法で縮小されるサイズは、これまでに紹介したほかの方法と比べると微々たるものです。しかし、こういった数bytesの積み重ねが、結果として10Kbytes内に収まるかどうかの境目になることも十分あります。

IApplication app = IApplication.getCurrentApp();
app.terminate();
リスト4 一度インスタンスを作って実行

(IApplication.getCurrentApp()).terminate();
リスト5 即値を利用して実行

 

  リソースファイルの扱いによるサイズ縮小

 クラスファイルのほかに、プログラム内で使うリソースファイルの扱い方によってもファイルサイズを縮小することができます。

 また、GIFファイルなどを利用する場合は、できる限り減色することでファイルサイズを縮小することができます。さらに、GIFファイルを作成するツールによってもファイルサイズが若干違うため、いろいろなツールを使って、できるだけサイズの小さいファイルが生成できるツールを見つけることも有効です。

●ネットワークからリソースファイルをダウンロード

 使用するリソースファイルをネットワーク上に配置し、アプリケーション起動時にそれをダウンロードすることで、JARファイル内にリソースファイルを入れずに済みます。ただし、毎回ダウンロードしたり、複数のファイルをダウンロードすると、ユーザー側に負担がかかってしまうので、毎回必要な場合はスクラッチパッドへ保存して利用したり、複数ファイルが必要な場合は次に紹介する「バイナリ連結ファイル」の利用をお勧めします。

●バイナリ連結ファイルの利用

 バイナリ連結ファイルとは、使用する複数のリソースファイルをバイナリの状態で1つのファイルに連結したものです。ネットワークから複数のリソースをダウンロードするより、1つの連結ファイルをダウンロードする方がパケットもダウンロード時間も少なくて済みます。ただし、ダウンロードした連結ファイルからイメージを作成したりする場合は、一度スクラッチパッドへ保存する必要がありますし、連結ファイルのどの位置に画像ファイルがあるのか分からないと画像を作成することができません。

 なお、初回起動時に連結ファイルをダウンロードし、そのままスクラッチパッドへ保存しておけば、次回からはスクラッチパッドからデータを取り出すことができます。

 また、JARファイルに複数のリソースファイルが存在する場合は、連結ファイルにしてJARファイルに入れることでZIP圧縮形式の性質上、圧縮率が向上されファイルサイズが少なくなります。さらにこの場合は連結ファイルの入手にネットワークを利用しないため、毎回連結ファイルにアクセスし、画像1枚分のデータをスクラッチパッドへコピー、そのスクラッチパッドから画像あるいは音を作成することができます。スクラッチパッドへのアクセスが多くなるために時間はかかりますが、スクラッチパッドの使用容量は使用する画像の最大サイズまでで済むため、スクラッチパッドの使用容量を減らしたい場合に有効です。

 連結ファイルを作る方法はいろいろありますが、ここでは、私がこの内容のために作ったJavaアプリケーションを紹介します。非常に単純な内容ですが、ソースファイルも付属していますのでご自由にお使いください。

 使い方は、コンソールでBinaryConnectorを実行し、引数に出力ファイル名、以下に連結させたいリソースファイル名を並べます。

>java.exe BinaryConnector [output file] [ resource file ]]]

 例えば、リソースファイルの「image1.gif、image2.gif、sound.mld」を binary.datファイルに出力する場合は、

>java.exe BinaryConnector binary.dat image1.gif image2.gif sound.mld

として実行します。

  RetroGuardを利用する

 LGPLで公開されている「RetroGuard」というツールを使うと、JARファイルのサイズを100bytesほど縮小することができます。本来このツールは、クラスファイルの逆コンパイルを防止するために作られたものですが、副産物としてファイルを縮小してくれるので、ファイルサイズを削りたいiアプリではよく使われています。

●RetroGuardの使い方

 もともとDoJa(iアプリ)やMIDPなどのJ2ME環境に対して作られたものではないため、J2ME環境で使うには少々複雑となります。

 以下、今回のサンプルとして紹介したCache2.javaにRetroGuardを使ってみます。J2ME Wireless SDK for the DoJaで作成したCache2のJARファイルがc:\J2MEWSDK4DOJA\apps\Cache2\binにあり、RetroGuardのZIPファイルをダウンロード・解凍した内容をc:\J2MEWSDK4DOJA\apps\Cache2\binにコピーして、以下の手順を踏みます。

1.作成したJARファイルにRetroGuardを実行する
 RetroGuardの本体であるretroguard.jarがあるフォルダと、J2ME Wireless SDK for the DoJaのlibフォルダにあるdojaapi.jarにクラスパスを設定して実行します。例えば下記のように実行すると、output.jarにRetroGuardが実行されたファイルが出力されます。

>java.exe -classpath .;c:\retroguard\retroguard.jar;
c:\J2MEWSDK4DOJA\lib\dojaapi.jar RetroGuard Cache2.jar output.jar
Cache2.jarにRetroGuardを実行してoutput.jarに結果を出力する

2.作成されたoutput.jarを解凍する
 せっかくJARファイルにしたファイルですが、JDK 1.3のjar.exeなどを使って解凍(展開)し、クラスファイルとリソースファイルに戻します。するとCache2.class、CacheCanvas.classとなっていたものがa.class、b.classに変化していることが分かると思います。これがRetroGuardが実行された結果です。

>jar.exe -xvf output.jar
RetroGuardによって出力されたファイルを解凍する

3.解凍されたクラスファイルに検証(preverify)を実行する
 a.class、b.classとなったクラスに検証と呼ばれる処理をします。これはJ2ME Wireless SDK for the DoJaが自動的にやってくれていた処理の1つですが、RetroGuardを実行するとこの作業が無効になってしまうため、再度実行する必要があります。

 なお、ソースファイルで作成したクラスファイルの数によっては、a.class、b.class以外にもクラスファイルが作成されたり、逆にa.classだけが作成されますが、すべてのクラスファイルに対して検証を行ってください。

>c:\J2MEWSDK4DOJA\bin\preverify.exe -classpath .;c:\J2MEWSDK4DOJA\lib\dojaapi.jar a b
解凍されたクラスファイル(この場合はa.class、b.class)に検証を実行する

4.検証済みのクラスファイルを使用するリソースファイルとともに再度JARファイル化する
 検証が実行されると、outputというフォルダが新たに作成されたと思います。このフォルダの中に検証が実行されたクラスファイルが出力されているので、プログラムで使用するリソースファイルと一緒に再度JARファイルにします。

 なお、DOS窓やバッチファイルでoutputフォルダへ移動するためには「cd output」とします。また、JARファイル化にはJDK 1.3のjar.exeを使用し、オプションに「-cvMf」と指定していますが、ここで小文字のmを指定すると違う意味になってしまうので注意が必要です。必ず大文字のMを指定してください。

>cd output
>jar.exe -cvMf Cache2.jar a.class b.class binary.dat
検証済みのクラスファイルとリソースファイルをJARファイル化する

 ここでまた最初と同じCache2.jarができましたが、これでJARファイルのサイズが縮小されているはずです。あとは当初J2ME Wireless SDK for the DoJaで作成されたJAMファイルはサイズが変わる前のものなので、メモ帳などのテキストエディタを使いJAMファイルの内容を変更します。JAMファイルで変更すべき個所は、JARファイルのファイルサイズを指定する「AppSize」と、IApplicationを継承したクラスを指定する「AppClass」となります。

 しかし、ここで注意すべきところがあります。AppSizeについては縮小化されたJARファイルのファイルサイズをそのまま代入してやれば良いのですが、AppClassについてはRetroGuardによって変換されたクラスファイル(ここではa.class、b.class)のどれになっているのかファイル名からは分かりません。実際にどちらになっているかは、RetroGuardを実行した際に出力されるretroguard.logファイルに書かれているのでそれを見ればいいのですが、元のプログラムによっては出力されるクラスファイル名が異なる場合があります。もっとも、一度retroguard.logファイルで確認したら、以降はまず同じファイルに変換されているので、毎回retroguard.logファイルを見る必要はありません。J2ME Wireless Toolkit for the DoJaで作成されるJAMファイル以外に、RetroGuard済み用のJAMファイルを用意しておけばAppSizeを変更するだけで大丈夫です。

  職人芸こそ携帯Javaアプリの面白さ

 今回紹介したことをすべて実行すれば、以前よりファイルサイズが随分小さくなるはずです。これでもまだ数十bytesファイルサイズがオーバーしている場合は、どこかに最適化できる個所があるはずですので、できる限りの時間をかけて考えてみてください。最初に限界と思ったところより数十bytesは縮小できると思います。無責任と取られるかもしれませんが、ケース・バイ・ケースで非常に地道な作業を積み重ねれば、どこかに何か道が開けるはずです。制限の非常に厳しい携帯Java環境では、そういった地道な作業ができるかどうかが、結果として生まれるアプリケーションに大きな差を生むことが実際にあります。

 制限が厳しいことで、そこで何をするかという発想力が求められると同時に、最近のPC環境ではあまり必要とされなくなった職人芸的プログラムも求められる。そこにこそ、携帯Javaアプリケーションの面白さがあるように思います。

 本連載は、今回を最終回とさせていただきます。これまでの連載が皆様のお役に立てば幸いです。

「連載 DoJaによるiアプリの開発入門」


 


 
@ITメールマガジン 新着情報やスタッフのコラムがメールで届きます(無料)

   
@ITトップMobile Connectionフォーラム トップ会議室利用規約プライバシーポリシーサイトマップ