Android應(yīng)用處理MotionEvent的過程

基于Android 7.0源碼分析

應(yīng)用收到Motion事件傳遞至Activity的過程

應(yīng)用對于Motion事件的處理比較復(fù)雜席覆,不同類型的事件處理方式不同:

  • Down事件 直接處理
  • Move事件 對于大多數(shù)Move事件囊咏,結(jié)合繪制過程處理陕见,當(dāng)應(yīng)用收到Vsync時煌集,處理一批Move事件(Move事件之間的間隔通常小于16ms)
  • Up事件 直接處理

直接處理事件的流程(Down事件為例)

下面從應(yīng)用UI線程的Looper開始分析

int Looper::pollInner(int timeoutMillis) {
    ......
    // 從此喚醒
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    ......
    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        if (fd == mWakeEventFd) {
            if (epollEvents & EPOLLIN) {
                awoken();
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on wake event fd.", epollEvents);
            }
        } else {
            ssize_t requestIndex = mRequests.indexOfKey(fd);
            if (requestIndex >= 0) {
                int events = 0;
                if (epollEvents & EPOLLIN) events |= EVENT_INPUT;
                if (epollEvents & EPOLLOUT) events |= EVENT_OUTPUT;
                if (epollEvents & EPOLLERR) events |= EVENT_ERROR;
                if (epollEvents & EPOLLHUP) events |= EVENT_HANGUP;
                // 找到InputChannel的fd對應(yīng)的Request,封裝成Response
                // Request在應(yīng)用啟動注冊InputChannel時添加
                pushResponse(events, mRequests.valueAt(requestIndex));
            } else {
                ALOGW("Ignoring unexpected epoll events 0x%x on fd %d that is "
                        "no longer registered.", epollEvents, fd);
            }
        }
    }
    ......
    // Invoke all response callbacks.
    for (size_t i = 0; i < mResponses.size(); i++) {
        Response& response = mResponses.editItemAt(i);
        if (response.request.ident == POLL_CALLBACK) {
            int fd = response.request.fd;
            int events = response.events;
            void* data = response.request.data;
#if DEBUG_POLL_AND_WAKE || DEBUG_CALLBACKS
            ALOGD("%p ~ pollOnce - invoking fd event callback %p: fd=%d, events=0x%x, data=%p",
                    this, response.request.callback.get(), fd, events, data);
#endif
            // Invoke the callback.  Note that the file descriptor may be closed by
            // the callback (and potentially even reused) before the function returns so
            // we need to be a little careful when removing the file descriptor afterwards.
            // 調(diào)用回調(diào)的handleEvent方法哥倔,這里callback為NativeInputEventReceiver
            int callbackResult = response.request.callback->handleEvent(fd, events, data);
            if (callbackResult == 0) {
                removeFd(fd, response.request.seq);
            }

            // Clear the callback reference in the response structure promptly because we
            // will not clear the response vector itself until the next poll.
            response.request.callback.clear();
            result = POLL_CALLBACK;
        }
    }
    return result;
}

下面看NativeInputEventReceiverhandleEvent()

int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {
    ......
    if (events & ALOOPER_EVENT_INPUT) {
        JNIEnv* env = AndroidRuntime::getJNIEnv();
        // 處理InputDispatcher發(fā)送的輸入事件
        status_t status = consumeEvents(env, false /*consumeBatches*/, -1, NULL);
        mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
        return status == OK || status == NO_MEMORY ? 1 : 0;
    }
    ......
}

status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
        bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
    // consumeBatches為false
    if (consumeBatches) {
        mBatchedInputEventPending = false;
    }
    // outConsumedBatch為NULL
    if (outConsumedBatch) {
        *outConsumedBatch = false;
    }

    ScopedLocalRef<jobject> receiverObj(env, NULL);
    bool skipCallbacks = false;
    for (;;) {
        uint32_t seq;
        InputEvent* inputEvent;
        // 讀取輸入事件到inputEvent中辅搬,對于Down事件status為OK(0)
        status_t status = mInputConsumer.consume(&mInputEventFactory,
                consumeBatches, frameTime, &seq, &inputEvent);
        ......
        if (!skipCallbacks) {
            ......
            jobject inputEventObj;
            switch (inputEvent->getType()) {
            ......
            case AINPUT_EVENT_TYPE_MOTION: {
                if (kDebugDispatchCycle) {
                    ALOGD("channel '%s' ~ Received motion event.", getInputChannelName());
                }
                MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent);
                if ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {
                    *outConsumedBatch = true;
                }
                // 創(chuàng)建Java層的MotionEvent對象及其對應(yīng)的Native層的MotionEvent對象,返回JNI本地引用
                inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
                break;
            }
            ......
            }

            if (inputEventObj) {
                if (kDebugDispatchCycle) {
                    ALOGD("channel '%s' ~ Dispatching input event.", getInputChannelName());
                }
                // 調(diào)用Java層WindowInputEventReceiver(繼承自InputEventReceiver)的dispatchInputEvent方法
                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());
                skipCallbacks = true;
            }
        }
        ......
    }
}

