ソースコードの観点からハンドラーを分析する

Java開発者にとって、マルチスレッドとスレッド間通信は非常に重要であり、習得する必要のある不可欠な知識です。AndroidはJava言語に基づいて開発されているため、マルチスレッドとスレッド間通信もAndroid開発者にとって非常に重要ですが、開発プロセスのAndroid開発者として、マルチスレッドとの深い接触はあまりないようです。スレッド。プール。これは、Androidのスレッド間通信の重要な知識ポイントがGoogle開発者によってHandlerにカプセル化されており、Handlerがスレッド間通信を実装しているためです。開発者は、スレッド間通信を簡単に実現するために、ハンドラーのsendMessageを呼び出すだけで済みます。ハンドラーの使用とその実装原則を習得することが特に重要であることがわかります。それでこの記事は生まれました。

Androidの開発プロセスでは、時間のかかる操作(ネットワークリクエスト、データベースクエリなど)をサブスレッドに入れて実装し、実行結果をメインスレッドに渡し、次にメインスレッドに渡すことがよくあります。 UI操作を更新します。このプロセスにはスレッド間の通信が含まれます。通常の方法は、handler.sendMessageを使用して、子スレッドの実行結果をメインスレッドに通知することです。実際、ハンドラーは子スレッドとメインスレッドの間で通信できるだけでなく、任意の2つのスレッド間のデータ転送を実現することもできます。ハンドラーのサブスレッドとメインスレッド間のメッセージ転送の実装に関しては、それらを多く使用します。基本的に、ハンドラーはsendMessageメソッドを呼び出して、メインスレッドに転送されるデータメッセージを転送します。ここでは詳しく説明しません。

次に、handler.sendMessageメソッドから始めて、ハンドラーのソースコードを分析します。コードは次のように表示されます。

public final boolean sendMessage(@NonNull Message msg) {
    return sendMessageDelayed(msg, 0);
}

sendMessageメソッドを呼び出すだけでなく、postメソッドを呼び出してスレッド間通信を実現することもできます。postメソッドのソースコード実装を見てみましょう。

public final boolean post(@NonNull Runnable r) {
    return  sendMessageDelayed(getPostMessage(r), 0);
}

sendMessageメソッドまたはpostメソッドに関係なく、sendMessageDelayedメソッドが常に呼び出されることがわかりました。したがって、どのような使用法であっても、ソースコードレベルで呼び出されるメソッドは同じです。したがって、sendMessageDelayedメソッドの元のコードを直接分析できます。コードは次のように表示されます。

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

sendMessageAtTimeメソッドがsendMessageDelayedメソッドで返されることがわかります。このメソッドの意味は、以前に送信されなかったすべてのメッセージを指定された時間にメッセージキューに追加することです。sendMessageAtTimeメソッドで何が行われるかを見てみましょう。コードは次のように表示されます。

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(
                this + " sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    // 调用enqueueMessage将消息入队到消息队列中
    return enqueueMessage(queue, msg, uptimeMillis);
}

sendMessageAtTimeメソッドは、最終的にenqueueMessageメソッドを呼び出します。キューのデータ構造に精通している学生は、エンキューがエンキューに使用されることを知っているかもしれません。したがって、文字通り、enqueueMessageメソッドは、メッセージmsgをキューに入れることに関連している可能性があります。この推測でソースコードを読み続けて、この推測を確認できるかどうかを確認することもできます。次に、このenqueueMessageメソッドの実装を見てみましょう。コードは次のように表示されます。

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        // 调用了MessageQueue的enqueueMessage方法
        return queue.enqueueMessage(msg, uptimeMillis);
}

MessageQueue.enqueueMessageが返されるため、ロジックはHandlerからMessageQueueに切り替えられます。今、私たちはソースコードの分析を続けることを急いでいません。前述のように、Handlerは任意の2つのスレッド間の通信に適用できます。次に、Handlerを使用して2つの子スレッド間の通信を実装するコードを記述します。コードは次のように表示されます。

