Android觸摸事件的傳遞(四-1)--輸入系統(tǒng)-InputReader

了解更多印蔗,移步Android觸摸事件傳遞機(jī)制系列詳解

Android觸摸事件的傳遞(二)--輸入系統(tǒng)InputManagerService崖蜜,介紹IMS服務(wù)的啟動(dòng)過程會(huì)創(chuàng)建兩個(gè)native線程段只,分別是InputReader,InputDispatcher. 接下來從InputReader線程的執(zhí)行過程從threadLoop為起點(diǎn)開始分析

1 threadLoop

[-> InputReader.cpp]

bool InputReaderThread::threadLoop() {
    mReader->loopOnce(); //【見小節(jié)1.2】
    return true;
}
  • threadLoop返回值true代表的是會(huì)不斷地循環(huán)調(diào)用loopOnce()识埋。另外,如果當(dāng)返回值為false則會(huì) 退出循環(huán)。
  • 整個(gè)過程是不斷循環(huán)的地調(diào)用InputReaderloopOnce()方法年碘。

2 loopOnce();

  1. 查看InputReader配置是否修改,如界面大小展鸡、方向屿衅、鍵盤布局重新加載、指針?biāo)俣雀淖兊?/li>
  2. 從EventHub讀取事件娱颊,其中EVENT_BUFFER_SIZE = 256
  3. 處理事件
  4. 將事件傳到InputDispatcher

注:通過EventHub->getEvents()傲诵,獲取輸入事件和設(shè)備增刪事件,count為讀取的事件數(shù)量箱硕,mEventBuffer存儲(chǔ)著事件拴竹。由上一篇文章可知getEvents()是阻塞的,只有當(dāng)有事件或者被wake才會(huì)被喚醒向下執(zhí)行剧罩。

void InputReader::loopOnce() {
    int32_t oldGeneration;
    int32_t timeoutMillis;
    bool inputDevicesChanged = false;
    Vector<InputDeviceInfo> inputDevices;
    { // acquire lock
        AutoMutex _l(mLock);
 
        oldGeneration = mGeneration;
        timeoutMillis = -1;
    //查看InputReader配置是否修改栓拜,如界面大小、方向惠昔、鍵盤布局重新加載幕与、指針?biāo)俣雀淖兊?        uint32_t changes = mConfigurationChangesToRefresh;
        if (changes) {
            mConfigurationChangesToRefresh = 0;
            timeoutMillis = 0;
            refreshConfigurationLocked(changes); //刷新配置
         } else if (mNextTimeout != LLONG_MAX) {
            nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
            timeoutMillis = toMillisecondTimeoutDelay(now, mNextTimeout);
        }
    } // release lock
    ////從EventHub讀取事件,其中EVENT_BUFFER_SIZE = 256【見小節(jié)2.1】
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
 
    { // acquire lock
        AutoMutex _l(mLock);
        mReaderIsAliveCondition.broadcast();
 
        if (count) {//處理事件【見小節(jié)3.1】
            processEventsLocked(mEventBuffer, count);
        }
 
        if (mNextTimeout != LLONG_MAX) {
            nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
            if (now >= mNextTimeout) {
                mNextTimeout = LLONG_MAX;
                timeoutExpiredLocked(now);
            }
        }
 
        if (oldGeneration != mGeneration) {
            inputDevicesChanged = true;
            getInputDevicesLocked(inputDevices);
        }
    } // release lock
 
    // 發(fā)送一個(gè)消息镇防,該消息描述已更改的輸入設(shè)備啦鸣。
    if (inputDevicesChanged) {
        mPolicy->notifyInputDevicesChanged(inputDevices);
    }
    mQueuedListener->flush();  //將事件傳到InputDispatcher.
}

2.1 EventHub->getEvents(),獲取輸入事件和設(shè)備增刪事件

通過EventHub->getEvents()来氧,獲取輸入事件和設(shè)備增刪事件诫给,count為讀取的事件數(shù)量,mEventBuffer存儲(chǔ)著事件啦扬。由上一篇文章可知getEvents()是阻塞的中狂,只有當(dāng)有事件或者被wake才會(huì)被喚醒向下執(zhí)行。
Android觸摸事件的傳遞(三)--輸入系統(tǒng)EventHub

2.2 processEventsLocked 處理事件

  • 接著判斷count的值扑毡,如果有讀到事件(count大于0)胃榕,則調(diào)用
    processEventsLocked(mEventBuffer, count)處理事件。
  • processEventsLocked()函數(shù)中會(huì)遍歷所有的事件瞄摊,分別進(jìn)行處理勋又。其處理的事件類型分為四種:原始輸入事件、設(shè)備加載事件换帜、設(shè)備卸載事件及FINISHED_DEVICE_SCAN事件赐写。
  • 這里我們只關(guān)心對于設(shè)備自身產(chǎn)生的事件。也就是觸摸屏相關(guān)的事件膜赃。也就是processEventsForDeviceLocked函數(shù)中所進(jìn)行的操作。

