Android輸入管理InputManager之派送給Window


InputDispatcher#dispatchMotionLocked分發處理事件解析

三個步驟

  • 判斷事件是否觸摸事件
  • 尋找觸屏Window
  • 事件派發

InputDispatcher#dispatchMotionLocked方法

bool InputDispatcher::dispatchMotionLocked(nsecs_t currentTime, MotionEntry* entry, 
                DropReason* dropReason, nsecs_t* nextWakeupTime) {
    ....
    //判定事件源為手指觸屏
    bool isPointerEvent = entry->source & AINPUT_SOURCE_CLASS_POINTER;
 
    Vector<InputTarget> inputTargets;
    if (isPointerEvent) {// 觸屏事件
        //初始化Window目標到inputTargets數組,返回事件注入的結果
        injectionResult = findTouchedWindowTargetsLocked(currentTime,
                entry, inputTargets, nextWakeupTime, &conflictingPointerActions);
    } else {
        ...//非觸屏事件
    }

    //派發
    dispatchEventLocked(currentTime, entry, inputTargets);
}

injectionResult 成功找到Window目標,標志是0。

INPUT_EVENT_INJECTION_SUCCEEDED = 0
其他
INPUT_EVENT_INJECTION_PERMISSION_DENIED = 1,//失敗 無權限
INPUT_EVENT_INJECTION_FAILED = 2,//失敗 無目標
INPUT_EVENT_INJECTION_TIMED_OUT = 3//超時
  • findTouchedWindowTargetsLocked解析

初始化inputTargets數組
Vector<InputTarget>& inputTargets
InputWindowHandle類型的數組,確定觸屏windowHandle
Vector<sp<InputWindowHandle> > mWindowHandles;

遍歷InputWindowHandle數組元素,尋找觸屏InputWindowHandle。

findTouchedWindowTargetsLocked方法代碼段:

int32_t pointerIndex = getMotionEventActionPointerIndex(action);
//觸摸坐標(x,y)
int32_t x = int32_t(entry->pointerCoords[pointerIndex].
                getAxisValue(AMOTION_EVENT_AXIS_X));
int32_t y = int32_t(entry->pointerCoords[pointerIndex].
                getAxisValue(AMOTION_EVENT_AXIS_Y));
size_t numWindows = mWindowHandles.size();
for (size_t i = 0; i < numWindows; i++) {
    sp<InputWindowHandle> windowHandle = mWindowHandles.itemAt(i);
    const InputWindowInfo* windowInfo = windowHandle->getInfo();
    ...
    int32_t flags = windowInfo->layoutParamsFlags;
    if (windowInfo->visible) {//信息說明窗體可見
        //flags信息說明窗體支持可觸摸
        if (! (flags & InputWindowInfo::FLAG_NOT_TOUCHABLE)) {
            isTouchModal = (flags & (InputWindowInfo::FLAG_NOT_FOCUSABLE
                            | InputWindowInfo::FLAG_NOT_TOUCH_MODAL)) == 0;
            if (isTouchModal || windowInfo->touchableRegionContainsPoint(x, y)) {
                newTouchedWindowHandle = windowHandle;
                break; //發現觸屏Window,退出遍歷
            }
        }

        if (maskedAction == AMOTION_EVENT_ACTION_DOWN
                        && (flags & InputWindowInfo::FLAG_WATCH_OUTSIDE_TOUCH)) {
            int32_t outsideTargetFlags = InputTarget::FLAG_DISPATCH_AS_OUTSIDE;
            if (isWindowObscuredAtPointLocked(windowHandle, x, y)) {
                outsideTargetFlags |= InputTarget::FLAG_WINDOW_IS_OBSCURED;
            }
            mTempTouchState.addOrUpdateWindow(
                            windowHandle, outsideTargetFlags, BitSet32(0));
        }
     }
}
...
mTempTouchState.addOrUpdateWindow(newTouchedWindowHandle, targetFlags, pointerIds);

touchableRegionContainsPoint判斷是否包含觸摸坐標(x,y),如果包含說明觸摸點在該窗體上。
根據找到的newTouchedWindowHandle更新或者增加mTempTouchState結構體中windows元素內容
windows是保存mTempTouchState(TouchState)結構體中TouchedWindow類型的數組,遍歷windows,若TouchedWindow的windowHandle與newTouchedWindowHandle相等,更新TouchedWindow中的targetFlags與pointerIds值。