public void handlerTestMethod() {
        Thread childThread1 = new Thread() {
            @Override
            public void run() {
//                Looper.prepare();
                handler = new Handler() {
                    @Override
                    public void handleMessage(@NonNull Message msg) {
                        super.handleMessage(msg);
                        Log.e("TAG", "childThread handleMessage received:" + msg.obj);
                        Toast.makeText(MainActivity.this,
                                ((String) msg.obj), Toast.LENGTH_SHORT).show();
                    }
                };
//                Looper.loop();
            }
        };

        Thread childThread2 = new Thread() {
            @Override
            public void run() {
                try {
                    // 模拟耗时操作
                    Thread.sleep(3000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                // 线程2给线程1发消息
                Message message = handler.obtainMessage();
                message.obj = "Hello thread1";
                handler.sendMessage(message);
            }
        };

        childThread1.setName("线程1");
        childThread1.start();
        childThread2.setName("线程2");
        childThread2.start();
    }

コードは比較的単純なので、これ以上説明しません。コメントアウトされているコードが2行あることに注意してください。彼を無視して、このコードを実行し(エミュレーターを使用するのが最善であることに注意してください)、次のエラーを見つけましょう。

写真に描かれている線がこのエラーの原因です。(Huaweiの携帯電話を使用して1時間以上カードをデバッグしました。エラーが報告されていないことがわかりました。コードに問題があるのではないかといつも思っていました。コードを数回チェックしたところ、コードがテストマシンの原因である可能性があることに気付く前に修正しました。シミュレータをインストールすると、エラーメッセージが表示されることがありますが、これは本当に不正行為です!)エラーメッセージに記載されています。

Looper.prepare()を呼び出さなかったスレッドThread [線程1,5、main]内にハンドラーを作成できません

Looper.prepare()を呼び出さなかったスレッドでハンドラーを作成できないことを意味しているようです。ここで、ルートに戻って理由を見つけます。これはハンドラーの作成時のエラーであるため、新しいHandler()メソッドをフォローアップし、最終的に呼び出されるメソッドコードは次のとおりです。

public Handler(@Nullable Callback callback, boolean async) {
        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());
            }
        }
        
        // 关键代码1。。。
        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.myLooperメソッドが呼び出され、このメソッドの戻り値mLooperが空であることがわかります。空の場合、例外がスローされます。異常情報は上に掲載されています。上図の異常情報は、ルーパータイプの空のmLooperオブジェクトが原因であることがわかります。次に、Looper.myLooperメソッドのコードを見てください。

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

このメソッドは、ThreadLocal.get()によって取得されたオブジェクトを返します。ThreadLocalについての詳細な説明はしません。ここでは、特に詳細な記事「ThreadLocalソースコードの解釈」をお勧めします。ThreadLocal.get()メソッドはLooperのmyLooperメソッドで呼び出されます。ThreadLocalはThreadLocalMapを内部的に維持します。これは、キーがThreadLocalであるコードに配置された値のマップセットと単純に見なすことができます。Looper.prepare()メソッドは、上記のエラーレポートに記載されています。次に、このメソッドの実装を見てみましょう。コードは次のように表示されます。

private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));
    }

Looper.prepare()メソッドでは、最初に、ThreadLocalのgetメソッドの戻り値に対して空でない判断が行われます。ThreadLocal.get()は、現在のスレッドのスレッドローカル変数のコピーの値を返します。 。この値がnullでない場合、例外がスローされ、値がnullの場合にのみLooperオブジェクトがThreadLocalに設定されます。したがって、ここを見ると、子スレッドの新しいハンドラー(新しいハンドラーはLooper.myLooperメソッドを呼び出す)の前に、Looper.prepare()メソッドが呼び出されない(このメソッドはルーパーをThreadLocalサブジェクトに設定する)理由を理解できます。エラーの理由を報告してください。

Looper.myLooperメソッドはThreadLocalのgetメソッドと呼ばれ、Looper.prepareメソッドはThreadLocalのsetメソッドと呼ばれることを説明しました。(ThreadLocalのsetメソッドとgetメソッドの分析については、上記の推奨記事で非常に明確にしました)。ThreadLocal.getメソッドは、スレッドが一意にバインドされているルーパーのみを返すことができるようにします。これは、Looper.myLooperメソッドがLooperの一意性を保証し、Looper.myLooperがハンドラーの構築メソッドで呼び出されることを意味します。したがって、これは、LooperとHandlerが1対1で対応していることも意味します。(これは、スレッド内に1つのハンドラーのみが作成される場合です)。ただし、スレッドとハンドラーは1対1の関係ではありません。スレッドは複数のハンドラーを作成できます。

ここで、Looper.prepareメソッドの分析を続けます。sThreadLocal.set()メソッドを呼び出すと、Looperオブジェクトが新しくなります。次に、Looper構築メソッドを見てみましょう。コードは次のように表示されます。

private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

