fwrite

fwrite

好好生活
twitter
github
email

Handlerメカニズム

概述#

クラス説明
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);
    }

}

情報送信方法#

名称パラメータ:戻り値説明
postRunnable : booleanRunnable オブジェクトを Message として受け取ります。
postAtTimeRunnable, long : booleanRunnable オブジェクトを Message として受け取り、待機する規定時間に Message を送信します。
sendEmptyMessageint : boolean使用者が指定した int 情報を送信し、使用者自身が処理します。
sendMessageAtTimeMessge, int : boolean使用者が指定した int 情報を送信し、使用者自身が処理します。
sendMessageDelayedMessge, 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 メソッド#

名称パラメータ:戻り値説明
enqueueMessageMessage, long : boolean指定された時間内にキューに追加します。
nextvoid : Message要素をキューから取り出します。
removeMessagesHandler, int, Object : void特定の要素を削除します。
removeMessagesHandler, 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() {

        // もし既にdeposeされていれば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;

                        ... デバッグメッセージ

                        msg.markInUse();    // このMessageが使用されたことをマークします。
                        return msg;
                    }
                } else {
                    // もうメッセージはありません。
                    nextPollTimeoutMillis = -1;
                }

                // すべての保留中のメッセージが処理された後、終了メッセージを処理します。
                if (mQuitting) {        // 終了するかどうかを確認します。
                    dispose();
                    return null;
                }

                ... 省略IdleHandler
            }

            ... 省略部分
        }

IdleHandler#

IdleHandler は MessageQueue 内のインターフェースで、MessageQueue にメッセージがないときに使用者に通知し、どのように処理するかを決定できます。

// MessageQueue.java

    public static interface IdleHandler {
        // trueを返すと、設定したインターフェースが存続します。
		// falseを返すと、設定したインターフェースが削除されます。
        boolean queueIdle();
    }

同様に next メソッドを分析すると、MessageQueue には最大 4 つの IdleHandler を設定できることがわかります。

// MessageQueue.java

    Message next() {

        // もし既にdeposeされていれば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)であり、その役割は駆動することです。

名称パラメータ:戻り値説明
myLoopervoid : Looper現在のスレッドを主にして、現在の Thread の Looper を取得します。
getMainLoopervoid : 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 をキーとして保存し、値は各スレッドにコピーするデータです(各子スレッドはデータを保持します)。

//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)     // 一つのスレッドは一度だけ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 をキーとして保存し、値はジェネリックです。
現在のスレッド(currentThread)に基づいて値を取得します。

読み込み中...
文章は、創作者によって署名され、ブロックチェーンに安全に保存されています。