TouchState#addOrUpdateWindow方法
void InputDispatcher::TouchState::addOrUpdateWindow(const sp<InputWindowHandle>& 
            windowHandle,int32_t targetFlags, BitSet32 pointerIds){
    ...
    //遍歷TouchState中windows的每個TouchedWindow
    //找到與入參InputWindowHandle相同的TouchedWindow
    //更新其中的值
    for (size_t i = 0; i < windows.size(); i++) {
        TouchedWindow& touchedWindow = windows.editItemAt(i);
        if (touchedWindow.windowHandle == windowHandle) {
            touchedWindow.targetFlags |= targetFlags;
            if (targetFlags & InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT) {
                touchedWindow.targetFlags &= ~InputTarget::FLAG_DISPATCH_AS_IS;
            }
            touchedWindow.pointerIds.value |= pointerIds.value;
            return;
        }
    }
    
    //如果沒有找到,壓入棧頂新元素,創建一個TouchedWindow
    windows.push();

    TouchedWindow& touchedWindow = windows.editTop();//操作棧頂
    //新TouchedWindow賦值 
    touchedWindow.windowHandle = windowHandle;
    touchedWindow.targetFlags = targetFlags;
    touchedWindow.pointerIds = pointerIds;
}

確保TouchState的TouchedWindow列表中有一個是保存觸屏Window信息。

最后初始化InputTargets數組,通過addWindowTargetLocked方法完成InputTargets數組初始化。

findTouchedWindowTargetsLocked方法代碼段:

// Success!  Output targets.返回注入成功結果
injectionResult = INPUT_EVENT_INJECTION_SUCCEEDED;
//遍歷TouchedWindow數組
for (size_t i = 0; i < mTempTouchState.windows.size(); i++) {
    const TouchedWindow& touchedWindow = mTempTouchState.windows.itemAt(i);
    addWindowTargetLocked(touchedWindow.windowHandle, touchedWindow.targetFlags,
                touchedWindow.pointerIds, inputTargets);
}

這時TouchState的TouchedWindow數組中應該有一個TouchedWindow的windowHandle對應觸摸的Window。一般觸摸的情況下TouchedWindow應該只有一個元素吧。
用每個TouchedWindow中的InputWindowHandle信息初始化inputTargets。

addWindowTargetLocked方法

void InputDispatcher::addWindowTargetLocked(const sp<InputWindowHandle>& windowHandle,
        int32_t targetFlags, BitSet32 pointerIds, Vector<InputTarget>& inputTargets) {
    inputTargets.push();//將一項入棧,該項默認的構造方法構建

    const InputWindowInfo* windowInfo = windowHandle->getInfo();
    InputTarget& target = inputTargets.editTop(); //取出棧頂,授予對棧頂的訪問
    target.inputChannel = windowInfo->inputChannel;
    target.flags = targetFlags;
    target.xOffset = - windowInfo->frameLeft;
    target.yOffset = - windowInfo->frameTop;
    target.scaleFactor = windowInfo->scaleFactor;
    target.pointerIds = pointerIds;
}

最后一個被賦值的InputTarget位于棧頂。

InputTarget主要賦值內容:inputChannel,targetFlags,pointerIds。
TouchedWindow#InputWindowHandle中的windowInfo信息賦值InputTarget。
包括inputChannel,它屬于WindowState中保存的的服務端InputChannel。
于是InputTargets數組有元素就包含了服務端inputChannel。

  • dispatchEventLocked方法解析

1.尋找Connection

Connection代表通向窗體的一條數據鏈路,獲得Connection意味著拿到可寫入socket套接字,便可以將事件發布給窗體。
Connection是InputDispatcher內部類,在InputChannel通道注冊時創建。

Connection封裝了三個重要對象

  • InputChannel :傳輸通道,Socket套接字通信。
  • InputWindowHandle:Window相關對象。
  • InputPublisher :封裝事件的Message,事件發布。
sp<InputChannel> inputChannel; 
sp<InputWindowHandle> inputWindowHandle; 
InputPublisher inputPublisher;

在Connection構造方法中,傳入的InputChannel同時交給InputPublisher內部,所以InputPublisher內也包含inputChannel。
Connection構造方法代碼片段:inputPublisher(inputChannel)

遍歷inputTargets,其中inputTargets中有一個InputTarget包觸屏通道
內部包含sp<InputChannel> inputChannel
尋找Connection