Looperの構築メソッドは、MessageQueueオブジェクトを作成します。グローバルな外観を見ることができます。MessageQueueのコンストラクターは、Looperでのみ呼び出されます。上記では、Looperの一意性を分析し、MessageQueueのコンストラクターがLooperのコンストラクターで呼び出されるため、MessageQueueの一意性が保証されます。

この時点で、スレッド、Looper、およびMessageQueueの間に1対1の対応があることがわかります。

子スレッドで作成されたハンドラーが事前にLooper.prepareメソッドを呼び出さないと、例外がスローされます。上記の分析の結果、誰もが明確だと思います。しかし、問題が発生しています。メインスレッドでHandlerを使用するときに、Looper.prepare()メソッドとLooper.loop()メソッドを呼び出すことはありません。例外をスローせずに、子スレッドによるメインスレッドへのメッセージの配信を実行したのはなぜですか。スレッドは非常にうまくいっていますか?機能?実はこの質問はこんな感じです。Appは初期化時にActivityThreadのメインメソッドを実行することがわかっており、このメインメソッドはすでにLooper.prepareとLooper.loopを処理しています。ソースコードを簡単に見ることができます:

public static void main(String[] args) {
        
        //。。。。
        // 调用了Looper的prepareMainLooper方法
        Looper.prepareMainLooper();

       
        // 。。。。

        ActivityThread thread = new ActivityThread();
        thread.attach(false, startSeq);

        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }

        if (false) {
            Looper.myLooper().setMessageLogging(new
                    LogPrinter(Log.DEBUG, "ActivityThread"));
        }

        // End of event ActivityThreadMain.
        Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
        // 调用了Looper.loop方法
        Looper.loop();

        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

Looper.prepareMainLooper()メソッドがソースコードで呼び出され、Looper.loop()メソッドも呼び出されます。したがって、メインスレッドでHandlerを使用するためにLooper.prepare()メソッドを呼び出す必要はありません。

上記のサブスレッドがHandlerを使用する場合、2行のコードがコメントアウトされました。Looper.prepare()について説明したばかりで、Looper.loopメソッドについては触れていません。今、私はついにそれを取り出して簡単に分析する機会があります。まず、上記のコードがLooper.loopメソッドを呼び出さずにLooper.prepareのみを呼び出す場合、操作の結果、プログラムはエラーを報告しませんが、効果はありません。この時点で、Looper.loop()のコメントを放すと、再度実行した結果、Toastが正常にポップアップし、Logも出力されます。したがって、Looper.loop()メソッドがMessageに関連している可能性があると推測できます。それでは、この推測でソースコードを見てみましょう。コードは次のように表示されます。

public static void loop() {
        // 关键代码1
        final Looper me = myLooper();
        if (me == null) {
            throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
        }
        final MessageQueue queue = me.mQueue;

           
        for (;;) {
            // 关键代码2
            Message msg = queue.next(); // might block
            if (msg == null) {
                // No message indicates that the message queue is quitting.
                return;
            }

           
            // 代码省略。。。

            try {
                // 关键代码3
                msg.target.dispatchMessage(msg);
                。。。。
            } catch (Exception exception) {
                if (observer != null) {
                    observer.dispatchingThrewException(token, msg, exception);
                }
                throw exception;
            } finally {
                ThreadLocalWorkSource.restore(origWorkSource);
                if (traceTag != 0) {
                    Trace.traceEnd(traceTag);
                }
            }
            
            // 关键代码4。。。

            msg.recycleUnchecked();
        }
    }

ループメソッドのコードは非常に長く、いくつかの重要なポイントだけをマークしました。

キーコード1:上記で分析したように、myLooperメソッドはThreadLocal.getメソッドを呼び出して、Looperの一意性を保証します。

キーコード2: queue.next()メソッドの戻り値は、メッセージタイプのmsgオブジェクトです。したがって、MessageQueue.nextメソッドは、メッセージがキューを離れるメソッドである必要があります。

キーコード3:これは設計の明るい点です。後で分析します。

キーコード4:これも後で分析されます。

これまで、Looperの注意の一般的な分析を終了しました。次に、Handler MessageQueue(メッセージキュー)の使用におけるもう1つの重要な役割について見ていきます。MessageQueueのenqueueMessage(メッセージの追加)メソッドとnext(メッセージの読み取り)メソッドでは、メッセージの操作部分はスレッドセーフです。これらのコードはすべて、Synchronizedキーワードによって変更されます。MessageQueueのメッセージの操作は、実際には生産者/消費者モデルであり、生産者/消費者モデルのブロックには2つの状況があります。

