___ TOP _ BBS _ LINK __


■MIDPとは
■MIDPファミリ
●MIDP for PalmOS
●ezplus
●Javaアプリ
■J2ME Wireless Toolkit 1.0.3
■サンプル作成
●HelloWorld
●イメージ描画
●データの保存・読み込み
●HTTP通信・CGIとの連携

■MIDPとは

 MIDPとはMobile Information Device Profileの略で、携帯電話、デジタルカメラ、PalmやPocketPCなど一部のPDA(Personal Digital Assistance)を含む携帯機器で動作するJava仕様です。
日本ではau(KDDI)のezplusや、JPhoneのJavaアプリなどの携帯電話で採用されていることで有名です。

 このMIDPで定義されている「MIDlet」というアプリケーション形式に添うことで異なる携帯機器間で動作するアプリケーションを作成することができます。


■MIDPファミリ

 現在日本で使用可能なMIDPを採用している端末は以下の物があります。

・MIDP for PalmOS2001/12/10現在のバージョンは1.0。世界的に動作するMIDP環境。
・ezplus2001/12/10現在のバージョンはKDDIP-2.0。au(KDDI)が採用。MIDP+KDDIP
・Javaアプリ2002/02/26現在JPhoneの開発者サイトで仕様・エミュレータ公開。http://www.dp.j-phone.com/

■J2ME Wireless Toolkit 1.0.3

 J2ME Wireless Toolkit 1.0.3とはSun microsystemsから無償で提供されている標準的なMIDP開発環境です。非常に簡単な構造をしているため扱いやすくお勧めです。

 使用するには別途JDK1.3が必要となります。こちらも無償で以下のサイトからダウンロード可能です。JDK1.3をインストール後、J2ME Wireless Toolkitをインストールするようにしてください。

・Java2 Platform Standard Edition 1.3
http://java.sun.com/j2se/1.3/ja/index.html

 J2ME Wireless Toolkitは以下のサイトから日本語版がダウンロード可能です。

・J2ME Wireless Toolkit 1.0.3
http://java.sun.com/products/j2mewtoolkit/ja_download.html

・注意:JDK1.4が公開されましたが、J2ME系開発環境と相性が悪く正常に動作しないことがあります。そういった場合はJDK1.3をダウンロードして利用してください。


■サンプル作成

MIDPの基本的な部分のサンプルです。


●HelloWorld
 J2ME Wireless Toolkit 1.0.3を使ってお約束の「HelloWorld」を実行するMIDletを作成します。

・プロジェクト作成
 ファイル>新規作成、あるいは新規作成ボタンを押す事で開くダイアログに任意のプロジェクト名とクラス名「HelloWorld」を入力し、プロジェクトの作成ボタンを押します。

 次に「アプリケーション属性の設定」ダイアログが開きます。今回は「MIDlet-Vendor」を変更する(変更しなくても動作します)くらいですが、別のアプリケーションを作る際、RecordStore(データ保存)を使うのであればオプション項目の「MIDlet-Data-Size」に使用するバイト数を入力します。


RecordStoreを利用する場合はMIDlet-Data-Sizeに使用バイトを入力する。

 これで C:\j2mewtk\apps にHelloWorldフォルダが作成されたと思います。さらにこのHelloWorldフォルダの中に「bin、lib、res、src」という4つのフォルダが作成されます。このうち「binフォルダ」にはJavaのソースファイル(*.java)、「resフォルダ」にはMIDletで使用するリソースファイル(画像や音など)を配置することになります。

 なお、プロジェクト>属性設定で後から設定を変更することもできます。


・ソース記述
 メモ帳などのテキストエディタを利用して以下のような内容を「HelloWorld.java」として先ほどの「srcフォルダ」へ保存します。
 必要であればリソースファイル(画像・音など)を作成し、「resフォルダ」へ保存します。


import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

public class HelloWorld extends MIDlet implements CommandListener {

	static Command exit;

	public void startApp(){
		exit = new Command("Exit",Command.SCREEN,0);
		TextBox box = new TextBox("Title","HelloWorld!",65536,TextField.ANY);
		box.setCommandListener(this);
		box.addCommand(exit);
		Display.getDisplay(this).setCurrent(box);
	}
	public void pauseApp(){
	}
	public void destroyApp(boolean b){
	}
	public void commandAction(Command c,Displayable s){
		if(c==exit){
			notifyDestroyed();
		}
	}
}

