Androidメッセージメカニズムの原理を深く理解する


テンセントクラウドコミュニティへようこそ、テンセントの大量の技術実践干物をもっと獲得してください.
作者:汪毅雄
ガイド:Androidのメッセージメカニズムの原理を述べ,JavaからNativeコードまで整理し,その中で用いられるEpollモデルと結びつけて紹介した.
Androidのメッセージングは、システムの核心機能であり、どのように使うかについてはかなり熟知していると信じています.ここで簡単に言います.メッセージ・メカニズムの重要なクラスの機能は、次のように粗雑に考えられます.
Handler:メッセージ処理者
Looper:メッセージスケジューラ
MessageQueue:メッセージを格納する場所
プロセスの使用:
Looper.prepare > #$%^^& > Looper.loop(デッドループ)---loopからメッセージ>Handler処理
はい、ソースコードを直接見ましょう.
Javaレイヤ
メッセージメカニズムは、スレッドを伴うものであり、すなわち、上記のいくつかのクラスは、いずれのスレッドにおいてもインスタンス化することができる.
まずLooperを見ましょう.メインスレッドを例にとると、Androidプロセスが初期化され、prepareMainLooperが呼び出されます.
public static void prepareMainLooper() {
        prepare(false);
        synchronized (Looper.class) {
            ...
            sMainLooper = myLooper();
        }
    }
 private static void prepare(boolean quitAllowed) {
        ...
        sThreadLocal.set(new Looper(quitAllowed));
    }
 private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

以上の方法はLooper初期化であり、メインスレッドLooperであれば終了不可能なMessageQueueを作成し、looperインスタンスをスレッド独立(ThreadLocal)変数に入れる.
Looper#loop
public static void loop() {
        final Looper me = myLooper();
        final MessageQueue queue = me.mQueue;
        for (;;) {
            Message msg = queue.next(); 
            if (msg == null) {
                return;
            }
            ...
            try {
                msg.target.dispatchMessage(msg);
            } 
            ...
            msg.recycleUnchecked();
        }
    }

Looper prepareの後でloopすることができて、loopはとても簡単で、ずっとqueueの中でメッセージを持っていればいいので、targetつまりHandlerに渡して処理します.皆さんはこのような死の循環を不思議に思うかもしれませんが、実行するのはあまり乱暴ではありませんか?実はこの解決策はqueueです.next!!!後で話します.
Handler#dispatchMessage
public void dispatchMessage(Message msg) {
        if (msg.callback != null) {
            handleCallback(msg);
        } else {
            if (mCallback != null) {
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

handler受信後、messageのcallbackが空でないことが判明した場合、callbackのみが処理されます.(ちなみに、私たちが使っているhandler.post(Runnable)はたくさんありますが、実はこのRunnableはここのcallback、つまりpostのRunnableは実質的に優先度の高いMessageです)、なければhandler自体に渡すcallback処理(handler初期化時にcallback方式で構築可能)を試み、私たちがよく使うhandleMessageメソッドはありません.ここではよく書き換える方法です.
さらにメッセージの送信については,handlerがsendMessageメソッドを呼び出すのが一般的であるが,最終的にはこのメソッドがここまで来る.
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
        msg.target = this;
        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

MessageQueueに渡す
boolean enqueueMessage(Message msg, long when) {
        。。。
        synchronized (this) {
            。。。
                Message prev;
                for (;;) {
                    prev = p;
                    p = p.next;
                    if (p == null || when < p.when) {
                        break;
                    }
                    。。。
                }
                msg.next = p; // invariant: p == prev.next
                prev.next = msg;
            if (needWake){
               nativeWake(mPtr);
            }
        }
        return true;
    }

MessageQueueはメッセージをキューに挿入し、キュー内の各メッセージのポインタを順番に変更します.
あれ、Java層だけでメッセージメカニズム全体が通じるようですが、nativeコードはどこですか?何の役に立つの?
しかし、先ほどLooper初期化に言及した時にもMessageQueueが新設されます
MessageQueue(boolean quitAllowed) {
        mQuitAllowed = quitAllowed;
        mPtr = nativeInit();
    }

では、最初のnative方法が出てきました.このとき、MessageQueueこそメッセージメカニズム全体の核心であると推測できます.
Native層
上のJava層のコードに続いて、MessageQueueが構築されるとnativeInitが調整されます.
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    。。。
}
NativeMessageQueue::NativeMessageQueue() :
        mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    mLooper = Looper::getForThread();
    if (mLooper == NULL) {
        mLooper = new Looper(false);
        Looper::setForThread(mLooper);
    }
}

nativeレイヤがinitメソッドを呼び出すと、nativeレイヤにnative Looperが構築されます!native Looperの初期化を見てみましょう
Looper::Looper(bool allowNonCallbacks) :
        mAllowNonCallbacks(allowNonCallbacks), mSendingMessage(false),
        mPolling(false), mEpollFd(-1), mEpollRebuildRequired(false),
        mNextRequestSeq(0), mResponseIndex(0), mNextMessageUptime(LLONG_MAX) {
    mWakeEventFd = eventfd(0, EFD_NONBLOCK | EFD_CLOEXEC);
    ...
    rebuildEpollLocked();
}

ここでeventfdが作成され、コードは最新の8.0から来ており、この部分は5.0 pipeパイプのmWakeReadPipeFdとmWakeWritePipeFdとは少し異なり、前者は待機/応答、後者は読み取り/書き込みである.androidの選択方法の違いにすぎないので、詳しくは言いません.
void Looper::rebuildEpollLocked() {
    。。。
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    LOG_ALWAYS_FATAL_IF(mEpollFd < 0, "Could not create epoll instance: %s", strerror(errno));
    struct epoll_event eventItem;
    memset(& eventItem, 0, sizeof(epoll_event)); 
    eventItem.events = EPOLLIN;
    eventItem.data.fd = mWakeEventFd;
    int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);
    。。。
}

