Android 4.2 输入流程分析

安卓虚拟机Vectras VM实战:在手机上跑Windows NT 4.0与ME 虚拟机是现代计算环境中实现系统隔离与兼容性模拟的关键技术,而QEMU作为开源硬件虚拟化方案,能够通过软件模拟x86指令集,让Android设备运行跨架构的旧版操作系统。这种底层模拟原理,使得原本面向PC的Windows NT 4.0、Windows ME等历史系统,得以在手机或平板上重新启动。其技术价值在于,无需额外硬件,即可为老软件测试、教学演示和系统演变研究提供轻量级沙盒环境。在实际工程应用中,用户需要理解Android宿主资源分配、QEMU参数配置、磁盘镜像格式与显卡兼容性等要点,才能顺利跑通安装流程 阅读详情

Android 4.2 輸入流程研究心得

1. 前言

  Android中管理Input的兩個主要相關角色, 一是WindowManagerService, 一是跟View相關的ViewRootImpl. 基本原理方向從2.3到目前的4.2都一樣,在  Android app一啟動之後, ViewRootImpl 就會先跟 WindowManagerService 建立inputChannel, 一旦 WindowManagerService 有收到 event 就會經由 inputChannel 通知 ViewRootImpl 去共享內存中抓取 event. 雖然方向一樣, 但是裡面的架構有改,目前最新的版本是android 4.2, 所以以下的輸入事件處理程序是以4.2來說明, 以後的版本一定會再更改.到時候在研究.

 

2. 事件處理程序

  2.1 建立 InputChannel

  Android Activity 一啟動時會先利用 setContentView 來設置畫面, 最後會由 ViewRootImpl 中的 setView函數來做畫面的設置, setContentView 跟 setView 有甚麼關係, 並不是這裡的重點. 那是屬於Surface 的範圍. 接下來就來看 setView函數的實作, 跟輸入流程無關的程式碼就略掉.

//ViewRootImpl.java

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {

        synchronized (this) {    

            // do something

            mInputChannel = new InputChannel();

            // do something

            res = mWindowSession.addToDisplay(mWindow, mSeq,                            

                            mWindowAttributes,

                            getHostVisibility(), mDisplay.getDisplayId(),

                            mAttachInfo.mContentInsets, mInputChannel);

            //do something

        }

}

由此可知, new出來的 InputChannel 物件會傳入 mWindowSession 的 addToDisplay 函數中, 去查了一下mWindowSession 是由 IWindowSession 宣告, 一般 Ixxxx 的資料型態都是一個 Binder的介面, 通常功能實作都是會定義在由 Ixxxx 衍生的類別, 搜尋了一下程式碼, Session 繼承 IWindowSession , 因此 addToDisplay 函數必在 Session類別實作.

 

 

 

 

// Session.java

@Override

    public int addToDisplay(IWindow window, int seq,

                        WindowManager.LayoutParams attrs,

                       int viewVisibility, int displayId, Rect outContentInsets,

                       InputChannel outInputChannel) {

                 return mService.addWindow(this, window, seq, attrs,

                                       viewVisibility, displayId,

                                   outContentInsets, outInputChannel);

    }

 

InputChannel 物件 繼續傳入 WindowManagerService (mService 的資料型態是 WindowManagerService ) 的addWindow 函數.

// WindowManagerService.java

public int addWindow(Session session, IWindow client, int seq,

            WindowManager.LayoutParams attrs, int viewVisibility,

            int displayId,

            Rect outContentInsets, InputChannel outInputChannel) {

           //do something

           String name = win.makeInputChannelName();

           InputChannel[] inputChannels =

                InputChannel.openInputChannelPair(name);

           win.setInputChannel(inputChannels[0]);

           inputChannels[1].transferTo(outInputChannel);

           mInputManager.registerInputChannel(win.mInputChannel,

                                    win.mInputWindowHandle);

           //do something

}

之後就是利用 InputManagerService 的 registerInputChannel 函數將 WindowManagerService 和 ViewRootImpl之間的 InputChannel 建立起來. 一旦之間的通道建立起來, WindowManagerService 和 ViewRootImpl 之間就可以做輸入event的流程溝通. WindowManagerService -> ViewRootImpl, 這個方向是 WindowManagerService 通知ViewRootImpl 去共享內存去取input event. ViewRootImpl -> WindowManagerService, 這個方向是 ViewRootImpl通知 WindowManagerService 已經處理完共享內存的 input event了, 請 WindowManagerService 在檢查是否有新的input event, 若有的話就放入共享內存. 以下就來介紹 input event 流程.

 

        2.2 輸入事件處理流程:

                一開機的時候, SystemServer 會啟動 InputManagerService, 這時 InputManagerService 又會去啟動兩個Thread, InputReaderThread, InputDispatcherThread 和一個 EventHubInterface . 因此輸入事件處理流程跟此三位角色有關係. InputReaderThread 從 EventHubInterface 抓取新的input event, 然後在依各個的event type 進入各個的 event mapper,  各個的 event mapper 處理輸入事件完之後, InputReaderThread  將new input 放進 Queue中. 之後 InputDispatcherThread 再從Queue中取出Input Event 放入共享內存中. 此時再通知 View 去共享內存抓取new Input Event, 取完之後再通知 InputDispatcherThread 是否可以再放新的Input Event到共享內存中, 流程如下

首先先由InputReaderThread 啟動開始, 在android啟動 thread 是以下的形式.

// InputManager.cpp

mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);

這時候InputReaderThread 的 threadLoop 函數就會被觸發.

// InputReader.cpp

bool InputReaderThread::threadLoop() {

    mReader->loopOnce();

    return true;

}

 

void InputReader::loopOnce() {

    //do something

    // 從 /dev/input下獲得新的Input event

    mEventHub->getEvents(timeoutMillis, mEventBuffer,

                        EVENT_BUFFER_SIZE);

    //依各個的event type 進入各個的 event mapper

    processEventsLocked(mEventBuffer, count);

 

    //通知 InputDispatcherThread 去處理new input.

    mQueuedListener->flush();

    //do something

}

在 loopOnce 函式中作了三件事 getEvents, processEventsLocked, flush. 在這篇關心的是輸入事件處理流程, 所以就先來看從 EventHub 得到的 input event, InputReader 是如何藉著 processEventsLocked 去做處理.