思考:Native線程如何才能調(diào)用Java伏尼?原理是什么忿檩?

對于InputEventReceiverdispatchInputEvent()處理流程,在后文中分析爆阶。

下面看InputConsumerconsume()實現(xiàn)

status_t InputConsumer::consume(InputEventFactoryInterface* factory,
        bool consumeBatches, nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
    *outSeq = 0;
    *outEvent = NULL;

    // Fetch the next input message.
    // Loop until an event can be returned or no additional events are received.
    // 循環(huán)讀取事件直到讀取到特定的事件或者沒有事件可讀
    while (!*outEvent) {
        if (mMsgDeferred) {
            // mMsg contains a valid input message from the previous call to consume
            // that has not yet been processed.
            mMsgDeferred = false;
        } else {
            // Receive a fresh message.
            // 讀取Down事件放入InputMessage中燥透,result為OK
            status_t result = mChannel->receiveMessage(&mMsg);
            ......
        }

        switch (mMsg.header.type) {
        ......
        case AINPUT_EVENT_TYPE_MOTION: {
            // 查找事件所屬Batch,對于Down事件無Batch
            ssize_t batchIndex = findBatch(mMsg.body.motion.deviceId, mMsg.body.motion.source);
            ......
            // 創(chuàng)建MotionEvent
            MotionEvent* motionEvent = factory->createMotionEvent();
            if (! motionEvent) return NO_MEMORY;
            // 更新TouchState辨图,與Touch resample相關(guān)
            updateTouchState(&mMsg);
            // InputMessage初始化MotionEvent
            initializeMotionEvent(motionEvent, &mMsg);
            *outSeq = mMsg.body.motion.seq;
            *outEvent = motionEvent;
            break;
        }
        }
    }
    return OK;
}

對于Down事件等直接處理的事件兽掰,處理過程相對簡單,下面看Batch事件的處理過程徒役。

Move事件作為Batch處理的流程

Batch的第一個Move事件的處理

下面從NativeInputEventReceiverconsumeEvents()開始分析孽尽。

status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
        bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
    // consumeBatches為false
    if (consumeBatches) {
        mBatchedInputEventPending = false;
    }
    // outConsumedBatch為NULL
    if (outConsumedBatch) {
        *outConsumedBatch = false;
    }

    ScopedLocalRef<jobject> receiverObj(env, NULL);
    bool skipCallbacks = false;
    for (;;) {
        uint32_t seq;
        InputEvent* inputEvent;
        // 讀取Move事件,返回status為WOULD_BLOCK
        status_t status = mInputConsumer.consume(&mInputEventFactory,
                consumeBatches, frameTime, &seq, &inputEvent);
        if (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());
                            return DEAD_OBJECT;
                        }
                    }
                    // 防止Batch的后續(xù)Move事件再次請求
                    mBatchedInputEventPending = true;
                    if (kDebugDispatchCycle) {
                        ALOGD("channel '%s' ~ Dispatching batched input event pending notification.",
                                getInputChannelName());
                    }
                    // 調(diào)用WindowInputEventReceiver(繼承自InputEventReceiver)的dispatchBatchedInputEventPending方法忧勿,請求Vsync到來時處理Batch事件
                    env->CallVoidMethod(receiverObj.get(),
                            gInputEventReceiverClassInfo.dispatchBatchedInputEventPending);
                }
                return OK;
                ......
            }
            ......
        }
    }
}

status_t InputConsumer::consume(InputEventFactoryInterface* factory,
        bool consumeBatches, nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
    // consumeBatches為false
    ......
    *outSeq = 0;
    *outEvent = NULL;

    // Fetch the next input message.
    // Loop until an event can be returned or no additional events are received.
    while (!*outEvent) {
        if (mMsgDeferred) {
            // mMsg contains a valid input message from the previous call to consume
            // that has not yet been processed.
            mMsgDeferred = false;
        } else {
            // Receive a fresh message.
            // 讀取Move事件
            status_t result = mChannel->receiveMessage(&mMsg);
            if (result) {
                // 通常第二次讀取事件,result為WOULD_BLOCK
                // Consume the next batched event unless batches are being held for later.
                if (consumeBatches || result != WOULD_BLOCK) {
                    result = consumeBatch(factory, frameTime, outSeq, outEvent);
                    if (*outEvent) {
#if DEBUG_TRANSPORT_ACTIONS
                        ALOGD("channel '%s' consumer ~ consumed batch event, seq=%u",
                                mChannel->getName().string(), *outSeq);
#endif
                        break;
                    }
                }
                // 返回WOULD_BLOCK
                return result;
            }
        }

        switch (mMsg.header.type) {
        ......
        case AINPUT_EVENT_TYPE_MOTION: {
            // 對于Batch的首個Move事件杉女,batchIndex返回-1
            ssize_t batchIndex = findBatch(mMsg.body.motion.deviceId, mMsg.body.motion.source);
            ......
            // Start a new batch if needed.
            if (mMsg.body.motion.action == AMOTION_EVENT_ACTION_MOVE
                    || mMsg.body.motion.action == AMOTION_EVENT_ACTION_HOVER_MOVE) {
                // 創(chuàng)建新的Batch,Move事件添加到Batch
                mBatches.push();
                Batch& batch = mBatches.editTop();
                batch.samples.push(mMsg);
#if DEBUG_TRANSPORT_ACTIONS
                ALOGD("channel '%s' consumer ~ started batch event",
                        mChannel->getName().string());
#endif
                break;
            }
            ......
        }
        ......
        }
    }
    return OK;
}

