前書き
今までHandlerをUIスレッドへRunnableを渡す為にしか使ってませんでした。ですがHandlerについて改めて調べてみたら面白かったので、勉強のためにHandlerの用例と使い所を考えました。
参考にしたサイト
Handlerクラスの概要については以下の記事が非常にわかりやすかったです。
- https://realm.io/jp/news/android-thread-looper-handler
- http://d.hatena.ne.jp/sankumee/20120329/1333021847
サンプルコードについて
JUnit + Mockitoを使って書いてみました。
1. UIスレッドへRunnableを渡す
概要
Looper.getMainLooper()を引数にしてHandlerを生成し、Handler#post(Runnable runnable)すると、UIスレッド上でRunnableが実行されます。
サンプルコード
@Test
public void Handlerでメインスレッドに通知できる() {
final Handler handler = new Handler(Looper.getMainLooper());
new Thread(new Runnable() {
@Override
public void run() {
handler.post(new Runnable() {
@Override
public void run() {
//UIスレッド上で実行される
assertEquals(getMainLooper().getThread(), Thread.currentThread());
}
});
}
}).start();
}
使い所
WebAPIやDBアクセスといった時間のかかる処理の結果をUIスレッドに非同期で返す時によく使われます。
2. UIスレッドへMessageを渡す
Looper.getMainLooper()を引数にしてHandlerを生成してHandler#sendMessage(Message message)を呼ぶと、Handler#handleMessage(Message message)が呼ばれ引数のMessageが渡されます。
Messageを適切にハンドリングするため、この方法を使う場合はHandlerを継承してhandleMessageを実装する必要があります。
サンプルコード
private final static int MSG_HELLO = 123;
private final static int MSG_BYE = 343;
private final static String MSG_HELLO_OBJ = "konichiwa";
private final static String MSG_BYE_OBJ = "sayonara";
@Test
public void sendMessageするとonHandleMessageが呼ばれる() {
Handler handler = new Handler(getMainLooper()) {
@Override
public void handleMessage(Message msg) {
String messageObj = (String) msg.obj;
switch (msg.what) {
case MSG_HELLO:
assertEquals(MSG_HELLO_OBJ, messageObj);
break;
case MSG_BYE:
assertEquals(MSG_BYE_OBJ, messageObj);
break;
}
}
};
handler.sendMessage(handler.obtainMessage(MSG_HELLO, MSG_HELLO_OBJ));
handler.sendMessage(handler.obtainMessage(MSG_BYE, MSG_BYE_OBJ));
}
使い所
基本的にHandler#post()の方が良いと思ってます。余計な定数を定義する必要がないし、処理が分散しないので見通しが良いからです。
ただし、Handler#obtainMessage()はMesssageクラス内でプールしているオブジェクトを返すので、パフォーマンスが良いんじゃないかと思います。なのでかなり頻繁に呼び出す場合はHandler#sendMessage()の方がいいかもしれません。
AsyncTask.java の内部でProgress処理するためにHandler#sendMessage()を用いているのはパフォーマンスを考慮しているのかもしれないです。
3. 任意のスレッドへRunnableやMessageを渡す
1と2の派生ですが、UIスレッドだけでなく任意のスレッドへRunnableやMessageを渡すことができます。
ただし以下のように素直にやると失敗します。
失敗例
@Test
public void ThreadはLooperが付いていないのでHandlerが使えない() {
final Runnable mockR = mock(Runnable.class);
new Thread(new Runnable() {
@Override
public void run() {
try {
new Handler().post(mockR);
} catch (RuntimeException e) {
assertEquals(e.getMessage(), "Can't create handler inside thread that has not called Looper.prepare()");
}
}
}).start();
verify(mockR, never()).run();
}
メッセージにある通り、Looperがないスレッドの内部でHandlerを生成したので例外が飛びました。
Looper付きのスレッドであるHandlerThreadを使うと上手くいきます。
HandlerThreadを使ったサンプルコード
@Test
public void HandlerThreadではHandlerが使える() {
HandlerThread handlerThread = new HandlerThread("hoge");
handlerThread.start();
Runnable mockR = mock(Runnable.class);
new Handler(handlerThread.getLooper()).post(mockR);
verify(mockR, timeout(100).times(1)).run(); // 非同期にrun()が呼ばれるので少し遅延させる
}
使い所
あまり思いつかないのですが(スミマセン)、Serviceをバックグラウンドで動作させたい時に使えるかもしれません。例えばIntentService.java ではワーカースレッドへMessageを渡すためにHandlerを使っています。
IntentService.java
// onCreate()にて
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();
mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
private final class ServiceHandler extends Handler {
public ServiceHandler(Looper looper) {
super(looper);
}
@Override
public void handleMessage(Message msg) {
onHandleIntent((Intent)msg.obj);
stopSelf(msg.arg1);
}
}