Android 系統(tǒng)運行機制 【Looper】【Choreographer】篇

目錄:
1 MessageQueue next()
2 Vsync
3 Choreographer doFrame
4 input


系統(tǒng)是一個無限循環(huán)的模型艺谆, Android也不例外,進程被創(chuàng)建后就陷入了無限循環(huán)的狀態(tài)

系統(tǒng)運行最重要的兩個概念:輸入噪舀,輸出。

  • 輸入: 按鍵事件飘诗、觸摸事件与倡、鼠標事件、軌跡球事件
  • 輸出: Choreographer 如何指揮完成一幀的繪制

Android 中輸入 輸出 的往復循環(huán)都是在 looper 中消息機制驅動下完成的

looper 的循環(huán)中昆稿, messageQueue next 取消息進行處理纺座, 處理輸入事件, 進行輸出貌嫡, 完成和用戶交互

主線程

應用生命周期內會不斷 產(chǎn)生 message 到 messageQueue 中比驻, 有: java層 也有 native層

其中最核心的方法就是 messageQueue 的 next 方法该溯, 其中會先處理 java 層消息, 當 java 層沒有消息時候别惦, 會執(zhí)行 nativePollOnce 來處理 native 的消息 以及監(jiān)聽 fd 各種事件

從硬件來看狈茉, 屏幕不會一直刷新, 屏幕的刷新只需要符合人眼的視覺停留機制

24Hz 掸掸, 連續(xù)刷新每一幀氯庆, 人眼就會認為畫面是流暢的

所以我們只需要配合上這個頻率, 在需要更新 UI 的時候執(zhí)行繪制操作

如何以這個頻率進行繪制每一幀: Android 的方案是 Vsync 信號驅動扰付。

Vsync 信號的頻率就是 24Hz 堤撵, 也就是每隔 16.6667 ms 發(fā)送一次 Vsync 信號提示系統(tǒng)合成一幀。

監(jiān)聽屏幕刷新來發(fā)送 Vsync 信號的能力羽莺,應用層 是做不到的实昨, 系統(tǒng)是通過 jni 回調到 Choreographer 中的 Vsync 監(jiān)聽, 將這個重要信號從 native 傳遞到 java 層盐固。

總體來說 輸入事件獲取 Vsync信號獲取 都是先由 native 捕獲事件 然后 jni 到 java 層實現(xiàn)業(yè)務邏輯

執(zhí)行的是 messageQueue 中的關鍵方法: next


1 MessageQueue next()

next 主要的邏輯分為: java 部分 和 native 部分

java 上主要是取java層的 messageQueue msg 執(zhí)行荒给, 無 msg 就 idleHandler

java層 無 msg 會執(zhí)行 native 的 pollOnce@Looper

next@MessageQueue
native message

native looper 中 fd 監(jiān)聽封裝為 requestQueue, epoll_wait 將 fd 中的事件和對應 request 封裝為 response 處理刁卜, 處理的時候會調用 fd 對應的 callback 的 handleEvent

    /**
     * Waits for events to be available, with optional timeout in milliseconds.
     * Invokes callbacks for all file descriptors on which an event occurred.
     *
     * If the timeout is zero, returns immediately without blocking.
     * If the timeout is negative, waits indefinitely until an event appears.
     *
     * Returns POLL_WAKE if the poll was awoken using wake() before
     * the timeout expired and no callbacks were invoked and no other file
     * descriptors were ready.
     *
     * Returns POLL_CALLBACK if one or more callbacks were invoked.
     *
     * Returns POLL_TIMEOUT if there was no data before the given
     * timeout expired.
     *
     * Returns POLL_ERROR if an error occurred.
     *
     * Returns a value >= 0 containing an identifier if its file descriptor has data
     * and it has no callback function (requiring the caller here to handle it).
     * In this (and only this) case outFd, outEvents and outData will contain the poll
     * events and data associated with the fd, otherwise they will be set to NULL.
     *
     * This method does not return until it has finished invoking the appropriate callbacks
     * for all file descriptors that were signalled.
     */
    int pollOnce(int timeoutMillis, int* outFd, int* outEvents, void** outData);

native 層 pollOnce 主要做的事情是:

  • epoll_wait 監(jiān)聽 fd 封裝為 response
  • 處理所有 native message
  • 處理 response 回調 handleEvent 志电,handleEvent 很多回回調到 java 層

vsync 信號,輸入事件蛔趴, 都是通過這樣的機制完成的挑辆。


2 vsync

epoll_wait 機制 拿到的 event , 都在 response pollOnce pollInner 處理了

這里的 dispatchVsync 從 native 回到 java 層