對于Batch的首個Move事件鸳吸,創(chuàng)建新的Batch熏挎,Move事件添加到Batch,然后循環(huán)讀取晌砾,通常返回WOULD_BLOCK(無事件可讀)坎拐。最終調(diào)用WindowInputEventReceiverdispatchBatchedInputEventPending()

    // Called from native code.
    @SuppressWarnings("unused")
    private void dispatchBatchedInputEventPending() {
        onBatchedInputEventPending();
    }

    @Override
    public void onBatchedInputEventPending() {
        // 通常mUnbufferedInputDispatch為false
        if (mUnbufferedInputDispatch) {
            super.onBatchedInputEventPending();
        } else {
            // 向Choreographer添加CALLBACK_INPUT類型的回調(diào)
            scheduleConsumeBatchedInput();
        }
    }

    void scheduleConsumeBatchedInput() {
        if (!mConsumeBatchedInputScheduled) {
            mConsumeBatchedInputScheduled = true;
            // 當(dāng)Vsync到來時养匈,回調(diào)mConsumedBatchedInputRunnable
            mChoreographer.postCallback(Choreographer.CALLBACK_INPUT,
                    mConsumedBatchedInputRunnable, null);
        }
    }
  • Choreographer 負(fù)責(zé)接收顯示子系統(tǒng)分發(fā)的Vsync信號哼勇,協(xié)調(diào)動畫、輸入以及繪制

下面看Vsync到來之前呕乎,Batch的后續(xù)Move事件的處理积担。

Batch的后續(xù)Move事件的處理

下面仍然從NativeInputEventReceiverconsumeEvents()開始分析

status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
        bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
    ......
    // consumeBatches為false
    if (consumeBatches) {
        mBatchedInputEventPending = false;
    }
    // outConsumedBatch為null
    if (outConsumedBatch) {
        *outConsumedBatch = false;
    }

    ScopedLocalRef<jobject> receiverObj(env, NULL);
    bool skipCallbacks = false;
    for (;;) {
        uint32_t seq;
        InputEvent* inputEvent;
        // 讀取事件
        status_t status = mInputConsumer.consume(&mInputEventFactory,
                consumeBatches, frameTime, &seq, &inputEvent);
        // 這里status為WOULD_BLOCK
        if (status) {
            if (status == WOULD_BLOCK) {
                // mBatchedInputEventPending在Batch的首個Move事件處理時設(shè)為true
                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());
                            return DEAD_OBJECT;
                        }
                    }

                    mBatchedInputEventPending = true;
                    if (kDebugDispatchCycle) {
                        ALOGD("channel '%s' ~ Dispatching batched input event pending notification.",
                                getInputChannelName());
                    }
                    env->CallVoidMethod(receiverObj.get(),
                            gInputEventReceiverClassInfo.dispatchBatchedInputEventPending);
                    if (env->ExceptionCheck()) {
                        ALOGE("Exception dispatching batched input events.");
                        mBatchedInputEventPending = false; // try again later
                    }
                }
                // 直接返回
                return OK;
            }
        ......
        }
    }
}

