2011年9月6日火曜日

ThreadとHandlerでマルチスレッド処理化する

AndroidのActivityは描画処理を行うためのUIスレッドしかもたないシングルスレッド設計です。負荷が高い処理、待ち時間が発生する処理(ネットワーク通信をはじめとした非同期通信など)はUIスレッドを圧迫し、アプリケーションのレスポンスに影響します。UIスレッドの負荷を下げるには別スレッドを作成し、仕事を分散させるマルチスレッド処理の実装が必要です。
<2011/6/7 02:00 Message#obtainに関する記述を追加しました。Message#obtainを利用することでより効率的なメッセージ送信が可能です。>
<2011/6/7 13:00 volitale修飾子による最適化抑止を追加しました。>
今回はスレッド(java.lang.Thread)とハンドラ(Android.os.Handler)を使って、キューイングを実装します。キューイングとは異なるスレッドからのメッセージを一時的に蓄積し、相互のスレッドが意識し合うこと無く(非同期に)データ交換ができる仕組みです。紹介する内容とポイントは以下3点+1です。
• ハンドラの実装: Handlerは送られてきたメッセージを受け取り、処理する
• マルチスレッド化: UIスレッドの負荷が高いとアプリが応答しなくなる(ANR)が発生、ユーザビリティが低下する
• メッセージのキューイング: スレッドからUIスレッドへのメッセージ送信(非同期処理)
• メッセージの効率的生成: Message#obtainもしくはHandler#obtainMessageを利用する(メモリ効率化)
また、TechBoosterには非同期処理の実装方法として Asynctaskを使って非同期処理を行う 、 IntentServiceを使って非同期処理を行う を紹介しています。またスレッドの応用記事には
描画処理を分離した SurfaceViewで高速描画する(2) ゲームプログラミングの基本 、タイマーによるカウント Timerを使って定期実行する があります。あわせてどうぞ。
マルチスレッド処理のサンプルコードは続きからどうぞ

ハンドラの実装
ハンドラはメッセージを受け取り、処理するための仕組みです。
メソッドは以下を使用します。引数にはHandlerにメッセージを送るAndroid.os.Messageを渡します。
• Handler#handleMessage(Message msg)
public class ThreadActivity extends Activity implements Runnable {

private volitale Thread mLooper;
private Handler mHandler;

@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
final TextView tv = (TextView)findViewById(R.id.textview);

mHandler = new Handler(){
//メッセージ受信
public void handleMessage(Message message) {
//メッセージの表示
tv.setText((String) message.obj);
//メッセージの種類に応じてswitch文で制御すれば
//イベント制御に利用できます
};
};
}
[追記]3行目、複数スレッドから参照される変数はvolitale修飾子を利用します。複数スレッドが同時に動作する場合、コンパイラによる最適化の結果、同じ変数名でもスレッドごとに異なる値になるケースがあります。最適化はvolitale修飾子をつけることで明示的に抑制できます。[/追記]
12行目、onCreateメソッドのなかでハンドラを生成しています。10行目、ハンドラで利用するオブジェクトはデータが不正状態になることを避けるためfinal修飾子をつける必要があります。
14行目、ハンドラにhandleMessageメソッドを追加、メッセージの受信処理として、Android.os.Messageを受け取ります。
Messageでよく使うメンバは以下の2つです。
• public Object obj: 送信オブジェクト(サンプルではStringをキャストして利用)
• public int what : ユーザ定義のメッセージコード。int値で自由に定義可能
マルチスレッド化
スレッドの生成、破棄はonStart,onStopのタイミングでそれぞれ行います。
@Override
public void onStart(){
super.onStart();
mLooper = new Thread(this);
//スレッド処理を開始
if(mLooper != null ){
mLooper.start();
}
}

@Override
public void onStop(){
super.onStop();
//スレッドを削除
mLooper = null;
}
あたらしくスレッドmLooperを作成しました。4行目ではnew Thread(this)としています。
Threadのコンストラクタは以下の通りです。
• Thread(Runnable runnable)
サンプルのアクティビティではActivity implements Runnableと、Runnableを取り込んでいます。
メッセージのキューイング
public class ThreadActivity extends Activity implements Runnable {

//スレッドによる更新処理
public void run() {
long time = System.currentTimeMillis();
long count = 0;

while (mLooper != null) {

long now = System.currentTimeMillis();
if(now - time &gt; 1000){

//Message msg = new Message(); //非推奨
Message msg = Message.obtain(); //推奨
//Message msg = mHandler.obtainMessage(); //推奨
msg.obj = new String(&quot;ループが&quot;+ count + &quot;回終了しました&quot;);

//ハンドラへのメッセージ送信
mHandler.sendMessage(msg);

//スレッドの利用変数を初期化
time = now;
count++;
}
}
}
}
13,14,15行目:送信メッセージを宣言、作成しています。サンプルでは文字列を作成し、msg.objに代入しています。
17行目:メッセージのキューイングを実施します。(ハンドラの任意のタイミングで受信、処理されます)
追記:2011/06/07 02:00
メッセージの効率的生成 Message#ObtainとHandler#obtainMessage
記事初出では前述のサンプルコードにMessage msg = new Message();を利用していましたが、メモリ効率化の観点から、
• Message#obtain()メソッド
• Handler#obtainMessage()メソッド
を利用したほうが好ましいため、変更しました(14行目、15行目)。ご指摘ありがとうございました。
Android DevelopersのMassageでは、
While the constructor of Message is public, the best way to get one of these is to call Message.obtain() or one of the Handler.obtainMessage() methods, which will pull them from a pool of recycled objects.
と、obtainすることでリサイクル対象となったオブジェクトプールから再利用してMessageを生成する記載があります。
Message msg = new Message();は効率の観点から推奨されていません。new Message();はまったく利用できないわけではありませんが、パフォーマンスを向上させるためMessage#obtainを利用してください。Message#obtainの内部処理をみると、プールからリサイクル可能なオブジェクトが無くなれば、new Message()を実行していることがわかります。

0 件のコメント:

コメントを投稿