最初:現在のキューがいっぱいの場合は、この時点でenqueueMessage(エンキュー)操作を実行します。この時点でブロッキングが発生します。

2番目:現在のキューがすでに空の場合は、この時点で次の(デキュー)操作を実行します。このとき、ブロッキングも発生します。

enqueueMessageとMessageQueueの次のメソッドを分析する前に、まずエピソードに行きましょう。メッセージにはw​​henフィールドがあります。名前が示すように、このフィールドは時間に関連している必要があります。ソースコードのこのフィールドの説明を見てみましょう。

/**
     * The targeted delivery time of this message. The time-base is
     * {@link SystemClock#uptimeMillis}.
     * @hide Only for use within the tests.
     */
    @UnsupportedAppUsage
    @VisibleForTesting(visibility = VisibleForTesting.Visibility.PACKAGE)
    public long when;

翻訳すると、おそらく次のことを意味します:このメッセージの目標配信(送信)時間。sendMessageメソッドを呼び出すと、最終的にsendMessageAtTimeメソッドが呼び出されることがわかっています。このメソッドの2番目のパラメーターであるlong uptimeMillsは、このメッセージを遅延させる時間を表します。このパラメーターは、enqueueMessageメソッドを介してメッセージuptimeMillsを遅延させた後、メッセージをMessageQueueキューに追加します。このプロセスは、次の図からより直感的に理解できます。

このプロセスの後、最終的に優先キューを時系列で並べ替えることができ、メッセージの実行がキューの先頭に配置されます。

msg.whenの概念を理解した後、最初にMessageQueue.next()メソッドを分析しましょう。主なコードは次のとおりです。

Message next() {
       

        // 关键变量1
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        // 关键变量2:下次执行需要等待的时间
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            // 等待nextPollTimeoutMillis时间
            nativePollOnce(ptr, nextPollTimeoutMillis);

            synchronized (this) {
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                if (msg != null && msg.target == null) {
                    // Stalled by a barrier.  Find the next asynchronous message in the queue.
                    do {
                        prevMsg = msg;
                        msg = msg.next;
                    } while (msg != null && !msg.isAsynchronous());
                }
                if (msg != null) {
                    // 当前还没到msg的执行时刻
                    if (now < msg.when) {
                        // Next message is not ready.  Set a timeout to wake up when it is ready.
                        // 将要等待的时间赋值给变量nextPollTimeoutMills
                        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                    } else {
                        // Got a message.
                        mBlocked = false;
                        if (prevMsg != null) {
                            prevMsg.next = msg.next;
                        } else {
                            mMessages = msg.next;
                        }
                        msg.next = null;
                        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                        msg.markInUse();
                        // 返回msg
                        return msg;
                    }
                } else {
                    // No more messages.
                    nextPollTimeoutMillis = -1;
                }

                
                // 。。。
                                
                // 注释1:关键代码
                if (pendingIdleHandlerCount < 0
                        && (mMessages == null || now < mMessages.when)) {
                    pendingIdleHandlerCount = mIdleHandlers.size();
                }
                // 注释2:关键代码
                if (pendingIdleHandlerCount <= 0) {
                    // No idle handlers to run.  Loop and wait some more.
                    // 注释3:将mBlocked赋值为true
                    mBlocked = true;
                    continue;
                }

                // 。。。                

            }

            //。。。

            // Reset the idle handler count to 0 so we do not run them again.
            pendingIdleHandlerCount = 0;

            // While calling an idle handler, a new message could have been delivered
            // so go back and look again for a pending message without waiting.
            nextPollTimeoutMillis = 0;
        }
    }

次のメソッドには、一部が省略されていても多くのコードが含まれています。MessageQueueには、分析の焦点となるブロッキングウェイクアップメカニズムがあります。

nextPollTimeoutMills変数は、次のメッセージの実行の待機時間を表します。次のメソッドの最後に、この変数に値0が割り当てられていることがわかります。この場合、nativePollOnce(ptr、nextPollTimeoutMills)が無意味であることを意味しますか?次に、この質問に答えて、ソースコードで答えを見つけます。

まず、初めてトラバーサルを入力します。pendingIdleHandlerCount= -1で、nextPollTimeoutMills変数に値があります。これは、この時点でメッセージがnullではなく、MessageQueueにデータがあることを意味します。したがって、この時点のコードは次のとおりです。

if (pendingIdleHandlerCount < 0
        && (mMessages == null || now < mMessages.when)) {
    pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
    // No idle handlers to run.  Loop and wait some more.
    mBlocked = true;
    continue;
}