status_t InputConsumer::consume(InputEventFactoryInterface* factory,
        bool consumeBatches, nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
    ......
    *outSeq = 0;
    *outEvent = NULL;

    // Fetch the next input message.
    // Loop until an event can be returned or no additional events are received.
    while (!*outEvent) {
        if (mMsgDeferred) {
            // mMsg contains a valid input message from the previous call to consume
            // that has not yet been processed.
            mMsgDeferred = false;
        } else {
            // Receive a fresh message.
            // 讀取InputMessage,有事件返回OK,再次讀取往往返回WOULD_BLOCK
            status_t result = mChannel->receiveMessage(&mMsg);
            if (result) {
                // Consume the next batched event unless batches are being held for later.
                if (consumeBatches || result != WOULD_BLOCK) {
                    result = consumeBatch(factory, frameTime, outSeq, outEvent);
                    if (*outEvent) {
#if DEBUG_TRANSPORT_ACTIONS
                        ALOGD("channel '%s' consumer ~ consumed batch event, seq=%u",
                                mChannel->getName().string(), *outSeq);
#endif
                        break;
                    }
                }
                return result;
            }
        }
        switch (mMsg.header.type) {
        ......
        case AINPUT_EVENT_TYPE_MOTION: {
            // 查找Move事件所屬的Batch
            ssize_t batchIndex = findBatch(mMsg.body.motion.deviceId, mMsg.body.motion.source);
            if (batchIndex >= 0) {
                // 添加后續(xù)Move事件到Batch
                Batch& batch = mBatches.editItemAt(batchIndex);
                if (canAddSample(batch, &mMsg)) {
                    batch.samples.push(mMsg);
#if DEBUG_TRANSPORT_ACTIONS
                    ALOGD("channel '%s' consumer ~ appended to batch event",
                            mChannel->getName().string());
#endif
                // while循環(huán)繼續(xù)讀取
                    break;
                } else {
                    // We cannot append to the batch in progress, so we need to consume
                    // the previous batch right now and defer the new message until later.
                    mMsgDeferred = true;
                    // 通常猬仁,當(dāng)Up事件到來時帝璧,不能添加到Batch先誉,如果有先前的Batch事件,立即處理
                    status_t result = consumeSamples(factory,
                            batch, batch.samples.size(), outSeq, outEvent);
                    mBatches.removeAt(batchIndex);
                    if (result) {
                        return result;
                    }
#if DEBUG_TRANSPORT_ACTIONS
                    ALOGD("channel '%s' consumer ~ consumed batch event and "
                            "deferred current event, seq=%u",
                            mChannel->getName().string(), *outSeq);
#endif
                    break;
                }
            }
            ......
        }
        ......
        }
    }
}

Batch的后續(xù)Move事件的處理相對簡單的烁,只是將Move事件添加到Batch,下面分析Vsync信號到來后褐耳,Batch事件的處理。

收到Vsync后處理Batch Move事件

下面從App收到Vsync信號調(diào)用CALLBACK_INPUT類型的回調(diào)ConsumeBatchedInputRunnable開始分析渴庆。

    final class ConsumeBatchedInputRunnable implements Runnable {
        @Override
        public void run() {
            // 處理Batch事件
            doConsumeBatchedInput(mChoreographer.getFrameTimeNanos());
        }
    }

    void doConsumeBatchedInput(long frameTimeNanos) {
        if (mConsumeBatchedInputScheduled) {
            mConsumeBatchedInputScheduled = false;
            if (mInputEventReceiver != null) {
                // 調(diào)用WindowInputEventReceiver的consumeBatchedInputEvents處理事件
                if (mInputEventReceiver.consumeBatchedInputEvents(frameTimeNanos)
                        && frameTimeNanos != -1) {
                    // If we consumed a batch here, we want to go ahead and schedule the
                    // consumption of batched input events on the next frame. Otherwise, we would
                    // wait until we have more input events pending and might get starved by other
                    // things occurring in the process. If the frame time is -1, however, then
                    // we're in a non-batching mode, so there's no need to schedule this.
                    // 請求繪制下一幀時處理Batch事件
                    scheduleConsumeBatchedInput();
                }
            }
            // 處理事件
            doProcessInputEvents();
        }
    }

    public final boolean consumeBatchedInputEvents(long frameTimeNanos) {
        if (mReceiverPtr == 0) {
            Log.w(TAG, "Attempted to consume batched input events but the input event "
                    + "receiver has already been disposed.");
        } else {
            // mReceiverPtr為NativeInputEventReceiver的地址
            return nativeConsumeBatchedInputEvents(mReceiverPtr, frameTimeNanos);
        }
        return false;
    }

static jboolean nativeConsumeBatchedInputEvents(JNIEnv* env, jclass clazz, jlong receiverPtr,
        jlong frameTimeNanos) {
    sp<NativeInputEventReceiver> receiver =
            reinterpret_cast<NativeInputEventReceiver*>(receiverPtr);
    bool consumedBatch;
    // 調(diào)用NativeInputEventReceiver的consumeEvents方法
    // 這里參數(shù)consumeBatches為true, frameTimeNanos不為-1
    status_t status = receiver->consumeEvents(env, true /*consumeBatches*/, frameTimeNanos,
            &consumedBatch);
    if (status && status != DEAD_OBJECT && !env->ExceptionCheck()) {
        String8 message;
        message.appendFormat("Failed to consume batched input event.  status=%d", status);
        jniThrowRuntimeException(env, message.string());
        return JNI_FALSE;
    }
    return consumedBatch ? JNI_TRUE : JNI_FALSE;
}

下面看consumeEvents()的處理

