Threads
Overview
UI Thread
Android では、アプリケーション毎に一つのプロセスが作成され、UI Thread と呼ばれる単一のメインスレッドのみが、画面操作のやりとりを行なう。これにより Activity は、複数スレッドからアクセスされることはないため、スレッドセーフである必要はない。Android UI Toolkit android.(view|widget).*
もスレッドセーフではない。
言い換えると、通信などの時間のかかる処理で、UI Thread をブロックすると、一切の画面操作を受け付けずハングしてしまうことになる。規定の秒数(約5秒)を超えると “Application Not Responding” (ANR) 警告ダイアログが表示される。
これを避けるためには、別スレッドで処理させることが必要になるが、UI Thread 以外のスレッドから UI Toolkit を直接操作すると、ランタイムエラーにより強制終了する。
UI Thread 外から UI Toolkit にアクセスする場合は、以下の API を用いる。
public class MainActivity extends Activity {
...
private TextView mTextView;
...
private void waitFor(long msec) {
try {
Thread.sleep(msec);
} catch (Exception e) {
throw new Error(e);
}
}
// OK: Activity.runOnUiThread(Runnable)
private void accessInsideUiThread() {
new Thread(new Runnable() {
@Override
public void run() {
waitFor(5000L);
runOnUiThread(new Runnable() {
@Override
public void run() {
mTextView.setText("Access inside the UI thread.");
}
});
}
}).start();
}
// OK: View.post(Runnable)
private void postToMessageQueue() {
new Thread(new Runnable() {
@Override
public void run() {
waitFor(5000L);
mTextView.post(new Runnable() {
@Override
public void run() {
mTextView.setText("Access via MessageQueue");
}
});
}
}).start();
}
// NG: Do not block the UI thread.
private void blockUiThread() {
waitFor(5000L);
mTextView.setText("This is an incorrect solution.");
}
// NG: Do not access the Android UI toolkit from outside the UI thread.
private void accessOutsideUiThread() {
new Thread(new Runnable() {
@Override
public void run() {
waitFor(5000L);
// The application will crash with a runtime exception.
mTextView.setText("This doesn't work.");
}
}).start();
}
}
AsyncTask
AsyncTask により、別スレッドの処理を簡潔に記述することができる。
MeanTask.java
public class MeanTask extends AsyncTask<Integer, String, Float> {
private Listener mListener;
public interface Listener {
public void onProgress(String message);
public void onResult(Float result);
}
public MeanTask(Listener listener) {
mListener = listener;
}
@Override
protected Float doInBackground(Integer... params) {
int len = params.length;
int sum = 0;
for (int i = 0; i < len; i++) {
publishProgress(String.format(
"sum = %d + %d (%d/%d)", sum, params[i], i + 1, len));
sum += params[i];
}
return ((float) sum) / len;
}
@Override
protected void onProgressUpdate(String... progress) {
mListener.onProgress(progress[0]);
}
@Override
protected void onPostExecute(Float result) {
mListener.onResult(result);
}
}
MeanTaskClient.java
MeanTask.Listener listener = new MeanTask.Listener() { ... };
new MeanTask(listener).execute(1, 2, 3, 4);
AsyncTask 内に Activity への参照を持ちたい時は WeakReferece で持つようにする。直接保持すると、アクティビティのライフサイクルで破棄されず、メモリリークを起こす場合がある。
private WeakReference<MainActivity> mActivityRef;
public SomeAsyncTask(MainActivity activity) {
mActivityRef = new WeakReference<MainActivity>(activity);
}
@Override
protected void onPostExecute(Bitmap result) {
MainActivity activity = mActivityRef.get();
if (null != activity) {
...
}
}
Handler
Android では、スレッド毎に MessageQueue を持ち、Looper が順番に送信されたメッセージをキューから取り出し、処理を行なっている。
Handler により、このキューにメッセージを送信することができる。
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case MESSAGE_PROGRESS:
mProgressBar.setProgress((Integer) msg.obj);
break;
default:
break;
}
}
}
new Thread(new Runnable() {
@Override
public void run() {
handler.post(new Runnable() {
@Override
public void run () {
mProgressBar.setVisibility(View.VISIBLE);
}
});
for (int n = 1; n <= 100; n++) {
Message msg = handler.obtainMessage(MESSAGE_PROGRESS, n);
handler.sendMessage(msg);
}
}
}).start();
以下の2つのオブジェクトを送信することができる。
- Runnable
Handler#post
により MessageQueue に、Runnable オブジェクトがプールされる。- Looper は
Runnable#run
を実行する。
- Message
Handler#obtainMessage
で、Handler に関連づけられた Message オブジェクトを作成する。Handler#sendMessage
により MessageQueue に、Message オブジェクトがプールされる。- Looper は、Message に紐づけられている Handler を介して
Handler#handleMessage
を実行する。
複数スレッドからのメッセージを、単一スレッド上の MessageQueue に集めて Looper で処理を行なうので、同一 Looper を使う限り、スレッドセーフである必要がなく、不要な同期処理を避けることができる。
Handler は、必ず一つの Looper を持つ。コンストラクタで指定しない場合は、同一スレッドに存在する Looper が使われる。Looper が存在しない場合や、同一スレッドで複数 Looper を扱うとエラーになる。
UI Thread 内では、すでに Looper は割り当てられており、UI Thread 内で Looper を指定せずに Handler を作成した場合、UI Thread 上の Looper で実行されることになる。Runnable#run
や Handler#handleMessage
の中で UI Thread をブロックするような長時間の処理は行なってはならない。また、MessageQueue を圧迫しないように、メッセージ送信数も最小限にすべきである。
UI Thread の Looper は Looper.getMainLooper
で得られる。以下は Acitivity#runOnUiThread
と同じことである。
final Handler hander = new Handler(Looper.getMainLooper());
handler.post(new Runnable() { ... });
別スレッドの Handler を作成する場合は、以下のようになる。
public class LooperThread extends Thread {
public Hander handler;
...
@Override
public void run() {
Looper.prepare();
handler = new Handler() {
...
}
Looper.loop();
}
}
final LooperThread looperThread = new LooperThread(...);
looperThread.start();
looperThread.handler.post(new Runnable() { ... });