2013年4月25日木曜日

HandlerThreadを使うと何が出来るのか

HandlerThreadは、Handler経由でメッセージをsendできる拡張スレッド、のようです。下記は調べたメモです。

IntentServiceのソースコード
HandlerThreadを調べる発端となったIntentServiceは、Serviceのサブクラスです。ソースを見てみると、onCreate時にHandlerThreadを生成して、スタートさせています。

IntentService.javaのonCreate
@Override
public void onCreate() {
// TODO: It would be nice to have an option to hold a partial wakelock
// during processing, and to have a static startService(Context, Intent)
// method that would launch the service & hand off a wakelock.

super.onCreate();
HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
thread.start();

mServiceLooper = thread.getLooper();
mServiceHandler = new ServiceHandler(mServiceLooper);
}
さて、このHandlerThreadとは何者でしょうか。

HandlerThreadは内部にlooperを持つスレッド
HandlerThreadは、UIスレッドのようなlooperを内部に持つ拡張スレッドのようです。

Handlerは、引数なしで生成すると生成時のスレッドのlooperがsend先になります。

Handler.javaの引数なしコンストラクタ
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();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = null;
}
HandlerをUIスレッド以外からnewすると「Can't create handler inside thread that has not called Looper.prepare()」とよく怒られていたんですが、やっと合点がいきました。引数なしで生成されたHandlerは問答無用でUIスレッドにメッセージをsendするわけではなくて、newされたスレッドのlooperに対してメッセージをsendしていたんですね。

IntentService#onCreateでやっているように、new Handler(Looper looper)のコンストラクタを使うと、引数で渡されたlooperに対してメッセージをsendできるようです。

HandlerThreadを試してみる
上記を踏まえて、HandlerThreadの動作を試してみます。

まず、UIスレッドにHandler経由でメッセージをsendするコードです。

public class SampleHandlerThreadActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button btn1 = (Button) findViewById(R.id.button1);
btn1.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
Handler mainHandler = new Handler();
mainHandler.post(new Runnable() {
public void run() {
Log.v("hoge", "thread name:" + Thread.currentThread().getName());
}
});

}
});
}
}
実行結果
04-10 15:41:58.006: VERBOSE/hoge(902): thread name:main

postされたrunnableが、mainスレッド(つまりUIスレッド)で実行されたことが確認できます。

これを、post先を独自のスレッドになるように書き換えてみます。

独自のスレッドにHandlerからpostする
public class SampleHandlerThreadActivity extends Activity {
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
Button btn1 = (Button) findViewById(R.id.button1);
btn1.setOnClickListener(new OnClickListener() {
public void onClick(View v) {
HandlerThread hogeThread = new HandlerThread("hogeThread");
hogeThread.start();
Handler hogeThreadHandler = new Handler(hogeThread.getLooper());
hogeThreadHandler.post(new Runnable() {
public void run() {
Log.v("hoge", "thread name:" + Thread.currentThread().getName());
}
});


}
});
}
}
実行結果
04-10 15:49:14.966: VERBOSE/hoge(951): thread name:hogeThread

hogeThreadで、postしたrunnableが実行されていることを確認できました。

HandlerThreadはrunの中でLooper.prepare()する
なお、上記のコードではhogeThreadをstartさせてからHandlerをnewしています。HandlerThreadはstartされてからはじめてlooperを自身に設定するためです。

startするまえにgetLooperしてもnullが返るので、new Handler(Looper)は失敗します。

HandlerThreadのrun()
public void run() {
mTid = Process.myTid();
Looper.prepare();
synchronized (this) {
mLooper = Looper.myLooper();
notifyAll();
}
Process.setThreadPriority(mPriority);
onLooperPrepared();
Looper.loop();
mTid = -1;
}

0 件のコメント:

コメントを投稿