//InputReader.cpp

void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {

      //do something

      int32_t deviceId = rawEvent->deviceId;

      //do something

      processEventsForDeviceLocked(deviceId, rawEvent, batchSize);

      //do something

}

 

void InputReader::processEventsForDeviceLocked(int32_t deviceId,

        const RawEvent* rawEvents, size_t count) {

        //do something

        InputDevice* device = mDevices.valueAt(deviceIndex);

        if (device->isIgnored()) {

           //ALOGD("Discarding event for ignored deviceId %d.", deviceId);

           return;

        }

 

        device->process(rawEvents, count);

}

 

void InputDevice::process(const RawEvent* rawEvents, size_t count) {

      //do something

      for (const RawEvent* rawEvent = rawEvents; count--; rawEvent++) {

         //do something

          for (size_t i = 0; i < numMappers; i++) {

                InputMapper* mapper = mMappers[i];

                mapper->process(rawEvent);

            }

         //do something

      }

      //do something

}

由此可知, InputReader 會利用一個mapper來管理所收到的 input event, 這些mapper 有 SwitchInputMapper, VibratorInputMapper, KeyboardInputMapper, CursorInputMapper, TouchInputMapper, SingleTouchInputMapper, MultiTouchInputMapper, JoystickInputMapper等等. 由於這些mapper處理的架構都差不多, 就拿 TouchInputMapper 來作分析

//InputReader.cpp

void TouchInputMapper::process(const RawEvent* rawEvent) {

    // 處理以下的輸入事件, 並將各個的Touch value作相對應處理

    mCursorButtonAccumulator.process(rawEvent);

    mCursorScrollAccumulator.process(rawEvent);

    mTouchButtonAccumulator.process(rawEvent);

    if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {

        sync(rawEvent->when);

    }

}

 

void TouchInputMapper::sync(nsecs_t when) {

     //do something

     dispatchTouches(when, policyFlags);

     //do something

}

 

void TouchInputMapper::dispatchTouches(nsecs_t when, uint32_t policyFlags) {

     //do something

     // No pointer id changes so this is a move event.

     // The listener takes care of batching moves so we don't have to deal with that

     // here.

     dispatchMotion(when, policyFlags, mSource,

                    AMOTION_EVENT_ACTION_MOVE, 0, metaState,

                    buttonState,

                    AMOTION_EVENT_EDGE_FLAG_NONE,

                    mCurrentCookedPointerData.pointerProperties,

                    mCurrentCookedPointerData.pointerCoords,

                    mCurrentCookedPointerData.idToIndex,

                    currentIdBits, -1,

                    mOrientedXPrecision, mOrientedYPrecision,

                    mDownTime);

    

     //do something

}

 

void TouchInputMapper::dispatchMotion(nsecs_t when, uint32_t policyFlags,

        uint32_t source,

        int32_t action, int32_t flags, int32_t metaState, int32_t buttonState,

        int32_t edgeFlags,

        const PointerProperties* properties, const PointerCoords* coords,

        const uint32_t* idToIndex, BitSet32 idBits,

        int32_t changedId, float xPrecision, float yPrecision, nsecs_t downTime) {

 

        //do something

       

      NotifyMotionArgs args(when, getDeviceId(), source, policyFlags,

            action, flags, metaState, buttonState, edgeFlags,

            mViewport.displayId, pointerCount, pointerProperties,

            pointerCoords,

            xPrecision, yPrecision, downTime);

    getListener()->notifyMotion(&args);

}

 

InputListenerInterface* InputReader::ContextImpl::getListener() {

    return mReader->mQueuedListener.get();

}

最後會發現會將所收到的Touch motion 資訊打包成message, 然後利用 QueuedInputListener 將 message 推入mArgsQueue 向量裡.

// InputListener.h

Vector<NotifyArgs*> mArgsQueue;

 

// InputListener.cpp

void QueuedInputListener::notifyMotion(const NotifyMotionArgs* args) {

    mArgsQueue.push(new NotifyMotionArgs(*args));

}

InputReader 處理完有關 Touch Motion input event之後, 便會把新的 Touch Motion input event 放進InboundQueue 流程如下:

//InputReader.cpp

mQueuedListener->flush();

 

//InputListener.cpp

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

}

在此會發現會用個loop來呼叫mArgsQueue vector中的 notify 函數, 繼續看下去

 

 

//InputListener.cpp

void NotifyMotionArgs::notify(const sp<InputListenerInterface>& listener) const {

    listener->notifyMotion(this);

}

在此發現notifyMotion 函數是由 InputListenerInterface 類別的物件所帶出來的, 由字串來看此類別只是當作是一個介面, 所以實作必定是其類別衍生的類別. 搜尋了一下整個程式碼, 發現 InputDispatcherInterface 繼承InputListenerInterface,

而 InputDispatcher 繼承 InputDispatcherInterface類別, 所以 notifyMotion 函數必定實作在這類別中

// InputDispatcher.cpp

void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {

     //do something

    // Policy:

    // - Ignore untrusted events and pass them along.

    // - No special filtering for injected events required at this time.

    // - Filter normal events based on screen state.

    // - For normal events brighten (but do not wake) the screen if currently dim.

     mPolicy->interceptMotionBeforeQueueing(args->eventTime, /*byref*/

                                        policyFlags);

     bool needWake;

    { // acquire lock

        mLock.lock();

 

        if (shouldSendMotionToInputFilterLocked(args)) {

            mLock.unlock();

 

            //initialize motion event for secondary display

 

            mLock.lock();

        }

 

        // Just enqueue a new motion event.

        MotionEntry* newEntry = new MotionEntry(args->eventTime,

                args->deviceId, args->source, policyFlags,

                args->action, args->flags, args->metaState, args->buttonState,

                args->edgeFlags, args->xPrecision, args->yPrecision,

                args->downTime,

                args->displayId,

                args->pointerCount, args->pointerProperties,

                args->pointerCoords);

 

        needWake = enqueueInboundEventLocked(newEntry);

        mLock.unlock();

    } // release lock

   

    if (needWake) {

        mLooper->wake();

    }

}

 

bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {

    // 將新的 input event 放進 InboundQueue

    bool needWake = mInboundQueue.isEmpty();

    mInboundQueue.enqueueAtTail(entry);

    // do something

    return needWake;

}

 