2番目のif判定を実行し、最初にmBlockedをtrueに割り当ててから、続行を実行すると、現在のforループがジャンプアウトするため、メソッドの最後でのコードnextPollTimeoutMills = 0の割り当ては実行されません。

次に、参加するとき、MessageQueueメッセージキューは空のコードです。

if (msg != null) {
    if (now < msg.when) {
        // Next message is not ready.  Set a timeout to wake up when it is ready.
        nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
    } else {
        // Got a message.
        mBlocked = false;
        if (prevMsg != null) {
            prevMsg.next = msg.next;
        } else {
            mMessages = msg.next;
        }
        msg.next = null;
        if (DEBUG) Log.v(TAG, "Returning message: " + msg);
        msg.markInUse();
        return msg;
    }
} else {
    // No more messages.
    nextPollTimeoutMillis = -1;
}

elseのみを実行し、nextPollTimeoutMills変数を-1に割り当てることができます。分析を続けると、上記の判断の場合は同じ2つです。

if (pendingIdleHandlerCount < 0
        && (mMessages == null || now < mMessages.when)) {
    pendingIdleHandlerCount = mIdleHandlers.size();
}
if (pendingIdleHandlerCount <= 0) {
    // No idle handlers to run.  Loop and wait some more.
    mBlocked = true;
    continue;
}

最初のifが実行され、MidleHandlers.size()がpendingIdleHandlerCount変数に割り当てられます。ArrayList <IdleHandler>タイプのmIdleHandlersコレクションの追跡を続行します。ここで、mIdleHandlers.add(...)コードが実行されます。

public void addIdleHandler(@NonNull IdleHandler handler) {
    if (handler == null) {
        throw new NullPointerException("Can't add a null IdleHandler");
    }
    synchronized (this) {
        mIdleHandlers.add(handler);
    }
}

MessageQueueのaddIdleHandlerメソッドはActivityThreadでのみ呼び出され、自分で呼び出すことはありません。したがって、ほとんどの場合、mIdleHandlers.size()は0であり、したがって、変数pendingIdleHandlerCountの値も0であるため、今回は次のコードを使用します。

if (pendingIdleHandlerCount <= 0) {
    // No idle handlers to run.  Loop and wait some more.
    mBlocked = true;
    continue;
}

それは引き続き実行されるため、コードは現在のforループからジャンプして、変数mBlockedをtrueに割り当てます。

この場合、メッセージキューMessageQueueは空であり、nextPollTimeoutMillsの値は-1です。nativePollOnce(ptr、nextPollTimeoutMills)が時間内に通過するメソッドは-1です。このメソッドは、メインスレッドをアイドル状態に保ちます(ウェイクアップするタイミングについては、最初にサスペンスを残します)。したがって、この時点で、メインスレッドのLooper.loopメソッドのforループ内のMessage msg = queue.next();は常にここでブロックされます。このとき、メインスレッドは他のタスクを処理でき、msgは常にブロックしてメッセージが表示されない場合は待機します。そのため、メインスレッドはハンドラーを作成する必要はありませんが、常に実行できます。ルーパーは常に機能しているため、メインスレッドは常に中断され、終了することはありません。

ブロック後に目を覚ます

次のメソッドを分析した後、enqueueMessageメソッドを見てみましょう。コードは次のとおりです。

boolean enqueueMessage(Message msg, long when) {
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }

        //。。。

        synchronized (this) {
           
            //。。。

            msg.when = when;
            Message p = mMessages;
            boolean needWake;
            // 队列的队头为null时,将mBlocked的值赋给了needWake变量
            if (p == null || when == 0 || when < p.when) {
                // New head, wake up the event queue if blocked.
                msg.next = p;
                mMessages = msg;
                needWake = mBlocked;
            } else {
               // 。。。
            }

            // We can assume mPtr != 0 because mQuitting is false.
            if (needWake) {
                // 唤醒。。。
                nativeWake(mPtr);
            }
        }
        return true;
    }

enqueueMessageメソッドには多くのコードがありません。メインスレッドをウェイクアップする場合は、最後のnativeWakeメソッドを呼び出す必要があります。このメソッドの呼び出し条件はneedWakeがtrueであるため、上記で分析したmBlockedがtrueである場合、elseではなくifが実行された場合にのみneedWakeに割り当てられます。この状況の条件は、p(メッセージキューの先頭)が空の場合、または時刻がメッセージの先頭の実行時間に達していない場合です。

