Android系統原始碼剖析-事件分發

Jensen95發表於2019-03-04

前言

上一篇文章中,對於事件的監控和獲取做了分析,在拿到事件之後,後續是如何處理分發的呢?本篇文章主要針對在通過getEvent獲取到事件之後,後續的相關分發處理流程。

事件處理分發

InputReaderThread函式不斷地呼叫looperOnce函式,不斷的從中讀取事件,那麼下一個問題來了,讀取到事件要放置到哪裡,又在哪裡被消耗掉了呢?也就是事件接下來的流向問題。讓我們回到looperOnce之前。

事件分發

processEventsLocked

在呼叫了getEvent之後,又呼叫了函式processEventsLocked

void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
    for (const RawEvent* rawEvent = rawEvents; count;) {
        int32_t type = rawEvent->type;
        size_t batchSize = 1;
        if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {
            int32_t deviceId = rawEvent->deviceId;
            while (batchSize < count) {
                if (rawEvent[batchSize].type >= EventHubInterface::FIRST_SYNTHETIC_EVENT
                        || rawEvent[batchSize].deviceId != deviceId) {
                    break;
                }
                batchSize += 1;
            }
            processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
        } else {
            switch (rawEvent->type) {
            case EventHubInterface::DEVICE_ADDED:
                addDeviceLocked(rawEvent->when, rawEvent->deviceId);
                break;
            case EventHubInterface::DEVICE_REMOVED:
                removeDeviceLocked(rawEvent->when, rawEvent->deviceId);
                break;
            case EventHubInterface::FINISHED_DEVICE_SCAN:
                handleConfigurationChangedLocked(rawEvent->when);
                break;
            default:
                ALOG_ASSERT(false); // can't happen
                break;
            }
        }
        count -= batchSize;
        rawEvent += batchSize;
    }
}
複製程式碼

首先對於事件型別進行了判斷,如果事件不是合成事件,則會對其DeviceID進行判斷,通過對其判斷來確定batchSize等,然後對其進行處理,對於其他的型別事件,則會具體判斷,判斷是裝置的新增,裝置的移除,完成裝置掃描等等,然後對事件分別進行處理,這裡我們只關心對於裝置自身產生的事件。也就是觸控式螢幕相關的事件。也就是processEventsForDeviceLocked函式中所進行的操作。

事件派發到Device
void InputReader::processEventsForDeviceLocked(int32_t deviceId,
        const RawEvent* rawEvents, size_t count) {
    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
    if (deviceIndex < 0) {
        return;
    }

    InputDevice* device = mDevices.valueAt(deviceIndex);
    if (device->isIgnored()) {
        return;
    }

    device->process(rawEvents, count);
}
複製程式碼

根據事件獲得相應的裝置型別,然後將事件交給相應的裝置處理。判斷是否忽略該事件,如果不是忽略該事件,則會呼叫相應裝置的process方法進行處理。

事件派發到InputMapper

InputDevice的process方法

void InputDevice::process(const RawEvent* rawEvents, size_t count) {
     ....
    for (size_t i = 0; i < numMappers; i++) {
           InputMapper* mapper = mMappers[i];
           mapper->process(rawEvent);
    }
    ....
}
複製程式碼

這裡的事件又交給了InputMapper來處理

InputMapper

InputMapper對應了很多的子類,這裡根據事件的型別進行相應的派發,處理。 事件到了這裡之後,如何傳遞到應用層,這裡mapper->process進行了那些處理。這裡來看一下對於觸控式螢幕事件的處理函式。

void TouchInputMapper::process(const RawEvent* rawEvent) {
    mCursorButtonAccumulator.process(rawEvent);
    mCursorScrollAccumulator.process(rawEvent);
    mTouchButtonAccumulator.process(rawEvent);

    if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
        sync(rawEvent->when);
    }
}
複製程式碼

通過這裡的函式處理,我們繼續追蹤函式的資料流向。對於相關的事件處理,這裡最終執行的是將

void TouchInputMapper::sync(nsecs_t when) {
    .....
    processRawTouches(false /*timeout*/);
}
複製程式碼

