Android控件系統(六)——事件分發


Android版本:7.0(API27)android


經過前面的文章咱們瞭解到ViewRootImpl功能強大,總結而言有三個主要做用:bash

  • 連接WindowManager和DecorView的紐帶,更廣一點能夠說是Window和View之間的紐帶;
  • 完成View的繪製過程,包括measure、layout、draw過程;
  • 向DecorView分發收到的用戶發起的event事件,如按鍵,觸屏等事件;

這邊文章咱們將圍繞事件分發,來深刻分析按鍵和觸摸事件的分發流程。app

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

    @Override
    public void onInputEvent(InputEvent event, int displayId) {
        enqueueInputEvent(event, this, 0, true);
    }

    @Override
    public void onBatchedInputEventPending() {
        if (mUnbufferedInputDispatch) {
            super.onBatchedInputEventPending();
        } else {
            scheduleConsumeBatchedInput();
        }
    }

    @Override
    public void dispose() {
        unscheduleConsumeBatchedInput();
        super.dispose();
    }
}
複製代碼

ViewRootImpl是經過WindowInputEventReceiver這個內部類的onInputEvent方法來接收輸入事件的less

void enqueueInputEvent(InputEvent event,
            InputEventReceiver receiver, int flags, boolean processImmediately) {
    adjustInputEventForCompatibility(event);
    QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);

    // Always enqueue the input event in order, regardless of its time stamp.
    // We do this because the application or the IME may inject key events
    // in response to touch events and we want to ensure that the injected keys
    // are processed in the order they were received and we cannot trust that
    // the time stamp of injected events are monotonic.
    QueuedInputEvent last = mPendingInputEventTail;
    if (last == null) {
        mPendingInputEventHead = q;
        mPendingInputEventTail = q;
    } else {
        last.mNext = q;
        mPendingInputEventTail = q;
    }
    mPendingInputEventCount += 1;
    Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
            mPendingInputEventCount);

    if (processImmediately) {
        doProcessInputEvents();
    } else {
        scheduleProcessInputEvents();
    }
}
複製代碼

而後將event封裝成一個QueuedInputEvent對象,並加入到mPendingInputEventHead的鏈表中,最後調用doProcessInputEvents開始執行事件分發邏輯。async

void doProcessInputEvents() {
    // Deliver all pending input events in the queue.
    while (mPendingInputEventHead != null) {
        QueuedInputEvent q = mPendingInputEventHead;
        mPendingInputEventHead = q.mNext;
        if (mPendingInputEventHead == null) {
            mPendingInputEventTail = null;
        }
        q.mNext = null;

        mPendingInputEventCount -= 1;
        Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
                mPendingInputEventCount);

        long eventTime = q.mEvent.getEventTimeNano();
        long oldestEventTime = eventTime;
        if (q.mEvent instanceof MotionEvent) {
            MotionEvent me = (MotionEvent)q.mEvent;
            if (me.getHistorySize() > 0) {
                oldestEventTime = me.getHistoricalEventTimeNano(0);
            }
        }
        mChoreographer.mFrameInfo.updateInputEventTime(eventTime, oldestEventTime);

        deliverInputEvent(q);
    }

    // We are done processing all input events that we can process right now
    // so we can clear the pending flag immediately.
    if (mProcessInputEventsScheduled) {
        mProcessInputEventsScheduled = false;
        mHandler.removeMessages(MSG_PROCESS_INPUT_EVENTS);
    }
}
複製代碼

doProcessInputEvents中的while循環就是處理mPendingInputEventHead鏈表中的事件,當事件處理完畢後循環結束。能夠看出事件處理的核心邏輯是deliverInputEvent方法:ide

private void deliverInputEvent(QueuedInputEvent q) {
    Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
            q.mEvent.getSequenceNumber());
    if (mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
    }

    InputStage stage;
    if (q.shouldSendToSynthesizer()) {
        stage = mSyntheticInputStage;
    } else {
        stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
    }

    if (stage != null) {
        stage.deliver(q);
    } else {
        finishInputEvent(q);
    }
}
複製代碼

  那這裏的stage是什麼呢?其實在RootViewImpl中,將整個處理過程分爲不少階段,每一個階段使用不一樣的InputStage表示,如SyntheticInputStage、ViewPostImeInputStage、NativePostImeInputStage等等。這些階段是各類策略的組合,各策略負責的任務不一樣,這些策略以鏈表結構結構起來,當一個策略者沒有消費事件時,就傳遞個下一個策略者。   這個鏈表的創建是在RootViewImpl的setView函數中造成的:函數

CharSequence counterSuffix = attrs.getTitle();
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
        "aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
        "aq:ime:" + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
        "aq:native-pre-ime:" + counterSuffix);

mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage = earlyPostImeStage;
mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
複製代碼

其中觸摸和按鍵事件由ViewPostImeInputStage處理:oop

final class ViewPostImeInputStage extends InputStage {
    public ViewPostImeInputStage(InputStage next) {
        super(next);
    }

    @Override
    protected int onProcess(QueuedInputEvent q) {
        if (q.mEvent instanceof KeyEvent) {
            return processKeyEvent(q);
        } else {
            final int source = q.mEvent.getSource();
            if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                return processPointerEvent(q);
            } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                return processTrackballEvent(q);
            } else {
                return processGenericMotionEvent(q);
            }
        }
    }
}
複製代碼

由於ViewPostImeInputStage的代碼較多,上面只羅列出部分關鍵代碼。在onProcess方法中咱們看到了各種事件的分發處理:post

  • processKeyEvent:處理按鍵事件;
  • processPointerEvent:處理觸摸事件;
  • processTrackballEvent:處理軌跡事件;
  • processGenericMotionEvent:處理其它事件,如懸浮、遊戲手柄等;

到此爲止,android的事件分發出現了分水嶺,後續咱們將使用兩篇文章重點分析觸摸事件分發和按鍵事件分發。ui

觸摸事件

private int processPointerEvent(QueuedInputEvent q) {
    final MotionEvent event = (MotionEvent)q.mEvent;

    mAttachInfo.mUnbufferedDispatchRequested = false;
    mAttachInfo.mHandlingPointerEvent = true;
    boolean handled = mView.dispatchPointerEvent(event);
    maybeUpdatePointerIcon(event);
    maybeUpdateTooltip(event);
    mAttachInfo.mHandlingPointerEvent = false;
    if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
        mUnbufferedInputDispatch = true;
        if (mConsumeBatchedInputScheduled) {
            scheduleConsumeBatchedInputImmediately();
        }
    }
    return handled ? FINISH_HANDLED : FORWARD;
}
複製代碼

這裏是調用ViewRootImpl內部字段mView.dispatchPointerEvent就行觸摸事件分發,mView是什麼呢?經過前面文章的分析咱們能知道mView實際上是PhoneWindow內部維護的DecorView,而DecorView繼承FrameLayout,因此最終調用的是ViewGroup.dispatchPointerEvent

相關文章
相關標籤/搜索