ホーム > デベロッパ > J2ME / MIDP プログラミング > 第 2 回

第 2 回 : 仕様・設計と実装 ( IM "improve" の機能仕様と J2ME/MIDP のつまずきどころ)

はじめに

実際に IM "improve" を作成していきます。

IM "improve" の機能仕様を決定し、J2ME/MIDP 仕様上の制限をまとめます。


improveの機能

IM "improve" の機能仕様をまとめます。

表1 improve の概要
目標 シンプルだけれども実用的な IM
想定ユーザー数 登録:〜50アカウント、同時接続:〜25クライアント
端末 MIDP 1.0 (後に KDDI Phase 2.5 [1:ezplus],
J-PHONE 開発ガイド1.1.9の仕様 [2,3:J-PHONE]に対応)
サーバー Servlet on Tomcat 4.0.6
データベース PostgreSQL 7.3

入会・退会

初回起動時にユーザー登録します。2 回目以降は自動ログインします。退会も可能です。

ユーザーアカウント

アカウント名は自分で決めます。ニックネームも設けます。

IM の基本機能

友達管理機能

友達を追加、削除できます。友達をリスト表示できます。名前の横のアイコンが友達の状態です。(a)〜(e)が友達リスト画面です。なお赤で表示されているのは自分です。

図1画面イメージ
友達リスト(本人) 本人メニュー ステータス変更
(a) 友達リスト(本人) (b) 本人メニュー (c) ステータス変更
友達リスト(友達) 友達メニュー メッセージング
(d) 友達リスト(友達) (e) 友達メニュー (f) メッセージング

グループ

improve にはグループ機能は搭載しません。

友達の検索

improve には友達の検索機能は搭載しません。

状態確認機能

状態は次のように分類できます。

プロフィール

生年月日・住所・電話番号・趣味などのかなり静的な状態情報。

プレゼンス

オンライン、オフラインなどです。

ステータス

どこで何をしているかなどの状態。

improve での状態の扱い

プロフィール

電話番号以外は省略します。

プレゼンス

オンラインとオフラインの 2 つの状態とします。オフラインの場合は灰色信号を表示します。

ステータス

「いつでも OK (緑)・少しなら(黄)・ NG (赤)」の 3 つから選ぶか、自由文でも設定できます。友達リストでフォーカスをあわせると自由文がティッカーに流れます。

コミュニケーション機能

improve では 2 者間メッセージングのみ実装します。図1-(f)がメッセージング画面です。図 1 で右向き三角アイコンが、現在会話中の人を示しています。

その他の機能について

ポーリング

常時コネクションを張れない携帯 Java ではポーリングが必須です。図1-(f)の右下の「攻撃」コマンドで手動ポーリングもできます。

ティッカー

ティッカーとは文字列を左右に流して表示するコンポーネントです。図 1 で日時が表示されている部分がティッカー領域です。

セキュリティとプライバシー

improve ではセキュリティとプライバシーの機能は搭載しません。

機能のまとめ

画面遷移一覧

複雑な友達リストとメッセージング画面のみ Canvas で作成し、その他の画面は Panel を用います。

図2 画面遷移図
画面遷移図

機能一覧

表 3 は搭載機能一覧です。

表 3 improve 搭載予定機能
基本 入会・退会 初回登録、メニューから退会
アカウント切り替え 英数字 10 字以内、重複なし
アカウント名 英数 10 字以内、重複なし
ニックネーム 日本語を含む9字以内
パスワード 自動生成、ユーザー不可視
友達管理 表示 状態アイコン付き文字列リスト
グループ なし
追加 アカウント指定
削除 メニューから
検索 なし
状態確認 プロフィール 電話番号のみ
プレゼンス オンライン・オフライン
ステータス チャット許容度3段階、自由文15文字以内
コミュニケーション チャット 特定 2 者間、同時複数
電話 拡張予定
その他 ポーリング 自動 2 分間隔、チャット時手動併用
プレゼンス オンライン・オフライン
ティッカー 独自・優先度あり
セキュリティー なし
プライバシー保護 なし
遮断機能 なし

動作の確認

検証は以下のエミュレーターと実機で行いました。

タイマーのキャンセル

タイマーについて

MIDP では Java.util パッケージに Timer クラスと TimerTask クラスが用意されています。ティッカーやアイコンの点滅、ポーリング間隔調整に使います。

MIDP のタイマー

図3 MIDP のタイマ

DoJa のタイマー

DoJa ではタイマーとして com.nttdocomo.util パッケージにイベント-リスナ型の Timer クラスと TimerListener インターフェースが用意されています。

図4 DoJa のタイマ

DoJa と MIDP の Timer クラス

ティッカーに新しい文字列を設定するたびにタイマーを起動し、文字列が既定回数流れたら停止させるとします。