・ビルド
 Javaのソースファイル、必要な場合はリソースファイルが用意できたら「ビルドボタン」を押してMIDletを構成するJADファイルの作成とJavaソースファイル(*.java)のコンパイル・事前検証をします。

JAD(Java Application Descriptor)とはMIDletの情報を記録したテキストファイルで、標準的MIDPではこのJADファイルを先にダウンロードして、そのMIDletが端末で使用可能であるか?などを判断します。

 ビルドが正常に終了するとエミュレータによる実行ができるようになります。「デバイス」チョイスボックスで実行するエミュレータを選択し、「実行ボタン」を押す事でエミュレータが起動します。

・パッケージ化
 プロジェクト>パッケージを実行することでMIDletとresフォルダ内のリソースファイルはJARファイルへ保存されます。

 標準のMIDPでは先ほど用意したJADファイルとJARファイルという圧縮ファイルにMIDletのプログラムを格納し、端末側がJADファイル・JARファイルをダウンロードすることで実行可能となります。
 ただし、制限の多い携帯端末向けの仕様のため「JADファイル・JARファイル」という構成でダウンロードする端末と、JADファイル・JARファイルを端末独自の形式に変換しなければならない端末があります。

 JARファイルはJava標準の圧縮フォーマットで、MIDPではこのJARファイルへMIDletとそのリソースファイルを格納することでダウンロードサイズを縮小します。

 ビルド>パッケージ化によって作成されたJADファイルとJARファイルが「binフォルダ」へ保存されているのが確認できると思います。


●イメージの描画
 Canvasクラスを使って画像を表示してみます。

 MIDPではPNGファイルを扱う事ができますが、複数あるPNGフォーマットのうち端末によって表示できない場合もあります。
 一般的には256色までを扱うIndexColorのPNGフォーマットを使うといいと思います。

 基本的な作成法についてはHelloWorldを参照してください。

・アンカー
 MIDPではイメージの描画、文字の描画などでアンカーというオプションを付けます。これは指定した位置に「対象のどこをもってくるか」を指定する物です。

 例えば、画像(image)を画面(width,height)の中心に配置したい場合は以下のようにします。

g.drawImage(image,width/2,height/2,g.HCENTER | g.VCENTER);

 HCENTERとは水平方向の中心、VCENTERとは垂直方向の中心を示します。他には「TOP、BOTTOM、LEFT、RIGHT」が指定できます。

 ただし、文字の場合はVCENTERを利用することができません。その代わりにBASELINEを指定することができます。


import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;

public class ImageTest extends MIDlet {

	static TestCanvas canvas;

	public void startApp(){
		canvas = new TestCanvas(this);
		Display.getDisplay(this).setCurrent(canvas);
	}
	public void pauseApp(){}
	public void destroyApp(boolean b){}
}
class TestCanvas extends Canvas implements CommandListener {
	static MIDlet app;
	static Command exit;

	static int width,height;

	static Image image = null;

	public TestCanvas(MIDlet app){
		this.app = app;

		exit = new Command("Exit",Command.SCREEN,0);
		setCommandListener(this);
		addCommand(exit);

		width = getWidth();
		height = getHeight();

		try{
			image = Image.createImage("/test.png");
		}catch(Exception e){
		}
	}

	public void paint(Graphics g){
		g.setColor(0xFFFFFF);
		g.fillRect(0,0,width,height);

		if(image!=null){
			g.drawImage(image,width/2,height/2,g.HCENTER|g.VCENTER);
		}
	}

	public void commandAction(Command c,Displayable s){
		if(c==exit){
			app.notifyDestroyed();
		}
	}
}

●データの保存・読み込み
 MIDPではRecordStoreという端末側にデータを保存することができる機能が定義されています。これを利用することでゲームなどのセーブ・ロードなどが実現できたり、ユーザーの設定を保存することができます。

 RecordStoreはiアプリのスクラッチパッドと違い、ちょっと変わった構造をしています。任意の名前を付けたRecordStoreオブジェクトに複数のRecordと呼ばれる保存領域があります。