native:

int DisplayEventDispatcher::handleEvent(int, int events, void*) {
    if (events & (Looper::EVENT_ERROR | Looper::EVENT_HANGUP)) {
        ALOGE("Display event receiver pipe was closed or an error occurred.  "
              "events=0x%x",
              events);
        return 0; // remove the callback
    }
......
        dispatchVsync(vsyncTimestamp, vsyncDisplayId, vsyncCount, vsyncEventData);
    }

java:

    // Called from native code.
    @SuppressWarnings("unused")
    @UnsupportedAppUsage
    private void dispatchVsync(long timestampNanos, long physicalDisplayId, int frame) {
        onVsync(timestampNanos, physicalDisplayId, frame);
    }
    private final class FrameDisplayEventReceiver extends DisplayEventReceiver
            implements Runnable {

        @Override
        public void onVsync(long timestampNanos, long physicalDisplayId, int frame) {
        
            mTimestampNanos = timestampNanos;
            mFrame = frame;
            Message msg = Message.obtain(mHandler, this);
            msg.setAsynchronous(true);
            mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
        }

        @Override
        public void run() {
            mHavePendingVsync = false;
            doFrame(mTimestampNanos, mFrame);
        }
    }

收到 Vsync 信號后孝情, Choreographer 執(zhí)行 doFrame

應用層重要的工作幾乎都在 doFrame 中


3 Choreographer doFrame

首先看下 doFrame 執(zhí)行了什么:

        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
            AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);

            mFrameInfo.markInputHandlingStart();
            doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);

            mFrameInfo.markAnimationsStart();
            doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
            doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);

            mFrameInfo.markPerformTraversalsStart();
            doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

            doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
        } finally {
            AnimationUtils.unlockAnimationClock();
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }

UI 線程的核心工作就在這幾個方法中:

上述執(zhí)行 callback 的過程就對應了圖片中 依次處理 input animation traversal 這幾個關鍵過程

執(zhí)行的周期是 16.6ms鱼蝉, 實際可能因為一些 delay 造成一些延遲、丟幀


4 input

input 事件的整體邏輯和 vsync 類似

native handleEvent 箫荡,在 NativeInputEventReceiver 中處理事件蚀乔, 區(qū)分不同事件會通過 JNI

走到 java 層,WindowInputEventReceiver 然后進行分發(fā)消費

native :