status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
        bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
    // consumeBatches為true
    if (consumeBatches) {
        mBatchedInputEventPending = false;
    }
    // outConsumedBatch不為NULL
    if (outConsumedBatch) {
        *outConsumedBatch = false;
    }

    ScopedLocalRef<jobject> receiverObj(env, NULL);
    bool skipCallbacks = false;
    for (;;) {
        uint32_t seq;
        InputEvent* inputEvent;
        // 讀取Batch事件放入inputEvent中漱病,這里status通常返回OK
        status_t status = mInputConsumer.consume(&mInputEventFactory,
                consumeBatches, frameTime, &seq, &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());
                    return DEAD_OBJECT;
                }
            }

            jobject inputEventObj;
            switch (inputEvent->getType()) {
            ......
            case AINPUT_EVENT_TYPE_MOTION: {
                if (kDebugDispatchCycle) {
                    ALOGD("channel '%s' ~ Received motion event.", getInputChannelName());
                }
                MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent);
                // 處理Batch Move事件
                if ((motionEvent->getAction() & AMOTION_EVENT_ACTION_MOVE) && outConsumedBatch) {
                    *outConsumedBatch = true;
                }
                inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
                break;
            }
            ......
            }

            if (inputEventObj) {
                if (kDebugDispatchCycle) {
                    ALOGD("channel '%s' ~ Dispatching input event.", getInputChannelName());
                }
                // 調(diào)用InputEventReceiver的dispatchInputEvent方法處理
                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());
                skipCallbacks = true;
            }
        }
        ......
    }
}

status_t InputConsumer::consume(InputEventFactoryInterface* factory,
        bool consumeBatches, nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
    ......
    *outSeq = 0;
    *outEvent = NULL;

    // Fetch the next input message.
    // Loop until an event can be returned or no additional events are received.
    while (!*outEvent) {
        if (mMsgDeferred) {
            // mMsg contains a valid input message from the previous call to consume
            // that has not yet been processed.
            mMsgDeferred = false;
        } else {
            // Receive a fresh message.
            // 讀取消息,這里通常返回WOULD_BLOCK
            status_t result = mChannel->receiveMessage(&mMsg); 
            if (result) {
                // Consume the next batched event unless batches are being held for later.
                // consumeBatches為true把曼,處理Batch事件
                if (consumeBatches || result != WOULD_BLOCK) {
                    result = consumeBatch(factory, frameTime, outSeq, outEvent);
                    if (*outEvent) {
#if DEBUG_TRANSPORT_ACTIONS
                        ALOGD("channel '%s' consumer ~ consumed batch event, seq=%u",
                                mChannel->getName().string(), *outSeq);
#endif                  
                        // 這里(*outEvent不為NULL)杨帽,跳出while循環(huán)
                        break;
                    }
                }
                return result;
            }
        }
        ......
    }
    //返回
    return OK;
}

status_t InputConsumer::consumeBatch(InputEventFactoryInterface* factory,
        nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
    status_t result;
    for (size_t i = mBatches.size(); i > 0; ) {
        i--;
        Batch& batch = mBatches.editItemAt(i);
        ......
        nsecs_t sampleTime = frameTime;
        if (mResampleTouch) {
            sampleTime -= RESAMPLE_LATENCY;
        }
        // 找到事件早于sampleTime的事件
        ssize_t split = findSampleNoLaterThan(batch, sampleTime);
        if (split < 0) {
            continue;
        }
        // Batch中的事件合成一個MotionEvent
        result = consumeSamples(factory, batch, split + 1, outSeq, outEvent);
        const InputMessage* next;
        if (batch.samples.isEmpty()) {
            // Batch中無事件,移除Batch
            mBatches.removeAt(i);
            next = NULL;
        } else {
            // Batch中有晚于sampleTime的事件
            next = &batch.samples.itemAt(0);
        }
        if (!result && mResampleTouch) {
            // 重采樣嗤军,采樣事件信息存放到MotionEvent的mSamplexxx的最后
            // View層獲取的Move事件的信息為采樣事件信息
            // 針對next是否為NULL采樣兩種重采樣算法注盈,請參考http://www.masonchang.com/blog/2014/8/25/androids-touch-resampling-algorithm自行閱讀resampleTouchState實現(xiàn)
            resampleTouchState(sampleTime, static_cast<MotionEvent*>(*outEvent), next);
        }
        return result;
    }

無論直接處理還是Batch處理,最終都通過WindowInputEventReceiverdispatchInputEvent()處理叙赚,下面分析該過程老客。

事件從ViewRootImpl傳遞至Activity的過程

下面從WindowInputEventReceiverOnInputEvent()開始分析

    public void onInputEvent(InputEvent event) {
        // 調(diào)用enqueueInputEvent處理
        enqueueInputEvent(event, this, 0, true);
    }

    void enqueueInputEvent(InputEvent event,
            InputEventReceiver receiver, int flags, boolean processImmediately) {
        // receiver為WindowInputEventReceiver
        // flags為0, processImmediately為true
        adjustInputEventForCompatibility(event);
        // 事件封裝成QueuedInputEvent
        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;
        // 更新systrace中,應(yīng)用內(nèi)"aq:pending:xxx"信息(增加計數(shù))
        Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
                mPendingInputEventCount);

        if (processImmediately) {
            // 立即處理事件
            doProcessInputEvents();
        } else {
            scheduleProcessInputEvents();
        }
    }

    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;
            // 更新systrace中震叮,應(yīng)用內(nèi)"aq:pending:xxx信息"(減少計數(shù))
            Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
                    mPendingInputEventCount);

            // 對于Batch事件胧砰,eventTime為重采樣事件時間,oldestEventTime為Batch的首個Move事件的時間
            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);
        }
        ......
    }