void TouchInputMapper::processRawTouches(bool timeout) {
    if (mDeviceMode == DEVICE_MODE_DISABLED) {
        // Drop all input if the device is disabled.
        mCurrentRawState.clear();
        mRawStatesPending.clear();
        return;
    }

    // Drain any pending touch states. The invariant here is that the mCurrentRawState is always
    // valid and must go through the full cook and dispatch cycle. This ensures that anything
    // touching the current state will only observe the events that have been dispatched to the
    // rest of the pipeline.
    const size_t N = mRawStatesPending.size();
    size_t count;
    for(count = 0; count < N; count++) {
        const RawState& next = mRawStatesPending[count];

        // A failure to assign the stylus id means that we're waiting on stylus data
        // and so should defer the rest of the pipeline.
        if (assignExternalStylusId(next, timeout)) {
            break;
        }

        // All ready to go.
        clearStylusDataPendingFlags();
        mCurrentRawState.copyFrom(next);
        if (mCurrentRawState.when < mLastRawState.when) {
            mCurrentRawState.when = mLastRawState.when;
        }
        cookAndDispatch(mCurrentRawState.when);
    }
    if (count != 0) {
        mRawStatesPending.removeItemsAt(0, count);
    }

    if (mExternalStylusDataPending) {
        if (timeout) {
            nsecs_t when = mExternalStylusFusionTimeout - STYLUS_DATA_LATENCY;
            clearStylusDataPendingFlags();
            mCurrentRawState.copyFrom(mLastRawState);
            cookAndDispatch(when);
        } else if (mExternalStylusFusionTimeout == LLONG_MAX) {
            mExternalStylusFusionTimeout = mExternalStylusState.when + TOUCH_DATA_TIMEOUT;
            getContext()->requestTimeoutAtTime(mExternalStylusFusionTimeout);
        }
    }
}
複製程式碼

在相關的函式呼叫之後,最終呼叫了dispatchTouches

void TouchInputMapper::cookAndDispatch(nsecs_t when) {
    // Always start with a clean state.
    mCurrentCookedState.clear();

    // Apply stylus buttons to current raw state.
    applyExternalStylusButtonState(when);

    // Handle policy on initial down or hover events.
    bool initialDown = mLastRawState.rawPointerData.pointerCount == 0
            && mCurrentRawState.rawPointerData.pointerCount != 0;

    uint32_t policyFlags = 0;
    bool buttonsPressed = mCurrentRawState.buttonState & ~mLastRawState.buttonState;
    if (initialDown || buttonsPressed) {
        // If this is a touch screen, hide the pointer on an initial down.
        if (mDeviceMode == DEVICE_MODE_DIRECT) {
            getContext()->fadePointer();
        }

        if (mParameters.wake) {
            policyFlags |= POLICY_FLAG_WAKE;
        }
    }

    // Consume raw off-screen touches before cooking pointer data.
    // If touches are consumed, subsequent code will not receive any pointer data.
    if (consumeRawTouches(when, policyFlags)) {
        mCurrentRawState.rawPointerData.clear();
    }

    // Cook pointer data.  This call populates the mCurrentCookedState.cookedPointerData structure
    // with cooked pointer data that has the same ids and indices as the raw data.
    // The following code can use either the raw or cooked data, as needed.
    cookPointerData();

    // Apply stylus pressure to current cooked state.
    applyExternalStylusTouchState(when);

    // Synthesize key down from raw buttons if needed.
    synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_DOWN, when, getDeviceId(), mSource,
            policyFlags, mLastCookedState.buttonState, mCurrentCookedState.buttonState);

    // Dispatch the touches either directly or by translation through a pointer on screen.
    if (mDeviceMode == DEVICE_MODE_POINTER) {
        for (BitSet32 idBits(mCurrentRawState.rawPointerData.touchingIdBits);
                !idBits.isEmpty(); ) {
            uint32_t id = idBits.clearFirstMarkedBit();
            const RawPointerData::Pointer& pointer =
                    mCurrentRawState.rawPointerData.pointerForId(id);
            if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS
                    || pointer.toolType == AMOTION_EVENT_TOOL_TYPE_ERASER) {
                mCurrentCookedState.stylusIdBits.markBit(id);
            } else if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_FINGER
                    || pointer.toolType == AMOTION_EVENT_TOOL_TYPE_UNKNOWN) {
                mCurrentCookedState.fingerIdBits.markBit(id);
            } else if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_MOUSE) {
                mCurrentCookedState.mouseIdBits.markBit(id);
            }
        }
        for (BitSet32 idBits(mCurrentRawState.rawPointerData.hoveringIdBits);
                !idBits.isEmpty(); ) {
            uint32_t id = idBits.clearFirstMarkedBit();
            const RawPointerData::Pointer& pointer =
                    mCurrentRawState.rawPointerData.pointerForId(id);
            if (pointer.toolType == AMOTION_EVENT_TOOL_TYPE_STYLUS
                    || pointer.toolType == AMOTION_EVENT_TOOL_TYPE_ERASER) {
                mCurrentCookedState.stylusIdBits.markBit(id);
            }
        }

        // Stylus takes precedence over all tools, then mouse, then finger.
        PointerUsage pointerUsage = mPointerUsage;
        if (!mCurrentCookedState.stylusIdBits.isEmpty()) {
            mCurrentCookedState.mouseIdBits.clear();
            mCurrentCookedState.fingerIdBits.clear();
            pointerUsage = POINTER_USAGE_STYLUS;
        } else if (!mCurrentCookedState.mouseIdBits.isEmpty()) {
            mCurrentCookedState.fingerIdBits.clear();
            pointerUsage = POINTER_USAGE_MOUSE;
        } else if (!mCurrentCookedState.fingerIdBits.isEmpty() ||
                isPointerDown(mCurrentRawState.buttonState)) {
            pointerUsage = POINTER_USAGE_GESTURES;
        }

        dispatchPointerUsage(when, policyFlags, pointerUsage);
    } else {
        if (mDeviceMode == DEVICE_MODE_DIRECT
                && mConfig.showTouches && mPointerController != NULL) {
            mPointerController->setPresentation(PointerControllerInterface::PRESENTATION_SPOT);
            mPointerController->fade(PointerControllerInterface::TRANSITION_GRADUAL);

            mPointerController->setButtonState(mCurrentRawState.buttonState);
            mPointerController->setSpots(mCurrentCookedState.cookedPointerData.pointerCoords,
                    mCurrentCookedState.cookedPointerData.idToIndex,
                    mCurrentCookedState.cookedPointerData.touchingIdBits);
        }

        if (!mCurrentMotionAborted) {
            dispatchButtonRelease(when, policyFlags);
            dispatchHoverExit(when, policyFlags);
            dispatchTouches(when, policyFlags);
            dispatchHoverEnterAndMove(when, policyFlags);
            dispatchButtonPress(when, policyFlags);
        }

        if (mCurrentCookedState.cookedPointerData.pointerCount == 0) {
            mCurrentMotionAborted = false;
        }
    }

    // Synthesize key up from raw buttons if needed.
    synthesizeButtonKeys(getContext(), AKEY_EVENT_ACTION_UP, when, getDeviceId(), mSource,
            policyFlags, mLastCookedState.buttonState, mCurrentCookedState.buttonState);

    // Clear some transient state.
    mCurrentRawState.rawVScroll = 0;
    mCurrentRawState.rawHScroll = 0;

    // Copy current touch to last touch in preparation for the next cycle.
    mLastRawState.copyFrom(mCurrentRawState);
    mLastCookedState.copyFrom(mCurrentCookedState);
}

