zoukankan      html  css  js  c++  java
  • Android Input 之 InputMananger(Native)

    直接从inputflinger开始分析Android是获取到键盘输入和屏幕触控事件的流程。代码路径如下:

    frameworks/native/services/inputflinger

    frameworks/native/include/input

    frameworks/native/libs/input

    frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp

    InputManager

    InputManager是Android 输入事件处理的核心,其创建了两个线程:

    1. InputReaderThread

      该线程负责从获取linux 输入事件,并根据配置文件中的设置对linux输入事件进行预处理,最后将其放入到一个事件队列中,等待另一个InputDispatcherThread处理。

    2. InputDispatcherThread

      从事件队列中获取事件并将其异步的分发到应用程序。

    InputManager的分析主要分为三步部分:

    1. InputManager的创建
    2. InputReaderThread
    3. InputDispatcherThread

    开干!!!

    InputManager是如何创建的

    首先介绍一下其成员变量。

    sp<InputReaderInterface> mReader;
    sp<InputReaderThread> mReaderThread;	//读线程
    
    sp<InputDispatcherInterface> mDispatcher;
    sp<InputDispatcherThread> mDispatcherThread; // 事件分发线程
    
    • InputReaderInterface

      其实际类型就是InputReader。其负责从EventHub中获取linux raw event,并进行相应的预处理。

    • InputDispatcherInterface

      实际类型就是InputDispatcher。用于通知Android系统有事件输入。

    再来看一下其构造函数的实现。

    InputManager::InputManager(
            const sp<EventHubInterface>& eventHub,
            const sp<InputReaderPolicyInterface>& readerPolicy,
            const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
        // 实例化
        mDispatcher = new InputDispatcher(dispatcherPolicy);
        mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
    	//创建前面提到的两个线程
    	mReaderThread = new InputReaderThread(mReader);
    	mDispatcherThread = new InputDispatcherThread(mDispatcher);
    }
    

    额,有三个参数,关于EventHub,点这里

    另外两个?

    //frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
    NativeInputManager::NativeInputManager(jobject contextObj,
            jobject serviceObj, const sp<Looper>& looper) :
            mLooper(looper), mInteractive(true) {
        // ...
        // EventHub在这里初始化
        sp<EventHub> eventHub = new EventHub();
        
        mInputManager = new InputManager(eventHub, this, this);
    }
    
    • InputReaderPolicyInterface

      实际类型是NativeInputManager,其接口的实际实现都是在InputManagerService中,NativeInputManager只是通过jni调用其java端的实现。

      InputReader构造时用到了其作为参数,后面分析InputReader时,再来分析起作用。

      类注释:

      Input reader policy interface.

      The input reader policy is used by the input reader to interact with the Window Manager and other system components.

    • InputDispatcherPolicyInterface

      实际类型是NativeInputManager,其接口的实际实现都是在InputManagerService中,NativeInputManager只是通过jni调用其java端的实现。

      TODO: 待补充

    最后,在InputManagerService中,会通过JNI调用InputMananger::start,这时,我们的两个线程就要开始工作了。。。

    输入事件读取线程(InputReaderThread)

    image-20201211003303059

    创建InputReader

    InputReader构造函数:

    //mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
    InputReader::InputReader(const sp<EventHubInterface>& eventHub,
            const sp<InputReaderPolicyInterface>& policy,
            const sp<InputListenerInterface>& listener) :
            mContext(this), mEventHub(eventHub), mPolicy(policy),
            mGlobalMetaState(0), mGeneration(1),
            mDisableVirtualKeysTimeout(LLONG_MIN), mNextTimeout(LLONG_MAX),
            mConfigurationChangesToRefresh(0) {
        mQueuedListener = new QueuedInputListener(listener);
    
        { // acquire lock
            AutoMutex _l(mLock);
    
            refreshConfigurationLocked(0);
    
            updateGlobalMetaStateLocked();
        } // release lock
    }
    
    

    构建步骤:

    1. 创建QueuedInputListener实例。

      class QueuedInputListener : public InputListenerInterface {
      protected:
          virtual ~QueuedInputListener();
      
      public:
          explicit QueuedInputListener(const sp<InputListenerInterface>& innerListener);
      
          virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args);
          virtual void notifyKey(const NotifyKeyArgs* args);
          virtual void notifyMotion(const NotifyMotionArgs* args);
          virtual void notifySwitch(const NotifySwitchArgs* args);
          virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args);
      
          void flush();
      
      private:
          sp<InputListenerInterface> mInnerListener;
          Vector<NotifyArgs*> mArgsQueue;
      };
      

      image-20201212133628206

      QueuedInputListener::mInnerListener实际上就是InputDispatcher的实例。当InputReader处理完事件后将RawEvent转换成NotifyArgs对象,并调用QueuedInputListener的成员函数notify*(...)(继承至InputListenserInterface)将其放入到QueuedInputListener::mArgsQueue中。然后再调用QueueInputListener::flush将队列中的NotifyArgs交由InputDispatcher处理。

      void QueuedInputListener::notifyKey(const NotifyKeyArgs* args) {
          mArgsQueue.push(new NotifyKeyArgs(*args));
      }
      
      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();
      }
      

      NotifyKeyArgs为例:

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

      最终,还是调用了InputDispatcher::notifyKey

      到这里,事件就从InputReader转交给InputDispatcher处理。

      但是,如下函数还是在InputReaderThread线程中执行,InputDispatcherThread主要干甚???

      virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args);
      virtual void notifyKey(const NotifyKeyArgs* args);
      virtual void notifyMotion(const NotifyMotionArgs* args);
      virtual void notifySwitch(const NotifySwitchArgs* args);
      virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args);
      
    2. refreshConfigurationLocked(int changes)

      根据changes更新所有输入设备的配置信息。

      void InputReader::refreshConfigurationLocked(uint32_t changes) {
          mPolicy->getReaderConfiguration(&mConfig);
      	// /system/etc/excluded-input-devices.xml
          mEventHub->setExcludedDevices(mConfig.excludedDeviceNames);
      
          if (changes) {
              ALOGI("Reconfiguring input devices.  changes=0x%08x", changes);
              nsecs_t now = systemTime(SYSTEM_TIME_MONOTONIC);
      
              if (changes & InputReaderConfiguration::CHANGE_MUST_REOPEN) {
                  mEventHub->requestReopenDevices();
              } else {
                  for (size_t i = 0; i < mDevices.size(); i++) {
                      InputDevice* device = mDevices.valueAt(i);
                      device->configure(now, &mConfig, changes);
                  }
              }
          }
      }
      
      • mPolicy->getReaderConfiguration(&mConfig);这个实际实现在NativeInputMnanager中,通过jni回调java函数,获取一些配置信息和一些常量(比如双击之间的最小时间和最大时间间隔)并将其保存到InputReaderConfiguration中。

      • mEventHub->setExcludedDevices(mConfig.excludedDeviceNames);告诉EventHub有些设备我们是不要处理的。

        EventHub打开输入设备后,会通过ioctl获取到设备名称,然后在根据其是否在禁用设备列表中来决定是否使用该输入设备。

      • 后面就是根据changes的值来选择性的执行一些操作。

        InputDevice...后面会提到的。。。

    3. updateGlobalMetaStateLocked

      void InputReader::updateGlobalMetaStateLocked() {
          mGlobalMetaState = 0;
      
      	//所以,我接入两个键盘,一个按下 shift 另一个按下 a, 也会变成 A???
          for (size_t i = 0; i < mDevices.size(); i++) {
              InputDevice* device = mDevices.valueAt(i);
              mGlobalMetaState |= device->getMetaState();
          }
      }
      

      似乎和组合按键的处理有关。

    InputReadThread的主循环

    bool InputReaderThread::threadLoop() {
        mReader->loopOnce();
        return true;
    }
    

    额,没毛病。重点就是InputReadr::loopOnce,主要功能就是通过EventHub::getEvents获取事件,然后将处理相应的事件(通过调用与事件关联的InputDeviceprocess方法处理事件),最后就是将处理后的事件(都在QueuedInputListener中存着)都交友InputDispatcher处理(通过``QueuedInputListener::flush函数)。

    void InputReader::loopOnce() {
        ...
    	// 从 EventHub 中获取事件。具体包括 输入设备的接入、移除,还有就是按键或触摸等
        size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
    
     
         processEventsLocked(mEventBuffer, count);
    	...
        mQueuedListener->flush();
    }
    

    processEventsLocked

    EventHub中获取到的事件有两种,一种是输入设备的产生的事件信息比如鼠标移动,按下键盘,触摸屏幕等,还有一种就是输入设备的热插拔信息,比如蓝牙输入设备的连入和断开。

    EventHub中,使用Device来描述输入设备;而在InputReader中,使用InputDevice来表示输入设备。前者主要是保存输入设备的硬件信息(从内核获取到的);后者主要是描述这个输入设备的功能。

    void InputReader::processEventsLocked(const RawEvent* rawEvents, size_t count) {
        for (const RawEvent* rawEvent = rawEvents; count;) {
            int32_t type = rawEvent->type;
            size_t batchSize = 1;
            if (type < EventHubInterface::FIRST_SYNTHETIC_EVENT) {
                int32_t deviceId = rawEvent->deviceId;
                //批量处理啦,大多数时候,事件都是有同一个输入设备触发的。。。
                while (batchSize < count) {
                    if (rawEvent[batchSize].type >= EventHubInterface::FIRST_SYNTHETIC_EVENT
                            || rawEvent[batchSize].deviceId != deviceId) {
                        break;
                    }
                    batchSize += 1;
                }
                processEventsForDeviceLocked(deviceId, rawEvent, batchSize);
            } else {
                // 处理输入设备的消息。
                switch (rawEvent->type) {
                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;
        }
    }
    

    InputDevice

    先来看一下InputDevice提供了那些功能,后面分析事件处理的时候就很容易了。

    只分析InputDevice的创建。。

    先来看看其成员变量

        InputReaderContext* mContext;
        int32_t mId;//  通过这个Id就能获取到EventHub中的`Device`,进而获取到与其关联的配置信息		
        int32_t mGeneration;
        int32_t mControllerNumber;
        InputDeviceIdentifier mIdentifier;	// 同EvnetHub::Device::InputDeviceIdentifier
        String8 mAlias;
        uint32_t mClasses;//同EventHub::Device::mClasses,输入设备接入时,EventHub通过从驱动获取信息以及解析配置文件,来判断该输入设备的类型(同一个设备可能属于多种输入类型)。
    
        Vector<InputMapper*> mMappers;	// 每一种输入类型,对应一个 InputMapper
    
        uint32_t mSources;	// 感觉和 classes有冲突啊,
        bool mIsExternal;
        bool mHasMic;
        bool mDropUntilNextSync;
    
        typedef int32_t (InputMapper::*GetStateFunc)(uint32_t sourceMask, int32_t code);
        int32_t getState(uint32_t sourceMask, int32_t code, GetStateFunc getStateFunc);
    
        PropertyMap mConfiguration;	// 对应IDC文件
    

    创建过程:

    // 根据 class的取值添加 InputMapper
    InputDevice* device = createDeviceLocked(deviceId, controllerNumber, identifier, classes);
    // 和前面的InputReader::refreshConfigurationLocked类似,
    device->configure(when, &mConfig, 0);
    device->reset(when);	//忽略
    

    这里介绍一下configure

    void InputDevice::configure(nsecs_t when, const InputReaderConfiguration* config, uint32_t changes) {
        mSources = 0;
    
        if (!isIgnored()) {
            if (!changes) { // first time only
                mContext->getEventHub()->getConfiguration(mId, &mConfiguration);
            }
            
            if (!changes || (changes & InputReaderConfiguration::CHANGE_KEYBOARD_LAYOUTS)) {
                if (!(mClasses & INPUT_DEVICE_CLASS_VIRTUAL)) {
    				// 从InputManangerService中获取键盘布局,该键盘布局是从设置中加载的。
    				// 不同国家的键盘布局可能不一样,需要动态设置
                    sp<KeyCharacterMap> keyboardLayout =
                            mContext->getPolicy()->getKeyboardLayoutOverlay(mIdentifier);
    				// 将应用设置的布局 和 输入设备关联的布局文件合并。 overlay具备最高有限级
                    if (mContext->getEventHub()->setKeyboardLayoutOverlay(mId, keyboardLayout)) {
                        bumpGeneration();
                    }
                }
            }
    	    // 所以,为什么要这样???直接在创建 InputMapper时加上不就好了?
            size_t numMappers = mMappers.size();
            for (size_t i = 0; i < numMappers; i++) {
                InputMapper* mapper = mMappers[i];
                mapper->configure(when, config, changes);
                mSources |= mapper->getSources();
            }
        }
    }
    

    以键盘为例,其使用的布局通常是在generic.kl中定义的,这个布局文件是按照qwerty形式的键盘定义的,有些键盘的布局不是这样的,如果我们要正常使用这种键盘,就需要在设置中将键盘布局更改为何我们键盘对应的布局。

    frameworks/base/packages/InputDevices/res/raw目录下定义了很多国家的键盘 kcm 文件。

    在kcm文件中,通过map key 来重新创建 linux key code和android key code的映射关系,这个优先级大于kl文件中定义的映射关系。

    处理事件

    实际工作由InputDevice::process完成。

    void InputDevice::process(const RawEvent* rawEvents, size_t count) {
        size_t numMappers = mMappers.size();
        for (const RawEvent* rawEvent = rawEvents; count--; rawEvent++) {
    
            if (mDropUntilNextSync) {
                if (rawEvent->type == EV_SYN && rawEvent->code == SYN_REPORT) {
                    mDropUntilNextSync = false;
                } else {
                }
            } else if (rawEvent->type == EV_SYN && rawEvent->code == SYN_DROPPED) {
                mDropUntilNextSync = true;
                reset(rawEvent->when);
            } else {
                for (size_t i = 0; i < numMappers; i++) {
                    InputMapper* mapper = mMappers[i];
                    mapper->process(rawEvent);
                }
            }
        }
    }
    

    之前提到过,创建InputDevice时,会根据输入设备的classes来添加InputMapper,我们以键盘为例,其对应的InputMapperKeyboardInputMapper

    KeyBoardInputMapper

    通过getevent -l我们能看到操作键盘时InputReader需要处理的事件。

    #按下
    /dev/input/event2: EV_MSC       MSC_SCAN             0007002c            
    /dev/input/event2: EV_KEY       KEY_SPACE            DOWN                
    /dev/input/event2: EV_SYN       SYN_REPORT           00000000            
    #松开
    /dev/input/event2: EV_MSC       MSC_SCAN             0007002c            
    /dev/input/event2: EV_KEY       KEY_SPACE            UP                  
    /dev/input/event2: EV_SYN       SYN_REPORT           00000000   
    

    一次操作对应的事件顺序都是固定的:EV_MSCEV_KEYEV_SYN

    void KeyboardInputMapper::process(const RawEvent* rawEvent) {
        switch (rawEvent->type) {
        case EV_KEY: {
            int32_t scanCode = rawEvent->code;
            int32_t usageCode = mCurrentHidUsage;
            mCurrentHidUsage = 0;
            if (isKeyboardOrGamepadKey(scanCode)) {
               	// 处理键值
                processKey(rawEvent->when, rawEvent->value != 0, scanCode, usageCode);
            }
            break;
        }
        case EV_MSC: {
            if (rawEvent->code == MSC_SCAN) {
                mCurrentHidUsage = rawEvent->value;
            }
            break;
        }
        case EV_SYN: {
            if (rawEvent->code == SYN_REPORT) {
                mCurrentHidUsage = 0;
            }
        }
        }
    }
    

    解释一下 usageCode

    在 kl文件中, 有如下语法

    key usage 0xXXXXXXXX A

    0xXXXXXXXX 就是usage code ,对应的就是 EV_MSC MSC_SCAN 0007002c 中的0007002c 。

    usageCode 的优先级高于 scanCode

    void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t scanCode,
            int32_t usageCode) {
        int32_t keyCode;
        int32_t keyMetaState;
        uint32_t policyFlags;
    
    	// scanCode 和 usageCode是linux key Code
    	// 这里将其按照 kl 文件 或 kcm 文件中的`map key`将其转换成 Android Key Code
    	// 然后按照 KCM 中定义的规则,将其转换成相应的 Android Key Code
    	// 并且获取  keyMetaState(组合按键) 和 policyFlags (kl文件中定义, 默认为0, VIRTUAL GESTURE 和 FUNCTION)
        if (getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, mMetaState,
                                  &keyCode, &keyMetaState, &policyFlags)) {
            keyCode = AKEYCODE_UNKNOWN;
            keyMetaState = mMetaState;
            policyFlags = 0;
        }
    	...
            
        NotifyKeyArgs args(when, getDeviceId(), mSource, policyFlags,
                down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
                AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, keyMetaState, downTime);
    	//上报事件,QueuedInputListener::notifyKey.
    	// 本质上就是 将 NotifyKeyArgs 放入了其 持有的 队列中.....
        getListener()->notifyKey(&args);
    }
    

    得,在这里将事件放入到QueuedInputListener中的队列,然后在InputReader::loopOnce中将这些事件逐一交由InputDispatcher处理。

    输入事件分发线程(InputDispatcherThread)

  • 相关阅读:
    WEB环境安装步骤v1.2
    将m3u8格式转成mp4格式
    MySQL简介及安装v0.1
    使用脚本pull阿里云的k8s镜像并更改标签
    常用脚本
    常用命令
    记录一下环境变量IFS特定场景使用技巧
    hp-unix创建和更改LV
    HP-UNIX常用命令
    Linux集群搭建
  • 原文地址:https://www.cnblogs.com/liutimo/p/14118093.html
Copyright © 2011-2022 走看看