このとき、DoJa で Timer クラスの start() と stop() を使うように、MIDP で Timer クラスの schedule() でタスクを登録し、MyTimerTask クラスの cancel() でタスクを停めるのを繰り返そうとすると … ?

cancel() は停止じゃない!

MIDP において MyTimerTask クラスの cancel() メソッドを呼んだ後、再度同じ MyTimerTask クラスのインスタンスを schedule() で登録しようとすると、例外 IllegalStateException が発生します。

TimerTask クラスの cancel() メソッドはタスクの破棄であり、停止ではありません。タイマの発生前に同じインスタンスを再登録しても同様な例外が発生します。

また、 Timer クラスの cancel() メソッドを呼んだ後に schedule() メソッドを呼ぶと、例外 IllegalStateException が発生します。

Timer クラスと TimerTask クラスは Java.util パッケージにあり、その仕様は Java2 SE と同じです。DoJa から Java の開発を始めた開発者にとってつまずきやすいポイントです。

タイマタスクを使い回すには?

キャンセルされた MyTimerTask インスタンスは破棄しなければなりません。正常にタスクを終了した MyTimerTask インスタンスを再び schedule() で登録することは構いません。(ただし ezplus の仕様で、Phase 2.5 からは同一の MyTimerTask インスタンスを一度しか登録できないので注意してください。[1:ezplus])

しかし、キャンセルするたびに MyTimerTask クラスのインスタンスを作り直していたのでは効率が落ちます。フラグを 1 つ用意し、フラグが立っていればタイマが発生しても何もしないように run() メソッドを実装すれば、同じ MyTimerTask のインスタンスが使いまわせます。

サンプルプログラム「TimerTest」

サンプルプログラム TimerTest を見ていきましょう。

図5 TimerTest のスクリーンショット
TimerTestScreenshot

TimerTest サンプルでは 2 つのタイマタスクを用います。「登録」でそれぞれをワンショットで 3 秒後と 5 秒後にスケジュールし、「キャンセル」で両タスクをキャンセルします。特に、以下の点を実際に確認してみてください。

■ タイマの登録前・登録後・発生後を問わず、 キャンセルした後に登録を行うと例外 IllegalStateException が発生する

■ タイマの登録後、 タイマの発生前に再度登録を行うと例外 IllegalStateException が発生する

※ WTK104 エミュレータでは少し挙動が異なり、タイマ発生後の単なる再登録においても IllegalStateException が発生します。

TimerTestMIDlet クラス (TimerTestMIDlet.Java)

MIDlet を継承したアプリのメインクラスです。詳細は「第 1 回 : 携帯 Java と IM 」を参照してください。

TimerTestForm クラス (TimerTestForm.Java)

Form を継承したアプリのメイン画面です。

ダイアログを作ろう

確認ダイアログ

友達リストから友達を削除するときや、アプリケーションの終了確認など、「Yes」か「No」の確認ダイアログをプログラム上で手軽に使えると便利です。

図6 確認ダイアログの例
確認ダイアログの例

DoJa の場合

com.nttdocomo.ui パッケージに Dialog クラスがあります。

Dialog dialog = new Dialog(Dialog.DIALOG_YESNO, "削除確認");

dialog.setText("削除してもよろしいですか?");

int result = dialog.show();

とすることで確認ダイアログが表示され、 ユーザがどのボタンを押したのかを show() メソッドの返り値として得られます。

MIDP の場合

MIDP には DoJa の Dialog に対応するクラスがありません。MIDP では、2 通りの確認ダイアログのプログラム実装方法があります。

他の画面遷移とは、例えばログイン画面でログインが完了したら友達リストに移るとか、「メニュー」キーが押されたらメニューを表示するなどです。

1.は、MIDP では標準的な方法です。2.は DoJa の Dialog に似たクラスを用意する方法です。

improve では、2.の方法で確認ダイアログの実装します。

Alert クラス

MIDP にも、Javax.microedition.lcdui パッケージの Alert クラスというダイアログがあります。Alert クラスはデータの表示やエラーの通知を行うためのものです。タイムアウトも設定できます。ただし、ユーザは入力できません。

図7 Alert クラスの表示サンプル
表示サンプル1 表示サンプル2

DialogTest1 の実装

「表示」を押すと「Yes」「No」のついた確認ダイアログが表示されます。いずれかのソフトキーを押すと、結果が「ダイアログの結果:」に表示されます。

図8 DialogTest1 のスクリーンショット
DialogTest1のScreenShot

DialogTest1MIDlet クラス (DialogTest1MIDlet.Java)

MIDlet を継承したアプリのメインクラスです。

この MIDlet の Display を外部から参照するために、10行目で Display 型の変数 display を public で宣言し、18行目で代入しています。

DialogTest1Form クラス (DialogTest1Form.Java)