到此為止, InputReader Thread 已經把新的Touch motion event放進InboundQueue了. 接下來就來看InputDispatcher Thread 的輸入事件流程.

同樣的從 InputDispatcherThread 的啟動流程開始分析

// InputDispatcher.cpp

bool InputDispatcherThread::threadLoop() {

    mDispatcher->dispatchOnce();

    return true;

}

 

 

 

void InputDispatcher::dispatchOnce() {

     nsecs_t nextWakeupTime = LONG_LONG_MAX;

    { // acquire lock

        AutoMutex _l(mLock);

        mDispatcherIsAliveCondition.broadcast();

 

        // Run a dispatch loop if there are no pending commands.

        // The dispatch loop might enqueue commands to run afterwards.

        if (!haveCommandsLocked()) {

            dispatchOnceInnerLocked(&nextWakeupTime);

        }

 

        // Run all pending commands if there are any.

        // If any commands were run then force the next poll to wake up

        //immediately.

        if (runCommandsLockedInterruptible()) {

            nextWakeupTime = LONG_LONG_MIN;

        }

    } // release lock

 

    // Wait for callback or timeout or wake.  (make sure we round up, not down)

    nsecs_t currentTime = now();

    int timeoutMillis = toMillisecondTimeoutDelay(currentTime,

                                      nextWakeupTime);

    mLooper->pollOnce(timeoutMillis);  // 用來管理message queue, 負責接收        

                                   // 或發送 message.

}

 

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {

     //do something

    // Ready to start a new event.

    // If we don't already have a pending event, go grab one.

    if (! mPendingEvent) {

        if (mInboundQueue.isEmpty()) {

           //do something

        } else {

           // Inbound queue has at least one entry.

            mPendingEvent = mInboundQueue.dequeueAtHead();

        }

        //do something

        // Get ready to dispatch the event.

        resetANRTimeoutsLocked();   //因為已經取到input event, 所以 reset

                                  //ANR timer.

    }

   

    //do something

    switch (mPendingEvent->type) {

    case EventEntry::TYPE_CONFIGURATION_CHANGED:

       //do something

       break;

    case EventEntry::TYPE_DEVICE_RESET:

       //do something

       break;

    case EventEntry::TYPE_KEY:

       //do something

       break;

    case EventEntry::TYPE_MOTION:

       MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);

       //do something

       done = dispatchMotionLocked(currentTime, typedEntry,

                &dropReason, nextWakeupTime);

       break;

    default:

       break;

    }

 

    if (done) {

        if (dropReason != DROP_REASON_NOT_DROPPED) {

            dropInboundEventLocked(mPendingEvent, dropReason);

        }

 

        releasePendingEventLocked();

        *nextWakeupTime = LONG_LONG_MIN;  // force next poll to wake up

                                             // immediately

    }

 

}

 

bool InputDispatcher::dispatchMotionLocked(

        nsecs_t currentTime, MotionEntry* entry, DropReason* dropReason,

        nsecs_t* nextWakeupTime) {

       

        1. // Preprocessing.

        2. // Clean up if dropping the event.

        3. // support sending secondary display events to input monitors

 

        // Dispatch the motion.

        // do soemthing

       dispatchEventLocked(currentTime, entry, inputTargets);

}

 

void InputDispatcher::dispatchEventLocked(nsecs_t currentTime,

        EventEntry* eventEntry, const Vector<InputTarget>& inputTargets) {

     //do something

     for (size_t i = 0; i < inputTargets.size(); i++) {

        const InputTarget& inputTarget = inputTargets.itemAt(i);

        ssize_t connectionIndex =

                    getConnectionIndexLocked(inputTarget.inputChannel);

        if (connectionIndex >= 0) {

            sp<Connection> connection =

                     mConnectionsByFd.valueAt(connectionIndex);

            prepareDispatchCycleLocked(currentTime, connection, eventEntry,

                                    &inputTarget);

        } else {

            //do something

        }

    }

}

 

void InputDispatcher::prepareDispatchCycleLocked(nsecs_t currentTime,

        const sp<Connection>& connection, EventEntry* eventEntry, const

        InputTarget* inputTarget) {

       1. // Skip this event if the connection status is not normal.

         // We don't want to enqueue additional outbound events if the connection

         //is broken.

 

      // Split a motion event if needed.

      if (inputTarget->flags & InputTarget::FLAG_SPLIT) {

        ALOG_ASSERT(eventEntry->type == EventEntry::TYPE_MOTION);

 

         //do something

         enqueueDispatchEntriesLocked(currentTime, connection,

                    splitMotionEntry, inputTarget);

         //do something

         return;

        }

    }

 

    // Not splitting.  Enqueue dispatch entries for the event as is.

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

 

    // Enqueue dispatch entries for the requested modes.

    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,

            InputTarget::FLAG_DISPATCH_AS_HOVER_EXIT);

    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,

            InputTarget::FLAG_DISPATCH_AS_OUTSIDE);

    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,

            InputTarget::FLAG_DISPATCH_AS_HOVER_ENTER);

    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,

            InputTarget::FLAG_DISPATCH_AS_IS);

    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,

            InputTarget::FLAG_DISPATCH_AS_SLIPPERY_EXIT);

    enqueueDispatchEntryLocked(connection, eventEntry, inputTarget,

            InputTarget::FLAG_DISPATCH_AS_SLIPPERY_ENTER);

 

    // If the outbound queue was previously empty, start the dispatch cycle going.

    if (wasEmpty && !connection->outboundQueue.isEmpty()) {

        startDispatchCycleLocked(currentTime, connection);

    }

}

 

void InputDispatcher::enqueueDispatchEntryLocked(

        const sp<Connection>& connection, EventEntry* eventEntry, const

        InputTarget* inputTarget,

        int32_t dispatchMode) {

 

        //do something

       

        // This is a new event.

        // Enqueue a new dispatch entry onto the outbound queue for this

        // connection.

        DispatchEntry* dispatchEntry = new DispatchEntry(eventEntry,

            inputTargetFlags, inputTarget->xOffset, inputTarget->yOffset,

            inputTarget->scaleFactor);

 

         // Apply target flags and update the connection's input state.

 

        // Remember that we are waiting for this dispatch to complete.

 

        // Enqueue the dispatch entry.

        connection->outboundQueue.enqueueAtTail(dispatchEntry);

}

 