[-> InputReader.cpp]

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; //同一設(shè)備的事件打包處理
            }
            //數(shù)據(jù)事件的處理【見小節(jié)3.3】
            processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
        } else {
            switch (rawEvent->type) {
            case EventHubInterface::DEVICE_ADDED:
                //設(shè)備添加
                addDeviceLocked(rawEvent->when, rawEvent->deviceId);
                break;
            case EventHubInterface::DEVICE_REMOVED:
                //設(shè)備移除
                removeDeviceLocked(rawEvent->when, rawEvent->deviceId);
                break;
            case EventHubInterface::FINISHED_DEVICE_SCAN:
                //設(shè)備掃描完成
                handleConfigurationChangedLocked(rawEvent->when);
                break;
            default:
                ALOG_ASSERT(false);//不會(huì)發(fā)生
                break;
            }
        }
        count -= batchSize;  //count減少已處理事件個(gè)數(shù)揉忘,表示剩余事件個(gè)數(shù)
        rawEvent += batchSize;  //rawEvent指針向后移動(dòng)batchSize個(gè)RawEvent對象跳座,也就是指到該處理的事件上端铛。
    }
}

2.2.1事件派發(fā)到Device

根據(jù)事件獲得相應(yīng)的設(shè)備類型,然后將事件交給相應(yīng)的設(shè)備處理疲眷。判斷是否忽略該事件禾蚕,如果不是忽略該事件,則會(huì)調(diào)用相應(yīng)設(shè)備的process方法進(jìn)行處理狂丝。

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);
}

2.2.3 事件派發(fā)到InputMapper

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

void InputDevice::process(const RawEvent* rawEvents, size_t count) {
     ....
    for (size_t i = 0; i < numMappers; i++) {
           InputMapper* mapper = mMappers[i];
            //調(diào)用具體mapper來處理
           mapper->process(rawEvent);
    }
    ....
}
160fe0a1c21df76b.png
  • InputMapper對應(yīng)了很多的子類换淆,這里根據(jù)事件的類型進(jìn)行相應(yīng)的派發(fā),處理几颜。
  • 事件到了這里之后倍试,如何傳遞到應(yīng)用層,這里mapper->process進(jìn)行了那些處理蛋哭。
  • 這里來看一下對于觸摸屏事件的處理函數(shù)县习。
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);
    }
}

追蹤數(shù)據(jù)流向
通過process函數(shù)處理,我們繼續(xù)追蹤函數(shù)的數(shù)據(jù)流向谆趾。對于相關(guān)的事件處理躁愿,這里最終執(zhí)行的是將

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);
        }
    }
}
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();
      ....
}
  • 在相關(guān)的函數(shù)調(diào)用之后,最終調(diào)用了dispatchTouches
  • 對于dispatchTouches中沪蓬,會(huì)根據(jù)記錄的上一次的觸摸位置彤钟,對事件的類型進(jìn)行判斷,然后做相應(yīng)的分發(fā)跷叉,事件類型有抬起逸雹,下落,移動(dòng)等性芬,然后對相應(yīng)的事件進(jìn)行分發(fā)峡眶。無論是對于何種類型的事件派發(fā),最終被調(diào)用到的都是dispatchMotion()方法植锉。
void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) {
      ....
    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函數(shù)

InputListenerInterface* InputReader::ContextImpl::getListener() {
    return mReader->mQueuedListener.get();
}

notifyMotion函數(shù)實(shí)現(xiàn)

void QueuedInputListener::notifyMotion(const NotifyMotionArgs* args) {
    mArgsQueue.push(new NotifyMotionArgs(*args));
}

我們將觸摸相關(guān)的事件進(jìn)行包裝之后辫樱,將其加入到一個(gè)ArgsQueue隊(duì)列,到此俊庇,我們已經(jīng)將數(shù)據(jù)加入到參數(shù)隊(duì)列中

InputReader的loopOnce過程, 可知當(dāng)執(zhí)行完processEventsLocked()過程, 然后便開始執(zhí)行mQueuedListener->flush()過程

2.3 QueuedInputListener的flush方法

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();
}

遍歷整個(gè)mArgsQueue數(shù)組, 在input架構(gòu)中NotifyArgs的實(shí)現(xiàn)子類主要有以下幾類:

  • NotifyConfigurationChangedArgs
  • NotifyKeyArgs
  • NotifyMotionArgs
  • NotifySwitchArgs
  • NotifyDeviceResetArgs