複製程式碼
void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) {
      ....
    dispatchMotion();
      ....
}
複製程式碼

對於dispatchTouches中,會根據記錄的上一次的觸控位置,對事件的型別進行判斷,然後做相應的分發,事件型別有抬起,下落,移動等,然後對相應的事件進行分發。無論是對於何種型別的事件派發,最終被呼叫到的都是dispatchMotion()方法。

對於相關事件的分發最終呼叫到了dispatchMotion(),對事件資料進行組裝之後,呼叫了

void TouchInputMapper::dispatchMotion() {
   ....
   NotifyMotionArgs args(when, getDeviceId(), source, policyFlags,
            action, actionButton, flags, metaState, buttonState, edgeFlags,
            mViewport.displayId, pointerCount, pointerProperties, pointerCoords,
            xPrecision, yPrecision, downTime);
    getListener()->notifyMotion(&args);
}
複製程式碼

getListener函式

InputListenerInterface* InputReader::ContextImpl::getListener() {
    return mReader->mQueuedListener.get();
}
複製程式碼

notifyMotion函式實現

void QueuedInputListener::notifyMotion(const NotifyMotionArgs* args) {
    mArgsQueue.push(new NotifyMotionArgs(*args));
}
複製程式碼

這裡可以看到,我們將觸控相關的事件進行包裝之後,將其加入到一個ArgsQueue佇列,到此,我們已經將資料加入到引數佇列中,到此事件從裝置檔案獲取到寫入流程已經完成,這裡讓我們再回到loopOnce方法中,最後呼叫了QueuedInputListenerflush方法,