void InputDispatcher::startDispatchCycleLocked(nsecs_t currentTime,

        const sp<Connection>& connection) {

 

       // do something

 

       // 檢查outboundQueue 中是否有新的 input event.

       while (connection->status == Connection::STATUS_NORMAL

            && !connection->outboundQueue.isEmpty()) {

            // 從 outboundQueue 中取出新的 input event.

            DispatchEntry* dispatchEntry = connection->outboundQueue.head;

            // Publish the event.

            EventEntry* eventEntry = dispatchEntry->eventEntry;

 

            switch (eventEntry->type) {

            case EventEntry::TYPE_KEY:

                //do something

               break;

            case EventEntry::TYPE_MOTION:

                //do something

                // Publish the motion event.

                status =              

                connection->inputPublisher.publishMotionEvent(

                   // some argument.

                );

                break;

            default:

               return;

            }

       }

 

}

 

//InputTransport.cpp

status_t InputPublisher::publishMotionEvent(

        uint32_t seq,

        int32_t deviceId,

        int32_t source,

        int32_t action,

        int32_t flags,

        int32_t edgeFlags,

        int32_t metaState,

        int32_t buttonState,

        float xOffset,

        float yOffset,

        float xPrecision,

        float yPrecision,

        nsecs_t downTime,

        nsecs_t eventTime,

        size_t pointerCount,

        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.edgeFlags = edgeFlags;

    msg.body.motion.metaState = metaState;

    msg.body.motion.buttonState = buttonState;

    msg.body.motion.xOffset = xOffset;

    msg.body.motion.yOffset = yOffset;

    msg.body.motion.xPrecision = xPrecision;

    msg.body.motion.yPrecision = yPrecision;

    msg.body.motion.downTime = downTime;

    msg.body.motion.eventTime = eventTime;

    msg.body.motion.pointerCount = pointerCount;

    for (size_t i = 0; i < pointerCount; i++) {

        msg.body.motion.pointers[i].properties.copyFrom(pointerProperties[i]);

        msg.body.motion.pointers[i].coords.copyFrom(pointerCoords[i]);

    }

    return mChannel->sendMessage(&msg);

}

 

status_t InputChannel::sendMessage(const InputMessage* msg) {

    size_t msgLength = msg->size();

    ssize_t nWrite;

    do {

        nWrite = ::send(mFd, msg, msgLength, MSG_DONTWAIT |

               MSG_NOSIGNAL);

    } while (nWrite == -1 && errno == EINTR);

 

    if (nWrite < 0) {

        int error = errno;

        if (error == EAGAIN || error == EWOULDBLOCK) {

            return WOULD_BLOCK;

        }

        if (error == EPIPE || error == ENOTCONN) {

            return DEAD_OBJECT;

        }

        return -error;

    }

 

    if (size_t(nWrite) != msgLength) {

        return DEAD_OBJECT;

    }

    return OK;

}

上面呼叫了哪麼多層函數, InputDispatcherThread 就只做了以下幾件事:

1. 從InboundQueue取 new input event 處理, 處理完放進 OutboundQueue

2. 從 OutboundQueue 取 new input event 處理, 處理完放進 inputMessage.

3. 利用::send 函數送 input Message.

世上萬物常是一體兩面, 在程式設計上也是如此, 因此有了sendMessage函數設計, 必有一個receiveMessage的函數. 所以有了 InputChannel::sendMessage 就會提供 InputChannel::receiveMessage 函數作接收message的處理. 搜尋了一下程式碼發現呼叫 InputChannel::receiveMessage 函數的地方是在InputConsumer::consume 函數中. 而這consume函數又是在哪被呼叫呢? 此流程牽扯到Thread 通訊課題, 在這不做詳細分析, 為了篇幅只能做簡單的說明, 在android中每一個Process都有一個looper, 此looper裡有一個messageQueue, messageQueue中有很多來自此process中不同的thread所發出來的message. 管理這些message 就是這個名為 looper 的物件. 因此就去搜尋一下跟looper 相關的程式碼, 發現 android 有定義一個 LooperCallback 的抽象類別, 此抽象類別裡有一個pure virtual function handleEvent函數, 因此只要找到誰繼承LooperCallback 類別, 就能找到 handleEvent 函數實作的地方. 最後在NativeInputEventReceiver 發現 handleEvent 函數實作足跡.

 

// android_view_InputEventReceiver.cpp

int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {

    //do something

    status_t status = consumeEvents(env, false /*consumeBatches*/, -1);

    // do something

}

 

status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,

        bool consumeBatches, nsecs_t frameTime) {

    //do something

    bool skipCallbacks = false;  // 用來做為通知是否已完成dispach input event

                            // 的一個判斷值.

    for (;;) {

        uint32_t seq;

        InputEvent* inputEvent;

        //果然在這裡發現了呼叫 InputConsumer::consume 函數的地方

        status_t status = mInputConsumer.consume(&mInputEventFactory,

                consumeBatches, frameTime, &seq, &inputEvent);

 

        //do something

        if (!skipCallbacks) {

            jobject inputEventObj;

            switch (inputEvent->getType()) {

            case AINPUT_EVENT_TYPE_KEY:

                inputEventObj = android_view_KeyEvent_fromNative(env,

                        static_cast<KeyEvent*>(inputEvent));

                break;

 

            case AINPUT_EVENT_TYPE_MOTION:

                inputEventObj =

                      android_view_MotionEvent_obtainAsCopy(env,

                             static_cast<MotionEvent*>(inputEvent));

                break;

 

            default:

                assert(false); // InputConsumer should prevent this from ever

                           // happening

                inputEventObj = NULL;

            }

 

            if (inputEventObj) {

                env->CallVoidMethod(mReceiverObjGlobal,

                     gInputEventReceiverClassInfo.dispatchInputEvent,

                     seq, inputEventObj);

                if (env->ExceptionCheck()) {

                    skipCallbacks = true;

                }

            } else {

                skipCallbacks = true;

            }

        }

 

        if (skipCallbacks) {

            mInputConsumer.sendFinishedSignal(seq, false);

        }

 

    }

}