for (size_t i = 0; i < inputTargets.size(); i++) {
  const InputTarget& inputTarget = inputTargets.itemAt(i);
  ssize_t connectionIndex = getConnectionIndexLocked(inputTarget.inputChannel);
  if (connectionIndex >= 0) {//說明找到Connection的位置
    sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
    prepareDispatchCycleLocked(currentTime, connection, eventEntry, &inputTarget);
  }
}

getConnectionIndexLocked獲取connectionIndex,connectionIndex是mConnectionsByFd中Fd的索引值,根據connectionIndex獲取Connection。

ssize_t InputDispatcher::getConnectionIndexLocked(const sp<InputChannel>& inputChannel) {
    ssize_t connectionIndex = mConnectionsByFd.indexOfKey(inputChannel->getFd());
    if (connectionIndex >= 0) {
        sp<Connection> connection = mConnectionsByFd.valueAt(connectionIndex);
        if (connection->inputChannel.get() == inputChannel.get()) {
            return connectionIndex;
        }
    }
    return -1;
}

根據InputChannel的Fd在mConnectionsByFd中查找Connection,Connection也保存了當時傳入的服務端InputChannel,而InputTarget的InputChannel是InputWindowHandle交給的,保證這兩個通道相同,才能通過正確的鏈路傳給窗體。

mConnectionsByFd保存socket句柄與Connection的Map
KeyedVector<int, sp<Connection> > mConnectionsByFd;

系統創建每一個窗體,均會為其注冊一個服務端InputChannel,創建鏈路Connection并建立InputChannel的Fd與Connection的關系存儲Map表。

依次走到3個方法派送

  • prepareDispatchCycleLocked
  • enqueueDispatchEntriesLocked
  • startDispatchCycleLocked
void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,const sp<Connection>& connection, 
            EventEntry* eventEntry, const InputTarget* inputTarget) {
    ...
    //
    enqueueDispatchEntriesLocked(currentTime, connection, eventEntry, inputTarget);
}

void InputDispatcher::enqueueDispatchEntriesLocked(nsecs_t currentTime,const sp<Connection>& connection, 
            EventEntry* eventEntry, const InputTarget* inputTarget) {
    bool wasEmpty = connection->outboundQueue.isEmpty();

    // If the outbound queue was previously empty, start the dispatch cycle going.
    if (wasEmpty && !connection->outboundQueue.isEmpty()) {
        startDispatchCycleLocked(currentTime, connection);
    }
}
2.發布事件

在startDispatchCycleLocked中,開始真正的事件發布。

1.Connection的inputPublisher對象是發布者,實現在InputTransport.cpp文件中,可處理發布不同事件,有觸摸publishMotionEvent、Key事件publishKeyEvent等。
2.將傳入的將事件信息(例motionEntry->deviceId)進一步封裝,產生InputMessage對象。
3.依賴服務端InputChannel派送。

startDispatchCycleLocked方法片段

...
status = connection->inputPublisher.publishMotionEvent(dispatchEntry
            ->seq,motionEntry->deviceId, motionEntry->source,..);
...

InputPublisher#publishMotionEvent方法,主要工作是封裝InputMessage,利用InputPublisher的InputChannel發送。

status_t InputPublisher::publishMotionEvent(
        uint32_t seq,
        int32_t deviceId,
        ....
        const PointerProperties* pointerProperties,
        const PointerCoords* pointerCoords) {
    ....
    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.flags = flags;
    ...
    msg.body.motion.downTime = downTime;
    msg.body.motion.eventTime = eventTime;
    msg.body.motion.pointerCount = pointerCount;

    return mChannel->sendMessage(&msg);
}

發送對象是InputMessage類型

3.發送

將InputMessage對象寫入InputChannel的socket套接字
將數據寫入到套接字mFd發送緩沖區。

status_t InputChannel::sendMessage(const InputMessage* msg) {
    size_t msgLength = msg->size();
    ssize_t nWrite;
    do {
        //調用Linux send方法
        nWrite = ::send(mFd, msg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);
    } while (nWrite == -1 && errno == EINTR);
}

最終向服務端InputChannel的套接字寫入了事件信息InputMessage。

Java層IMS服務創建與注冊InputChannel通道

WMS#addWindow方法調用IMS服務提供的InputChannel創建與注冊方法。