void QueuedInputListener::flush() {
    size_t count = mArgsQueue.size();
    for (size_t i = 0; i < count; i++) {
        NotifyArgs* args = mArgsQueue[i];
        args->notify(mInnerListener);
        delete args;
    }
    mArgsQueue.clear();
}
複製程式碼

NotifyArgs的notify函式實現

void NotifyMotionArgs::notify(const sp<InputListenerInterface>& listener) const {
    listener->notifyMotion(this);
}
複製程式碼

對於這個listener的建立來自於InputReader構建的時候。

mQueuedListener = new QueuedInputListener(listener);
複製程式碼
 mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
複製程式碼

而這裡的Listener則是InputDispatcher,InputDispatcher 的notifyMotion實現原始碼。

void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
    .....
   MotionEvent event;
   event.initialize(args->deviceId, args->source, args->action, args->actionButton,
                    args->flags, args->edgeFlags, args->metaState, args->buttonState,
                    0, 0, args->xPrecision, args->yPrecision,
                    args->downTime, args->eventTime,
                    args->pointerCount, args->pointerProperties, args->pointerCoords);
    ....
  MotionEntry* newEntry = new MotionEntry(args->eventTime,
                args->deviceId, args->source, policyFlags,
                args->action, args->actionButton, args->flags,
                args->metaState, args->buttonState,
                args->edgeFlags, args->xPrecision, args->yPrecision, args->downTime,
                args->displayId,
                args->pointerCount, args->pointerProperties, args->pointerCoords, 0, 0);
   needWake = enqueueInboundEventLocked(newEntry);
    ....
   if (needWake) {
      mLooper->wake();
   }
}
複製程式碼

在該函式中,所做的事情是對於所傳遞的引數,構造MotionEntry,然後將其加入到enqueueInboundEventLocked之中。然後喚醒其中的looper。

bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
    bool needWake = mInboundQueue.isEmpty();
    mInboundQueue.enqueueAtTail(entry);
    ...
    //進行一些事件和視窗相關的判斷處理
}
複製程式碼

Dispatcher開啟的執行緒中,每次迴圈的操作如何?

bool InputDispatcherThread::threadLoop() {
    mDispatcher->dispatchOnce();
    return true;
}
複製程式碼

Dispatcher下dispatchOnce的實現

void InputDispatcher::dispatchOnce() {
    ...
   dispatchOnceInnerLocked(&nextWakeupTime);
    ...
}
複製程式碼
void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
   ....
   mPendingEvent = mInboundQueue.dequeueAtHead();
   ....

   switch (mPendingEvent->type) {
        case EventEntry::TYPE_MOTION: {
        MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);
        if (dropReason == DROP_REASON_NOT_DROPPED && isAppSwitchDue) {
            dropReason = DROP_REASON_APP_SWITCH;
        }
        if (dropReason == DROP_REASON_NOT_DROPPED
                && isStaleEventLocked(currentTime, typedEntry)) {
            dropReason = DROP_REASON_STALE;
        }
        if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {
            dropReason = DROP_REASON_BLOCKED;
        }
        done = dispatchMotionLocked(currentTime, typedEntry,
                &dropReason, nextWakeupTime);
        break;
    }
    ....
   }
}
複製程式碼

從mInboudQueue中,獲取到事件,然後對事件型別進行判斷,判斷之後呼叫了dispatchMotionLocked函式,來繼續進行事件的傳遞。

bool InputDispatcher::dispatchMotionLocked(
        nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason, nsecs_t* nextWakeupTime) {
      ....
      Vector<InputTarget> inputTargets;
if (isPointerEvent) {
        // Pointer event.  (eg. touchscreen)
      //尋找目標視窗
        injectionResult = findTouchedWindowTargetsLocked(currentTime,
                entry, inputTargets, nextWakeupTime, &conflictingPointerActions);
    } else {
        // Non touch event.  (eg. trackball)
        injectionResult = findFocusedWindowTargetsLocked(currentTime,
                entry, inputTargets, nextWakeupTime);
    }
    ....
    dispatchEventLocked(currentTime, entry, inputTargets);
    return true;
}
複製程式碼
  • dispatchEventLocked
void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,
    EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) {
    ....
    pokeUserActivityLocked(eventEntry);
    .....
    for (size_t i = 0; i < inputTargets.size(); i++) {
        const InputTarget& inputTarget = inputTargets.itemAt(i);

        ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
        if (connectionIndex >= 0) {
            sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
            prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget);
        } 
    }
}
複製程式碼

