携帯アプリの容量削減方法


携帯向け Java アプリケーションで容量削減するための方法を紹介します。

容量削減について / 削減方法 / 戻る / トップページ


容量削減について

携帯電話向けの Java アプリケーションを書く際に役に立つ、 容量を減らす方法を列挙します。 経験的な情報のため、 間違っていたり逆効果になってしまう場合もありますので、ご注意ください。
 
また、いずれの手法も可読性や保守性を損ないやすいため、 気をつけてお使い下さい。

容量削減方法

クラスファイルは 1 ないし 2 個
 
jar ファイルに圧縮した時に、class ファイルの数に応じたデータが減り、 一つのクラス内でのメソッド呼び出しが増え、最適化されやすくなるため、 ファイルサイズが削減されます。
 
高レベル API を使用する時 (iアプリでは Panel クラス、 MIDP では Screen クラスのサブクラスを使う時)、 大抵の場合 1 クラスにまでできます。
 
低レベル API を使用する場合 (iアプリ、MIDP とも Canvas クラスをサブクラス化して使う場合)、 大抵の場合 2 クラスにまでできます。 Canvas クラスとアプリケーション起動用クラス (iアプリでは IApplication クラスのサブクラス、 MIDP では MIDlet クラスのサブクラス) の二つです。
 
Obfuscator、Optimizer の利用
 
識別子の削減等は Obfuscator、Optimizer を利用すると良いです。 Obfuscator は元々逆コンパイル対策のため、識別子を意味のわからない 文字に変換するツールですが、大抵は識別子を短くするために、 class ファイルの容量を少し減らすことができます。 Optimizer はその名のとおり容量や速度を最適化するツールです。
 
一部の容量最適化はこれらツールに任せて、可読性の低下を避けたほうが 良いと思います。 Optimizer → Obfuscator → preverify.exe の順番にかけます。
 
余談ですが、preverify.exe は class ファイル中からデバッグ情報などの 余分な情報を省くため、コンパイル時にデバッグ情報の抑制を行っても 行わなくても結果は同じになります。
 
識別子を短くする
 
class ファイルには識別子情報が含まれているため、 識別子の文字数が多いとそれだけ class ファイルが肥大化します。
 
大抵の Obfuscator は識別子を 1 文字にしてくれます。 RetroGuard などの Obfuscator を通した後は、 アプリケーション起動用のクラス (iアプリでは IApplication クラスのサブクラス、 MIDP では MIDlet クラスのサブクラス) の名前も変化するため、注意が必要です。
 
メソッドを減らす
 
class ファイルにはメソッド名やその引数を記述したテーブル (コンスタントプール) が含まれており、 メソッドを減らすとこれらテーブルが減るため、 ファイルサイズが抑制できます。 一ヶ所からしか呼ばれない private メソッドなど インライン展開してみると良いです。
 
Optimizer が処理してくれることがあります。
 
フィールド変数を減らす
 
フィールド変数も同様に class ファイルにその名前や型などの情報が 書き込まれるため、できるだけ少なくすると効果があります。
 
1 つのメソッド内でしか使用しないフィールド値は、メソッド内で ローカル変数として宣言します (元々そのような場合は少ないはずですが)。
 
クラスやメソッド・定数の展開
 
定数 (final static 宣言されたフィールド値) は class ファイルに識別子 として記録されるので、展開してしまえばそれらの情報を省くことができます。
 
Optimizer が処理してくれることがあります。
 
ローカル変数の節約
 
ローカル変数も数を減らすことで、スタック確保のコードが減ることがあります。 共有できる変数は共有してしまうと効果があります。
 
Optimizer が処理してくれることがあります。
 
例外処理をまとめる
 
try〜catch 節は一つにまとめたほうが、バイトコード量は減ります。
 
メソッド・フィールド名を同じにする
 
class ファイルにはメソッド名やフィールド名が記述されており、 メソッド名やフィールド名に同じ名前を使うことで共有される場合があります。 これは識別子全てについていえるため、クラス名と他の識別子名でも 共有されることがあります。
 
大抵の Obfuscator はその本来の機能から、識別子の共有化を行ってくれます。
 
int 型を多用
 
キャストを減らすためです。 バイトコードでは命令によっては全ての型をサポートしていないものがあり、 それらは内部的に int に変換されたりします。 そのため、キャスト用のコードが含まれてしまうことがあるのです。
 
switch に注意
 
switch 文はバイトコード化されると飛び先の情報の保存の仕方で lookupswitch と tableswitch の 2 種類に分かれます。 case の値が離散的かどうかによってコンパイラが ある程度使い分けてくれますが、それぞれ容量や速度に一長一短あります。
 
そのため、case の値を連続値にしたりすることでどちらの命令に 置き換えられるかが変化し、容量が変わることがあります。 switch 文を if 文に置き換えても容量が変動します。 逆に増える可能性もありますが、 switch 文はバイトコードを多く消費する傾向があるため、 試してみると良いと思います。
 
定数を 0 周辺にする
 