addWindow代碼段:

public int addWindow(Session session, IWindow client, int seq,
            LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {
    .....
    WindowState win = new WindowState(this, session, client, token, 
                    attachedWindow, appOp[0], seq, attrs, viewVisibility, 
                    displayContent);
    ...
    if (outInputChannel != null && (attrs.inputFeatures
                    & LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
        String name = win.makeInputChannelName();
        //創建Java層InputChannel數組
        InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
        //服務端InputChannel
        win.setInputChannel(inputChannels[0]);
        //客戶端InputChannel
        inputChannels[1].transferTo(outInputChannel);    
        mInputManager.registerInputChannel(win.mInputChannel, 
                      win.mInputWindowHandle);
    }
    .....
}

服務端InputChannel交給WindowState注冊到InputDispatcher。在WindowState中同時交給內部InputWindowHandle。

WindowState#setInputChannel方法
void setInputChannel(InputChannel inputChannel) {
    mInputChannel = inputChannel;
    mInputWindowHandle.inputChannel = inputChannel;
}

客戶端InputChannel交給了outInputChannel。outInputChannel是addWindow的入參,即通過mWindowSession.addToDisplay方法傳入的mInputChannel,最終保存在ViewRootImpl中。

因此,InputChannel數組的一對InputChannel,一個注冊給了InputDispatcher,另一個交給應用程序ViewRootImpl。

在WMS#addWindow方法中

IMS服務提供Java層InputChannel的創建與注冊
創建:InputChannel靜態方法
InputChannel.openInputChannelPair(inputChannelName)
注冊:IMS方法
registerInputChannel(InputChannel ,InputWindowHandle)

  • InputChannel的創建

包括Java層與Native層的InputChannel,各一對,保存數組中。
JNI#nativeOpenInputChannelPair方法

public static InputChannel[] openInputChannelPair(String name) {
    ...
    return nativeOpenInputChannelPair(name)
}

C++代碼創建Java層InputChannel對象

  • 構造一個Java層jobjectArray數組對象channelPair,數組元素的類型是gInputChannelClassInfo的clazz。
    初始化值為"android/view/InputChannel",所以創建的Java對象為InputChannel。
  • 創建Java層的InputChannel對象jobject ,將創建的Native層InputChannel封裝成NativeInputChannel,指針賦值給Java層mPtr變量。
  • 最后將jobject對象為jobjectArray賦值, 數組channelPair,將其返回給Java層,即InputChannel[]。
  • Java層的InputChannel對象引用Native層NativeInputChannel指針。
JNI#nativeOpenInputChannelPair代碼段:

sp<InputChannel> serverChannel;
sp<InputChannel> clientChannel;
//Native層InputChannel的創建
status_t result = InputChannel::openInputChannelPair(name, serverChannel, 
                clientChannel);

jobjectArray channelPair = env->NewObjectArray(2, gInputChannelClassInfo.clazz, NULL);
//Java層InputChannel的創建
jobject serverChannelObj = android_view_InputChannel_createInputChannel(env,
            new NativeInputChannel(serverChannel));
jobject clientChannelObj = android_view_InputChannel_createInputChannel(env,
            new NativeInputChannel(clientChannel));
//兩個InputChannel對象設置到數組
env->SetObjectArrayElement(channelPair, 0, serverChannelObj);
env->SetObjectArrayElement(channelPair, 1, clientChannelObj);

return channelPair;//返回Java數組

InputChannel[0]代表服務端通道serverChannelObj。InputChannel[1]代表客戶端通道clientChannelObj。

InputChanne#openInputChannelPair方法

status_t InputChannel::openInputChannelPair(const String8& name,
        sp<InputChannel>& outServerChannel, sp<InputChannel>& outClientChannel) {
    int sockets[2];
    //socketpair創建一對套接字描述符,保存在sockets數組中
    if (socketpair(AF_UNIX, SOCK_SEQPACKET, 0, sockets)) {
        ...失敗處理
    }

    //setsockopt設置套接字
    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));

    //創建服務InputChannel(C++層)
    String8 serverChannelName = name;
    serverChannelName.append(" (server)");
    outServerChannel = new InputChannel(serverChannelName, sockets[0]);

    //創建客戶端InputChannel(C++層)
    String8 clientChannelName = name;
    clientChannelName.append(" (client)");
    outClientChannel = new InputChannel(clientChannelName, sockets[1]);
    return OK;
}