獲得目標輸入,根據InputChannel獲取相應的連線,然後呼叫prepareDispatchCycleLocked(),進行事件的派發。 enqueueDispatchEntriesLocked,在該方法中又呼叫了startDispatchCycleLocked方法。其實現為

void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,
        const sp<Connection>& connection) {
   EventEntry* eventEntry = dispatchEntry->eventEntry;
    ....
   switch (eventEntry->type) {
      ....
    case EventEntry::TYPE_MOTION: {
      status = connection->inputPublisher.publishMotionEvent( ....);    
      break;
    }
    ....
   }
    ...
}
複製程式碼

至此呼叫了connection 的inputPublisher的publishMotionEvent方法將事件分發消耗。

InputPublisher定義在InputTransport.cpp中

status_t InputPublisher::publishMotionEvent(...) {
  ....
  InputMessage msg;
  msg.header.type = InputMessage::TYPE_MOTION;
  msg.body.motion.seq = seq;
  msg.body.motion.deviceId = deviceId;
  msg.body.motion.source = source;
  msg.body.motion.action = action;
  msg.body.motion.actionButton = actionButton;
  msg.body.motion.flags = flags;
  msg.body.motion.edgeFlags = edgeFlags;
  msg.body.motion.metaState = metaState;
  msg.body.motion.buttonState = buttonState;
  msg.body.motion.xOffset = xOffset;
  msg.body.motion.yOffset = yOffset;
  msg.body.motion.xPrecision = xPrecision;
  msg.body.motion.yPrecision = yPrecision;
  msg.body.motion.downTime = downTime;
  msg.body.motion.eventTime = eventTime;
  msg.body.motion.pointerCount = pointerCount;
  for (uint32_t i = 0; i < pointerCount; i++) {
      msg.body.motion.pointers[i].properties.copyFrom(pointerProperties[i]);
      msg.body.motion.pointers[i].coords.copyFrom(pointerCoords[i]);
  }
    return mChannel->sendMessage(&msg);
}
複製程式碼

該方法所執行的操作是利用傳入的觸控資訊,構建點選訊息,然後通過InputChannel將訊息傳送出去。這裡引出了InputChannel,在此,我們通InputPublisher的建立反推出InputChannel是何時被引入的,何時被建立的。從而進一步分析其作用。在分析之前先讓我們來對上述的分析過程做一個總結。

觸控事件讀取分發過程

ReaderThread開啟後會從EventHub中輪詢獲取時間,獲取到事件之後,對手將進行一系列的處理,最終將經過一系列處理封裝的事件資訊通過InputChannel傳送出去。

事件產生讀取分發

到此,對於輸入事件,我們已經分析到了InputChannel,對於其上的具體分析轉化,將是接下來分析的核心。

InputChannel

從上面分析可以看到事件傳遞部分最後是通過InputChannel所傳送出去的,那麼InputChannel是在何時被建立的呢?何時被InputManager所使用的呢?同時,InputReaderThread和InputDispatcherThread是執行在SystemServer程式中的,而我們的應用程式是和其不在同一個程式中的。這之間一定也是有程式間的通訊機制在裡面。具體又是如何實現的呢?對於InputChannel這邊是事件傳遞過程中一個比較核心的點。

InputChannel的建立是在 ViewRootImplsetView方法中。

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    ....
  if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
       mInputChannel = new InputChannel();
   }
  ....
  res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
  ....
}
複製程式碼

這裡對於ViewRootImpl和WindowSession相關暫且不介紹,對於這方面的知識,需要很大的篇幅來介紹,這裡先只是講到是在這裡建立的,對於其相關的內容將在後續的文章中介紹。這裡首先是建立了一個InputChannel,然後將其呼叫了WindowSessionaddToDisplay方法將其作為引數傳遞。

public InputChannel() {
}
複製程式碼