さらにrebuildEpollLockedという方法では、epoll_createはepoll専用のファイル記述子、EPOLL_を作成しました.SIZE_HINTは、mEpollFdでモニタできる最大ファイル記述子数を表します.最後にepollを呼び出すctlは、mWakeEventFdファイル記述子のEpollイベントを監視する.すなわち、mWakeEventFdにコンテンツが読み取り可能である場合、現在待機中のスレッドを起動する.
ここで知らない人はめまいがするかもしれませんが、Android native層はEpollモデルを使っています.Epollモデルとは何ですか?まず簡単に紹介します.
Epoll(必見!!)
なぜ導入するのですか?
Looperでloopの時に言及したように、androidは簡単に乱暴に本当に何もしていない死の循環を実行することはありません.さっき言ったように、問題はqueueです.next.Epollがやったことは、もしあなたのqueueに実行できるメッセージがなければ、休んでもいいです.メッセージがあるなら、私が教えてあげます.これnextは「ブロック」(休眠)がここにある.
Epollの簡単な紹介
1、従来のブロック型I/O(書きながら読む)では、1つのスレッドで1つのIOストリームしか処理できません.
2、一つのスレッドが複数のストリームを処理したい場合、非ブロック、ポーリングI/O方式を採用することができるが、従来の非ブロックが複数のストリームを処理する場合、すべてのストリームを遍歴するが、すべてのストリームにデータがなければ、CPUを無駄にする..そこでselectとepollの2つの一般的なエージェント方式が現れた.
3、selectはそのような無差別ポーリングのエージェント方式である.epollはEvent pollと理解でき,すなわちエージェントがストリームをエージェントする場合もイベントを伴うため,対応するイベントがあれば無差別ポーリングを避けることができる.
4、その通常の操作は:epoll_create(epollを作成)、epoll_ctl(epollにストリームのイベントを追加/削除)、epoll_wait(イベントの発生を一定時間待つ)
eventItem.events = EPOLLIN;
eventItem.data.fd = mWakeEventFd;
int result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeEventFd, & eventItem);

では、Looper初期化のコードと結びつけて、epollがここで何をしたのかを読みましょう.
mEpollFdエージェントへ、登録、mWakeEventFdストリームというデータ流入イベント(EPOLLIN)
これで皆さんもわかるでしょう...
次にMessageQueueは初期化後、nativeでLooperを作成します.メッセージの送信とnativeレイヤでの表現の抽出を続けた.実はnative層は主にメッセージのスケジューリングを担当しています.例えば、いつスレッドをブロックし、いつスレッドを起動し、CPUの浪費を避けることができます.
native送信
送信はnativeが比較的簡単で、handlerがメッセージを送信すると、MessageQueueのenqueueMessageに、スレッドがブロックされている場合はnativeWakeを呼び出してスレッドを喚起します.
void NativeMessageQueue::wake() {
    mLooper->wake();
}
 void Looper::wake() {
    。。。
    uint64_t inc = 1;
    ssize_t nWrite = TEMP_FAILURE_RETRY(write(mWakeEventFd, &inc, sizeof(uint64_t)));
    if (nWrite != sizeof(uint64_t)) {
      。。。。
    }
}

ここでTEMP_FAILURE_RETRYはマクロ定義であり、その名の通りmWakeEventFdストリームに無駄なデータを書き続けて成功するまでqueueを呼び覚ますことを試みる.next.この部分はあまり言わない.
nativeメッセージ抽出
つまりnext
Message next() {
        。。。
        for (;;) {
           。。。
            nativePollOnce(ptr, nextPollTimeoutMillis);
           。。。
        }
    }

見えますが、またデッドサイクル(渋滞)です.下を見続けます
void NativeMessageQueue::pollOnce(JNIEnv* env, jobject pollObj, int timeoutMillis) {
    。。。
    mLooper->pollOnce(timeoutMillis);
    。。。
}

mLooper->pollOnce
mLooper->pollInner
int Looper::pollInner(int timeoutMillis) {
    。。。
    int result = POLL_WAKE;
    mPolling = true;
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);

    mPolling = false;
    mLock.lock();

    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeEventFd) {
            if (epollEvents & EPOLLIN) {
                awoken();
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);
            }
        } 
        。。。
    }
    。。。
    mLock.unlock();
    。。。
    return result;
}

ここで、epoll_に気づきましたwaitメソッド、ここではしばらく(メッセージと組み合わせて計算された)受信したイベントの数が得られ、queueにとっては空きです.(ブロック)状態.この時間が過ぎたら、イベント数を見て、0であればタイムアウトを意味します.そうでなければ、すべてのイベントを巡り、mWakeEventFdがあるか、EPOLLINイベントがあるかを見て、スレッドを本当に起こし、空き状態を解除します.
メッセージメカニズムのnative層での主な表現はこれらである.
最後に、粗くて正確ではない図を描いて参考学習に供します.
関連読書
Android ViewとWindowの関係
Android 7.0におけるContentProviderの実現原理
クラウドサーバーは20元/月から、更に千元の継続費の大きい贈り物を享受します
この文章はすでに作者がテンセントクラウド技術コミュニティに発表することを授権して、転載して原文の出所を明記してください