由這段程式碼可以發現, 當利用InputConsumer 物件收到由 InputDispatcherThread 送過來的 InputEvent message時, 便會利用一個 boolean variable skipCallbacks 當作一個是否完成dispatch input event的判斷值. 一收到input event馬上分析為是 Key event type 還是 Motion event type. 由於我們這裡是以 Motion event type 為例, 所以就分析Motion event type 這個 case流程.

 

// android_view_InputEventReceiver.cpp

status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,

        bool consumeBatches, nsecs_t frameTime) {

     //do something

     case AINPUT_EVENT_TYPE_MOTION:

                inputEventObj =

                      android_view_MotionEvent_obtainAsCopy(env,

                             static_cast<MotionEvent*>(inputEvent));

                break;

     //do something

}

 

// android_view_MotionEvent.cpp

jobject android_view_MotionEvent_obtainAsCopy(JNIEnv* env, const

      MotionEvent* event) {

          // 回呼 MotionEvent  Java Layer 配置一塊 MotionEvent type 的

          // 物件.

          jobject eventObj =  

            env->CallStaticObjectMethod(gMotionEventClassInfo.clazz,

                                gMotionEventClassInfo.obtain);

          // eventObj exception handler

          // 從MotionEvent 的java layer 讀取MotionEvent 物件指標值.

          MotionEvent* destEvent =  

                  android_view_MotionEvent_getNativePtr(env, eventObj);

          if (!destEvent) {

               destEvent = new MotionEvent();

               android_view_MotionEvent_setNativePtr(env, eventObj,

                                                 destEvent);

           }

 

         destEvent->copyFrom(event, true);

         return eventObj;

}

到此利用java layer來配置一塊 MotionEvent 物件來使用. 為何要這麼大費周章還要去回呼Java layer作配置物件的動作,推論應該是要藉由DVM的 GC來做回收未用到的記憶體. 接下來繼續分析後續動作

// android_view_InputEventReceiver.cpp

status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,

        bool consumeBatches, nsecs_t frameTime) {

        //do something

        //do something

        // 將上面所得到的MotionEvent 物件指標值, 在傳入 Java Layer 的 

        // 函數裡

        env->CallVoidMethod(mReceiverObjGlobal,

                      gInputEventReceiverClassInfo.dispatchInputEvent,

                        seq, inputEventObj);

        //do something

}

 

//InputEventReceiver.java

// Called from native code.

@SuppressWarnings("unused")

private void dispatchInputEvent(int seq, InputEvent event) {

        mSeqMap.put(event.getSequenceNumber(), seq);

        onInputEvent(event);

}

 

 

/**

     * Called when an input event is received.

     * The recipient should process the input event and then call {@link

       #finishInputEvent}

     * to indicate whether the event was handled.  No new input events will be

       received

     * until {@link #finishInputEvent} is called.

     *

     * @param event The input event that was received.

*/

public void onInputEvent(InputEvent event) {

        finishInputEvent(event, false);

}

 

到這裡先停一下, 若繼續分析下去, 會發現似乎直接就呼叫 finishInputEvent 函數, 然後就送finish message 通知InputDispatcherThread 之後, 就完成了這一個輸入事件流程了. 然而, 依照過去寫app的經驗, 一般在app若要接收一些Touch event 會有個 call back function onTouch 函數要實作, 方便app可以接收到Touch event 然後作相關的應用.然而剛剛分析的輸入事件的流程似乎沒有去呼叫 onTouch 函數, 這到底是怎麼一回事? 原因在於 InputEventReceiver這個類別, 這個類別是屬於 abstract 類別, 在java的語法中, abstract 類別即使類別中的成員函數都有實作, 也是無法實體化的, 因此只能有abstract 類別的衍生類別才能實體化. 搜尋了一下程式碼發現了幾個abstract InputEventReceiver類別的衍生類別, 跟InputEvent 處理有關的衍生類別就是 WindowInputEventReceiver , 原因如下, 再一開始ViewRootImpl在作 setView 時, 除了new 一個新的 InputChannel 物件之後, 又 new 了一個 WindowInputEventReceiver 物件. 此WindowInputEventReceiver 類別正好又 overwrite onInputEvent 函數, 因此可以大膽推測dispatchInputEvent 呼叫的 onInputEvent 函數, 會是此類別的 onInputEvent 函數, 就在從 WindowInputEventReceiver 中的 onInputEvent函數開始分析.

// ViewRootImpl.java

// 此類別是定義在ViewRootImpl 類別中, 最前面又是掛 final, 在java的語法// 中代表此類別只能給 ViewRootImpl 類別中使用且無法被繼承.

final class WindowInputEventReceiver extends InputEventReceiver {

      //data member

      @Override

        public void onInputEvent(InputEvent event) {

            enqueueInputEvent(event, this, 0, true);

        }

      // member function

}

 

 

void enqueueInputEvent(InputEvent event,

            InputEventReceiver receiver, int flags, boolean processImmediately) {

        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 = mFirstPendingInputEvent;

        if (last == null) {

            mFirstPendingInputEvent = q;

        } else {

            while (last.mNext != null) {

                last = last.mNext;

            }

            last.mNext = q;

        }

 

        if (processImmediately) {

            doProcessInputEvents();    //一般的input event都是即使的

        } else {

            scheduleProcessInputEvents();

        }

    }

 

void doProcessInputEvents() {

        while (mCurrentInputEvent == null && mFirstPendingInputEvent != null)   

        {

            QueuedInputEvent q = mFirstPendingInputEvent;

            mFirstPendingInputEvent = q.mNext;

            q.mNext = null;

            mCurrentInputEvent = q;

            deliverInputEvent(q);

        }

 

        // We are done processing all input events that we can process right now

        // so we can clear the pending flag immediately.

       

    }

 

