概述#
クラス | 説明 |
---|---|
Handler | 実際にイベントを処理する場所 |
Looper | 送信するイベントがあるかどうかをループで確認し、MessageQueue からイベントを取得して Handler に処理を渡す(キューが空の場合は休眠に入る) |
MessageQueue | 実行する必要があるタスクを保存する、一般的には同じタイプの Object、Message を保存するデータ構造のキュー |
Message | 実行する必要があるイベント |
1 つの Thread は 1 つの Looper に対応しています
1 つの Looper は 1 つの MessageQueue に対応しています
1 つの MessageQueue には複数の Message があります
各 Message は最大で 1 つの Handler にイベントを処理させます
Thread と Handler は 1 対多の関係です
Handler#
Message を受け取り処理します
Message を MessageQueue に保存します
// Handler.java
public class Handler {
...
final Looper mLooper;
final MessageQueue mQueue;
final Callback mCallback;
...
public interface Callback {
boolean handleMessage(@NonNull Message msg);
}
// 通常は使用者がイベント処理をオーバーライドするために使用されます
public void handleMessage(@NonNull Message msg) {
}
// 分岐、3つの方法があります
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
}
上記の分配イベントには 3 つの方法があり、この 3 つの分配方法には優先順位があります、前のものが処理されれば後の方法は使用されません
Message 自体の callback
Callback インターフェースの実装
Handler#handleMessage
関数のオーバーライド
Handler を通じて情報を MessageQueue に入れます、方法には Post と Send の 2 つの系列があり、送信時間を制御できます
Post 系列:散発的な情報を Message に変換してから Send で送信します
関数 | 機能 |
---|---|
boolean post(Runnable r) | 情報を直接送信します |
boolean postAtTime(Runnable, long updatetimeMillis) | 固定(指定)時間にその情報を送信します |
Send 系列:パラメータは直接 Message です |
関数 | 機能 |
---|---|
boolean sendEmptyMessage(int what) | 情報を直接送信します |
boolean sendMessageArFrontOfQueue(Message msg) | メッセージを情報キューの最前面に送信します |
boolean sendMessageAtTime(int what, long updatetimeMillis) | 固定(指定)時間にその情報を送信します |
boolean sendMessageDelayed(Message msg, long delayMillis) | 数ミリ秒後にその情報を送信します |
Post の実装方法
// Handler.java
public class Handler {
public final boolean post(@NonNull Runnable r) {
return sendMessageDelayed(getPostMessage(r), 0);
}
// callbackをラップして送信します
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain(); // Messageリソースを取得し、作成を繰り返さないようにし、リソースを再利用します
m.callback = r; // Runnableコールバック関数を直接設定します
return m;
}
// 最後に現在の時間 + 延長を計算し、その後sendMessageAtTime関数を呼び出します
public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
if (delayMillis < 0) {
delayMillis = 0;
}
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
// 規定の時間に情報を送信します
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) {
// このThreadにMessageQueueが指定されていない場合はエラーをスローします
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
}
情報送信方法#
名称 | パラメータ:戻り値 | 説明 |
---|---|---|
post | Runnable : boolean | Message として Runnable オブジェクトを受け取ります |
postAtTime | Runnable, long : boolean | Message として Runnable オブジェクトを受け取り、指定された時間に Message を送信します |
sendEmptyMessage | int : boolean | 使用者が指定した int 情報を送信し、使用者自身が処理します |
sendMessageAtTime | Messge, int : boolean | 使用者が指定した int 情報を送信し、使用者自身が処理します |
sendMessageDelayed | Messge, long : boolean | 指定された時間後(遅延)に Message を送信します |
Post & Send#
// Send ...
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
Message msg = Message.obtain();
msg.what = what;
return sendMessageDelayed(msg, delayMillis);
}
// Post ...
public final boolean post(Runnable r)
{
return sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
// 最終的にすべてsendMessageAtTimeに送信されます
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
MessageQueue queue = mQueue;
if (queue == null) { // 1.
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
Message の処理#
dispatchMessage から、Message の処理順序が予め設定されていることがわかります
msg.callback (Runnable)
mCallback (インターフェース)
handleMessage (メソッド)
public void handleMessage(Message msg) {
// 使用者がオーバーライドします
}
/**
* システムメッセージをここで処理します。
*/
public void dispatchMessage(Message msg) {
// callbackの優先度が最も高い
if (msg.callback != null) { // msg.callbackはRunnableです
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
MessageQueue & Message#
その名の通り、これはFIFO キューであり、ネイティブメソッドによってメモリポインタを使用して Queue を作成します
private native static long nativeInit();
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
Queue メソッド#
名称 | パラメータ:戻り値 | 説明 |
---|---|---|
enqueueMessage | Message, long : boolean | 指定された時間内にキューに追加します |
next | void : Message | 要素をキューから取り出します |
removeMessages | Handler, int, Object : void | 特定の要素を削除します |
removeMessages | Handler, Runnable, Object : void | 特定の要素を削除します |
MessageQueue#
MessageQueue は Queue であるため、FIFO の機能があります
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit();
}
private native static int nativeInit();
MessageQueue の作成#
Looper のコンストラクタに接続されており、Looper が作成されると MessageQueue も同時に作成されます
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
Looper が MessageQueue から Message を取得する#
// MessageQueue.java
Message next() {
// もし既に廃棄されていればmPtrは0に設定されます
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // 最初の反復中のみ-1
// 次のメッセージの時間
int nextPollTimeoutMillis = 0;
// 無限ループに入ります、つまりnextはブロックします
for (;;) {
... 省略部分
synchronized (this) {
// 次のメッセージを取得しようとします。見つかった場合は返します。
final long now = SystemClock.uptimeMillis();
Message prevMsg = null; // 前のメッセージ
Message msg = mMessages; // 現在のメッセージ
if (msg != null && msg.target == null) { // targetはHandlerです
// 送信可能なMessageを見つけました
do {
prevMsg = msg;
msg = msg.next; // 次のMessageに切り替えます
} while (msg != null && !msg.isAsynchronous());
}
// Messageを再度確認します
if (msg != null) {
// 時間を確認します
if (now < msg.when) { // このメッセージの時間がまだ来ていません
// 次のメッセージは準備ができていません。準備ができたときに起きるようにタイムアウトを設定します。
// 残り時間を計算します
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// 時間が来ており、Messageがあります
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next; // 前のMessageは現在のMessageです
} else {
mMessages = msg.next; // 次に使用するMessageに切り替えます
}
// 接続を切断します !! GCが検出して回収できるようにします !!! (到達性分析)
msg.next = null;
... debugメッセージ
msg.markInUse(); // このMessageが使用されたことをマークします
return msg;
}
} else {
// もうメッセージはありません。
nextPollTimeoutMillis = -1;
}
// すべての保留中のメッセージが処理された後に終了メッセージを処理します。
if (mQuitting) { // 終了するかどうかを確認します
dispose();
return null;
}
... 省略部分
}
... 省略部分
}
IdleHandler#
IdleHandler は MessageQueue 内のインターフェースで、MessageQueue にメッセージがないときに使用者に通知し、どのように処理するかを決定できます
// MessageQueue.java
public static interface IdleHandler {
// trueを返すと、設定したインターフェースが存続します
// falseを返すと、設定したインターフェースが削除されます
boolean queueIdle();
}
同様に next メソッドを分析すると、MessageQueue には最大 4 つの IdleHandler を設定できます
// MessageQueue.java
Message next() {
// もし既に廃棄されていればmPtrは0に設定されます
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // 最初の反復中のみ-1
// 次のメッセージの時間
int nextPollTimeoutMillis = 0;
// 無限ループに入ります、つまりnextはブロックします
for (;;) {
... 省略部分
synchronized (this) {
... 省略部分
// IdleHandlerが設定されているか確認します
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
if (pendingIdleHandlerCount <= 0) {
// 実行するIdleHandlerがありません。ループしてさらに待ちます。
mBlocked = true;
continue;
}
// 最大4つのIdleHandlerを設定できます
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// ループ
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // ハンドラへの参照を解放します
boolean keep = false;
try {
// メッセージがないときにインターフェースに通知します
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
// falseを返すと削除されます
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
... 省略部分
}
}
Looper#
これはメッセージの重要な要素(Handler、Message、MessageQueue)であり、その役割は駆動することです
名称 | パラメータ:戻り値 | 説明 |
---|---|---|
myLooper | void : Looper | 現在のスレッドを基に、現在の Thread の Looper を取得します |
getMainLooper | void : Looper | メインスレッドの Looper を取得します |
Looper - ThreadLocal#
最後に Looper は全体の Handler メカニズムを循環させる動力です。Looper があって初めて Handler は正常に情報を受信し、送信できます
Looper には MessageQueue が含まれています(コンストラクタ内で構築されています)
// ava/android/os/Looper.java
public final class Looper {
// プライベート関数
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
}
新しいスレッドを作成するたびに、そのスレッドのために Looper を準備する必要があります。さもなければ、Handler がメッセージを送信するとクラッシュします(上記で確認されると述べました)。作成には以下のステップがあります
Looper の準備作業(prepare 関数)
Handler を作成してメッセージを送信&受信します
Looper が動作を開始します(loop 関数)
class LooperThread extends Thread {
private Handler handler;
public void run() {
// 1
Looper.prepare();
// 2.
handler = new Handler() {
@Override
public void handleMessage(Message msg) {
//... メッセージを処理します
}
}
// 3.
Looper.loop();
}
}
ThreadLocal について簡単に説明します。ThreadLocal はスレッド隔離を実現するもので、スレッド Thread をキーとして保存し、値は各スレッドにコピーしたいデータです(各サブスレッドは 1 つのデータを保持します)
//android/os/Looper.java
public final class Looper {
// スレッドのデータをコピーするのはLooperです
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) // 1つのスレッドは1回だけprepareできます
throw new RuntimeException("Only one Looper may be created per thread");
}
// 現在のThreadにLooperを設定します
sThreadLocal.set(new Looper(quitAllowed));
}
}
Handler を作成すると、Handler のコンストラクタ内で現在のスレッドから Looper を取得します
// Handler
public Handler(@Nullable Callback callback, boolean async) {
...
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
// -----------------------------------------------------------------
// Looper
public static @Nullable Looper myLooper() {
// sThreadLocalは静的で、現在のThreadをキーとしてLooperを取得します
return sThreadLocal.get();
}
Looper & Handler#
Looper と Handler の関係は new Handler()このコンストラクタから見ることができ、myLooper メソッドを通じて現在のスレッドの ThreadLocal からそのスレッドが持つ Looper を取得します
// Handlerのコンストラクタ
public Handler() {
this(null, false);
}
public Handler(Callback callback, boolean async) {
... 無視
// 現在のスレッドのLooperを取得します
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 = callback;
mAsynchronous = async;
}
//Looper...
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
Looper & UI Thread#
Android の Activity のメインスレッドは ActivityThread の main () です
public static void main(String[] args) {
...
Looper.prepareMainLooper(); //"1. "
ActivityThread thread = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
//"2. "
sMainThreadHandler = thread.getHandler();
}
...
// ActivityThreadMainのイベントの終わり。
...
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
メインスレッドの Looper は閉じることができず、クラス同期を使用します
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
// 属性...
private static Looper sMainLooper; // Looper.classによって保護されています
Looper#loop
は、まず現在のスレッドが持つ Looper を取得し、次に Looper 内部にある MessageQueue を取得し、最後にメッセージキュー全体を無限にループします
// Looper.java
public static void loop() {
final Looper me = myLooper();
...
final MessageQueue queue = me.mQueue;
...
for (;;) {
//
Message msg = queue.next(); // ブロックする可能性があります
if (msg == null) {
// メッセージがない場合はメッセージキューが終了しています。
return;
}
...
try {
// dispatchMessageを呼び出しているのは誰かを解決します。targetはMessageQueue内のHandlerです
msg.target.dispatchMessage(msg);
end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
} finally {
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
...
msg.recycleUnchecked();
}
}
ThreadLocal#
ThreadLocal の機能は主にスレッド隔離を実現することです(スレッドデータを隔離するため)。簡単に言うと、ThreadLocal を Map のように考えることができます(実際には Map ではありませんが)。キーは Thread、値はジェネリックです
// 前述のLooper.prepare()内部でset()メソッドが使用されています
public void set(T value) {
Thread t = Thread.currentThread(); //"1. "
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
// 前述のHandler()コンストラクタ内部でget()メソッドが使用されています
public T get() {
Thread t = Thread.currentThread(); //"2. "
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
これは現在呼び出している Thread.currentThread をキーとして保存し、値はジェネリックです
現在のスレッドに基づいて値を取得します