在InputChannel中的方法都為呼叫了相應的native方法。這裡呼叫的addToDisplay將會把InputChannel新增到WindowManagerService中。會呼叫WMS的addWindow方法。

 public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {
      ....

      final boolean openInputChannels = (outInputChannel != null
                    && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
      if  (openInputChannels) {
          win.openInputChannel(outInputChannel);
      }
      ....
}
複製程式碼

對於InputChannel的相關處理呼叫了WindowState的openInputChannel方法。

void openInputChannel(InputChannel outInputChannel) {
    if (mInputChannel != null) {
        throw new IllegalStateException("Window already has an input channel.");
     }
     String name = makeInputChannelName();
     InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
     mInputChannel = inputChannels[0];
     mClientChannel = inputChannels[1];
     mInputWindowHandle.inputChannel = inputChannels[0];
     if (outInputChannel != null) {
       mClientChannel.transferTo(outInputChannel);
       mClientChannel.dispose();
       mClientChannel = null;
      } else {
         mDeadWindowEventReceiver = new DeadWindowEventReceiver(mClientChannel);
      }
       mService.mInputManager.registerInputChannel(mInputChannel, mInputWindowHandle);
}
複製程式碼

首先呼叫了InputChannel的openInputChannelPair方法,該方法呼叫了InputChannel的native方法nativeOpenInputChannelPair,建立了兩個InputChannel,對其中一個通過InputManager進行了InputChannel的註冊。對於InputChannel的相關Native的實現是在InputTransport中,nativeOpenInputChannelPair的原始碼如下。

status_t InputChannel::openInputChannelPair(const String8& name,
        sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
    int sockets[2];
    if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
        status_t result = -errno;
        outServerChannel.clear();
        outClientChannel.clear();
        return result;
    }

    int bufferSize = SOCKET_BUFFER_SIZE;
    setsockopt(sockets[0], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[0], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[1], SOL_SOCKET, SO_SNDBUF, &bufferSize, sizeof(bufferSize));
    setsockopt(sockets[1], SOL_SOCKET, SO_RCVBUF, &bufferSize, sizeof(bufferSize));

    String8 serverChannelName = name;
    serverChannelName.append(" (server)");
    outServerChannel = new InputChannel(serverChannelName, sockets[0]);

    String8 clientChannelName = name;
    clientChannelName.append(" (client)");
    outClientChannel = new InputChannel(clientChannelName, sockets[1]);
    return OK;
}
複製程式碼

這裡的Socket建立用到了 Linux中的Socketpaire,在之前的版本中是通過管道來實現的,但是 建立的管道只能是單向的 -- mode 只能是 "r" 或 "w" 而不能是某種組合--使用者只能選擇要麼往裡寫,要麼從中讀,而不能同時在一個管道中進行讀寫。實際應用中,經常會有同時進行讀寫的要求。

Linux實現了一個源自BSD的socketpair呼叫可以實現上述在同一個檔案描述符中進行讀寫的功能(該呼叫目前也是POSIX規範的一部分 。該系統呼叫能建立一對已連線的(UNIX族)無名socket。在Linux中,完全可以把這一對socket當成pipe返回的檔案描述符一樣使用,唯一的區別就是這一對檔案描述符中的任何一個都可讀和可寫。

socketpair產生的檔案描述符是一對socket,socket上的標準操作都可以使用,其中也包括shutdown。——利用shutdown,可以實現一個半關閉操作,通知對端本程式不再傳送資料,同時仍可以利用該檔案描述符接收來自對端的資料。

status_t InputChannel::sendMessage(const InputMessage* msg) {
    size_t msgLength = msg->size();
    ssize_t nWrite;
    do {
        nWrite = ::send(mFd, msg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);
    } while (nWrite == -1 && errno == EINTR);
     .....
    return OK;
}
複製程式碼

接收訊息,通過讀socket的方式來讀取訊息。

status_t InputChannel::receiveMessage(InputMessage* msg) {
    ssize_t nRead;
    do {
        nRead = ::recv(mFd, msg, sizeof(InputMessage), MSG_DONTWAIT);
    } while (nRead == -1 && errno == EINTR);
    ......
    return OK;
}
複製程式碼

通過之前的分析,我們已經追蹤到了通過InputChannel傳送訊息的程式碼,接收端的訊息處理是如何呢?是如何觸發開始接收訊息,訊息如何在傳到InputChannel之後,進行的進一步的資料傳遞呢?分析之前,這裡先對上面InputChannel進行一個總結。

InputChannel資料傳輸

之前的setView中,我們建立了InputChannel之後,開啟了對於InputChannel中輸入事件的監聽。

if (mInputChannel != null) {
   if (mInputQueueCallback != null) {
       mInputQueue = new InputQueue();
       mInputQueueCallback.onInputQueueCreated(mInputQueue);
  }
   mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                            Looper.myLooper());
}
複製程式碼

WindowInputEventReceiver的建構函式如下,其繼承自InputEventReceiver。

final class WindowInputEventReceiver extends InputEventReceiver {
     public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
         super(inputChannel, looper);
     }
      ....
}
複製程式碼