バイトコードレベルで、0 付近の定数は特別扱いされており、 短いコード量で処理が実現されています。 -1〜5 なら 1 byte、0〜255 なら 2 byte、 -32768〜32767 なら 3 byte、それ以外は 2〜5 byte 程度です。
 
そのため、定数は -1〜5 あたりでつけると良いです。 また、-32768〜32767 を越える場合は、計算によって導いた方が 効率が良い時さえあります。
 
配列初期化子による配列の初期化
 
配列を初期化子で初期化するコードでは要素は default 値を多くしたほうが 最適化されやすくなります。
 
もともと配列は new された時点でその要素がすべて default 値で初期化 されているため、0 を代入するバイトコードは生成されなくなります。 例えば、
      int[] array={0,1,0,0,0};
      int[] array=new int[5];
      array[0]=0;
      array[1]=1;
      array[2]=0;
      array[3]=0;
      array[4]=0;
と分解されて、
      int[] array=new int[5];
      array[1]=1;
と最適化されます。
 
配列の定数での初期化は文字列定数から
 
数値の定数配列の初期化を行う際、数値ではなく文字定数をコード中に記述し、 プログラム中に数値に変換してやると効率が良くなります。
 
例えば、
      int[] data={1,2,3,...};
とする時には
      String data0="\u0001\u0002\u0003...";
      int[] data=new int[data0.length()];
      for (int i=0;i<=data.length-1;i++)
      {
        data[i]=(int)(data0.charAt(i));
      }
とします。
 
ただし、コンパイラは \u 表現を対応する Unicode 文字に直した後に コンパイルを開始するため、\u005c は \ に解釈され、 エスケープシーケンス表現と解釈されてしまいます。 そのため、この文字の場合は \\ に置き換えてやる必要があります。
 
同様に \n (\u000a), \r (\u000c), \" (\u0022), \t (\u0009) などがこれに当てはまります。これらを自動化するツールを作ると 良いと思います。
 
この最適化手法は初期化する配列が長ければ長いほど有効です。
 
ネットワークからリソースファイルをダウンロード
 
大抵の携帯向け Java アプリケーションのサイズ規定は アプリケーションの容量とデータ保存領域 (スクラッチパッド、レコードストア) の容量の両方で規定されています。
 
そこで、データ類をネットワークからダウンロードしてデータ保存領域に 保存すれば、それだけ容量の上限を最大限に利用できます。
 
さらに、アプリケーション起動の度にネットワークからデータを ダウンロードすれば、メモリーの許す範囲で大きなデータを扱えます。 ただし、この方法はアプリケーション起動の度にパケット代が かかってしまうため、あまり良い方法とはいえません。
 
リソースファイルを連結する
 
jar ファイルの圧縮は zip 圧縮の拡張となっています。zip 圧縮はファイル 単位で圧縮を行い、それらを詰め合わせていくため、 多くのファイルをまとめるとファイルに関する情報も zip ファイルに含まれます。 そのため、ファイル数が減ればそれだけ jar ファイルが小さくなります (1 ファイルで数百バイト違ったと思います)。
 
そこで、画像などのリソースファイルは連結させて一つの ファイルにしてしまい、プログラム中で分解しながらオブジェクト生成を していくと良い結果が得られます。
 
画像のパレット数削減、減色
 
画像ファイルの色数が減れば、当然そのファイルサイズは小さくなります。 減色ツールによって減色のされ方に差がありますので、大きく減色しても 絵の壊れにくいツールを使用すると効果的です。当然のことながら、 減色専用のツールを使用したほうが効果的なことが多いです。
 
長い文字定数をディスクリプタファイルに押し込める
 
容量の制約はアプリケーションサイズ (jar ファイルサイズ) にかけられて いるため、ディスクリプタファイル側 (i アプリでは ADF (jam ファイル) の AppParam 属性、MIDP では jad ファイル中の任意の属性) にデータを 押し込んでしまうわけです。仕様の裏をかいたちょっとずるい容量の 確保の方法です。
 
また、アプリケーション側で変換してやることで、文字定数以外も 入れられます。この場合は、変換用コードの増量を考慮しないと、逆効果に なることもあります。
 
PNG 画像ファイルの余分なチャンクの削減
 
PNG ファイルには様々な情報をチャンクとして付加できます。しかし、 単に携帯電話のアプリケーションで描画を行うには不要な チャンクも多くあります。 PhotoShop などで出力したファイルには不要なチャンクが 多くつけられているので、減色ツールやチャンクエディターで これらを取り除くことで効果があります。
 
PNG 画像ファイルのフィルターの最大限の活用
 
PNG 画像ファイルには圧縮前にフィルターをかける指示を行うチャンクがあり、 適切なフィルターを指示してやることで圧縮がかかりやすくなります。
 
そのため、適切なフィルターを探し出し、 そのチャンクを付加してやることで効果的な圧縮がかかります。 そのためのツールとしては、 SutopW がお勧めです。
 
なお、PNG ファイルと JAR ファイルの圧縮には同じ Inflate 圧縮が 使われているため、適切に容量削減された PNG ファイルは JAR ファイル圧縮時の圧縮率はほぼ 0% となります。

戻る