下面看deliverInputEvent()的實現(xiàn)

    private void deliverInputEvent(QueuedInputEvent q) {
        // systrace中苇瓣,應(yīng)用內(nèi)"deliverInputEvent"的開始
        Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
                q.mEvent.getSequenceNumber());
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
        }

        InputStage stage;
        // q.mFlags為0
        if (q.shouldSendToSynthesizer()) {
            stage = mSyntheticInputStage;
        } else {
            // 對于普通的MotionEvent shouldSkipIme返回true
            stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
        }

        if (stage != null) {
            // 傳遞給EarlyPostImeInputStage處理尉间,多個InputStage采用職責(zé)鏈模式
            stage.deliver(q);
        } else {
            finishInputEvent(q);
        }
    }

下面看EarlyPostImeInputStage的對MotionEvent的處理

    public final void deliver(QueuedInputEvent q) {
        if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {
            forward(q);
        } else if (shouldDropInputEvent(q)) {
            finish(q, false);
        } else {
            // 調(diào)用onProcess處理,然后調(diào)用apply
            apply(q, onProcess(q));
        }
    }

    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) {
                // 普通的MotionEvent為Pointer事件
                return processPointerEvent(q);
            }
        }
        return FORWARD;
    }

    private int processPointerEvent(QueuedInputEvent q) {
        final MotionEvent event = (MotionEvent)q.mEvent;
        // Translate the pointer event for compatibility, if needed.
        if (mTranslator != null) {
            mTranslator.translateEventInScreenToAppWindow(event);
        }
        // Enter touch mode on down or scroll.
        final int action = event.getAction();
        if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_SCROLL) {
            // 確保窗口處于touch模式
            ensureTouchMode(true);
        }
        // Offset the scroll position.
        if (mCurScrollY != 0) {
            event.offsetLocation(0, mCurScrollY);
        }
        // Remember the touch position for possible drag-initiation.
        if (event.isTouchEvent()) {
            // 記錄坐標(biāo)击罪,對與Batch事件是重采樣坐標(biāo)
            mLastTouchPoint.x = event.getRawX();
            mLastTouchPoint.y = event.getRawY();
            mLastTouchSource = event.getSource();
        }
        // 返回FORWARD
        return FORWARD;
    }

    protected void apply(QueuedInputEvent q, int result) {
        // 根據(jù)onProcess返回的結(jié)果哲嘲,進(jìn)行相應(yīng)的處理
        if (result == FORWARD) {
            // forward中調(diào)用onDeliverToNext
            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);
        }
    }

    protected void onDeliverToNext(QueuedInputEvent q) {
        if (DEBUG_INPUT_STAGES) {
            Log.v(mTag, "Done with " + getClass().getSimpleName() + ". " + q);
        }
        這里mNext為NativePostImeInputStage
        if (mNext != null) {
            mNext.deliver(q);
        } else {
            finishInputEvent(q);
        }
    }

EarlyPostImeInputStage處理完事件后,傳遞給NativePostImeInputStage處理媳禁,NativePostImeInputStage的處理過程非常簡單眠副,下面直接看ViewPostImeInputStage的處理。

    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) {
                // 處理Pointer MotionEvent
                return processPointerEvent(q);
            } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                return processTrackballEvent(q);
            } else {
                return processGenericMotionEvent(q);
            }
        }
    }

    private int processPointerEvent(QueuedInputEvent q) {
        final MotionEvent event = (MotionEvent)q.mEvent;
        mAttachInfo.mUnbufferedDispatchRequested = false;
        // 這里eventTarget為mView也就是DecorView
        final View eventTarget =
                (event.isFromSource(InputDevice.SOURCE_MOUSE) && mCapturingView != null) ?
                        mCapturingView : mView;
        mAttachInfo.mHandlingPointerEvent = true;
        // 調(diào)用DecorView的dispatchPointerEvent方法
        boolean handled = eventTarget.dispatchPointerEvent(event);
        maybeUpdatePointerIcon(event);
        mAttachInfo.mHandlingPointerEvent = false;
        if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
            mUnbufferedInputDispatch = true;
            if (mConsumeBatchedInputScheduled) {
                scheduleConsumeBatchedInputImmediately();
            }
        }
        // handled為true竣稽,事件處理完成
        // handled為false囱怕,事件沒有處理,繼續(xù)傳遞給下一個InputStage毫别,當(dāng)InputStage為null時娃弓,事件處理完成
        return handled ? FINISH_HANDLED : FORWARD;
    }

    public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
            // 調(diào)用dispatchTouchEvent繼續(xù)處理
            return dispatchTouchEvent(event);
        } else {
            return dispatchGenericMotionEvent(event);
        }
    }

    public boolean dispatchTouchEvent(MotionEvent ev) {
        // 這里cb為Activity,Activity實現(xiàn)了Window.Callback
        final Window.Callback cb = mWindow.getCallback();
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }

至此拧烦,Motion事件已經(jīng)傳遞給Activity忘闻,下面看Motion事件在Activity窗口內(nèi)傳遞的過程。

Motion事件在Activity窗口內(nèi)傳遞的過程

下面從ActivitydispatchTouchEvent()開始分析

    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            // 用戶交互回調(diào)
            onUserInteraction();
        }
        // 調(diào)用PhoneWindow的superDispatchTouchEvent
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        // Activity內(nèi)沒有View處理(可處理窗口范圍外的事件)
        return onTouchEvent(ev);
    }
  • 這里可以重寫ActivitydispatchTouchEvent截獲所有Touch事件

下面看PhoneWindowsuperDispatchTouchEvent()的實現(xiàn)

    public boolean superDispatchTouchEvent(MotionEvent event) {
        // 調(diào)用DecorView的superDispatchTouchEvent方法
        return mDecor.superDispatchTouchEvent(event);
    }

    public boolean superDispatchTouchEvent(MotionEvent event) {
        // DecorView繼承自FrameLayout, FrameLayout繼承自ViewGroup
        // 調(diào)用ViewGroup的dispatchTouchEvent方法
        return super.dispatchTouchEvent(event);
    }

下面分析ViewGroupdispatchTouchEvent()實現(xiàn)

    public boolean dispatchTouchEvent(MotionEvent ev) {
        // 用于調(diào)試目的
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }
        ......
        // 根據(jù)安全策略過濾事件
        if (onFilterTouchEventForSecurity(ev)) {
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // Handle an initial down.
            // Down事件恋博,清理TouchState
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                // Throw away all previous state when starting a new touch gesture.
                // The framework may have dropped the up or cancel event for the previous gesture
                // due to an app switch, ANR, or some other state change.
                // 清理先前Touch事件的TouchStage齐佳,通常不需要(up事件時會清理),但在app switch债沮、ANR等一些情況下
                // 之前的Touch事件的up/cancel事件可能被丟棄
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            // Check for interception.
            // 檢查是否需要攔截事件
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    // 檢查是否攔截炼吴,默認(rèn)不攔截Motion事件,子類可重寫
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                // 對于非Down事件疫衩,如果沒有touch targets硅蹦,攔截
                intercepted = true;
            }

            // Check for cancelation.
            // 檢查是否需要取消
            final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;
            ......
            if (!canceled && !intercepted) {

                // If the event is targeting accessiiblity focus we give it to the
                // view that has accessibility focus and if it does not handle it
                // we clear the flag and dispatch the event to all children as usual.
                // We are looking up the accessibility focused host to avoid keeping
                // state since these events are very rare.
                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                        ? findChildWithAccessibilityFocus() : null;
                // 對于Down事件
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                    final int actionIndex = ev.getActionIndex(); // always 0 for down
                    final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;
                    ......
                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        // Find a child that can receive the event.
                        // Scan children from front to back.
                        // 從前到后查找子View,找到能夠接收事件的孩子
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);
                            ......
                            // View是否可以接收Pointer事件闷煤,事件坐標(biāo)是否在View區(qū)域內(nèi)
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

                            newTouchTarget = getTouchTarget(child);
                            // 分發(fā)Down事件給子View
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // Child wants to receive touch within its bounds.
                                // 子View處理了事件
                                mLastTouchDownTime = ev.getDownTime();
                                if (preorderedList != null) {
                                    // childIndex points into presorted list, find original index
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                // 子View封裝為TouchTarget童芹,添加到TouchTarget鏈表中
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
                            ......
                        }
                        ......
                    }
                }
            }

            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                // 沒有touch targets,ViewGroup像普通View一樣分發(fā)
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                // Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                // 遍歷TouchTarget鏈表分發(fā)事件
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        // 已經(jīng)分發(fā)給該TouchTarget鲤拿,如Down事件
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        // 分發(fā)非Down事件給TouchTarget對應(yīng)的子View
                        // 如果cancelChild為true假褪,分發(fā)CANCEL事件
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            // cancelChild為true,刪除當(dāng)前的TouchTarget
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }

            // Update list of touch targets for pointer up or cancel, if needed.
            if (canceled
                    || actionMasked == MotionEvent.ACTION_UP
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                // 如果是CANCEL或者UP事件近顷,重置TouchState
                resetTouchState();
            } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
                final int actionIndex = ev.getActionIndex();
                final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
                removePointersFromTouchTargets(idBitsToRemove);
            }
        }
        ......
        // 返回處理結(jié)果
        return handled;
    }