InputEventReceiver的建構函式原始碼如下

public InputEventReceiver(InputChannel inputChannel, Looper looper) {
     ....
     mInputChannel = inputChannel;
     mMessageQueue = looper.getQueue();
     mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
                inputChannel, mMessageQueue);
  }
複製程式碼

這裡呼叫了native方法來做初始化,相關的native方法的實現在android_view_InputEventReceiver.cpp

static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
        jobject inputChannelObj, jobject messageQueueObj) {
   ....
  sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
            inputChannelObj);
  sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
  sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,
            receiverWeak, inputChannel, messageQueue);
    status_t status = receiver->initialize();
  .....
}
複製程式碼

根據傳入的InputChannelMessageQueue,建立一個NativeInputEventReceiver,然後呼叫其initialize方法。

status_t NativeInputEventReceiver::initialize() {
    setFdEvents(ALOOPER_EVENT_INPUT);
    return OK;
}
複製程式碼

initialize()方法中,只呼叫了一個函式setFdEvents

void NativeInputEventReceiver::setFdEvents(int events) {
    if (mFdEvents != events) {
        mFdEvents = events;
        int fd = mInputConsumer.getChannel()->getFd();
        if (events) {
            mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL);
        } else {
            mMessageQueue->getLooper()->removeFd(fd);
        }
    }
}
複製程式碼

從InputConsumer中獲取到channel的fd,然後呼叫Looper的addFd方法。

int ALooper_addFd(ALooper* looper, int fd, int ident, int events,
        ALooper_callbackFunc callback, void* data) {
    return ALooper_to_Looper(looper)->addFd(fd, ident, events, callback, data);
}
複製程式碼

Looper的addFd的實現如下

int Looper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data) {
    Request request;
    request.fd = fd;
    request.ident = ident;
    request.events = events;
    request.seq = mNextRequestSeq++;
    request.callback = callback;
     request.data = data;
     if (mNextRequestSeq == -1) mNextRequestSeq = 0;
     struct epoll_event eventItem;
     request.initEventItem(&eventItem);
     ssize_t requestIndex = mRequests.indexOfKey(fd);
      if (requestIndex < 0) {
          int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);
          if (epollResult < 0) {
                return -1;
            }
         mRequests.add(fd, request);
       } 
}
複製程式碼

該方法所執行的操作就是對傳遞的fd新增epoll監控,Looper會迴圈呼叫pollOnce方法,而pollOnce方法的核心實現就是pollInner。其程式碼大致實現內容為等待訊息的到來,當有訊息到來後,根據訊息型別做一些判斷處理,然後呼叫其相關的callback。我們當前是對於開啟的socket的一個監聽,當有資料到來,我們便會執行相應的回撥。這裡對於InputChannel的回撥是在呼叫了NativeInputEventReceiver的handleEvent方法。

int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {
    .....
   if (events & ALOOPER_EVENT_INPUT) {
        JNIEnv* env = AndroidRuntime::getJNIEnv();
        status_t status = consumeEvents(env, false /*consumeBatches*/, -1, NULL);
        mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
        return status == OK || status == NO_MEMORY ? 1 : 0;
    }
    ....
    return 1;
}
複製程式碼

對於Event的處理,這裡呼叫consumeEvents來對事件進行處理。

status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
        bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
    ...
    for(;;) {
      ...
     InputEvent* inputEvent;
     status_t status = mInputConsumer.consume(&mInputEventFactory,
                consumeBatches, frameTime, &seq, &inputEvent);
        ...
    }
   ...
}
複製程式碼

InputConsumer是在InputTransport中做的宣告。

status_t InputConsumer::consume(InputEventFactoryInterface* factory,
        bool consumeBatches, nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
    while (!*outEvent) {
         ....
         status_t result = mChannel->receiveMessage(&mMsg);
          ....
    }
}
複製程式碼

呼叫consume方法會持續的呼叫InputChannel的receiveMessage方法來從socket中讀取資料。到這裡,我們已經將寫入socket的事件讀出來了。InputChannel在建立之後,通過為其InputEventReceiver對其fd進行epoll監控,當有變動的時候,呼叫InputChannel來接收訊息。InputChannel又是如何被設定到InputDispatcher之中的呢?在呼叫openInputChannel方法,建立Socket的完成之後,呼叫該方法。