private void deliverInputEvent(QueuedInputEvent q) {

     // do something

     if (q.mEvent instanceof KeyEvent) {

                deliverKeyEvent(q);

     } else {

             final int source = q.mEvent.getSource();

             if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0)

            {

                    deliverPointerEvent(q);

            } else if ((source &

                    InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {

                    deliverTrackballEvent(q);

            } else {

                    deliverGenericMotionEvent(q);

            }

     }

 

    // do something

}

 

private void deliverPointerEvent(QueuedInputEvent q) {

     // If there is no view, then the event will not be handled.

     if (mView == null || !mAdded) {

            finishInputEvent(q, false);

            return;

     }

     // Translate the pointer event for compatibility, if needed.

 

     // Enter touch mode on down or scroll.

    

     // Offset the scroll position.

 

     // Remember the touch position for possible drag-initiation.

 

     // Dispatch touch to view hierarchy.

        boolean handled = mView.dispatchPointerEvent(event);

 

     if (handled) {

            finishInputEvent(q, true);

            return;

     }

 

     // Pointer event was unhandled.

     finishInputEvent(q, false);

}

 

由此可以發現只要檢查到沒有view或是無法掌握的input event就會被ignore掉不去處理.最後會去呼叫 View 類別的dispatchTouchEvent 函數.

//View .java

public boolean dispatchTouchEvent(MotionEvent event) {

        if (mInputEventConsistencyVerifier != null) {

            mInputEventConsistencyVerifier.onTouchEvent(event, 0);

        }

 

        if (onFilterTouchEventForSecurity(event)) {

            //noinspection SimplifiableIfStatement

            ListenerInfo li = mListenerInfo;

            if (li != null && li.mOnTouchListener != null && (mViewFlags &

                                  ENABLED_MASK) == ENABLED

                    && li.mOnTouchListener.onTouch(this, event)) {

                return true;

            }

            // 主要是用來實作觸控的一般通用的功能, ex press, click, long

            //  press etc.

            if (onTouchEvent(event)) {

                return true;

            }

        }

 

        if (mInputEventConsistencyVerifier != null) {

            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);

        }

        return false;

    }

終於發現呼叫 onTouch 函數的地方了. 在這裡只要app有註冊 onTouchListener 介面, onTouch 函數就會被觸發了. 到目前為止分析的方向是WindowManagerService -> ViewRootImpl, 接下來 ViewRootImpl 處理完 input event 之後,再來分析 ViewRootImpl -> WindowManagerService 這方向. 由前面的 deliverPointerEvent 函數分析中, 會發現都會解由 finishInputEvent 來完成這一次的輸入事件流程. 就由 finishInputEvent 函數開始分析

// ViewRootImpl.java

private void finishInputEvent(QueuedInputEvent q, boolean handled) {

     //do something

     if (q.mReceiver != null) {

            q.mReceiver.finishInputEvent(q.mEvent, handled);

     } else {

            q.mEvent.recycleIfNeededAfterDispatch();

     }

     //do something

}

 

// InputEventReceiver.cpp

public final void finishInputEvent(InputEvent event, boolean handled) {

    //do something

    nativeFinishInputEvent(mReceiverPtr, seq, handled);

    //do something

}

 

// android_view_InputEventReceiver.cpp

static void nativeFinishInputEvent(JNIEnv* env, jclass clazz, jint receiverPtr,

        jint seq, jboolean handled) {

      sp<NativeInputEventReceiver> receiver =

            reinterpret_cast<NativeInputEventReceiver*>(receiverPtr);

      status_t status = receiver->finishInputEvent(seq, handled);

     // exception handler

}

 

status_t NativeInputEventReceiver::finishInputEvent(uint32_t seq, bool handled) {

     // do something

     status_t status = mInputConsumer.sendFinishedSignal(seq, handled);

    // exception handler

}

 

// InputTransport.cpp

status_t InputConsumer::sendFinishedSignal(uint32_t seq, bool handled) {

    // do something

    // Send finished signal for the last message in the batch.

    return sendUnchainedFinishedSignal(seq, handled);

}

 

status_t InputConsumer::sendUnchainedFinishedSignal(uint32_t seq, bool handled) {

    InputMessage msg;

    msg.header.type = InputMessage::TYPE_FINISHED;

    msg.body.finished.seq = seq;

    msg.body.finished.handled = handled;

    return mChannel->sendMessage(&msg);

}

 

一旦ViewRootImpl 藉由 InputChannel物件傳送 finish 的 message 時, 這時

InputDispatcher 類別的 handleReceiveCallback 函數就會被觸發. 原因在於InputDispatcher 在初始化的時候有去做register InputChannel 的動作, 在 register InputChannel時, 會在自己new 出來的 looper 物件上增加一個 callback function  handleReceiveCallback.

// InputDispatcher.cpp

mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);

 

int InputDispatcher::handleReceiveCallback(int fd, int events, void* data) {

    // do something

    for (;;) {

      // do something

       status = connection->inputPublisher.receiveFinishedSignal(&seq,

                                                  &handled);

       // do something

 

       d->finishDispatchCycleLocked(currentTime, connection, seq,

                                  handled);

       gotOne = true;

    }

}

 

// InputTransport.cpp

status_t InputPublisher::receiveFinishedSignal(uint32_t* outSeq, bool* outHandled) {

    //do something

    status_t result = mChannel->receiveMessage(&msg);

 

    //do something

 

    *outSeq = msg.body.finished.seq;

    *outHandled = msg.body.finished.handled;

    return OK;

}

 

// InputDispatcher.cpp

void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime,

        const sp<Connection>& connection, uint32_t seq, bool handled) {

     //do something

     // Notify other system components and prepare to start the next dispatch cycle.

    onDispatchCycleFinishedLocked(currentTime, connection, seq, handled);

}

 

void InputDispatcher::onDispatchCycleFinishedLocked(

        nsecs_t currentTime, const sp<Connection>& connection, uint32_t seq,

        bool handled) {

    CommandEntry* commandEntry = postCommandLocked(

    & InputDispatcher::doDispatchCycleFinishedLockedInterruptible);

    commandEntry->connection = connection;

    commandEntry->eventTime = currentTime;

    commandEntry->seq = seq;

    commandEntry->handled = handled;

}

 

void InputDispatcher::doDispatchCycleFinishedLockedInterruptible(

        CommandEntry* commandEntry) {

     //do something

     // Start the next dispatch cycle for this connection.

        startDispatchCycleLocked(now(), connection);

}

由此可知在 InputDispatcher 一收到InputConsumer 物件送的 finish message之後, 就馬上去呼叫startDispatchCycleLocked 函數去檢查 outboundQueue 裡面還有沒有新的input event. 若有的話就放入InputMessage 共享內存, 然後通知 ViewRootImpl 去共享內存抓取新的 input event. 若沒有新的 input event, 就不做事等待有新的input event進 outboundQueue.