NotifyArgs的notify函數(shù)實(shí)現(xiàn)

void NotifyMotionArgs::notify(const sp<InputListenerInterface>& listener) const {
    listener->notifyMotion(this);
}

對于這個(gè)listener的創(chuàng)建來自于InputReader構(gòu)建的時(shí)候狮暑。

mQueuedListener = new QueuedInputListener(listener);
 mReader = new InputReader(eventHub, readerPolicy, mDispatcher);

InputDispatcher
而這里的Listener則是InputDispatcherInputDispatchernotifyMotion實(shí)現(xiàn)源碼辉饱。

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);
    ....
  //構(gòu)造MotionEntry搬男,然后將其加入到enqueueInboundEventLocked之中
  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();//喚醒其中的looper
   }
}

在該函數(shù)中,所做的事情是對于所傳遞的參數(shù)彭沼,構(gòu)造MotionEntry缔逛,然后將其加入到enqueueInboundEventLocked之中。然后喚醒其中的looper

bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
    bool needWake = mInboundQueue.isEmpty();
    mInboundQueue.enqueueAtTail(entry);
    ...
    //進(jìn)行一些事件和窗口相關(guān)的判斷處理
}

總結(jié)

觸摸事件流程2.png

補(bǔ)充--設(shè)備增加

1. addDeviceLocked

void InputReader::addDeviceLocked(nsecs_t when, int32_t deviceId) {
    ssize_t deviceIndex = mDevices.indexOfKey(deviceId);
    if (deviceIndex >= 0) {
        return; //已添加的相同設(shè)備則不再添加
    }

    InputDeviceIdentifier identifier = mEventHub->getDeviceIdentifier(deviceId);
    uint32_t classes = mEventHub->getDeviceClasses(deviceId);
    int32_t controllerNumber = mEventHub->getDeviceControllerNumber(deviceId);
   
    InputDevice* device = createDeviceLocked(deviceId, controllerNumber, identifier, classes);
    device->configure(when, &mConfig, 0);
    device->reset(when);
    mDevices.add(deviceId, device); //添加設(shè)備到mDevices
    ...
}

2 createDeviceLocked

InputDevice* InputReader::createDeviceLocked(int32_t deviceId, int32_t controllerNumber,
        const InputDeviceIdentifier& identifier, uint32_t classes) {
    //創(chuàng)建InputDevice對象
    InputDevice* device = new InputDevice(&mContext, deviceId, bumpGenerationLocked(),
            controllerNumber, identifier, classes);
    ...

    //獲取鍵盤源類型
    uint32_t keyboardSource = 0;
    int32_t keyboardType = AINPUT_KEYBOARD_TYPE_NON_ALPHABETIC;
    if (classes & INPUT_DEVICE_CLASS_KEYBOARD) {
        keyboardSource |= AINPUT_SOURCE_KEYBOARD;
    }
    if (classes & INPUT_DEVICE_CLASS_ALPHAKEY) {
        keyboardType = AINPUT_KEYBOARD_TYPE_ALPHABETIC;
    }
    if (classes & INPUT_DEVICE_CLASS_DPAD) {
        keyboardSource |= AINPUT_SOURCE_DPAD;
    }
    if (classes & INPUT_DEVICE_CLASS_GAMEPAD) {
        keyboardSource |= AINPUT_SOURCE_GAMEPAD;
    }

    //添加鍵盤類設(shè)備InputMapper
    if (keyboardSource != 0) {
        device->addMapper(new KeyboardInputMapper(device, keyboardSource, keyboardType));
    }

    //添加鼠標(biāo)類設(shè)備InputMapper
    if (classes & INPUT_DEVICE_CLASS_CURSOR) {
        device->addMapper(new CursorInputMapper(device));
    }

    //添加觸摸屏設(shè)備InputMapper
    if (classes & INPUT_DEVICE_CLASS_TOUCH_MT) {
        device->addMapper(new MultiTouchInputMapper(device));
    } else if (classes & INPUT_DEVICE_CLASS_TOUCH) {
        device->addMapper(new SingleTouchInputMapper(device));
    }
    ...
    return device;
}

該方法主要功能:

  • 創(chuàng)建InputDevice對象褐奴,將InputReadermContext賦給InputDevice對象所對應(yīng)的變量
  • 根據(jù)設(shè)備類型來創(chuàng)建并添加相對應(yīng)的InputMapper按脚,同時(shí)設(shè)置mContext.

input設(shè)備類型有很多種,以上代碼只列舉部分常見的設(shè)備以及相應(yīng)的InputMapper:

  • 鍵盤類設(shè)備:KeyboardInputMapper
  • 觸摸屏設(shè)備:MultiTouchInputMapper或SingleTouchInputMapper
  • 鼠標(biāo)類設(shè)備:CursorInputMapper