public void registerInputChannel(InputChannel inputChannel,
            InputWindowHandle inputWindowHandle) {
   if (inputChannel == null) {
      throw new IllegalArgumentException("inputChannel must not be null.");
   }
   nativeRegisterInputChannel(mPtr, inputChannel, inputWindowHandle, false);
}
複製程式碼

nativeRegisterInputManger

static void nativeRegisterInputChannel(JNIEnv* env, jclass /* clazz */,
        jlong ptr, jobject inputChannelObj, jobject inputWindowHandleObj, jboolean monitor) {
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);

    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
            inputChannelObj);
    if (inputChannel == NULL) {
        throwInputChannelNotInitialized(env);
        return;
    }

    sp<InputWindowHandle> inputWindowHandle =
            android_server_InputWindowHandle_getHandle(env, inputWindowHandleObj);

    status_t status = im->registerInputChannel(
            env, inputChannel, inputWindowHandle, monitor);
    if (status) {
        String8 message;
        message.appendFormat("Failed to register input channel.  status=%d", status);
        jniThrowRuntimeException(env, message.string());
        return;
    }

    if (! monitor) {
        android_view_InputChannel_setDisposeCallback(env, inputChannelObj,
                handleInputChannelDisposed, im);
    }
}
複製程式碼

NativeInputManager的registerInputChannel還會呼叫到InputDispatcher的registerInputChannel,會通過InputChannel建立相應的Connection,同時將InputChannel加入到相應的監控之中。在上面對程式碼的分析之中,獲取InputChannel,就是通過這個Connection來獲取的。

status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel,
        const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {
    { // acquire lock
        AutoMutex _l(mLock);

        if (getConnectionIndexLocked(inputChannel) >= 0) {
            return BAD_VALUE;
        }

        sp<Connection> connection = new Connection(inputChannel, inputWindowHandle, monitor);

        int fd = inputChannel->getFd();
        mConnectionsByFd.add(fd, connection);

        if (monitor) {
            mMonitoringChannels.push(inputChannel);
        }

        mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
    } // release lock

    // Wake the looper because some connections have changed.
    mLooper->wake();
    return OK;
}
複製程式碼

經過InputManager的層層傳遞,最終會到達InputDispatcher之中,然後對其進行封裝,並在其內部進行儲存,同時也傳遞了相應的視窗的控制程式碼,方便了後期在事件傳遞的時候,對於視窗的判斷。

####ViewRootImpl

事件在從socket讀出之後,經過傳遞,最終會呼叫到ViewRootImpl的enqueueInputEvent方法。

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

     QueuedInputEvent last = mPendingInputEventTail;
     if (last == null) {
         mPendingInputEventHead = q;
         mPendingInputEventTail = q;
      } else {
          last.mNext = q;
          mPendingInputEventTail = q;
      }
      mPendingInputEventCount += 1;

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

enqueueInputEvent方法從InputEventReceiver中獲取到InputEvent,然後將其加入到當前的事件佇列之中,最後呼叫doProcessInputEvents來進行處理。

void doProcessInputEvents() {
    while (mPendingInputEventHead != null) {
            QueuedInputEvent q = mPendingInputEventHead;
            mPendingInputEventHead = q.mNext;
            if (mPendingInputEventHead == null) {
                mPendingInputEventTail = null;
            }
            q.mNext = null;

            mPendingInputEventCount -= 1;
      
            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);
        }

        if (mProcessInputEventsScheduled) {
            mProcessInputEventsScheduled = false;
            mHandler.removeMessages(MSG_PROCESS_INPUT_EVENTS);
        }
    }
複製程式碼

遍歷所有的訊息,如果事件型別為觸控式螢幕事件,對其進行相應的時間修改,最後對於每一個處理完成的事件呼叫deliverInputEvent,

private void deliverInputEvent(QueuedInputEvent q) {
     
     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);
      }
 }
複製程式碼

在事件分發環節,首先進行事件的一個判斷,通過shouldSkipIme來判斷是否傳遞給輸入法,然後決定使用何種InputStage進行訊息的繼續傳遞,這裡實現了多種InputStage,對於每一個型別的InputStage都實現了一個方法process方法來針對不同型別的事件做處理,如果是觸控式螢幕類的訊息,最終會將事件的處理轉交到View的身上。

InputStage中的事件如何傳遞處理,傳遞處理之後,如何進行

對於InputStage涉及的篇幅較多,這裡也不再展開,當訊息到達ViewRootImpl中後,接下來就是在View間的派發。

參考文章

Linux 上實現雙向程式間通訊管道

相關文章