這里簡單總結(jié)下Touch事件的處理:

  • 處理Down事件時生音,確定TouchTarget樹,此后MOVE/UP事件沿著TouchTarget樹分發(fā)
  • 分發(fā)MOVE/UP事件時窒升,如果中途被攔截缀遍,則向子樹發(fā)送CANCEL事件,刪除子View對應(yīng)的TouchTarget
  • 處理UP/CANCEL事件時饱须,會重置TouchTarget

對于后續(xù)的處理過程域醇,這里暫不討論,請自行擼碼蓉媳,歡迎討論交流歹苦。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市督怜,隨后出現(xiàn)的幾起案子殴瘦,更是在濱河造成了極大的恐慌,老刑警劉巖号杠,帶你破解...
    沈念sama閱讀 206,214評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件蚪腋,死亡現(xiàn)場離奇詭異,居然都是意外死亡姨蟋,警方通過查閱死者的電腦和手機屉凯,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,307評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來眼溶,“玉大人悠砚,你說我怎么就攤上這事√梅桑” “怎么了灌旧?”我有些...
    開封第一講書人閱讀 152,543評論 0 341
  • 文/不壞的土叔 我叫張陵绑咱,是天一觀的道長。 經(jīng)常有香客問我枢泰,道長描融,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,221評論 1 279
  • 正文 為了忘掉前任衡蚂,我火速辦了婚禮窿克,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘毛甲。我一直安慰自己年叮,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,224評論 5 371
  • 文/花漫 我一把揭開白布玻募。 她就那樣靜靜地躺著只损,像睡著了一般。 火紅的嫁衣襯著肌膚如雪补箍。 梳的紋絲不亂的頭發(fā)上改执,一...
    開封第一講書人閱讀 49,007評論 1 284
  • 那天,我揣著相機與錄音坑雅,去河邊找鬼辈挂。 笑死,一個胖子當(dāng)著我的面吹牛裹粤,可吹牛的內(nèi)容都是我干的终蒂。 我是一名探鬼主播,決...
    沈念sama閱讀 38,313評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼遥诉,長吁一口氣:“原來是場噩夢啊……” “哼拇泣!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起矮锈,我...
    開封第一講書人閱讀 36,956評論 0 259
  • 序言:老撾萬榮一對情侶失蹤霉翔,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后苞笨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體债朵,經(jīng)...
    沈念sama閱讀 43,441評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,925評論 2 323
  • 正文 我和宋清朗相戀三年瀑凝,在試婚紗的時候發(fā)現(xiàn)自己被綠了序芦。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,018評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡粤咪,死狀恐怖谚中,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤宪塔,帶...
    沈念sama閱讀 33,685評論 4 322
  • 正文 年R本政府宣布磁奖,位于F島的核電站,受9級特大地震影響蝌麸,放射性物質(zhì)發(fā)生泄漏点寥。R本人自食惡果不足惜艾疟,卻給世界環(huán)境...
    茶點故事閱讀 39,234評論 3 307
  • 文/蒙蒙 一来吩、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧蔽莱,春花似錦弟疆、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,240評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至仪糖,卻和暖如春柑司,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背锅劝。 一陣腳步聲響...
    開封第一講書人閱讀 31,464評論 1 261
  • 我被黑心中介騙來泰國打工攒驰, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人故爵。 一個月前我還...
    沈念sama閱讀 45,467評論 2 352
  • 正文 我出身青樓玻粪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親诬垂。 傳聞我的和親對象是個殘疾皇子劲室,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,762評論 2 345

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

  • 一、前言 Android應(yīng)用的開發(fā)過程不可能不涉及到Touch事件的處理结窘,簡單地如設(shè)置OnClickListene...
    SparkInLee閱讀 7,016評論 3 20
  • 觸屏是用戶和手機交互的基礎(chǔ)很洋,手指觸屏?xí)r產(chǎn)生一系列事件,控制視圖改變隧枫,在樹形視圖中喉磁,事件從頂層向下傳遞。 View和...
    gczxbb閱讀 600評論 0 1
  • 在Android開發(fā)中悠垛,事件分發(fā)機制是一塊Android比較重要的知識體系线定,了解并熟悉整套的分發(fā)機制有助于更好的分...
    Jwennnnnnnnnn閱讀 3,931評論 9 63
  • 1 概述 當(dāng)Android系統(tǒng)捕獲到觸摸事件后,如何準(zhǔn)確地傳遞給真正需要這個事件的View呢确买?Android系統(tǒng)給...
    小蕓論閱讀 5,017評論 2 38
  • 怎么才能遇見真是的自己斤讥,也許在最孤獨無助的深夜吧?有人說這個世界有兩種人,一種是像我這樣的一種是不像我這樣的芭商!可是...
    夢云帆閱讀 145評論 3 3