C++代碼創建Native層InputChannel對象

  • InputChannel代表一個通道,每個InputChannel內部均有一個socket句柄,用于進行socket通信。
  • 創建完畢后,openInputChannelPair的入參serverChannel與clientChannel指針就指向了Native層InputChannel對象
  • InputChannel的注冊

IMS服務registerInputChannel負責注冊
本質是通過InputDispatcher進行注冊,告訴InputDispatcher注冊這個通道
JNI#nativeRegisterInputChannel方法

服務端InputChannel事件接收架構圖如下:

InputChannel服務端事件接收架構圖解.png

服務端InputChannel注冊到InputDispatcher

IMS#registerInputChannel方法
public void registerInputChannel(InputChannel inputChannel,
            InputWindowHandle inputWindowHandle) {
  nativeRegisterInputChannel(mPtr, inputChannel, inputWindowHandle, false);
}

JNI#nativeRegisterInputChannel方法

nativeRegisterInputChannel代碼段:
static void nativeRegisterInputChannel(JNIEnv* env, jclass jlong ptr, jobject inputChannelObj, 
            jobject inputWindowHandleObj, jboolean monitor) {
    //根據ptr指針獲取本地NativeInputManager
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);

    //根據Java層入參InputWindowHandle對象和InputChannel對象,找到對應Native對象
    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
            inputChannelObj);
    ...
    sp<InputWindowHandle> inputWindowHandle =
            android_server_InputWindowHandle_getHandle(env, inputWindowHandleObj);
    ...
    //調用NativeInputManager的注冊通道方法
    status_t status = im->registerInputChannel(
            env, inputChannel, inputWindowHandle, monitor);
}

NativeInputManager#registerInputChannel注冊方法,實質是觸發InputDispatcher的注冊方法

mInputManager->getDispatcher()->registerInputChannel(inputChannel, 
                inputWindowHandle, monitor)

InputDispatcher注冊registerInputChannel輸入通道,主要功能:創建Connection,保存Fd與Connection的Map,保存Fd與Connection的關系表,增加對Fd監聽。

InputDispatcher#registerInputChannel代碼

status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel,
        const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {
    { //上鎖
        AutoMutex _l(mLock);
        .......
        sp<Connection> connection = new Connection(inputChannel, 
                    inputWindowHandle, monitor);

        int fd = inputChannel->getFd();
        mConnectionsByFd.add(fd, connection);
        ....
        mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);
    } //釋放鎖

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

InputDispatcher的registerInputChannel注冊方法,Looper是InputDispatcher構造方法創建,線程在服務進程中,服務創建過程在Android輸入管理InputManager之服務啟動文章中參考。handleReceiveCallback是接收事件回調方法,最后喚醒Looper。

注冊完成后,InputDispatcher增加一條派發通道。


ViewRootImpl創建窗體輸入事件監聽器WindowInputEventReceiver

監聽的本質:
在客戶端應用程序,借助Native層Looper將InputChannel通道的套接字Fd交給底層epoll進行事件流監視,將監視的事件流派發給ViewRootImpl中的樹視圖。

客戶端ViewRootImpl監聽觸摸事件接收器結構圖如下

客戶端ViewRootImpl事件接收器架構圖解.png

以下代碼段是在ViewRootImpl#setView方法中初始化監聽器的過程。

//窗體注冊到WMS,初始化通道mInputChannel
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
...
//ViewRootImpl中注冊事件監聽接收器WindowInputEventReceiver
if (mInputChannel != null) {
    if (mInputQueueCallback != null) {
        mInputQueue = new InputQueue();
        mInputQueueCallback.onInputQueueCreated(mInputQueue);
     }
    mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,Looper.myLooper());
}

WindowInputEventReceiver類繼承InputEventReceiver抽象類
InputEventReceiver構造方法

public InputEventReceiver(InputChannel inputChannel, Looper looper) {
    //InputChannel與Looper為空異常判斷
    ....
    mInputChannel = inputChannel;
    mMessageQueue = looper.getQueue();
    mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
                inputChannel, mMessageQueue);
}

JNI#nativeInit負責初始化底層,創建底層NativeInputEventReceiver接收器,mReceiverPtr保存接收器對象指針。