int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {
    // Allowed return values of this function as documented in LooperCallback::handleEvent
    constexpr int REMOVE_CALLBACK = 0;
    constexpr int KEEP_CALLBACK = 1;


    if (events & ALOOPER_EVENT_INPUT) {
        JNIEnv* env = AndroidRuntime::getJNIEnv();
        status_t status = consumeEvents(env, false /*consumeBatches*/, -1, nullptr);
        mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
        return status == OK || status == NO_MEMORY ? KEEP_CALLBACK : REMOVE_CALLBACK;
    }

status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
        bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
 
    for (;;) {
        uint32_t seq;
        InputEvent* inputEvent;

        status_t status = mInputConsumer.consume(&mInputEventFactory,
                consumeBatches, frameTime, &seq, &inputEvent);
        if (status != OK && status != WOULD_BLOCK) {
            ALOGE("channel '%s' ~ Failed to consume input event.  status=%s(%d)",
                  getInputChannelName().c_str(), statusToString(status).c_str(), status);
            return status;
        }

        if (status == WOULD_BLOCK) {
            if (!skipCallbacks && !mBatchedInputEventPending && mInputConsumer.hasPendingBatch()) {
                // There is a pending batch.  Come back later.
                if (!receiverObj.get()) {
                    receiverObj.reset(jniGetReferent(env, mReceiverWeakGlobal));
                    if (!receiverObj.get()) {
                        ALOGW("channel '%s' ~ Receiver object was finalized "
                              "without being disposed.",
                              getInputChannelName().c_str());
                        return DEAD_OBJECT;
                    }
                }

                mBatchedInputEventPending = true;
                if (kDebugDispatchCycle) {
                    ALOGD("channel '%s' ~ Dispatching batched input event pending notification.",
                          getInputChannelName().c_str());
                }

                env->CallVoidMethod(receiverObj.get(),
                                    gInputEventReceiverClassInfo.onBatchedInputEventPending,
                                    mInputConsumer.getPendingBatchSource());
                if (env->ExceptionCheck()) {
                    ALOGE("Exception dispatching batched input events.");
                    mBatchedInputEventPending = false; // try again later
                }
            }
            return OK;
        }
        assert(inputEvent);

        if (!skipCallbacks) {
            if (!receiverObj.get()) {
                receiverObj.reset(jniGetReferent(env, mReceiverWeakGlobal));
                if (!receiverObj.get()) {
                    ALOGW("channel '%s' ~ Receiver object was finalized "
                            "without being disposed.", getInputChannelName().c_str());
                    return DEAD_OBJECT;
                }
            }

            jobject inputEventObj;
            switch (inputEvent->getType()) {
            case AINPUT_EVENT_TYPE_KEY:
                if (kDebugDispatchCycle) {
                    ALOGD("channel '%s' ~ Received key event.", getInputChannelName().c_str());
                }
                inputEventObj = android_view_KeyEvent_fromNative(env,
                        static_cast<KeyEvent*>(inputEvent));
                break;

            case AINPUT_EVENT_TYPE_MOTION: {
                if (kDebugDispatchCycle) {
                    ALOGD("channel '%s' ~ Received motion event.", getInputChannelName().c_str());
                }
                MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent);
                if ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {
                    *outConsumedBatch = true;
                }
                inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
                break;
            }
            case AINPUT_EVENT_TYPE_FOCUS: {
                FocusEvent* focusEvent = static_cast<FocusEvent*>(inputEvent);
                if (kDebugDispatchCycle) {
                    ALOGD("channel '%s' ~ Received focus event: hasFocus=%s, inTouchMode=%s.",
                          getInputChannelName().c_str(), toString(focusEvent->getHasFocus()),
                          toString(focusEvent->getInTouchMode()));
                }
                env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.onFocusEvent,
                                    jboolean(focusEvent->getHasFocus()),
                                    jboolean(focusEvent->getInTouchMode()));
                finishInputEvent(seq, true /* handled */);
                continue;
            }
            case AINPUT_EVENT_TYPE_CAPTURE: {
                const CaptureEvent* captureEvent = static_cast<CaptureEvent*>(inputEvent);
                if (kDebugDispatchCycle) {
                    ALOGD("channel '%s' ~ Received capture event: pointerCaptureEnabled=%s",
                          getInputChannelName().c_str(),
                          toString(captureEvent->getPointerCaptureEnabled()));
                }
                env->CallVoidMethod(receiverObj.get(),
                                    gInputEventReceiverClassInfo.onPointerCaptureEvent,
                                    jboolean(captureEvent->getPointerCaptureEnabled()));
                finishInputEvent(seq, true /* handled */);
                continue;
            }
            case AINPUT_EVENT_TYPE_DRAG: {
                const DragEvent* dragEvent = static_cast<DragEvent*>(inputEvent);
                if (kDebugDispatchCycle) {
                    ALOGD("channel '%s' ~ Received drag event: isExiting=%s",
                          getInputChannelName().c_str(), toString(dragEvent->isExiting()));
                }
                env->CallVoidMethod(receiverObj.get(), gInputEventReceiverClassInfo.onDragEvent,
                                    jboolean(dragEvent->isExiting()), dragEvent->getX(),
                                    dragEvent->getY());
                finishInputEvent(seq, true /* handled */);
                continue;
            }

            default:
                assert(false); // InputConsumer should prevent this from ever happening
                inputEventObj = nullptr;
            }

            if (inputEventObj) {
                if (kDebugDispatchCycle) {
                    ALOGD("channel '%s' ~ Dispatching input event.", getInputChannelName().c_str());
                }
                env->CallVoidMethod(receiverObj.get(),
                        gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
                if (env->ExceptionCheck()) {
                    ALOGE("Exception dispatching input event.");
                    skipCallbacks = true;
                }
                env->DeleteLocalRef(inputEventObj);
            } else {
                ALOGW("channel '%s' ~ Failed to obtain event object.",
                        getInputChannelName().c_str());
                skipCallbacks = true;
            }
        }
    }
}

