Android | 输入系统(IMS)

news2024/9/27 19:18:20

前言

一般情况下很多同学对于点击事件的认识都只存在于从 Activity 开始的,然后从 Window 中进行分发,甚至有些人可能也只知道 onTouchEventdispatchTouchEvetn 这几个方法,对于 View 层的了解都不属性。

自从对于应用层面的分发过程了解清楚之后,就一直忍不住想知道只个事件到底是怎么产生的,到底是从哪里来,要往哪里去,具体的派发机制是怎样的。虽然在开发的过程中搞懂应用层面的也就够了,但是在好奇心的驱使下,我还是忍不住的开始了…

本篇文章中的源码全部来自于 https://cs.android.com/ ,基于 Android 12

组成部分

说起组成部分,我们最熟悉的就是 View 中的事件传递了。除此之外,还有两个大部分,第一个就是输入系统部分,也就是 InputManagerService(IMS) 和 WindowManagerService(WMS) 部分。如下图所示:

image-20230109160549376

输入系统

输入系统主要分为输入子系统和 IMS 组成。Android 中的输入设备有很多,例如屏幕,鼠标,键盘等都是输入设备,对于应用开发者,接触最多的也就是屏幕了。当输入设备可用时,Linux会在 /dev/input 中创建对应的设备节点。用户操作输入设备就会产生各种事件,这些事件的原始信息就会被 Linux内核中的输入子系统采集,原始信息由 Kernel space 的驱动层一直传递到设备结点。

IMS 的工作就是监听 /dev/input 下所有设备的节点,当有数据时就会进行加工处理,并交给 WMS 派发给合适的 Window。

WMS 处理

WMS 本身的主要职责就是 Window(窗口) 管理等,具体的如下图。

202210191446431.webp

因为 WMS 的主要职责就是窗口管理,而事件最终也是要交给合适的 Window 来往下派发的,所以说 WMS 就是输入系统的中转站了,WMS 作为 Window 的管理者,会将输入事件交给合适的 Window来处理。

View 处理

这个应该是我们最熟悉的了,一般情况下,输入事件最终都会交给 View 进行处理。

IMS 的创建和启动

与 WMS 一样,IMS 也是在 SystemServer 中创建的 ,在 run 方法中调用了 startOtherServers 方法

private void startOtherServices(@NonNull TimingsTraceAndSlog t) {
		 inputManager = new InputManagerService(context);

     wm = WindowManagerService.main(context, inputManager, !mFirstBoot, mOnlyCore,
            new PhoneWindowManager(), mActivityManagerService.mActivityTaskManager);
          
     inputManager.start();
}

上面通过构造方法创建了 IMS ,接着调用 WMS 的 mian 方法创建了 WMS ,并且传入了 IMS ,我们直达 WMS 是事件的 中转站,所以传入 IMS 并不会感到意外。最后调用了 IMS 的 start 方法。

IMS 构造方法

public InputManagerService(Context context) {
    this.mContext = context;
    //1
    this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());

    mStaticAssociations = loadStaticInputPortAssociations();
    mUseDevInputEventForAudioJack =
            context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
    Slog.i(TAG, "Initializing input manager, mUseDevInputEventForAudioJack="
            + mUseDevInputEventForAudioJack);
    //2
    mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
}

在注释1出创建了处于 android.display 线程的 handler,这样通过 handlerr 发送的消息对应的线程执行。android.display 线程是系统共享的单例前台线程,可以用做一些低延时显示的相关操作,WMS 的创建也是在 android.display 中创建的。

注释2 调用了调用了 nativeInit 方法,可以看出来这是个 JNI 调用

static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
        jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
    sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
    if (messageQueue == nullptr) {
        jniThrowRuntimeException(env, "MessageQueue is not initialized.");
        return 0;
    }
		//1
    NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
            messageQueue->getLooper());
    im->incStrong(0);
    return reinterpret_cast<jlong>(im);
}

注释1处创建了 NativeInputManager ,并将该对象指针返回给了 java framwork 层,下次需要使用 native 层 NativeInputManager 对象的时候 ,直接传递这个指针就可以访问了。

接着继续看一下 NativeInputManager 构造方法

NativeInputManager::NativeInputManager(jobject contextObj,
        jobject serviceObj, const sp<Looper>& looper) :
        mLooper(looper), mInteractive(true) {
    JNIEnv* env = jniEnv();
		......
    //1      
    mServiceObj = env->NewGlobalRef(serviceObj);
 		
    //2      
    InputManager* im = new InputManager(this, this);
    mInputManager = im;
    defaultServiceManager()->addService(String16("inputflinger"), im);
}

注释1,将 java 层传下来的 IMS 对象保存在 mServiceObj 中。

注释2,创建 InputManager 对象:

InputManager::InputManager(
        const sp<InputReaderPolicyInterface>& readerPolicy,
        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
    //1
    mDispatcher = createInputDispatcher(dispatcherPolicy);
    mClassifier = new InputClassifier(mDispatcher);
    //2
    mReader = createInputReader(readerPolicy, mClassifier);
}