Form を継承したアプリのメイン画面です。

図9 DialogTest1 の意図されたシーケンス図
DialogTest1の意図されたシーケンス図

DialogTest1 の実行

DialogTest1 をエミュレータで実行し、「表示」を押してみます。

図10 DialogTest1 で「表示」を押したところ
表示をおしたところ

ソフトキーのラベルは「Yes」と「No」に更新されましたが、確認ダイアログは表示されず、ソフトキーの入力も受け付けなくなり、アプリが固まってしまいました…?

MIDP のイベント処理

MIDP のイベント処理は、MIDP1.0 の仕様書[4:JSR-37]の 9.3 Event Handling に定義されています。それによると、MIDP の実装系はイベントの発生をコールバックによってアプリに通知しますが、UI コールバックには 4 種類があります。

これらの UI コールバックは直列化され、並列に実行されることはありません。また、UI コールバックは直前の UI コールバックが終了した後、呼び出されます。

1.の「抽象コマンド」とは、DialogTest1Form.Java の41行と135行の commandAction() メソッドに渡される Command クラスのことです。Command クラスはソフトキー入力に対応し、これを処理したいクラスでは CommandListener インターフェースを実装して、commandAction() メソッドにキー入力に対する処理内容を記述します。

ちなみに、前節のタイマイベントは UI イベントとはみなされません。したがって、タイマコールバック(TimerTask#run() の呼び出し)と UI コールバックは同時に処理される可能性があります。ただし、同一 TimerTask オブジェクトへのタイマコールバックは直列化されます。

commandAction()

DialogTest1 を System.out.println() でトレースしてみると、

で停止していることがわかります。このときの状況は先程述べたように、以下のようでした。

2.の理由は、先ほどの Command クラスによる UI コールバック、すなわち CommandListener インターフェースの commandAction() メソッド呼び出しが直列化されているためだと考えられます。つまり、41行の DialogTest1Form#commandAction() をまだ終了していないので、たとえキー入力があっても、135行の DialogForm#commandAction() が実行されないのです。

1.の理由については実装依存の部分があるので推測の域を出ませんが、Canvas の paint() が直列化されるのと同様に、Screen クラス(Form の親クラス。第1回の記事を参照)の描画も UI コールバックとして直列化されているためだと思われます。(実際、MIDP Reference Implementation 1.0.3 では Displayable (Canvas と Screen の親クラス)に抽象メソッド paint() があり、Canvas と Screen の描画をポリモーフィズムで処理しています。)この仮定の下では、やはり41行の DialogTest1Form#commandAction() を終了していないことが画面が更新されない原因です。

図11 DialogTest1 の実際のシーケンス図(固まる)
DialogTest1の実際のシーケンス図

結局、アプリが固まる原因は commandAction() の中で wait() して停まってしまい、同メソッドから返っていないことです。これを回避するために、コマンドの処理をスレッド化します。

コマンド処理をスレッド化「DialogTest2」

commandAction() をただちに返すようにするため、commandAction() 内で新たにスレッドを起動し、すべての処理をそのスレッドにまかせるように DialogTest1 を変更します。

DialogTest2MIDlet クラス (DialogTest2MIDlet.Java)

MIDlet を継承したアプリのメインクラスです。使用する Form クラス以外は DialogTest1MIDlet.Java と同じです。

DialogTest2Form クラス (DialogTest2Form.Java)

Form を継承したアプリのメイン画面です。DialogTest1Form クラスから変更された部分を特に説明します。

以下に DialogTest2 のシーケンス図を示します。

図12 DialogTest2 のシーケンス図
DialogTest2のシーケンス図

DialogTest2 の実行

DialogTest2 をエミュレータで実行します。

図13 DialogTest2 の実行結果
DialogTest2 の実行結果1 DialogTest2 の実行結果2
(a) DialogTest2 で「表示」を押したところ (b) 確認ダイアログで「Yes」を押したところ

今度は期待通りに動作しました。このとき、エミュレータのメッセージ欄には DialogTest2Form クラスと DialogForm クラスの commandAction() を実行したスレッドが出力されています。

DialogTest2Form#commandAction() Thread[@487789072,5]

DialogForm#commandAction() Thread[@487789072,5]

両者は同一スレッドから呼び出されています。UI イベントの処理が直列化されているため、単一スレッドで実装されているのです。実機においても同様に実装される可能性があります。commandAction() からすぐに返らなければならないときは、注意する必要があります。たとえば通信などの長時間処理を書いてしまうと、アプリが固まったように見えます。

まとめ

エキストラ トラック!

各キャリアの仕様・拡張機能など

J-PHONE、KDDI の Java ( J2ME/MIDP ) の仕様をまとめます。詳細は J-PHONE、KDDI のドキュメントをご確認ください。

参考文献