Android4.4 4.2keyguard锁屏流程梳理 Android4.4 锁屏流程梳理 刚毕业不久由于项目需要就接触到锁屏,从2.2到4.1都解过bug,也定制过一些功能。4.1之前的锁屏工作不难,但很费时间,因为它的逻辑,视图,资源分别分布在不同的路径下,就像散落在海边沙滩上的珠子,想串起来还是蛮费劲的。最开始时锁屏就是改个字段也要全编译生成img。后来新技能get,会针对修改的地方进行单编译,但每次编译jar,导入手机,重启看效果也是不方便的 阅读详情

相关推荐

Android 输入法框架流程整理

Android 输入法框架流程整理 个人简单整理, 如有错误请指正 文章目录Android 输入法框架流程整理1. 基本简介1.1 输入法框架构成1.2 输入法交互框架2. 输入法初始化过程2.1 输入法客户端( IMM )的初始化过程2.2 输入法管理端( IMMS )的初始化过程2.3 输入法服务端( IMS )的初始化过程3. 输入法的启用过程3.1 客户端从控件获取焦点开始3.2 控制端开...

Jeson 笔记 5876

Android系统触屏事件传递派发浅析(二)

上一篇文章提到在InputDispatcher中,connection调用inputPublisher.publishMotionEvent后分发就完成了,然后将dispatchEntry放入到waitQueue队列,大概是有的事件必须等应用回复,收到应用回复后,dispatchEntry将从waitQueue队列中移出. 要弄明白事件如何传递给应用,不得不看看inputPublisher.pu

karizhang的专栏 2043

android 开机动画和开机视频流程分析基于android4.4

   android开机画面是由应用程序bootanimation来负责显示的,主要类BootAnimation.cpp直接继承于android中定义的一个Thread,接地继承了RefBase类,并且重写了RefBase类的成员函数onFirstRef,因此,当一个BootAnimation对象第一次被引用的时,这个BootAnimation对象的成员函数onFirstRef就会被调用。    ...

tzr0330的专栏 3182

Android 4.0 事件输入(Event Input)系统

1. TouchScreen功能在Android4.0下不工作        原来在Android2.3.5下能正常工作的TouchScreen功能,移植到Android 4.0就不能正常工作了。凭直觉,Android4.0肯定有鬼。真是不看不知道,一看吓一跳。在Android 4.0中,Event Input地位提高了,你看看,在Adroid2.3.5中,它在frameworks/base/l

MyArrow的专栏 5万+

Android 4.4 设置DNS及DNS解析请求流程分析

1,将网络连接获取的DNS设置到系统中 1.1DNS获取后先设置LinkProperty 在Android Frameworks中不同的网络interface(eth0、wlan0等)都会有一个独立的LinkProperties对象来保持自己的网络信息 1.2通知ConnectivityService DNS信息已获取成功 不管是dhcp、pppoe或者是静态设置,在成功获取...

cc0410的博客 5153

Android 4.2 InCallScreen UI浅析

InCallScreen是什么 本文来自http://blog.csdn.net/yihongyuelan 转载请务必注明出处 本文以MTK平台Android 4.2为分析对象,MTK在原生的基础之上添加了许多自己的东西,因此个别地方跟原生代码以及QCOM的代码不同,请读者知悉。 在电话的呼出流程中,我们最后需要按下拨号键,才能将电话拨打出去,那么在按下拨号键之后

Keep Awake,Keep Studying,Keep Moving,Keep... 1万+

Input 输入事件防止屏幕睡眠流程

adb shell getevent 收到了输入事件 /dev/input/event3: 0003 0039 00000646 /dev/input/event3: 0003 0035 00000177 /dev/input/event3: 0003 0036 00000098 /dev/input/event3: 0001 014a 00000001 /dev/input/event3: 00...

一杯咖啡的专栏 1588

UE4 安卓触摸事件相关问题

UE4在安卓端获取输入信息时候,似乎会把触摸事件和PC端的鼠标事件弄混在一起,导致出现一些奇怪的问题。 1. /* 安卓端会把触摸事件读取成PC端的鼠标事件,因为Turn和LookUp在PC端开发时候一般都绑定在鼠标X轴,Y轴上, * 所以这里要对安卓端做一些特殊处理。 */ #if !PLATFORM_ANDROID PlayerInputComponent->BindAx...

执手画眉弯的博客 6002

Android R input (四) 之 InputChannel之发送事件处理反馈

文章托管在gitee上 Android Notes , 同步csdn Client处理完input事件后, 会向ims发送finish反馈信号,即向对端的server InputChannel发送反馈, server 端InputChannel被InputDispatcher所管理 .这个操作通常在InputEventReceiver的finishInputEvent方法. InputEventReceiver#finishInputEvent /// @frameworks/base/core/jav.

pecuyu 2359

【input系统】MotionEvent的分解

在打开多窗口的时候,比如A,B两个应用窗口,我们通过onTouchEvent查看log可以发现,在A窗口上的点击事件不会传给B窗口,当有多个点击事件时,A,B接收到的事件是不一样的,也就是说,在InputDispatcher的notifyMotion中的一个MotionEvent,在传送给app的时候,会根据窗口进行拆分。可以看出,touch事件(MotionEvent)的传送多么的不容易,那么多的操作,多次加工,千辛万苦才传给了接收者。在应用中也有类似的拆分处理,

aaajj的专栏 740

逆向实战:拼多多anti-content加密参数的全链路解析与动态对抗策略

本文深入解析了拼多多anti-content加密参数的全链路生成逻辑与动态对抗策略。通过逆向工程实战,详细拆解了包含设备指纹、用户行为采集和动态密钥生成在内的12个关键环节,并提供了可持续对抗的精准模拟方案和自动化更新机制,帮助开发者有效应对拼多多的反爬体系。

weixin_30687051的博客 160

android 4.2 input 流程分析,Android 4.2 输入流程分析

// InputDispatcher.cppbool InputDispatcherThread::threadLoop() {mDispatcher->dispatchOnce();return true;}void InputDispatcher::dispatchOnce() {nsecs_t nextWakeupTime = LONG_LONG_MAX;{ // acquire lo...

weixin_30154537的博客 171

实战STM32G0驱动DRV8870/AT8870:PWM+DMA精准调速与H桥衰减模式详解

