2012年7月27日金曜日
java.text.MessageFormat
MessageFormatは何をするか
アプリケーションでは、エンドユーザに表示するメッセージがほとんどは定められたフォーマットに沿って出力されています。
たとえば、
ユーザIDを入力してください。
金額を入力してください。
などのメッセージでは、【を入力してください。】は同じです。
MessageFormatは、このようなメッセージを統一したパターンで簡単にフォーマットすることができます。
MessageFormatの使用方法(手順)
1)メッセージパターンを定義します。
例:{0}を入力してください。
フォーマットしたい要素は{}で囲まれます。
2)置き換えデータを配列に設定し、MessageFormat.formatメソッドでフォーマットします。
例:String message = MessageFormat.format("{0}を入力してください。", new String[]{"ユーザID"});
戻り値は置き換えた後の文字列になります。
java.text.MessageFormat構造
java.text.Format
�java.text.MessageFormat
■コンストラクタ
◇ MessageFormat(String pattern)
◇ MessageFormat(String pattern, Locale locale)
パラメータ pattern:メッセージフォーマット・パターン
パラメータ locale:ロケール
■MessageFormat主たるメソッド
◇ StringBuffer format(Object[] arguments, StringBuffer result, FieldPosition pos)
◇ StringBuffer format(Object arguments, StringBuffer result, FieldPosition pos)
◇ static String format(String pattern, Object... arguments)
※JDK1.5以前のバージョンでは、static String format(String pattern, Object []arguments)になります。
指定された文字列パターンをフォーマット要素に置き換えフォーマットします。
◇ Object[] parse(String source)
文字列を解析してパターンとマッチしたオブジェクトの配列を返します。
■フォーマット要素の定義
フォーマット要素は{}で囲まれます。
{ ArgumentIndex }
{ ArgumentIndex , FormatType }
{ ArgumentIndex , FormatType , FormatStyle }
◇ ArgumentIndex:0,1,2...などのインデックスを表す数字。置き換えデータの配列のインデックス
◇ FormatType:number(数字)、date(日付)、time(日時)、choice(ある範囲の数値)などの文字
◇ FormatStyle:
short
medium
long
full
integer
currency
percent
SubformatPattern(サブフォーマットパターン)
◇ 定義例:
{0}
{0,number,integer}
{1,number,#.##}
サンプル
TestMessageFormat.java
view plaincopy to clipboardprint?
import java.text.MessageFormat;
import java.text.ParseException;
import java.util.Calendar;
import java.util.Date;
public class TestMessageFormat {
/**
* MessageFormatのよくある使い方
*/
public static void main(String[] args) {
System.out.println("********* 1 ***********");
String message = MessageFormat.format("{0}を入力してください。", new Object[]{"ユーザID"});
System.out.println(message);
System.out.println("********* 2 ***********");
String pattern2 = "a={0,number,#.##} , b={1,number,integer} , c={2,date}";
Object []arguments = {
new Float(123.4567),
new Integer(345),
new Date()
};
String text2 = MessageFormat.format(pattern2, arguments);
System.out.println(pattern2);
System.out.println(text2);
System.out.println("********* 3 ***********");
String pattern3 = "a={0} , b={1,number,percent} , c={2,time}";
System.out.println(pattern3);
Object []arguments3 = {
"Hello MessageFormat!",
new Float(0.778),
Calendar.getInstance().getTime()
};
MessageFormat messageFormat = new MessageFormat(pattern3);
String text3 = messageFormat.format(arguments3);
System.out.println(text3);
System.out.println("********* 4 ***********");
String pattern4 = "a={0} , b={1} , c={2}";
String message4 = "a=100 , b=あいうえお , c=2009/03/05";
System.out.println(pattern4);
System.out.println(message4);
MessageFormat messageFormat4 = new MessageFormat(pattern4);
try {
Object []objs = messageFormat4.parse(message4);
System.out.println(objs[0]);
System.out.println(objs[1]);
System.out.println(objs[2]);
} catch (ParseException e) {
e.printStackTrace();
}
}
}
実行結果
********* 1 ***********
ユーザIDを入力してください。
********* 2 ***********
a={0,number,#.##} , b={1,number,integer} , c={2,date}
a=123.46 , b=345 , c=2009/03/05
********* 3 ***********
a={0} , b={1,number,percent} , c={2,time}
a=Hello MessageFormat! , b=78% , c=14:38:25
********* 4 ***********
a={0} , b={1} , c={2}
a=100 , b=あいうえお , c=2009/03/05
100
あいうえお
2009/03/05
2012年7月26日木曜日
difference between getMainLooper() and Looper.myLooper()
I'm now trying to resurrect one project. There was an exception on getMainLooper()... I thought that may be there's a problem with MainLooper initialization and added Looper.prepareMainLoop() before that. Exception telling me that there's already a looper for that object was thrown... Then I tried to replace getMainLooper() with Looper.myLooper() and it worked... But I didn't understand why=) In fact I don't get the difference between this two things. I think that on the place where getMainLooper() was used in my project it's the best place for the true main looper of the application but I got what I got.. Please explain. Thank you for your attention |
The difference is that Looper.preapreMainLooper()
prepares looper in main UI thread. Android applications normally do not call this function. As main thread has its looper prepared long before first activity, service, provider or broadcast receiver is started.
But Looper.prepareLooper()
prepares Looper
in current thread. After this function is called, thread can call Looper.loop()
to start processing messages with Handler
s.
So, in your case you had two threads - X and Y. The X thread is the main UI thread that has its looper already prepared by Android. When you are in Y thread and you're callingLooper.prepareMainLooper()
you're trying to prepare looper in X thread (main thread). This fails because X's looper is laredy prepared. But when you call Looper.prepareLooper()
in Y thread, you're actually preparing looper in Y thread and therefore ready to call Looper.loop()
AndroidのHandlerクラスの使い勝手を良くする
UI 関連の処理をおこないたい場合、(Handler クラスのデフォルトコンストラクタを使うと)「UI スレッド内で Handler インスタンスを作成し、Runnable インスタンスを別途作成して当該 Handler インスタンスの post メソッドに渡す」というコーディングが必要で、そのままコーディングすると次のようになるが、
// UI スレッド//// Handler インスタンスを作る。別スレッドで使うというユースケースが// 大半なので、final を付けることが多くなる。final Handler handler = new Handler();
// UI スレッドではない別のスレッド//// UI に関連する処理をスケジュールする。handler.post(new Runnable() {
public void run() {
// UI スレッドで実行する処理を記述する。
}
});
面倒なので、「(1) 常に UI スレッドに紐付けられ、(2) 自クラスで Runnable インターフェースを実装する」Handler クラスのサブクラスを次のように実装してみる。
public class UiHandler extends Handler implements Runnable
{
public UiHandler()
{
// Looper.getMainLooper() で UI スレッドの Looper を取得する。
super(Looper.getMainLooper());
}
public UiHandler(Handler.Callback callback)
{
// Looper.getMainLooper() で UI スレッドの Looper を取得する。
super(Looper.getMainLooper(), callback);
}
public boolean post()
{
// 自分で Runnable インターフェースを実装しているので、// post メソッドに this を渡すことができる。return post(this);
}
public boolean postAtFrontOfQueue()
{
return postAtFrontOfQueue(this);
}
public boolean postAtTime(Object token, long uptimeMillis)
{
return postAtTime(this, token, uptimeMillis);
}
public boolean postAtTime(long uptimeMillis)
{
return postAtTime(this, uptimeMillis);
}
public boolean postDelayed(long delayMillis)
{
return postDelayed(this, delayMillis);
}
public void run()
{
// UI スレッドで実行する処理を記述する。// サブクラスでオーバーライドすべき。}
}
これにより、「任意のスレッド内で」(←面倒削減ポイント)当該サブクラスのインスタンスを作成できる上、Handler と Runnable を別々に管理する必要もなくなり、下記のようなコーディングが可能となる。
new UiHandler() {
public void run() {
// UI スレッドで実行する処理を記述する。
}
}.post();
AndroidのHandlerとLooper
前置き
本稿では、AndroidのHandlerクラスやLooperクラスを取り上げます。これらは、Androidでイベントドリブンのプログラムを書くときに欠かせないものです。
本文
イベントドリブン
死語でしょうか。もしかしたら最も古いデザインパターンの一つかもしれません。以下のような特徴を持ちます。
- あるスレッドが、無限ループを持つ
- ループでは、メッセージを待ち、メッセージが来たらそれを処理する
- 送られたメッセージはキューに蓄積され、届いた順に処理される
この無限ループのことを「メッセージループ」、メッセージを処理するコードのことを「メッセージハンドラ(あるいは単にハンドラ)」、キューのことを「メッセージキュー」と呼びます。また「メッセージ」のことは「イベント」と呼ぶ場合もあります。
イベントドリブンで良く見かける問題
例えば「ボタンがタッチされた」というメッセージに対するハンドラを書いているとしましょう。次のようなシナリオを想像して下さい。
- このハンドラの中では、タッチされたアイコンを大きくしてから効果音を鳴らす
- アイコンを大きくする処理は非同期処理である(つまりアイコンを大きくしても、すぐに画面が更新されるわけではなく、再描画メッセージがキューに送られるだけ)
- 効果音は同期処理ですぐに鳴る
ここで、画面が更新されてから効果音が鳴るようにしたい場合は、「ボタンがタッチされた」というメッセージのハンドラの中で効果音を鳴らすわけにはいきません。一旦ハンドラを終わらせて、再描画メッセージが処理された後で効果音を鳴らす必要があります。そのため、「ボタンがタッチされた」というメッセージのハンドラの中では、自スレッド宛てに「効果音を鳴らせ」メッセージを送るだけにしておきます。
こういうケースが一般的に何と呼ばれているのか不明ですが、自分は「一旦ループに返す」とか「次の周回で処理する」とか言ってました。
Androidでのイベントドリブン
Androidには、イベントドリブンを実現するためのクラスが揃っています。Looperクラスがメッセージループに、Handlerクラスがメッセージハンドラに、Messageクラスがメッセージに、MessageQueueクラスがメッセージキューに、それぞれ相当します。
ActivityベースのAndroidアプリを作るとメインスレッド(別名UIスレッド)が自動的に生成されますが、このスレッドはイベントドリブン構造になっており、ユーザ操作(キーやタッチ)や再描画要求といったメッセージを処理します。なので、例えばボタンをタッチしたところでブレークをかけ、コールスタックを見ると、Looper#loop()を通っていることが確認できます。
Looperクラス
Looperクラスはメインスレッドだけのものではありません。独自スレッド内でイベントドリブンしたい場合もLooperクラスを使うことができます。
new Thread(new Runnable() { public void run() { Looper.prepare(); Looper.loop(); }}).start();
prepare()で、LooperオブジェクトとMessageQueueオブジェクトが生成され、カレントスレッドと関連付けられます。loop()で、カレントスレッド上でメッセージループが回り始めます。どちらもクラスメソッドです。また、引数にスレッドを指定することはできません(つまり、常にカレントスレッドに対して作用するということです)。
ただし、上記のコードでは誰もメッセージを送れないし、仮にメッセージが届いても何も処理されません。メッセージを送ったり処理するためにはHandlerオブジェクトが必要です。
Handlerクラス
メインスレッドや独自スレッドのLooper上で独自のメッセージを処理したい場合は、Handlerクラスを継承した独自クラスを作ります。さらにHandler#handleMessage()メソッドをオーバーライドし、その中で届いたメッセージを処理します。そうしておけば、Handler#sendMessage()を使ってMessageオブジェクトを送信することができます。結構、面倒くさいですね。
ただ、前述の「次の周回で処理する」くらいであれば、独自のメッセージを用意したりHandlerクラスを継承する必要はありません。Handlerには、Messageオブジェクトの他にもRunnableオブジェクトを送る機能があるのです。ここでのRunnableオブジェクトは、MessageオブジェクトとHandler#handleMessage()がセットになったようなものです。
public void onCreate(Bundle savedInstanceState) { ... handler = new Handler(); } public void onClick(View arg0) { handler.post(new Runnable() { public void run() { // 次の周回で行いたい処理。 }}); }
このように、あらかじめHandlerオブジェクトを生成しておき、そこへ、次の周回で行いたい処理を実装したRunnableオブジェクトをpost()するだけです。Handlerオブジェクトをfieldにせずに、一時オブジェクトにしてもOKかもしれません(ダメかもしれません)。生成したHandlerオブジェクトは、カレントスレッド(及び、そのLooperオブジェクトやMessageQueueオブジェクト)に対応付けられます。
Handlerコンストラクタの引数にはLooperオブジェクトを指定することもできるので、Handlerオブジェクトをカレントスレッド以外のスレッドに対応付けることが可能です(Looper生成時とは対照的ですね)。
new Thread(new Runnable() { public void run() { // メインスレッド用のHandlerを生成。 Handler mainHandler = new Handler(Looper.getMainLooper()); mainHandler.post(new Runnable() { public void run() { ... }}); }}).start();
ただし、どのスレッドに対応付ける場合でも、そのスレッド上でLooper#prepare()しておく必要があります。
クラス関連
イベントドリブンに関係するクラスの関連図を示します。
Threadオブジェクト、Looperオブジェクト、及びMessageQueueオブジェクトは、それぞれ1つずつ。それに対してHandlerオブジェクトは複数個あっても構いません。また、Handlerオブジェクトを使って送ることができるのは、RunnableオブジェクトかMessageオブジェクトです。図では、Runnable用のHandlerとMessage用のHandlerに分かれているように見えますが、そういう意図はありません(混在可能です)。
各オブジェクト間の関連を取得するメソッドを挙げます。「C/I」のCはクラスメソッド、Iはインスタンスメソッドです。
クラス | メソッド | C/I | 何を返すか |
---|---|---|---|
Looper | myLooper() | C | カレントスレッドのLooper |
Looper | myQueue() | C | カレントスレッドのMessageQueue |
Looper | getMainLooper() | C | メインスレッドのLooper |
Looper | getThread() | I | 当該Looperのスレッド |
Handler | getLooper() | I | 当該HandlerのLooper |
Handlerを取得するメソッドがありませんね。Handlerは自分で生成して覚えておくもの、ということでしょう。Looper#getThread()はあるのに、Looper#getQueue()が無いのは不便な気がします。
その他のクラス
MessageQueueクラスには、アイドル時に行いたい処理を登録する機能があります。
HandlerThreadクラスは、最初からLooper#prepare()されたThreadクラスです。名前に反して、Handlerオブジェクトに関する世話は焼いてくれません。またLooper#loop()のコールもプログラマの責任です。HandlerThread#getLooper()により、当該スレッドに対応付けられたLooperオブジェクトを得ることができます。
Android 現在実行している処理がUIスレッドかどうか判定する
別スレッドからUIを操作するとCalledFromWrongThreadExceptionで爆死してしまいます。
あまり自分は経験していませんが、
今の処理ってUIスレッドだっけー!?と思う時がある場合があるかもしれません。
下手すると、別スレッドからもUIスレッドからも使うメソッドの中でUI操作をしていて、
たまたま今まで別スレッドでUI操作までたどり着いてなかった、なんて事もありえます。(これはもう設計がおかしいすけど)
本番コードで使うかはともかく、あるソースを見たとき、コレってUIスレッド?別スレッド?と思う時があるかもしれません。
そういう時の判定メソッドを作りました。
private boolean isCurrent(){
return Thread.currentThread().equals(getMainLooper().getThread());
}
です。
getMainLooper()はContextのメソッドなので注意して下さい。
単純にThreadから現在のスレッドを取り出し、メインのLooperから取ったスレッドと比較してます。
例えばActivityのonResume()でこうやると
@Override
protected void onResume() {
super.onResume();
Log.d(TAG, "onResume:"+isCurrent());
}
onResume:true
が出力されます。
適当に
new Thread(){
public void run() {
Log.d(TAG, "Thread1:"+isCurrent());
};
}.start();
こんな事すると、
Thread1:false
となります。
そんな感じです。
Handlerクラスの正しい使い方(Androidでスレッド間通信)
いやいや、それは事実だが、それだけでない。
Handlerクラスはスレッド間通信のための仕組みである。もっと正確に言うと、Handlerインスタンスを生成したスレッドへイベントを送るための仕組みなのである。当たり前だと思う人も多いかもしれないが、多くの人はこのことを理解できていない。
ソースレベルで説明してみる。よく書かれるソースは以下のような感じである。
Handler handler = new Handler(); // (1)
handler.post(new Runnable() {
@Override
public void run() {
// UI部品への操作;
return;
}
});
このソースのポイントは(1)である。(1)を実行したスレッドへHandler.post()で指定したRunnableが送られる。つまり、UIスレッドで(1)を実行すれば、UIスレッドへ送られ、別スレッドで(1)を実行すれば、別スレッドへ送られる。
では、その実体をAndroidのソースHandlerのコンストラクタのソースを見て、確認してみる。
public Handler() {
if (FIND_POTENTIAL_LEAKS) {
final Class<? extends Handler> klass = getClass();
if ((klass.isAnonymousClass() || klass.isMemberClass() ||
klass.isLocalClass()) &&
(klass.getModifiers() & Modifier.STATIC) == 0) {
Log.w(TAG, "The following Handler class should be static or
leaks might occur: " +
klass.getCanonicalName());
}
}
mLooper = Looper.myLooper(); // ポイント(1)
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called
Looper.prepare()");
}
mQueue = mLooper.mQueue; // ポイント(2)
mCallback = null;
}
ポイントは2点。ポイント(1)で呼び出しスレッドの宛先(Looper)を取り出し、ポイント(2)で宛先のポスト(mQueue)へRunnableを送りつけている。このソースを見るとよくわかるのだが、Handlerクラスは決してUIスレッドへRunnableを送りつけるためのものではない。Handlerインスタンスを生成したスレッドへイベントを送りつけるための仕組みである。
Runnableを送りつけたい宛先を明示したい場合は、もう一つのコンストラクタであるHandler(Looper
looper)を使う。これを使えば、任意のThreadへRunnableを送りつけることができる。
この仕組みさえ知っていれば、任意のスレッドとの通信が可能となる。例えば、WebViewを使っている場合は、WebViewの本体であるWebViewCoreThreadと通信することが可能となる。
例えば、こんな感じ。
public class HogeTask extends Thread {
public HogeTask() {
}
public void execute(Looper toLooper, String data) {
// ここでスレッドでしたい処理を記述
new Handler(toLooper).post(new Runnable() {
public void run() {
// ここで宛先toLooperでしたい処理を記述
return;
}
});
}
}
呼び出し側はこんな感じかな。
new HogeTask().execute(toLooper, "hogehage");
toLooperには宛先スレッドのLooperを入れるとよい。Looperの取得の仕方は、対象となるスレッド上でLooper.myLooper()とすればよい。ちなみにUIスレッドのLooperを取得したい場合は、Looper.getMainLooper()を呼べばよい。
これで、HandlerとLooperの神髄を理解することができたと思う。これだけの知識があれば、スレッド間通信はお手の物のはずだ。
定期処理ハンドラを用いて、Viewを再描画する
1 2 3 | //定期処理ハンドラ public class SampleHandler extends Handler { } |
1 2 3 4 5 6 | public class SampleHandler extends Handler { @Override public void handleMessage(Message msg) { sampleView.invalidate(); //1. } } |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | public class SampleHandler extends Handler { @Override public void handleMessage(Message msg) { sampleView.invalidate(); //2. if (sampleHandler!= null ) sampleHandler.sleep( 1000 ); //3. } //スリープメソッド public void sleep( long delayMills) { //使用済みメッセージの削除 removeMessages( 0 ); sendMessageDelayed(obtainMessage( 0 ),delayMills); //4. } } |
1 2 3 4 5 6 7 | @Override public void onResume() { super .onResume(); //定期処理ハンドラの生成と実行 sampleHandler= new SampleHandler(); sampleHandler.sleep( 0 ); } |
また、定期処理ハンドラを停止したい場合は、以下のようにします。
1 | sampleHandler= null ; |
アプリを中断、終了する際は、忘れずにこの処理を記述しましょう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 | public class SampleView extends View { private static int count = 0 ; //コンストラクタ public SampleView(Context context) { super (context); setBackgroundColor(Color.WHITE); } //描画 @Override protected void onDraw(Canvas canvas) { count++; //5. //描画オブジェクトの生成 Paint paint= new Paint(); paint.setAntiAlias( true ); paint.setTextSize( 30 ); //Countの描画 canvas.drawText( "Count>" +count, 0 , 40 * 1 ,paint); //6. } } |