static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,
        jobject inputChannelObj, jobject messageQueueObj) {
    //根據java層InputChannel找到對應Native層InputChannel
    sp<InputChannel> inputChannel = android_view_InputChannel_
                    getInputChannel(env,inputChannelObj);
    //根據java層MessageQueue找到對應Native層消息隊列
    sp<MessageQueue> messageQueue = android_os_MessageQueue_
                    getMessageQueue(env, messageQueueObj);
    //創建Native層接收器,inputChannel交給mInputConsumer(inputChannel)
    sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,
            receiverWeak, inputChannel, messageQueue);
    status_t status = receiver->initialize();//初始化
    //返回給Java層指針
    receiver->incStrong(gInputEventReceiverClassInfo.clazz); 
    return reinterpret_cast<jlong>(receiver.get());
}

NativeInputEventReceiver構造方法初始化mInputConsumer,mMessageQueue。InputConsumer封裝了InputChannel。
NativeInputEventReceiver#initialize接收器初始化,設置fd事件監聽。

status_t NativeInputEventReceiver::initialize() {
    setFdEvents(ALOOPER_EVENT_INPUT);
    return OK;
}

從InputConsumer的InputChannel中拿到Fd。

//InputChannel的Fd
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);
        }
    }
}

請求Looper提供對fd的監聽支持addFd(fd, 0, events, this, NULL)
參數fd代表監聽句柄,參數events代表事件類型,值是ALOOPER_EVENT_INPUT即1。這兩個參數會寫入結構體struct epoll_event eventItem,作為epoll_ctl的入參。
初始化eventItem,events是addFd傳入的值ALOOPER_EVENT_INPUT。

int epollEvents = 0;
if (events & EVENT_INPUT) epollEvents |= EPOLLIN;//EVENT_INPUT是1
if (events & EVENT_OUTPUT) epollEvents |= EPOLLOUT;//EPOLLOUT是2
memset(eventItem, 0, sizeof(epoll_event)); 
eventItem->events = epollEvents;
eventItem->data.fd = fd;

參數this代表回調對象LooperCallback,是處理Looper事件的回調類,NativeInputEventReceiver繼承LooperCallback。
NativeInputEventReceiver實現了底層消息的回調handleEvent方法,當監聽的句柄fd發生事件,觸發NativeInputEventReceiver#handleEvent方法。
處理事件方法,負責構建Java層的事件實體對象,并回調Java方法。
int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data)

NativeInputEventReceiver#handleEvent處理事件消息

事件處理流程圖如下所示

NativeInputEventReceiver事件處理流程圖.png
  • 事件處理流程

NativeInputEventReceiver#handleEvent方法代碼段:
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;
}

NativeInputEventReceiver#consumeEvents消費事件方法
這里分析只針對觸摸事件,根據Native層MotionEvent,構造Java層的MotionEvent對象。
Java層對象是在android/view/MotionEvent

NativeInputEventReceiver#consumeEvents代碼段:
case AINPUT_EVENT_TYPE_MOTION: {              
    MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent);
    f ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {
        *outConsumedBatch = true;
    }
    //inputEventObj是Java層MotionEvent對象
    inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
    break;
}
...
if (inputEventObj) {
    ...               
    env->CallVoidMethod(receiverObj.get(),
                        gInputEventReceiverClassInfo.dispatchInputEvent,
                        seq, inputEventObj);
    ...         
} 

通過env的CallVoidMethod方法調Java層方法,gInputEventReceiverClassInfo.clazz對應的類是
android/view/InputEventReceiver。
CallVoidMethod觸發Java層InputEventReceiver#dispatchInputEvent方法。入參seq和inputEventObj,inputEventObj是Java層MotionEvent對象。

InputEventReceiver#dispatchInputEvent方法
private void dispatchInputEvent(int seq, InputEvent event) {
    mSeqMap.put(event.getSequenceNumber(), seq);
    onInputEvent(event);
}

dispatchInputEvent中調用onInputEvent,WindowInputEventReceiver重寫onInputEvent。

WindowInputEventReceiver重寫的onInputEvent
@Override
public void onInputEvent(InputEvent event) {
    enqueueInputEvent(event, this, 0, true);
}

因此WindowInputEventReceiver最終調用的方法是ViewRootImpl的enqueueInputEvent,從接收器進入ViewRootImpl,開始View事件責任鏈處理以及后續View事件傳遞。
ViewRootImpl#enqueueInputEvent方法