本文详细介绍了如何使用STM32G0单片机结合DRV8870/AT8870电机驱动芯片实现直流有刷电机的精准控制。重点讲解了PWM与DMA结合实现高效调速的硬件配置与软件驱动方法,并深入剖析了H桥快衰减与慢衰减模式的工作原理及应用场景,为嵌入式电机控制项目提供了完整的实战解决方案。

c7d8e9的博客 595

android 4.2 input 流程分析,Android 4.2 Input 流程分析

// InputDispatcher.cppbool InputDispatcherThread::threadLoop() {mDispatcher->dispatchOnce();return true;}void InputDispatcher::dispatchOnce() {nsecs_t nextWakeupTime = LONG_LONG_MAX;{ // acquire lo...

weixin_42411097的博客 204

【feature_column处理】- indicator_column(指示列)和embedding_column(嵌入列)

1. 开胃菜 我们先来了解一下tf.feature_column.input_layer def input_layer(features, feature_columns, weight_collections=None, trainable=True, cols_to_vars=None, cols_to_output_tensors=None)

DataAlgo 1487

Windows 11安装realtek audio control

Windows 11安装realtek audio control,解决无法链接RPC问题

m0_46144825的博客 8万+

Android 4.2 輸入流程研究心得

Android 4.2 輸入流程研究心得 http://www.eoeandroid.com/home.php?mod=space&uid=10407&do=blog&id=5070 Android KEY事件读取及分发(转)  http://jimmyhavedream.blog.163.com/blog/static/530858332013423285779/

wangkaiblog的专栏 710

WD西部数据2TB,2.5寸移动硬盘,因为磁头坏了,长时间通电导致划片划伤,维修过程通过反复更换磁头

WD西部数据2TB,2.5寸移动硬盘,因为磁头坏了,长时间通电导致划片划伤,维修过程通过反复更换磁头,才能利用专业设备,里面找到,镜像,把内容数据恢复出来。 1.说一下经历,借用的别人移动硬盘,去帮别人维修,拷数据出来,别人的文件,数据比较多,且复杂,而且是在PE系统,接口什么的是2.0,没有很好的思考就全选复制拷贝了; 2,拷贝的过程,极其慢,文件多,且文件有损坏,移动硬盘一直处于通电的状态,大概从下午,两点多开始100多G的数据,一直卡住,到,晚上7 。8点拷完还是没拷完。打开的时候就已经,就一直

weixin_43766920的博客 4303

Android 4.2短信发送流程小记

Android 4.2短信发送流程。 1.      ComposeMessageActivity接收用户输入并且响应用户点击事件。 函数调用顺序confirmSendMessageIfNeeded() ->sendMessage() sendMessage中会调用到WorkingMessage的send方法。 2.      WorkingMessage的send方法会对此时消息做判断,

dragonawu的专栏 1391

RTKlib操作手册--使用样例数据演示

本文将使用样例数据演示如何使用RTKLIB进行rtk差分定位。

samur2的博客 7783

coredump文件的原理是啥

Core Dump(核心转储)是操作系统在程序崩溃(如段错误、非法指令等)时自动生成的一个文件,它包含了程序崩溃瞬间的。通过 core dump,开发者可以像“时间回溯”一样检查程序崩溃瞬间的状态,是调试复杂问题的利器!终止时,Linux 内核会生成 core 文件。不会生成 core dump,因为它们是“优雅终止”信号。这样 core 文件会直接压缩存储,节省磁盘空间。的文件(类似可执行文件),可以用。Core 文件本质上是一个。

二进制君 383

Android4.2移植的debug之路——不明黑色光圈

Android 4.2的移植工作仍然进行中,目前遇到一个bug:在qemu上,android界面出来后,用鼠标点击屏幕时会有一个黑色的小光圈,这个光圈不会跟着鼠标移动,只有点击后再拖动它才会跟着动,如果我想启动某个应用程序,需要把该光圈拖到应用程序图标位置上,然后双击才能启动。上图:     一个让人措手不及的bug,从未见过,又找不到相关的资料,只能“始作俑者”了。这是驱动问题?还是a

zengdaquan007的专栏 4720

133.在 Vue3 中使用 OpenLayers 实现画多边形、任意编辑、遮罩与剪切处理功能

《Vue3+OpenLayers实现多边形绘制与区域处理》摘要:本文介绍如何基于Vue3+OpenLayers+ol-ext实现地图多边形交互功能。核心功能包括:鼠标绘制任意多边形、拖动顶点编辑边界、遮罩显示(非选区变红)、选区剪切显示及功能重置。技术方案采用OpenLayers处理地图渲染,ol-ext实现高级遮罩/剪切效果,ElementPlus提供UI控件。该方案适用于城市规划、园区展示等需要区域标记的场景,完整代码展示了从初始化、绘制、编辑到特效处理的全流程实现。

小彭的博客 962

Android4.2.2的Stagefright维护编解码器的数据流

这里是他们自己的源代码阅读点滴总结属性,转请注明出处,谢谢。欢迎和大家分享。qq:1037701636 email:gzzaigcn2012@gmail.comAndroid源代码版本号Version:4.2.2; 硬件平台 全志A31 前沿:在前面的博文中,基本提到的是stagefright相关的控制流,详细分析了android架构中的MediaExtractor、AwesomePlayer...

weixin_34253126的博客 189

假人有限元模型

假人模型,车辆碰撞假人有限元模型,适合做乘员安全的研究者利用

android 蓝牙通讯demo

android 下蓝牙通讯demo,实现了发现蓝牙,使能蓝牙,通过蓝牙发送消息。

android 4.2 input 流程分析,Android Input流程

一.input 系统初始化安卓系统启动时,会开启SystemServer进程,SystemServer执行main函数,调用startOtherService()初始化windowManagerService和InputManagerService等InputManagerService的构造函数如下,mPtr是一个指向nativeInputManager对象的指针.mPtr = nativeIn...

weixin_34326484的博客 339

文档:亲测可用解决vnc灰屏,黑屏,鼠标大黑叉情况

主要解决vnc-viewer灰屏,黑屏,鼠标大黑叉等现象,详细见文档。

安卓开机启动流程

安卓开机启动流程

似霰的博客 4880

VB摄像头监控

该VB模块可对摄像头捕获的画面进行录像等,可在创建的窗口进行控制

上一篇: Android启动Activity流程中的Token分析
下一篇: MPEG介绍
leopard21
博客等级 码龄23年 25粉丝 56原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值