參考

Android系統(tǒng)源碼剖析-事件分發(fā)
Android 輸入系統(tǒng)(三)InputReader

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末敦冬,一起剝皮案震驚了整個(gè)濱河市辅搬,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌脖旱,老刑警劉巖堪遂,帶你破解...
    沈念sama閱讀 221,888評(píng)論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異萌庆,居然都是意外死亡溶褪,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,677評(píng)論 3 399
  • 文/潘曉璐 我一進(jìn)店門踊兜,熙熙樓的掌柜王于貴愁眉苦臉地迎上來竿滨,“玉大人,你說我怎么就攤上這事捏境∮谟危” “怎么了?”我有些...
    開封第一講書人閱讀 168,386評(píng)論 0 360
  • 文/不壞的土叔 我叫張陵垫言,是天一觀的道長贰剥。 經(jīng)常有香客問我,道長筷频,這世上最難降的妖魔是什么蚌成? 我笑而不...
    開封第一講書人閱讀 59,726評(píng)論 1 297
  • 正文 為了忘掉前任,我火速辦了婚禮凛捏,結(jié)果婚禮上担忧,老公的妹妹穿的比我還像新娘。我一直安慰自己坯癣,他們只是感情好瓶盛,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,729評(píng)論 6 397
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著示罗,像睡著了一般惩猫。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上蚜点,一...
    開封第一講書人閱讀 52,337評(píng)論 1 310
  • 那天轧房,我揣著相機(jī)與錄音,去河邊找鬼绍绘。 笑死奶镶,一個(gè)胖子當(dāng)著我的面吹牛迟赃,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播厂镇,決...
    沈念sama閱讀 40,902評(píng)論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼捺氢,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了剪撬?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,807評(píng)論 0 276
  • 序言:老撾萬榮一對情侶失蹤悠反,失蹤者是張志新(化名)和其女友劉穎残黑,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體斋否,經(jīng)...
    沈念sama閱讀 46,349評(píng)論 1 318
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡梨水,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,439評(píng)論 3 340
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了茵臭。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片疫诽。...
    茶點(diǎn)故事閱讀 40,567評(píng)論 1 352
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖旦委,靈堂內(nèi)的尸體忽然破棺而出奇徒,到底是詐尸還是另有隱情,我是刑警寧澤缨硝,帶...
    沈念sama閱讀 36,242評(píng)論 5 350
  • 正文 年R本政府宣布摩钙,位于F島的核電站,受9級(jí)特大地震影響查辩,放射性物質(zhì)發(fā)生泄漏胖笛。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,933評(píng)論 3 334
  • 文/蒙蒙 一宜岛、第九天 我趴在偏房一處隱蔽的房頂上張望长踊。 院中可真熱鬧,春花似錦萍倡、人聲如沸身弊。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,420評(píng)論 0 24
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽佑刷。三九已至,卻和暖如春酿炸,著一層夾襖步出監(jiān)牢的瞬間瘫絮,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,531評(píng)論 1 272
  • 我被黑心中介騙來泰國打工填硕, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留麦萤,地道東北人鹿鳖。 一個(gè)月前我還...
    沈念sama閱讀 48,995評(píng)論 3 377
  • 正文 我出身青樓,卻偏偏與公主長得像壮莹,于是被迫代替她去往敵國和親翅帜。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,585評(píng)論 2 359

推薦閱讀更多精彩內(nèi)容

  • 序言 最近在看Android觸摸屏事件相關(guān)的源碼命满,為了對整個(gè)事件體系的了解涝滴,所以對事件相關(guān),從事件的產(chǎn)生胶台,寫入設(shè)備...
    Jensen95閱讀 724評(píng)論 0 3
  • http://www.reibang.com/p/2bff4ecd86c9本篇博客主要是過一下Android I...
    wbo4958閱讀 7,840評(píng)論 4 20
  • 前言 上一篇文章中歼疮,對于事件的監(jiān)控和獲取做了分析,在拿到事件之后诈唬,后續(xù)是如何處理分發(fā)的呢韩脏?本篇文章主要針對在通過g...
    Jensen95閱讀 616評(píng)論 0 2
  • ??JavaScript 與 HTML 之間的交互是通過事件實(shí)現(xiàn)的赡矢。 ??事件,就是文檔或?yàn)g覽器窗口中發(fā)生的一些特...
    霜天曉閱讀 3,500評(píng)論 1 11
  • 從手指接觸屏幕到MotionEvent被傳送到Activity或者View阅仔,中間究竟經(jīng)歷了什么吹散?Android中觸...
    看書的小蝸牛閱讀 23,574評(píng)論 15 80