プリプロセッサを使ってJ2MEアプリケーションの移植を自動化するはじめにJavaは理論上は移植可能なので、Javaモバイルアプリケーションを開発するときにも、すべてのJava対応デバイス上で正しく実行されるはずだと思いがちです。ところが、理論上の事柄が大抵そうであるように、これも実際にはうまくいきません。J2MEモバイルアプリケーションはまだ日の浅い技術ですが、多くの開発者は、こうした共通利用の問題がMIDP2.0やJTWIのような新しいイニシアチブによって簡単に解決されることはないだろうと述べています。 J2MEはグローバルに移植できるとしても、J2MEアプリケーションはそうではないというのが現実です。つまり、バイトコードはすべてのJavaハンドセットで正しく実行されますが、アプリケーションの動作はハンドセットごとに個別に調整しなければならないのです。モバイルデバイスは1200種もありますが、いずれも能力が異なり、MIDPを含めて種々のJavaプラットフォームをサポートしており、またオプションのAPIとオプションのAPIパーツをサポートしています。さらに、これらの実装にはそれぞれ独自のバグもあります(デバイスの特性/特徴の細分化の例については、下記の補足説明を参照)。 そのため、典型的な開発サイクルでは、移植とテストにかかる時間が全体の40〜80パーセントに上ることになります(実際にどれくらいの比率になるかは、開発者の経験レベルとサポートするデバイスの数によります)。 移植したモバイルアプリケーションを実際の携帯電話でテストするのは必ずしも簡単なことではありません。よくできたエミュレータを使用すれば、実際のデバイスのバグがすべて再現されるはずですが、常にエミュレータがあるとは限りませんし、たとえあっても決して当てにできるわけではありません。 バグにはもう1つ難しい問題があります。バグに対処するには、バグをソースコード上で解決するか、不具合のある機能を無効化しなければなりませんが、このような対策がファームウェアのバージョンによってすべて異なるものになる可能性があります。特に厄介なのは、仮想マシンにバグがある場合です。これは、メーカーが仮想マシンをハードウェアレベルでハンドセットに統合する際に問題になります。 モバイルオペレータは配布されたミッドレットの品質をコントロールする必要があります。なぜなら、低品質のミッドレットはオペレータサービスに影響を与えるからです。オペレータは比較的うまくできている古いミッドレットを新しいデバイスモデルのために交換し、広範に使用できるようにする必要もあります。そのため、開発者はアプリケーションを新しいデバイスにすばやく移植できなければならないのです。 デバイスの特徴の細分化 デバイスの能力 ![]() Javaプラットフォーム ![]() MIDPプラットフォーム ![]() MIDP用のオプションのAP ![]() 移植の自動化は必要か一番のポイントは、そのアプリケーションでサポートできるデバイスの数です。これが、自動化という投資の効果を左右する最大の要因になります。移植を自動化することには次のような利点があります。
まず、各デバイスモデルの特異性を考慮する必要があります。利用できる場合にはいつでもオプションの機能を使用すべきです。デバイスに応じてアプリケーションでサポートすべき機能の例を表1に示します。 表1 デバイスモデルの特異性の例
内製の移植ソリューションアプリケーションの移植を自動化することに決めたら、実際に何をすればよいのでしょうか。もちろん外注することもできますが、自社内でやるつもりなら、基本的に4つの方法の中から選ぶことになります。
このアプローチは、単に一連のモデル(例えばNokia Serie40 Edition 1)ごとにアプリケーションを開発するというものです。この場合の問題点は、サポートするAPIが多いほど、あるいはアプリケーションでそのデバイスに力点を置くほど、シリーズを細分化することになります。というのも、高度なAPIに対するサポートによってシリーズ内のわずかな差異が際立つからです。例えば、同じような2つのデバイスでも、画面上のイメージの数によってパフォーマンスが大きく違ってきます。
このオプションでは、実行中にアプリケーションをテストします。例えば、モデルがNokiaハンドセットだとすると、アプリケーションは実行中にこのデバイスモデルを検出し、モデルに応じた適切な動作を選択します。
これで、特定のハンドセットでのみ使用可能なメソッド(全画面モードなど)を呼び出すことができます。それぞれの実装ごとにクラスを作成する必要があります(
NokiaCanvas、SiemensCanvas、StandardCanvas)。次に例を示します。try { Class.forName("com.nokia.mid.ui.FullCanvas"); Class myClass = Class.forName("NokiaCanvas"); myCanvas = (ICanvas)(myClass.newInstance()); } catch (Exception exception1) { try { Class.forName("com.siemens.mp.color_game.GameCanvas"); Class myClass = Class.forName("SiemensCanvas"); myCanvas = (ICanvas)(myClass.newInstance()); } catch (Exception exception2) { myCanvas = (ICanvas) new StandardCanvas(); } } 基本的にインターフェイス(
Icanvas)と3つの実装(Nokiaデバイス、Siemensデバイス、標準MIDPデバイスについて1つずつ)を作成します。それからプロプラエタリAPIが利用可能かどうか確認するために
Class.forNameを使用します。例外がスローされなければ、NokiaCanvasを使用します。そうでなければ、現在のデバイスがこのAPIをサポートしていないことになります。その場合は、もう1つのAPI(例えばSiemens)をテストします。もう1つの例外がスローされた場合は、標準キャンバスを使用しなければなりません。このソリューションではデバイスの各モデルの動作のロジックを各アプリケーションに組み込むことを想定しているので、すぐに無理がきます。
Allen Lauは最近のDevXの記事で、AOPによる細分化問題の解決方法を論じています。彼のアイデアは、アプリケーションロジックを一箇所にまとめ、コードを部分的に追加/削除することでアプリケーションのコードを修正するというものです。
このアプローチでは、いくつかの問題が解決されますが、アプリケーションの構造をそれぞれのプラットフォームに合わせて修正しなければならないので、結局のところ、もう1つの補足的なソリューションを使用するはめになるかもしれません。なぜなら、アプリケーションを各デバイスモデルに合わせて最適化するのは非常に難しい場合があるからです。オプションのAPIをサポートするときは特にそうです。
さらに、この方法では各デバイスモデルに合わせてコンテンツ(イメージやサウンドなど)を修正するという問題が解決されません。Java拡張機能を使用すればソースコードを自動的に変換できますが、これは興味深いアプローチであっても、実際には作業量が増えてしまいます。
プリプロセッサを使用すると、条件によってソースコードが自動的にアクティブまたは非アクティブになります。
例えば、Nokiaデバイスで全画面モードを設定するには、
Canvasではなく、FullCanvasを拡張する必要があります。MIDP2デバイスでは、setFullScreenModeを呼び出す必要があります。MIDP1デバイスでは、これは可能でないので、非全画面モードのままです。//#ifdef NOKIA extends com.nokia.mid.ui.FullCanvas //#else extends Canvas //#endif { : : //#ifndef MIDP2 setFullScreenMode(true); //#endif プリプロセッサによってこのソースコードが処理されるので、ディレクティブを設定します。従って、Nokiaデバイス用のアプリケーションを生成するには次のようにします。
//#define NOKIA
プリプロセッサは次のものを生成します。
extends com.nokia.mid.ui.FullCanvas
{
MIDP2デバイスの場合(
//#define MIDP2)は、次のものを生成します。extends Canvas { setFullScreenMode(true); このソリューションでは、ソースコードの1つの本体を各デバイスにモデルに合わせて修正できます。ディレクティブを含むリファレンスソースコードを開発するだけで済むのです。処理済みファイルに加えられたその他の変更は、次のプリプロセスですべて失われます。
このソリューションはプリプロセスという古い発想に頼っていますが、いろいろなデバイスモデルへの移植を試みるときに遭遇する問題をすべて解決できる、柔軟性の高い唯一の方法です。
プリプロセッサを使った移植に必要なことソースコードのバージョンを1つだけにするというのが基本原則です。これがプリプロセスされて、それぞれのデバイスモデルに適合したコードが生成されます。 留意すべき点を以下に列挙します。
イメージやサウンドなどのリソースを各デバイスモデルの能力に適合させるためには、変換ツールを使う必要があります。イメージの最適化も必要です(例えば、.pngイメージのヘッダーからオプションの情報を取り除いたり、小さなイメージを大きなイメージにまとめたり、各イメージの色の数を減らしたり、リソースをプリロードしたりします)。 同じ特性や機能や動作を持つ一連のJavaデバイスを作成する必要があるかもしれません。そうすれば、各デバイスモデルに対してではなく、このシリーズに対して1つのアプリケーションを生成できます。このシリーズの機能は、アプリケーションの中で使用したい機能に本質的に依存することになります。使用するオプションの機能が多いほど、細分化の度合いが増大します。 予期しないリソースを考慮に入れようとすると、アプリケーションの更新が必要になります。例えば、デバイスが大きな.jarファイルをサポートしていて、大きなヒープメモリを持っていれば、アプリケーションのバックグラウンドイメージを格納できます。そうでなければ、イメージは.jarファイルには入れられないので、単純に描画する必要があります。その場合、.jarサイズは小さくなり、ヒープメモリはあまり消費されません。 モバイルプログラミングでは画面サイズが重要な問題となるので、あらゆる技法を(イメージの動的変換やパッケージング前の変換など)駆使して、イメージサイズを小さくするように努力してください。 さらに、システムとのやりとりを管理することも大切です。例えば、着信の際にサウンドを中止し、ミッドレットを一時停止するといった具合です。 具体例MyGameというゲームを、MIDP1、MIDP2、MIDP1 NokiaUI、MIDP1 Motorolaのデバイスに移植するものとします 移植手順の概要は次のとおりです。
SoundManagerクラスの作成
このデバイスはサウンドとバックライトをサポートしていないので、メソッドは空になります。
このデバイスはサウンドとバックライトをサポートしているので、
//#ifdef MIDP2と//#endifの間のコード行が選択されます。
リスト1にコードを示します。 リスト1 SoundManagerクラス
//#ifdef MIDP2 import javax.microedition.media.*; import javax.microedition.media.control.*; import javax.microedition.media.control.ToneControl; //#endif //#ifdef NOKIAUI import com.nokia.mid.sound.*; import com.nokia.mid.ui.*; //#endif //#ifdef MOTOROLA import com.motorola.multimedia.*; //#endif import javax.microedition.lcdui.*; import java.io.*; public class SoundManager { Display display; public SoundManager(Display display) { this.display = display; } public void doLight() { //#ifdef MIDP2 display.flashBacklight (duration); //#endif //#ifdef NOKIAUI try { DeviceControl.setLights (0,100); } catch (Exception exception) { } //#endif //#ifdef MOTOROLA try { Lighting.backlightOn(); } catch (Exception exception) { } //#endif } public void doSound() { //#ifdef MIDP2 try { InputStream is = getClass().getResourceAsStream("music.mid"); Player audioPlayer = Manager.createPlayer(is, "audio/midi"); audioPlayer.start(); } catch (IOException ioe) { } //#endif //#ifdef NOKIAUI Sound sound = new Sound (1000, 100); sound.play(1); //#endif } } 「BUILD.XML」ファイルの作成「BUILD.XML」ファイルの中で適切なディレクトリを選択します。これは手作業になるので、デバイスごとにXMLファイルを作成し、それらを保持しておくことが望ましいでしょう。 MIDP2プロファイルの場合は、「SoundManager.java」ファイルのプリプロセスによって次の出力が生成されます。 import javax.microedition.media.*; import javax.microedition.media.control.*; import javax.microedition.media.control.ToneControl; import javax.microedition.lcdui.*; import java.io.*; public class SoundManager { Display display; public SoundManager(Display display) { this.display = display; } public void doLight() { display.flashBacklight (duration); } public void doSound() { try { InputStream is = getClass().getResourceAsStream("music.mid"); Player audioPlayer = Manager.createPlayer(is, "audio/midi"); audioPlayer.start(); } catch (IOException ioe) { } } } MIDP1プロファイルの場合は、次の出力が生成されます。 import javax.microedition.lcdui.*; import java.io.*; public class SoundManager { Display display; public SoundManager(Display display) { this.display = display; } public void doLight() { } public void doSound() { } } デバイスのプロファイルは次の行で選択します。
<wtkpreprocess srcdir="src" destdir="outputsrc"
symbols="MIDP2" verbose="false"/>
Antennaは属性シンボルの内容を取り(この例ではMIDP2)、次のように各ファイルの先頭に //#define MIDP2 //#ifdef MIDP2 import javax.microedition.media.*; import javax.microedition.media.control.*; import javax.microedition.media.control.ToneControl; //#endif //#ifdef NOKIAUI import com.nokia.mid.sound.*; import com.nokia.mid.ui.*; //#endif //#ifdef MOTOROLA import com.motorola.multimedia.*; //#endif import javax.microedition.lcdui.*; import java.io.*; プリプロセスされたソースコードのビルド次にプリプロセスされたソースコードをビルドします。つまり、作成した2つのファイルをコンパイルするわけです。 .jadファイルの名前とその内容をタグの中で指定します。 <wtkjad jadfile="outputin${midlet.name}.jad" jarfile="outputin${midlet.name}.jar" name="${midlet.name}" vendor="You" version="1.0" target=""> <midlet name="${midlet.name}" icon="/icon.png" class="game.${midlet.name}"/> <attribute name="MIDlet-Icon" value="/icon.png"/> </wtkjad> アイコンに対して常に同じ名前を使用するようお勧めします(例:icon.png)。 ミッドレットをパッケージ化するには、.jarファイルを作成します。.jadファイルに統合するリソースを選択してください。このプロセスの簡単なガイドラインを以下に示します。
次の表2では、右側の欄がコードで、左側の欄がその説明になっています。 「RUN.XML」ファイルの作成エミュレータを実行するためには、XMLファイルを作成する必要があります。エミュレータはWTKのディレクトリ「wtklibdevices」にインストールします。WTKに表示されるデバイス名を使用し(デバイスのディレクトリ名)、それを属性デバイスの次の行で指定してください。
<wtkrun jadfile="outputin${midlet.name}.jad"
device="DefaultColorPhone"/>
次の表3では、右側の欄が「RUN.XML」ファイルのコードで、左側の欄がその説明になっています。 この例では、いくつかのデバイスについて基本的なサウンドとバックライトをサポートしています。しかし、実際には、その他の機能(キーボード定数、全画面サポート、画面リフレッシュループ、フレームレートの調整、イメージとサウンドの形式など)もサポートする必要があるでしょう。 完全なプロダクションチェーンへの取り組みデプロイメントとテストが非常に重要であることを忘れないでください。デバイスモデルごとに細かな手作業を繰り返さなければならないので、モバイル開発は単調で退屈な作業になりがちです。.jadファイルや.jarの名前をいちいち変更したり、各.jadファイルの内容を修正するといった作業を想像してみてください。あるいは、テストのためのテストWAPサーバへのアップロードが手作業で、300とか400のデバイスについてFTPクライアントを使用しなければならないとしたらどうなると思いますか。 オプションのAPI(Bluetooth、3D、ファイル接続、SMS、MMSなど)とオプションのAPIパーツ(カメラサポート、オーディオ記録、.jpegなど)をすべて考慮に入れることが望ましいでしょう。デバイスの中により強力な機能を提供できるものがある場合には、すべてのデバイスについて同じアプリケーションを生成し続けるのは避けてください。 移植は基礎的な問題ですが、最適化され充実したモバイルアプリケーションを生成するうえで大きな障害とはなりません。結局のところ、完全に共通利用できるモバイルアプリケーションは、モバイルデバイスの構造のせいで不可能です。ますます複雑になるソフトウェアを埋め込むというのが世の趨勢であり、従って常に実装で問題が生じることになるのです。 著者紹介Bruno Delb(Bruno Delb)
フランス語で書かれたJ2MEに関する最初の書籍の著者で、Net InnovationsおよびUnified Mobilesの創始者でもある。Unified MobilesはUMAK(Unified Mobile Application frameworK)に基づいた統一モバイルアプリケーション開発という概念を生み出した。UMAKはマルチプラットフォームアプリケーション(J2ME、DoJa、Webアプレット)の開発を促進する完備したフレームワークである。UMAKはデバイスに関する非常に詳細な知識ベースとJavaテストスイートと生産性ツールスイートと構成エンジンを基にしており、各デバイスモデルの各機能を考慮に入れている。
|