・こんなイメージ?

 一つのRecordStoreには任意の名前を付けれますが、そこに含まれるRecordsは1から始まるインデックスで管理されます。音楽CDに例えるとCDの名前を指定することはできるが、曲は番号で管理されている・・・よけい分からない。(--;;

 RecordStoreを利用するにはJADファイルに「MIDlet-Data-Size」を設定する必要があります。ただし、プログラムで使用するサイズと全く同じバイト数確保しただけでは足りなくなる端末(ezplus)があるので注意してください。


import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import javax.microedition.rms.*;

public class RecordTest extends MIDlet implements CommandListener {

	static Command exit,write,read;
	static RecordStore record = null;

	static TextBox text = null;

	static int recordID = 1;
	static String title = "memo";
	static int size = 10240 - 4 /*ezplus対策*/;

	public void startApp(){
		exit = new Command("終了",Command.SCREEN,3);
		write = new Command("書込み",Command.SCREEN,1);
		read = new Command("読込み",Command.SCREEN,2);
		text = new TextBox("Memo","",size,TextField.ANY);
		text.setCommandListener(this);
		text.addCommand(exit);
		text.addCommand(write);
		text.addCommand(read);

		Display.getDisplay(this).setCurrent(text);
	}

	public void pauseApp(){}
	public void destroyApp(boolean b){
		if(record!=null){
			try{
				record.closeRecordStore();
			}catch(Exception e){
				log(e.toString());
			}
		}
	}

	public void commandAction(Command c,Displayable s){
		try{
			if(record==null){
				try{
					record = RecordStore.openRecordStore(memo,false);
				}catch(Exception ee){
					record = RecordStore.openRecordStore(memo,true);
					record.addRecord(new byte[size],0,size);
				}
			}
			if(c==exit){
				destroyApp(true);
				notifyDestroyed();
			}
			if(c==write){
				byte[] b = (text.getString()).getBytes();
				record.setRecord(recordID,b,0,b.length);
			}
			if(c==read){
				byte[] b = record.getRecord(recordID);
				text.setString(new String(b));
			}
		}catch(Exception e){
			log(e.toString());
		}
	}

	void log(String mes){
		Alert alert = new Alert("info");
		alert.setString(mes);
		Display.getDisplay(this).setCurrent(alert);
	}
}

●HTTP通信・CGIとの連携
 MIDPという仕様の特徴としてHTTP通信が標準で用意されていることがあります。これによりWebサーバーと連携したMIDletを作成することが可能となります。

 ただし、MIDPを基本とした仕様を採用している端末でも「HTTP通信ができない」あるいは「大きく制限がある」ということがあるので注意が必要です。
 実際、Palmには標準で通信機能がありませんし、ezplusフェーズ1ではHTTP通信が全くできませんでした。ezplusフェーズ2でも予めJADに記述しておいた3つまでのURLにしかアクセスすることができません。

 その他、端末により通信が不安定であったり、GETメソッドを利用する際にエンコードが必要である、HTTPプロトコルに必要な情報が無いためにHttpConnection#setRequestProperty(String,String)で補う必要がある場合があります。

・CGIとGET、POSTで交信するサンプル
import javax.microedition.midlet.*;
import javax.microedition.lcdui.*;
import javax.microedition.io.*;
import java.io.*;

public class NetworkTest extends MIDlet implements CommandListener {

	static TextBox box;
	static Command exitCommand,cgiGet,cgiPost;

	static String url = "http://cgi.sinsen.org/midp/log.cgi";

	public NetworkTest(){
		box = new TextBox("sample","test data...",0xFFFF,TextField.URL);
		box.setCommandListener(this);
		exitCommand = new Command("Exit",Command.SCREEN,4);
		cgiGet = new Command("GET",Command.SCREEN,0);
		cgiPost = new Command("POST",Command.SCREEN,1);
		box.addCommand(exitCommand);
		box.addCommand(cgiGet);
		box.addCommand(cgiPost);
	}

	public void startApp(){
		Display.getDisplay(this).setCurrent(box);
	}

	public void pauseApp(){}
	public void destroyApp(boolean b){}

	public void commandAction(Command c,Displayable s){
		if(c==exitCommand){
			notifyDestroyed();
		}
		if(c==cgiGet){
			String str = box.getString().trim();
			String str2 = null;

			HttpConnection con = null;
			InputStream in = null;
			try{
				String _url = url+"?data="+str;

				con = (HttpConnection)Connector.open(_url);
				con.setRequestMethod(HttpConnection.GET);

				in = con.openInputStream();
				int length = (int)con.getLength();

				if(length<0){
					ByteArrayOutputStream _o = new ByteArrayOutputStream();
					byte[] buf = new byte[256];
					int t = 0;
					while((t=in.read(buf))!=-1){
						_o.write(buf,0,t);
					}
					_o.close();
					str2 = new String(_o.toByteArray());
				}else{
					byte[] buf = new byte[length];
					in.read(buf);
					str2 = new String(buf);
				}

			}catch(Exception e){
				log(e.toString());
			}finally{
				try{
					if(in!=null){
						in.close();
						in = null;
					}
					if(con!=null){
						con.close();
						con = null;
					}
				}catch(Exception ee){
				}
			}
			if(str2!=null) box.setString(str2);
		}
		if(c==cgiPost){
			String str = box.getString().trim();
			String str2 = null;
			HttpConnection con = null;
			InputStream in = null;
			OutputStream out = null;
			try{
				con = (HttpConnection)Connector.open(url);
				con.setRequestMethod(HttpConnection.POST);

				out = con.openOutputStream();
				out.write(str.getBytes());
				out.close();
				in = con.openInputStream();
				int length = (int)con.getLength();

				if(length<0){
					ByteArrayOutputStream _o = new ByteArrayOutputStream();
					byte[] buf = new byte[256];
					int t = 0;
					while((t=in.read(buf))!=-1){
						_o.write(buf,0,t);
					}
					_o.close();
					str2 = new String(_o.toByteArray());
				}else{
					byte[] buf = new byte[length];
					in.read(buf);
					str2 = new String(buf);
				}

			}catch(Exception e){
				log(e.toString());
			}finally{
				try{
					if(out!=null){
						out.close();
						out = null;
					}
					if(in!=null){
						in.close();
						in = null;
					}
					if(con!=null){
						con.close();
						con = null;
					}
				}catch(Exception ee){
				}
			}
			if(str2!=null) box.setString(str2);
		}
	}

	void log(String mes){
		Alert alert = new Alert("log");
		alert.setString(mes);
		alert.setTimeout(Alert.FOREVER);
		Display.getDisplay(this).setCurrent(alert);
	}
}

NetworkTestと交信するCGI(Perl)の例
#!/usr/local/bin/perl

require "jcode.pl";

$datafile = "log.txt";
$agent = $ENV{'HTTP_USER_AGENT'};

$method = $ENV{'REQUEST_METHOD'};
$method =~tr/a-z/A-A/;

if($method eq 'POST'){
	read(STDIN,$data,$ENV{'CONTENT_LENGTH'});
}else{
	$data = $ENV{'QUERY_STRING'};
}

$data=~tr/+/ /;
$data=~s/%([a-fA-F0-9][a-fA-F0-9])/pack("C",hex($1))/eg;
&jcode'convert(*data,"sjis");

$data=~s/\n//g;

# アクセスログを保存
open(FP,">>$datafile");
&lock($datafile);

print FP $agent."\t".(&getTime)."\t".$data."\n";

&unlock($datafile);
close(FP);

# NetworkTestへ返信
print "Content-type: text/plain\n\n";
print "Connection True!";


exit;

sub getTime {
	($sec,$min,$hour,$mday,$mon,$year,$wday,$yday,$isdst) = localtime(time);
	$mon = ($mon + 1);
	if ($sec < 10) { $sec = "0$sec"; }
	if ($min < 10) { $min = "0$min"; }
	if ($hour < 10) { $hour = "0$hour"; }
	if ($mday < 10) { $mday = "0$mday"; }
	if ($mon < 10) { $mon = "0$mon"; }
	if ($year < 90) { $year = "20$year"; }else{$year+=1900;}

	return "$year/$mon/$mday $hour:$min:$sec";
}
sub lock {
	local($dev ,$ino ,$mode ,$nlink,$uid  ,$gid);
	local($rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks);
	local($lockfile);
	$lockfile=$_[0].".lock";
	while (-e $lockfile) {
		($dev ,$ino ,$mode ,$nlink,$uid  ,$gid,
		 $rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks)=stat($lockfile);
		if (time()-$ctime>$_[1]) { last; }
		sleep(1);
	}
	open(LOCK,">".$lockfile);
	close(LOCK);
}
sub unlock {
	unlink $_[0].".lock";
}


up