java:

    final class WindowInputEventReceiver extends InputEventReceiver {
        public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
            super(inputChannel, looper);
        }

        @Override
        public void onInputEvent(InputEvent event) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "processInputEventForCompatibility");
            List<InputEvent> processedEvents;
            try {
                processedEvents =
                    mInputCompatProcessor.processInputEventForCompatibility(event);
            } finally {
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
            if (processedEvents != null) {
                if (processedEvents.isEmpty()) {
                    // InputEvent consumed by mInputCompatProcessor
                    finishInputEvent(event, true);
                } else {
                    for (int i = 0; i < processedEvents.size(); i++) {
                        enqueueInputEvent(
                                processedEvents.get(i), this,
                                QueuedInputEvent.FLAG_MODIFIED_FOR_COMPATIBILITY, true);
                    }
                }
            } else {
                enqueueInputEvent(event, this, 0, true);
            }
        }

        @Override
        public void onBatchedInputEventPending(int source) {
            // mStopped: There will be no more choreographer callbacks if we are stopped,
            // so we must consume all input immediately to prevent ANR
            final boolean unbuffered = mUnbufferedInputDispatch
                    || (source & mUnbufferedInputSource) != SOURCE_CLASS_NONE
                    || mStopped;
            if (unbuffered) {
                if (mConsumeBatchedInputScheduled) {
                    unscheduleConsumeBatchedInput();
                }
                // Consume event immediately if unbuffered input dispatch has been requested.
                consumeBatchedInputEvents(-1);
                return;
            }
            scheduleConsumeBatchedInput();
        }

        @Override
        public void onFocusEvent(boolean hasFocus, boolean inTouchMode) {
            windowFocusChanged(hasFocus, inTouchMode);
        }

        @Override
        public void dispose() {
            unscheduleConsumeBatchedInput();
            super.dispose();
        }
    }

input事件的處理流程:

輸入event deliverInputEvent

deliver的 input 事件會來到 InputStage

InputStage 是一個責任鏈菲茬, 會分發(fā)消費這些 InputEvent

下面以滑動一下 recyclerView 為例子, 整體邏輯如下:

native post input callback

vsync 信號到來派撕, 執(zhí)行 doFrame婉弹,執(zhí)行到 input 階段


do input callback

touchEvent 消費, recyclerView layout 一些 ViewHolder

onbindViewHolder

scroll 中 fill 結束终吼,會執(zhí)行 一個 recyclerView viewProperty 變化尿扯, 觸發(fā)了invalidate

invalidate 會走硬件加速粱玲, 一直到達 ViewRootImpl , 從而將 Traversal 的 callback post choreographer執(zhí)行到 traversal 階段就會執(zhí)行

滾動 schduleTraversals

ViewRootImpl 執(zhí)行 performTraversal 埃难, 會根據(jù)目前是否需要重新layout , 然后執(zhí)行l(wèi)ayout蚕钦, draw 等流程

Traversal

整個 input 到 traversal 結束,硬件繪制后, sync 任務到 GPU 肠虽, 然后合成一幀。

交給 SurfaceFlinger 來顯示玛追。

SurfaceFlinger 是系統(tǒng)進程税课, 每一個應用進程是一個 client 端, 通過 IPC 機制痊剖,client 將圖像顯示工作交給 SurfaceFlinger

launch 一個 app:

?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末韩玩,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子陆馁,更是在濱河造成了極大的恐慌找颓,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件叮贩,死亡現(xiàn)場離奇詭異击狮,居然都是意外死亡,警方通過查閱死者的電腦和手機妇汗,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進店門帘不,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人杨箭,你說我怎么就攤上這事寞焙。” “怎么了互婿?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵捣郊,是天一觀的道長。 經(jīng)常有香客問我慈参,道長呛牲,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任驮配,我火速辦了婚禮娘扩,結果婚禮上,老公的妹妹穿的比我還像新娘壮锻。我一直安慰自己琐旁,他們只是感情好,可當我...
    茶點故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布猜绣。 她就那樣靜靜地躺著灰殴,像睡著了一般。 火紅的嫁衣襯著肌膚如雪掰邢。 梳的紋絲不亂的頭發(fā)上牺陶,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天伟阔,我揣著相機與錄音,去河邊找鬼掰伸。 笑死皱炉,一個胖子當著我的面吹牛,可吹牛的內容都是我干的碱工。 我是一名探鬼主播娃承,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼怕篷!你這毒婦竟也來了历筝?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤廊谓,失蹤者是張志新(化名)和其女友劉穎梳猪,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體蒸痹,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡春弥,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了叠荠。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片匿沛。...
    茶點故事閱讀 39,965評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖榛鼎,靈堂內的尸體忽然破棺而出逃呼,到底是詐尸還是另有隱情,我是刑警寧澤者娱,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布抡笼,位于F島的核電站,受9級特大地震影響黄鳍,放射性物質發(fā)生泄漏推姻。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一框沟、第九天 我趴在偏房一處隱蔽的房頂上張望藏古。 院中可真熱鬧,春花似錦忍燥、人聲如沸校翔。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至孟辑,卻和暖如春哎甲,著一層夾襖步出監(jiān)牢的瞬間蔫敲,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工炭玫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留奈嘿,地道東北人。 一個月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓吞加,卻偏偏與公主長得像裙犹,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子衔憨,可洞房花燭夜當晚...
    茶點故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內容