Hatena::ブログ(Diary)

理系のためのTIPS集 @naokichick

2011/04/10

HandlerThreadとHandlerとLooperの関係

IntentServiceを使って非同期処理を行う - Tech Booster

上記で紹介されているIntentServiceについて、どういうものなのか調べようと思ったら、HandlerThreadというものが内部的に使われていました。

気になる名前のクラスなので、こちらを先に押さえることにします。

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;
    }

あわせて読みたい

勉強中: HandlerThread・Handler・Messageの使い方

HandlerThread | Android Developers

IntentService - Boring Days in tech

おすすめのAndroid

Google Androidプログラミング入門
江川 崇 竹端 進 山田 暁通 麻野 耕一 山岡 敏夫 藤井 大助 藤田 泰介 佐野 徹郎
アスキー・メディアワークス
売り上げランキング: 11701

Android Hacks ―プロが教えるテクニック & ツール
株式会社ブリリアントサービス
オライリージャパン
売り上げランキング: 174181

スパム対策のためのダミーです。もし見えても何も入力しないでください
ゲスト


画像認証

トラックバック - http://d.hatena.ne.jp/kaw0909/20110410/1302418486