sp<InputReaderInterface> createInputReader(const sp<InputReaderPolicyInterface>& policy,
                                           const sp<InputListenerInterface>& listener) {
    return new InputReader(std::make_unique<EventHub>(), policy, listener);
}

InputManager 构造方法中:

  1. 创建InputDispatcher ,该类主要用于对原始事件的分发,传递给 WMS

  2. 创建 InputReader,使用智能指针创建 EventHub 并传入。inputreader 会不断的读取 EventHub 中的原始信息进行加工并交给 InputDispatcher ,InputDispatcher 中保存了 Window 的信息(WMS 会将窗口的信息实时更新到 InputDispatcher 中),可以将事件信息派发到合适的窗口,InputReader 和 InputDispatcher 都是耗时操作,会在单独线程中执行。

    EventHub 通过 Linux 内核的 Notify 与 Epoll 机制监听设备节点,通过 EventHub 的 getEvent 函数读取设备节点的增删事件和原始输入事件。

小结

  1. 在 IMS 构造方法中,先创建了一个处于 android.display 的 Handler 对象。接着调用 native 层函数 nativeInit 创建了 NativeInputManager ,并且将该对象的地址转成 long 类型返回给了java 层。
  2. 在 NativeInputMnager 构造方法中保存了 IMS 的实例,并创建了 InputMnanager 对象。
  3. InputManager 中创建了 InputDispatcher 和 InputReader 对象,分别用于读取事件和分发事件。InputReader 会不断的从 EventHub 中读取原始事件信息并加工交给 InputDispatcher ,inputDispatcher 会将事件分发给合适的 Window。

IMS 的启动

在 IMS 创建完成之后,就会调用他的 start 方法进行启动

public void start() {
    nativeStart(mPtr);
    //.....
}

这里调用了 nativeStart 方法,又是一个 native 方法,不过这里传入了一个参数 mPtr,通过上面的分析可以知道,该参数是 native 层 NativeInputManager 对象的指针,在这里又将其传入了 native 层。

static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {
    NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);

    status_t result = im->getInputManager()->start();
    if (result) {
        jniThrowRuntimeException(env, "Input manager could not be started.");
    }
}