void enqueueInputEvent(InputEvent event,
            InputEventReceiver receiver, int flags, boolean processImmediately) {
    ....
    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();
    }
}

1:obtainQueuedInputEvent方法獲取QueuedInputEvent,mQueuedInputEventPool是QueuedInputEvent對象池的首指針,從mQueuedInputEventPool鏈表中獲取QueuedInputEvent,不存在就新建對象。將MotionEvent、InputEventReceiver和flag設置到QueuedInputEvent中。
2:mPendingInputEventTail是待處理的最后一個節點,如果不是空,將剛剛獲取的節點放到最后,如果是空,說明沒有待處理的節點,mPendingInputEventHead與mPendingInputEventTail指向當前QueuedInputEvent節點,只有一個節點需要處理。
3:doProcessInputEvents處理,循環遍歷QueuedInputEvent鏈表,mPendingInputEventHead開頭,直到mNext指向空。

void doProcessInputEvents() { 
    while (mPendingInputEventHead != null) {
        QueuedInputEvent q = mPendingInputEventHead;//拿到節點
        mPendingInputEventHead = q.mNext;//指向下一個
        if (mPendingInputEventHead == null) {
            mPendingInputEventTail = null;
        }
        q.mNext = null//每個處理節點的mNext置空,mPendingInputEventHead已經指向下一個。
        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);//發送事件
    }
    ....
}

while循環會處理完所有mPendingInputEventHead鏈表節點,最后mPendingInputEventHead與mPendingInputEventTail均變為null。

deliverInputEvent方法處理一個QueuedInputEvent節點,QueuedInputEvent節點封裝了MotionEvent對象。
InputStage責任鏈處理QueuedInputEvent節點。

InputStage處理鏈如下圖所示

InputStage處理鏈 .png
  • InputStage分析

責任鏈設計
鏈表派送與處理的對象:QueuedInputEvent事件對象

deliver派送事件入口

public final void deliver(QueuedInputEvent q) {
    if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
        forward(q);//有了結束標志就一直向后傳
    } else if (shouldDropInputEvent(q)) {//是否放棄事件
        finish(q, false);//false代表未處理狀態下的結束,增加結束標志,然后向后傳
    } else {
        apply(q, onProcess(q));
    }
}

1:forward(QueuedInputEvent q)方法,觸發onDeliverToNext派送給下一個對象,執行鏈表next節點的deliver派送方法,如果next為空,鏈式處理結束ViewRootImpl#finishInputEvent方法。
2:QueuedInputEvent有結束標志FLAG_FINISHED時,forward方法派送下一個對象。放棄事件時,finish(QueuedInputEvent q, boolean handled)結束方法,QueuedInputEvent加上FLAG_FINISHED標志,forward方法派送下一個對象。
3:以上都不滿足時進入apply,首先onProcess處理,結果傳給apply。:

InputStage子類重寫onProcess處理方法。
onProcess結果:FORWARD、FINISH_HANDLED、FINISH_NOT_HANDLED

apply(QueuedInputEvent q, int result)方法,根據onProcess結果選擇forward方法還是finish方法。

protected void apply(QueuedInputEvent q, int result) {
    if (result == FORWARD) {//自己未處理,向后傳
        forward(q);
    } else if (result == FINISH_HANDLED) {//自己節點處理過,加上結束標志,向后傳
        finish(q, true);
    } else if (result == FINISH_NOT_HANDLED) {//自己節點未處理成功,加上結束標志,向后傳
        finish(q, false);
    } else {
        throw new IllegalArgumentException("Invalid result: " + result);
    }
}

EarlyPostImeInputStage節點處理鍵盤事件,根據事件類型及其輸入源處理,觸摸事件派送到方法processPointerEvent(QueuedInputEvent q)中,返回FORWARD,繼續傳給下一個節點。
ViewPostImeInputStage節點,onProcess方法,根據輸入源類型判斷,如果是Touch事件派送到processPointerEvent處理,發送MotionEvet到View層次結構,若成功返回FINISH_HANDLED,這樣apply時QueuedInputEvent加上結束標志,后續節點便不再處理。

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

    mAttachInfo.mUnbufferedDispatchRequested = false;
    boolean handled = mView.dispatchPointerEvent(event);
    .....
    return handled ? FINISH_HANDLED : FORWARD;
}

View#dispatchPointerEvent方法就是Touch事件進入View樹形結構的入口方法。


Happy
End
^^

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容