条件が満たされた場合に実行されるコードは、最初にチームの現在のヘッドを次回実行されるメッセージに変更し、参加したいメッセージmsgをチームのヘッドに割り当ててから、mBlockedをneedWakeに割り当てます。

この時点で、MessageQueueのエンキューとデキューによって引き起こされるスレッドのブロックとウェイクアップの分析が完了します。

上記のLooper.loopメソッドを分析すると、まだ導入されていない2つの小さなテールがあります。それを分析してみましょう。

1つ目はmsg.target.dispatchMessageメソッドであり、msg.targetは実際にはハンドラーであるため、ここで説明するHandler.dispatchMessageメソッドです。

コードは次のように表示されます。

public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

それは意味します:

         最初:最初に、現在のメッセージにコールバックが定義されているかどうかを判断し、定義されている場合は、独自のコールバックを呼び出します。

         2番目: msgはcallBackを定義せず、現在のハンドラーがcallBackを定義しているかどうかを判断します。定義している場合は、現在のハンドラーのcallBackのhandlerMessageメソッドを呼び出します。

         3番目:最初の2つのcallBackが定義されていない場合、HandlerのhandleMessageメソッドが呼び出されてメッセージが処理されます。

ハンドラーによるメッセージの処理は、イベント配布プロセスでのタッチイベントの処理の原則と非常によく似ています。実際、これはすべて責任の連鎖モデルを使用しており、これは連鎖呼び出しの原則とも言えます。幅広いアプリケーションがあり、その多くはRxJavaで使用されています。その利点は、メッセージ処理の自由度を大幅に向上させることです。

最後に、msg.recycleUnCheckedメソッドを見てみましょう。コードは次のように表示されます。

void recycleUnchecked() {
        // Mark the message as in use while it remains in the recycled object pool.
        // Clear out all other details.
        flags = FLAG_IN_USE;
        what = 0;
        arg1 = 0;
        arg2 = 0;
        obj = null;
        replyTo = null;
        sendingUid = -1;
        when = 0;
        target = null;
        callback = null;
        data = null;

        synchronized (sPoolSync) {
            if (sPoolSize < MAX_POOL_SIZE) {
                next = sPool;
                sPool = this;
                sPoolSize++;
            }
        }
    }

recycleUnCheckedメソッドの役割は、使用中の可能性のあるメッセージをリサイクルすることです。そのアプローチは、メッセージフィールドを空にすることです。また、現在のsPool(メッセージバッファプール)のサイズが許可されているキャッシュの最大数よりも小さい場合は、リサイクルするメッセージの次のフィールドをsPoolにポイントし、sPoolをリサイクルするメッセージオブジェクトにポイントします。メッセージがsPoolバッファpool.headに配置されていること。

sPoolは何に使用されますか?

これは、ユーザーがメッセージオブジェクトをキャッシュするために使用します。ハンドラーを使用する場合、通常はHandler.obtainMessageを介してメッセージオブジェクトを取得し、Message.obtainメソッドが内部的に呼び出されます。新しいメッセージを作成するための新しいメッセージメソッドがないのはなぜですか。ここにオブジェクト?? 次に、getメソッドのコードを見てください。

   /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

メッセージオブジェクトが必要な場合、sPoolがnullでない場合、現在のsPool(メッセージオブジェクト)が返され、sPoolは前のsPoolの次のノードを指し、次に返されたの次のノードを指すことがわかります。メッセージが空(リンクされていないリンクリスト)を指している場合、sPoolSizeは現在キャッシュされているメッセージオブジェクトの数を記録します。sPoolが空の場合、キャッシュされたメッセージはありません。このとき、新しいメッセージオブジェクト(新しいメッセージ)を作成する必要があります。

メッセージオブジェクトを取得する設計では、フライウェイトモデルを使用します。これの利点は、バッファプールに参加した後、毎回新しいメッセージオブジェクトを作成する必要がないことです。これにより、メモリの断片化が減少し、メモリリークのリスクが効果的に減少します。

この時点で、Handlerの元のコードの分析が終了しました。論理は理解できますが、少し非論理的で明確なカタログがありません。いつも明快さが足りないと感じています。本当に何かがおかしいです。細心の注意を払っていただければ幸いです。良いコメントがあれば、メッセージを残してください。

ここでは、翔雪教室の先生方に感謝の意を表します。ハンドラーの説明は非常に徹底的で適切です。

おすすめ

転載: blog.csdn.net/zhourui_1021/article/details/105958936