将传过来的 ptr 转成了 NativeInputManager 对象,并且获取到 InputManager,再调用start` 函数。

status_t InputManager::start() {
    //1
    status_t result = mDispatcher->start();
    if (result) {
        ALOGE("Could not start InputDispatcher thread due to error %d.", result);
        return result;
    }
		//2
    result = mReader->start();
    if (result) {
        ALOGE("Could not start InputReader due to error %d.", result);
        mDispatcher->stop();
        return result;
    }
    return OK;
}

注释一调用了 IputDispatcher 的 start 方法,用于对事件进行分发

注释2 调用了 InputReader 的 start 方法,用于从 EventHub 中获取原始事件进行处理。

至于具体的流程我们下面在做分析

InputReader 读取事件

IMS 启动的时候会调用 native 层,通过 InputManager 来创建 InputReader 来读取事件,下面我们来具体分析一下

status_t InputReader::start() {
    if (mThread) {
        return ALREADY_EXISTS;
    }
    mThread = std::make_unique<InputThread>(
            "InputReader", [this]() { loopOnce(); }, [this]() { mEventHub->wake(); });
    return OK;
}

InputThread 继承了 Thread,native 的 Thread 内部有一个循环,当线程运行时,会调用 threadload 函数,如果返回了 true 并且没有调用 requestExit 函数,就会循环调用 threadloop 函数

class InputThreadImpl : public Thread {
public:
    explicit InputThreadImpl(std::function<void()> loop)
          : Thread(/* canCallJava */ true), mThreadLoop(loop) {}

    ~InputThreadImpl() {}
private:
    std::function<void()> mThreadLoop;

    bool threadLoop() override {
        mThreadLoop();
        return true;
    }
};

线程中执行的是 loopOnce 函数

void InputReader::loopOnce() {
    ......
    //1
    size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);

    { // acquire lock
        if (count) {
            //2
            processEventsLocked(mEventBuffer, count);
        }
    } // release lock
    ......
    //3  
    mQueuedListener->flush();   
}

注释一:调用 EventHub 的 getEvents 函数来获取设备节点的信息到 mEventBuffer 中,事件信息主要有两种,一种是设备的增删事件(设备事件),一种是原始的输入事件

注释二:对 mEventBuffer 中的输入事件信息进行加工处理,加工处理后的事件会交给 InputDispatcher 来处理

void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
  for (const RawEvent* rawEvent = rawEvents; count;) {
        int32_t type = rawEvent->type;
        size_t batchSize = 1;
        //1
        if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {
            int32_t deviceId = rawEvent->deviceId;
            while (batchSize < count) {
                if (rawEvent[batchSize].type >= EventHubInterface::FIRST_SYNTHETIC_EVENT ||
                    rawEvent[batchSize].deviceId != deviceId) {
                    break;
                }
                batchSize += 1;
            }
            //2
            processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
        } else {
            switch (rawEvent->type) {//3
                case EventHubInterface::DEVICE_ADDED:
                    addDeviceLocked(rawEvent->when, rawEvent->deviceId);
                    break;
                case EventHubInterface::DEVICE_REMOVED:
                    removeDeviceLocked(rawEvent->when, rawEvent->deviceId);
                    break;
                case EventHubInterface::FINISHED_DEVICE_SCAN:
                    handleConfigurationChangedLocked(rawEvent->when);
                    break;
                default:
                    ALOG_ASSERT(false); // can't happen
                    break;
            }
        }
        count -= batchSize;
        rawEvent += batchSize;
    }
}
void InputReader::addDeviceLocked(nsecs_t when, int32_t eventHubId) {
	...
	std::shared_ptr<InputDevice> device = createDeviceLocked(eventHubId, identifier);
	...
	mDevices.emplace(eventHubId, device);
	...
}

上面代码中遍历所有的输入事件,这些事件用 RawEvent 对象来表示,将原始事件和设备事件分开处理,其中设备事件分为三种类型,这些事件都是在 EventHub 的 getEvent 函数中生成的。

如果是 DEVICE_ADDED(设备添加事件),InputReader 会新建一个 InputDevice 对象,用来存储设备信息,并且会将 InputDevice 存储在 KeyedVector 类型的容器 mDevices 中。

注释一判断事件类型,true 表示原始输入事件,false 表示设备事件

注释二处理 deviceId 所对应设备的原始输入事件

注释三判断设备事件类型,根据具体情况进行处理

我们重点关注一下原始事件的处理,也就是 processEventsForDeviceLocked 函数

void InputReader::processEventsForDeviceLocked(int32_t eventHubId, const RawEvent* rawEvents,
                                               size_t count) {
    //1
    auto deviceIt = mDevices.find(eventHubId);
    if (deviceIt == mDevices.end()) {
        ALOGW("Discarding event for unknown eventHubId %d.", eventHubId);
        return;
    }
	  //2
    std::shared_ptr<InputDevice>& device = deviceIt->second;
    if (device->isIgnored()) {
        // ALOGD("Discarding event for ignored deviceId %d.", deviceId);
        return;
    }

    device->process(rawEvents, count);
}

mDevices 中保存 key 是 eventHub,而 value 就是 设备InputDevice

注释1处根据 eventHubId 查询 map 中设备的索引,然后根据索引的设备对象 InputDevice 调用 process 方法继续处理。

void InputDevice::process(const RawEvent* rawEvents, size_t count) {
    for (const RawEvent* rawEvent = rawEvents; count != 0; rawEvent++) {
       //默认为 false,如果设备输入事件缓冲区溢出,这个值为 true 
       if (mDropUntilNextSync) {
            if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
                mDropUntilNextSync = false;
            } else {
              .....
            }
        } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) {
            //缓冲区溢出
            ALOGI("Detected input event buffer overrun for device %s.", getName().c_str());
            mDropUntilNextSync = true;
            reset(rawEvent->when);
        } else {
            for_each_mapper_in_subdevice(rawEvent->deviceId, [rawEvent](InputMapper& mapper) {
                mapper.process(rawEvent);
            });
        }
        --count;
    }
}
inline void for_each_mapper_in_subdevice(int32_t eventHubDevice,
                                         std::function<void(InputMapper&)> f) {
  auto deviceIt = mDevices.find(eventHubDevice);
  if (deviceIt != mDevices.end()) {
     auto& devicePair = deviceIt->second;
     auto& mappers = devicePair.second;
     for (auto& mapperPtr : mappers) {
       f(*mapperPtr);
     }
   }
}

真正加工原始输入事件的是 InputMapper 对象,由于原始输入事件的类型很多,因此 InputMapper 有很多子类,用于加工不同的原始输入事件,例如 TouchInputMapper 用于处理触摸输入事件,KeyboardInputMapper 处理键盘输入事件等。

上面代码遍历每一个事件,然后再通过 for_each_mapper_in_subdevice 函数遍历 Mapper 类型,获取到合适的 InputMapper 子类对象,然后调用 process 方法:

  • 栗子一:键盘输入事件
    void KeyboardInputMapper::process(const RawEvent* rawEvent) {
        switch (rawEvent->type) {
            case EV_KEY: { //1
                int32_t scanCode = rawEvent->code;
                int32_t usageCode = mCurrentHidUsage;
                mCurrentHidUsage = 0;
    
                if (isKeyboardOrGamepadKey(scanCode)) {
                    //2
                    processKey(rawEvent->when, rawEvent->readTime, rawEvent->value != 0, scanCode,usageCode);
                }
                break;
            }
            //3
            case EV_MSC: {
                if (rawEvent->code == MSC_SCAN) {
                    mCurrentHidUsage = rawEvent->value;
                }
                break;
            }
            //4
            case EV_SYN: {
                if (rawEvent->code == SYN_REPORT) {
                    mCurrentHidUsage = 0;
                }
            }
        }
    }
    

    注释一,如果事件类型是按键类型的事件,就会调用注释二处的 processKey 函数

    注释三处为其他事件的处理,注释四为同步事件处理。

    我们主要来看一下 processkey 函数的处理

    void KeyboardInputMapper::processKey(nsecs_t when, nsecs_t readTime, bool down, int32_t scanCode,nt32_t usageCode) {
      
        NotifyKeyArgs args(getContext()->getNextId(), when, readTime, getDeviceId(), mSource,
                           getDisplayId(), policyFlags,
                           down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
                           AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, downTime);
        getListener()->notifyKey(&args);
    }
    

    processKey 会将加工后的键盘输入事件封装为 NotifyKeyArgs ,接着将其通知给 InputListenerInterface ,InputDispatcher 继承了 InputListenerInterface ,因此这里实际上调用了 InputDispatcher 的 notifyKey 函数,然后进行处理。

    void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {
    		//...
        bool needWake;
        { // acquire lock
            mLock.lock();
            if (shouldSendKeyToInputFilterLocked(args)) {
                mLock.unlock();
                policyFlags |= POLICY_FLAG_FILTERED;
                if (!mPolicy->filterInputEvent(&event, policyFlags)) {
                    return; // event was consumed by the filter
                }
                mLock.lock();
            }
            //1
            std::unique_ptr<KeyEntry> newEntry =
                    std::make_unique<KeyEntry>(args->id, args->eventTime, args->deviceId, args->source,
                                               args->displayId, policyFlags, args->action, flags,
                                               keyCode, args->scanCode, metaState, repeatCount,
                                               args->downTime);
            //2
            needWake = enqueueInboundEventLocked(std::move(newEntry));
            mLock.unlock();
        } // release lock
    
        if (needWake) {
            mLooper->wake();
        }
    }
    

    上面代码采用Mutex互斥锁的形式,注释一处根据 args 重新封装一个 KeyEvent ,代表一次按键数据,注释二根据 KeyEvetn 来判断是否需要将 InputDispatcher 唤醒,如果需要,就调用 wake 进行唤醒,InputDispatcher 被唤醒就会重新对输入事件进行分发。

  • 🌰2:触摸输入事件
    void TouchInputMapper::process(const RawEvent* rawEvent) {
        mCursorButtonAccumulator.process(rawEvent); //处理鼠标事件,type == EV_KEY 进行处理
        mCursorScrollAccumulator.process(rawEvent); //处理鼠标滚轮事件,type == EV_REL 进行处理
        mTouchButtonAccumulator.process(rawEvent);  //处理屏幕触摸事件,type == EV_KEY 进行处理
    
        if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
            sync(rawEvent->when, rawEvent->readTime);
        }
    }
    

    上面代码中,前三个是对事件进行判断,然后再进行处理,其中 EV_KEY 是键盘事件,EV_REL 是相对坐标事件。如果符合对应的条件,则会抽取对应的信息,例如坐标数据等

    接着进行判断,如果是 EV_SYN (同步事件),并且事件 code 等于 SYN_REPORT 则执行同步函数

    void TouchInputMapper::sync(nsecs_t when, nsecs_t readTime) {
        // Push a new state.
        mRawStatesPending.emplace_back();
    
        RawState& next = mRawStatesPending.back();
        next.clear();
        next.when = when;
        next.readTime = readTime;
    
        // Sync button state.
        next.buttonState =
                mTouchButtonAccumulator.getButtonState() | mCursorButtonAccumulator.getButtonState();
    
        // 同步滑动事件
        next.rawVScroll = mCursorScrollAccumulator.getRelativeVWheel();
        next.rawHScroll = mCursorScrollAccumulator.getRelativeHWheel();
        mCursorScrollAccumulator.finishSync();
    
        // 同步触摸事件
        syncTouch(when, &next);
    
        //.....
    
        //继续处理事件
        processRawTouches(false /*timeout*/);
    }
    

    继续看 processRawTouches 函数

    void TouchInputMapper::processRawTouches(bool timeout) {
        //....
        cookAndDispatch(when, readTime);
        //....   
    }
    
    void TouchInputMapper::cookAndDispatch(nsecs_t when, nsecs_t readTime) {
     		 //...
         //进行分发
         dispatchTouches(when, readTime, policyFlags);  
    		 //...
    }
    
    void TouchInputMapper::dispatchTouches(nsecs_t when, nsecs_t readTime, uint32_t policyFlags) {
        BitSet32 currentIdBits = mCurrentCookedState.cookedPointerData.touchingIdBits;
        BitSet32 lastIdBits = mLastCookedState.cookedPointerData.touchingIdBits;
        int32_t metaState = getContext()->getGlobalMetaState();
        int32_t buttonState = mCurrentCookedState.buttonState;
    
       
            // Dispatch pointer up events.
            while (!upIdBits.isEmpty()) {
                //.....
                dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_UP, 0,
                               isCanceled ? AMOTION_EVENT_FLAG_CANCELED : 0, metaState, buttonState, 0,
                               mLastCookedState.cookedPointerData.pointerProperties,
                               mLastCookedState.cookedPointerData.pointerCoords,
                               mLastCookedState.cookedPointerData.idToIndex, dispatchedIdBits, upId,
                               mOrientedXPrecision, mOrientedYPrecision, mDownTime);
                dispatchedIdBits.clearBit(upId);
                mCurrentCookedState.cookedPointerData.canceledIdBits.clearBit(upId);
            }
    
            // Dispatch move events if any of the remaining pointers moved from their old locations.
            // Although applications receive new locations as part of individual pointer up
            // events, they do not generally handle them except when presented in a move event.
            if (moveNeeded && !moveIdBits.isEmpty()) {
                //...
                dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_MOVE, 0, 0,
                               metaState, buttonState, 0,
                               mCurrentCookedState.cookedPointerData.pointerProperties,
                               mCurrentCookedState.cookedPointerData.pointerCoords,
                               mCurrentCookedState.cookedPointerData.idToIndex, dispatchedIdBits, -1,
                               mOrientedXPrecision, mOrientedYPrecision, mDownTime);
            }
    
            // Dispatch pointer down events using the new pointer locations.
            while (!downIdBits.isEmpty()) {
                 //...
                dispatchMotion(when, readTime, policyFlags, mSource, AMOTION_EVENT_ACTION_POINTER_DOWN,
                               0, 0, metaState, buttonState, 0,
                               mCurrentCookedState.cookedPointerData.pointerProperties,
                               mCurrentCookedState.cookedPointerData.pointerCoords,
                               mCurrentCookedState.cookedPointerData.idToIndex, dispatchedIdBits,
                               downId, mOrientedXPrecision, mOrientedYPrecision, mDownTime);
            }
        }
    }
    

    上面代码中,会根据记录的上一次触摸位置,对事件的类型进行判断,然后做相应的分发,事件类型有抬起,下落,移动等,然后进行对应的分发,无论是何种类型,最终调用的都是 dispatchMoion方法

    void TouchInputMapper::dispatchMotion(nsecs_t when, nsecs_t readTime, uint32_t policyFlags,
                                          uint32_t source, int32_t action, int32_t actionButton,
                                          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) {
    
    
        //最终形成的 NoteifyMotionArgs 对象
        NotifyMotionArgs args(getContext()->getNextId(), when, readTime, deviceId, source, displayId,
                              policyFlags, action, actionButton, flags, metaState, buttonState,
                              MotionClassification::NONE, edgeFlags, pointerCount, pointerProperties,
                              pointerCoords, xPrecision, yPrecision, xCursorPosition, yCursorPosition,downTime, std::move(frames));
        //回调到 InputDispatcher 的 notifyMotion 方法中
        getListener()->notifyMotion(&args);
    }
    
    void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
    		//.......
        bool needWake;
        { // acquire lock
            // Just enqueue a new motion event.
            std::unique_ptr<MotionEntry> newEntry =
                    std::make_unique<MotionEntry>(args->id, args->eventTime, args->deviceId,
                                                  ......);
            needWake = enqueueInboundEventLocked(std::move(newEntry));
            mLock.unlock();
        } // release lock
    
        if (needWake) {
            mLooper->wake();
        }
    }
    bool InputDispatcher::enqueueInboundEventLocked(std::unique_ptr<EventEntry> newEntry) {
        bool needWake = mInboundQueue.empty();
        //将事件添加到 minboundQueue 中
        mInboundQueue.push_back(std::move(newEntry));
        EventEntry& entry = *(mInboundQueue.back());
        traceInboundQueueLengthLocked();
    		//...
        return needWake;
    }
    

    上面将 args 封装为 MotionEntry 类型的对象,然后调用了 enqueueInboundEventLocked 方法,该方法中会将 MotionEntry t添加到队列中。然后根据返回值来判断是否需要唤醒InputDispatcher线程。

  • 小结

    通过上面 InputReader 的启动和两个例子我们可以看出:

    1. InputReader 只是调用 EventHub 的 getEvent 获取了原始事件,获取到事件后,就会根据原始事件找到对应的 InputDevice(设备对象)。
    2. 在 InputDevice 中,根据事件获取到对应的 InputMapper 用于加工事件。InputMapper 有很多子类,分别对应了很多事件类型,例如触摸事件,多点触摸事件,按键事件等。
    3. 上面也通过两个例子,来大致的看了一下事件被加工封装的过程,通过分析,最终我们可以发现事件被加工结束后都会通过 getListener 回调掉 InputDispatcher 中对应的两个方法,键盘事件最终被封装为 KeyEntry 对象,而触摸事件被封装成了 MotionEntry 对象。这两个方法最终都调用了 enqueueInboundEventLocked 方法,最后根据返回值来判断是否需要唤醒 InputDispatcher 线程,如果需要,就会进行唤醒,并重新进行事件的分发。

    image-20230113172259997

InputDspatcher 分发事件

上面将封装的事件压入到队列中。接着就是唤醒 InputDispatchert 线程进行分发处理了。

真正的事件派发是一个串行的过程,我们来从头开始来看一下 InputDispatcher 是如何进行派发的:

status_t InputDispatcher::start() {
    if (mThread) {
        return ALREADY_EXISTS;
    }
    mThread = std::make_unique<InputThread>(
            "InputDispatcher", [this]() { dispatchOnce(); }, [this]() { mLooper->wake(); });
    return OK;
}

创建了单独的线程运行,InputThread 接收三个参数,第一个是线程名字,第二个是执行 threadLoop 时的回调函数,第三个是线程销毁前唤醒线程的回调。

void InputDispatcher::dispatchOnce() {
    nsecs_t nextWakeupTime = LONG_LONG_MAX;
    { // acquire lock
        std::scoped_lock _l(mLock);
        mDispatcherIsAlive.notify_all();

        //1
        if (!haveCommandsLocked()) {
           //2
           dispatchOnceInnerLocked(&nextWakeupTime);
        }
        if (runCommandsLockedInterruptible()) {
            nextWakeupTime = LONG_LONG_MIN;
        }
        const nsecs_t nextAnrCheck = processAnrsLocked();
        nextWakeupTime = std::min(nextWakeupTime, nextAnrCheck);

        // We are about to enter an infinitely long sleep, because we have no commands or
        // pending or queued events
        if (nextWakeupTime == LONG_LONG_MAX) {
            mDispatcherEnteredIdle.notify_all();
        }
    } // release lock

    // 3
    nsecs_t currentTime = now();
    //4
    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);
    mLooper->pollOnce(timeoutMillis);
}

注释一 :用于检查 InputDispatcher 的缓存队列中是否有等待处理的命令,没有就会执行执行注释二

注释二 :将输入事件分发给合适的 Window

注释三 :获取当前时间

注释四 :计算需要睡眠的时间,调用 pollOnce 进入休眠,当 InputReader 有输入事件时,会唤醒 InputDispatcher,重新进行事件分发

我们主要来看一下注释二是如何进行事件分发:

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
    //.....
    // 如果 InputDispatcher 被冻结,则不进行派发操作 
    if (mDispatchFrozen) {
        if (DEBUG_FOCUS) {
            ALOGD("Dispatch frozen.  Waiting some more.");
        }
        return;
    }

    // 如果 isAppSwitchDue 为 true,说明没有及时响应 HOME 键等操作
    bool isAppSwitchDue = mAppSwitchDueTime <= currentTime; //1
    if (mAppSwitchDueTime < *nextWakeupTime) {//2
        *nextWakeupTime = mAppSwitchDueTime;
    }

    // 如果没有待分发的事件,去 mInboundQueue 取出一个事件
    if (!mPendingEvent) {
        //如果没有待分发的事件,就去 mInboundQueue 中取出一个事件
        if (mInboundQueue.empty()) {
            .......
            if (!mPendingEvent) {
                return;
            }
        } else {
            //不为空,则从头部取出一个事件
            mPendingEvent = mInboundQueue.front();
            mInboundQueue.pop_front();
            traceInboundQueueLengthLocked();
        }
        // Poke user activity for this event.
        if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {
            pokeUserActivityLocked(*mPendingEvent);
        }
    }

    ALOG_ASSERT(mPendingEvent != nullptr);
    bool done = false;
    DropReason dropReason = DropReason::NOT_DROPPED; //3
		.....
    switch (mPendingEvent->type) {
        case EventEntry::Type::CONFIGURATION_CHANGED:  .....
        case EventEntry::Type::DEVICE_RESET: .....
        case EventEntry::Type::FOCUS:  .....
        case EventEntry::Type::DRAG:  .....
        case EventEntry::Type::KEY:.....
        case EventEntry::Type::MOTION: {
            std::shared_ptr<MotionEntry> motionEntry =
                    std::static_pointer_cast<MotionEntry>(mPendingEvent);
            //如果没有及时响应窗口切换操作
            if (dropReason == DropReason::NOT_DROPPED && isAppSwitchDue) {
                dropReason = DropReason::APP_SWITCH;
            }
            //事件过期
            if (dropReason == DropReason::NOT_DROPPED && isStaleEvent(currentTime, *motionEntry)) {
                dropReason = DropReason::STALE;
            }
            //阻碍其他窗口获取事件
            if (dropReason == DropReason::NOT_DROPPED && mNextUnblockedEvent) {
                dropReason = DropReason::BLOCKED;
            }
            //5
            done = dispatchMotionLocked(currentTime, motionEntry, &dropReason, nextWakeupTime);
            break;
        }

        case EventEntry::Type::SENSOR: //.....
    }

    if (done) {
        if (dropReason != DropReason::NOT_DROPPED) {
            dropInboundEventLocked(*mPendingEvent, dropReason);
        }
        mLastDropReason = dropReason;
				//释放本次事件处理对象
        releasePendingEventLocked();
        //使得 inputDispatcher 能够快速处理下一个分发事件
        *nextWakeupTime = LONG_LONG_MIN; 
    }
}

上面代码主要截取了 Motion 事件相关的源码,主要的事情有下面几点:

如果 InputDispatcher 被冻结,则不进行派发操作,InputDispatcher 有三种状态,分别是正常,冻结,和禁用。

在注释一处的 mAppSwitchDueTime 代表了App窗口发送切换操作(例如按下 Home,挂电话)事件的最迟分发事件,如果这个时候 mAppSwitchDueTime 小于等于当前系统时间,说明没有及时响应窗口切换操作,则设置 true。

注释二 如果 mAppSwitchDueTime 小于 nextWakeupTime(下一次inputDispatcherThread醒来的时间),就直接进行赋值,这样当 InputDispatcher 处理完分发事件后,就会第一时间处理窗口切换操作。

注释三 代表了事件被丢弃的原因,默认值是 NOT_DROPPED ,代表不会丢弃

注释四 对事件类型进行区分,这里主要是看 Motion 事件。

注释五 通各种判断,最终会调用 dispatchMotionLocked 来为这个事件寻找合适的窗口。

最后,如果事件分发成功,则会调用 releasePendingEventLocked 函数,内部会释放本次事件处理使用到的对象,并且将 nextWakeupTime 值设置为 LONG_LONG_MIN ,这是为了让 InputDispatcher 能够快速处理下一个分发事件。

总结

本篇文章主要分析了一个原始事件从读取到分发的过程,由于篇幅原因,事件具体是如何分发到 View 上没有分析,这部分内容在下篇文章中在进行分析。

在最后来对本篇文章做一个小结

  1. SystemServer 创建 IMS ,并且传入 WMS 对象
  2. IMS 构造方法中,会调用 native 层函数,并且创建 nativeInputManager ,并且将地址转为 long 类型返回到 java 层
  3. NativeinputManager 内部会保存 IMS 实例,并且创建 InputManager 对象
  4. InputManager 创建 InputDispatcher 和 InputReader 对象,封闭用于读取和分发事件
  5. InputReader 会开启一个线程,通过调用 EventHub 的 getEvent 方法获取到设备节点中的原始输入事件
  6. InputReader 获取到事件后,就会根据原始事件找对对应的InputDevice(设备对象)
  7. 在InputDevice(设备对象)中根据对应的类型获取到对应的 InputMapper 用于加工事件,InputManager 有很多子类,分别对应了很多事件类型,本篇文章中讲的是键盘输入事件和触摸事件。
  8. InputManager 的子类加工完成后都会通过 getListener 进行回调,会掉到 InputDispatcher 中去。
  9. 回调到 InputDispatcher 中后对事件进行封装,被封装后的事件都会继承 EventEntry 类,最后这些事件会被添加到 mInboundQueue 队列中。
  10. 接着就会唤醒 InputDispatcher 线程,重新执行 dispatchOnce 函数,根据事件的具体类型,执行对应的函数,最终分发到对应的目标窗口

参考

Android 输入系统

https://juejin.cn/post/7169421307421917214#heading-10

https://www.jianshu.com/p/ad476f199e39

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/186907.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

logging日志模块详解

说到日志&#xff0c;无论是写框架代码还是业务代码都离不开日志的记录&#xff0c;其能给我们定位问题带来极大的帮助。 记录日志最简单的方式是在你想要记录的地方加上一句print。我相信无论是新手还是老鸟都经常这么干&#xff0c;在简单的代码或者小型项目中这么干一点问题…

这样吃才能有效补脑

核桃长得像大脑&#xff0c;还含有Ω-3&#xff0c;一直被认为补脑效果很好。但是现代科学研究发现&#xff0c;Ω-3并不是核桃专有的&#xff0c;很多坚果都有&#xff0c;所以核桃在补脑这方面并没有什么特殊功效。补脑其实就是维持大脑的正常工作&#xff0c;还要延缓大脑认…

SAP ADM100-Unit4 数据库工作原理:监控数据库

概览 除了执行数据备份之外,还需要对数据库进行大量的周期性检查。 课程目标 对数据库计划额外的周期性检查。 1、数据库定期监控 除了日常监控数据库备份外,还有大量的其他检查不得不定期执行。有的检查可以通过DBA Cockpit Planning Calendar来计划。 例如: 当存取数…

Mac电脑使用:查看本机已连接Wi-Fi密码的方法

前言 在使用Mac电脑的时候&#xff0c;电脑自身所连接成功的Wi-Fi一般都不显示密码&#xff0c;这是苹果出于安全考量的保护措施&#xff0c;但是有时候遇到新的设备想要连接已经连过的Wi-Fi&#xff0c;由于时间太久忘记Wi-Fi密码&#xff0c;这就需要查看一下电脑连接的Wi-Fi…

点击化学标记1817735-33-3,Pyrene-PEG5-propargyl,芘甲酰胺五聚乙二醇丙炔

Pyrene-PEG5-propargyl&#xff0c;芘甲酰胺-五聚乙二醇-丙炔Product structure&#xff1a;Pyrene-PEG5-propargyl结构式Product specifications&#xff1a;1.CAS No&#xff1a;1817735-33-32.Molecular formula&#xff1a;C30H33NO63.Molecular weight&#xff1a;503.64.…

终于有多位大神联手把计算机基础知识与操作系统讲清楚了

操作系统的定义 指的是控制和管理整个计算机系统的硬件和软件资源&#xff0c;并合理地组织调度计算机的工作和资源的分配&#xff0c;以提供给用户和其他软件方便的接口和环境&#xff0c;它是计算机系统中最基本的系统软件。 计算机系统的层级结构 1、用户 应用程序 2、操…

vue前端框架课程笔记(三)

目录条件渲染v-ifv-show列表渲染关于:key列表过滤watch属性实现computed属性列表排序表单数据收集input是否配置value属性过滤器本博客参考尚硅谷官方课程&#xff0c;详细请参考 【尚硅谷bilibili官方】 本博客以vue2作为学习目标&#xff08;请勿混淆v2与v3的代码规范&…

Azure 语音用人工智能改变游戏开发的三种方式

通过 Azure 认知服务的智能语音功能[1]&#xff0c;用户可以使用语音 SDK 开发工具包快速构建支持语音交互的各种应用。将语音转录为准确的文本 &#xff08;STT&#xff0c;或语音识别&#xff09;或者将文本转换成生动的语音 &#xff08;TTS&#xff0c;或语言合成&#xff…

大数据NiFi(十五):NiFi入门案例二

文章目录 NiFi入门案例二 一、配置“GenerateFlowFile”处理器 1、拖拽“Processor”在弹框中输入“GenerateFlowFile” <

如何解决缓存雪崩、击穿、穿透难题?

缓存雪崩、缓存击穿和缓存穿透这三个问题是我们在使用redis做缓存的时候要面临的&#xff0c;一旦发生这三个问题&#xff0c;就会导致大量的请求都积压到了数据库层&#xff0c;有可能导致数据库宕机&#xff0c;进入导致整个系统不可用。 下边&#xff0c;具体看一下这三个问…

Qt扫盲-Qt资源系统概述

Qt资源系统概述一、概述二、资源文件(.qrc)三、外部二进制资源四、内嵌资源五、压缩资源文件六、在应用中使用资源七、使用Library 库中的资源一、概述 Qt资源系统是一种独立于平台的机制&#xff0c;用于在应用程序的可执行文件中存储二进制文件。如果您的应用程序总是需要一…

Spring Boot学习之集成Dubbo+Zookeeper小案例

文章目录一 框架搭建1. [Dubbozookeeper下载和安装](https://blog.csdn.net/yang2330648064/article/details/128790320)二 项目创建2.1 服务者部分2.2 消费者部分2.3 注意点2.3.1 在service的实现类中配置服务注解&#xff0c;发布服务&#xff01;注意导包问2.3.2 服务接口的…

[Vulnhub] DC-8

下载链接&#xff1a;https://download.vulnhub.com/dc/DC-8.zip 知识点&#xff1a; sqlmap 注入出用户名密码msfvenom 生成脚本msf反弹shellsuid-exim 提权 目录 <1> 信息搜集 <2> 反弹shell (1) 利用kali自带webshell代码 (2) msfvenom制作反弹shell脚本…

SaaS平台数据表单组件设计技巧分享

SaaS平台数据表单组件设计技巧分享&#xff0c;数据表单方法&#xff1a;固定表头、固定侧栏、自定义栏、分页器、过滤器、数据排序、多选项同时操作、简单且简约、普通的字体样式、项目链接、鼠标悬停设计指南&#xff0c;为大家提供有关数据表单设计的实用性建议。在实际的数…

20克拉默法则、逆矩阵、体积

本节是关于行列式的最后一课&#xff0c;主要包括按各方面&#xff1a;求逆矩阵、克莱姆法则和体积 求逆矩阵 A-1 早在之前&#xff0c;就已经了解过求解逆矩阵的方法&#xff1a;高斯-若尔当求逆法。高斯-若尔当求逆法对于数值计算无懈可击&#xff0c;但很难想象这是如何做…

Hadoop基础之《(6)—Hadoop单机伪集群安装》

一、安装JDK yum install java-1.8* 二、关闭防火墙 systemctl status firewalld systemctl stop firewalld systemctl disable firewalld 三、配置ip地址和主机名映射 vi /etc/hosts 加入&#xff1a; 192.168.1.1 hadoop001 四、配置免密登录 1、生成公私钥 ssh-key…

设计模式第5式:装饰器模式

前言 当我们初学编程时&#xff0c;扩展程序功能一般习惯使用继承&#xff0c;使用继承有一些缺点&#xff0c;那就是容易造成类爆炸&#xff0c;并且容易继承一些不需要的特性。当我们学习完装饰器模式后&#xff0c;会发现善用组合会有比继承更好的效果。 正文 1、咖啡馆案…

全志A40i+Logos FPGA开发板(4核ARM Cortex-A7)硬件说明书(上)

前 言 本文档主要介绍TLA40iF-EVM工业评估板硬件接口资源以及设计注意事项等内容。 核心板的ARM端和FPGA端的IO电平标准一般为3.3V,上拉电源一般不超过3.3V,当外接信号电平与IO电平不匹配时,中间需增加电平转换芯片或信号隔离芯片。按键或接口需考虑ESD设计,ESD器件选型时需…

深入Java自动化探针技术的原理和实践

转至作者 蒋志伟&#xff1a;深入Java自动化探针技术的原理和实践 前言建议阅读时间 30~40分钟读者需要对Java JVM 一定了解&#xff0c;文章会系统的介绍Java 探针核心原理和技术实现&#xff0c;总结目前一些主流的框架方案。同时&#xff0c;接下来我会分享一篇关于 OpenTel…

你是如何学会正则表达式的?

前言 前言 正则表达式作为对字符串操作的一种逻辑公式&#xff0c;它使用一些特定字符及其组合组成“规则字符串”来对字符串进行过滤的操作&#xff0c;如在注册验证的时候我们就经常会用到正则表达式&#xff0c;但正则表达式的变式太多&#xff0c;我们不用完全的去记住每一…