android 按键事件响应流程(二)InputReader

虚拟机启动SystemServer后,SystemServer在StartOtherService中新建了InputManager对象。记下来看看InputManagerService中做了什么事情。
先看看InputManagerService的构造函数

/frameworks/base/services/core/java/com/android/server/input/InputManagerService.java
public InputManagerService(Context context) {
        this.mContext = context;
        //获得能和UI主进程交互的Handler
        this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());
        mUseDevInputEventForAudioJack =
                context.getResources().getBoolean(R.bool.config_useDevInputEventForAudioJack);
        //调用了这个native层的初始化函数
        mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
        LocalServices.addService(InputManagerInternal.class, new LocalService());
    }

这里看看这个nativeInit干了什么事情

private static native long nativeInit(InputManagerService service,Context context, MessageQueue messageQueue);
------JNI函数
/frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
{ "nativeInit","(Lcom/android/server/input/InputManagerService;Landroid/content/Context;Landroid/os/MessageQueue;)J",(void*) nativeInit },
------JNI方法
static jlong nativeInit(JNIEnv* env, jclass clazz,
        jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
    //获得一个native MessageQueue对象
    sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
    if (messageQueue == NULL) {
        jniThrowRuntimeException(env, "MessageQueue is not initialized.");
        return 0;
    }
    //new一个native InputManager对象,把刚才获得的native的message queue 对象传递给这个IM
    NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
            messageQueue->getLooper());
    im->incStrong(0);
    return reinterpret_cast<jlong>(im);
}

接下来看看这个native IM干了些什么事情。

/frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp
//这个函数有个内置函数,直接返回一个InputManager的强指针
inline sp<InputManager> getInputManager() const { return mInputManager; }
//
NativeInputManager::NativeInputManager(jobject contextObj,
        jobject serviceObj, const sp<Looper>& looper) :
        mLooper(looper), mInteractive(true) {
    JNIEnv* env = jniEnv();

    mContextObj = env->NewGlobalRef(contextObj);
    mServiceObj = env->NewGlobalRef(serviceObj);
    //处理一些所对象属性
    {
        AutoMutex _l(mLock);
        mLocked.systemUiVisibility = ASYSTEM_UI_VISIBILITY_STATUS_BAR_VISIBLE;
        mLocked.pointerSpeed = 0;
        mLocked.pointerGesturesEnabled = true;
        mLocked.showTouches = false;
    }
    //新建了一个EventHub,然后根据这个EventHub新建了一个CPP层的InputManager对象,
    sp<EventHub> eventHub = new EventHub();
    mInputManager = new InputManager(eventHub, this, this);
}

EventHub的构建函数

EventHub::EventHub(void) :
        mBuiltInKeyboardId(NO_BUILT_IN_KEYBOARD), mNextDeviceId(1), mControllerNumbers(),
        mOpeningDevices(0), mClosingDevices(0),
        mNeedToSendFinishedDeviceScan(false),
        mNeedToReopenDevices(false), mNeedToScanDevices(true),
        mPendingEventCount(0), mPendingEventIndex(0), mPendingINotify(false) {
    acquire_wake_lock(PARTIAL_WAKE_LOCK, WAKE_LOCK_ID);
    //创建一个epoll对象
    mEpollFd = epoll_create(EPOLL_SIZE_HINT);
    //初始化一个INotifyFd对象,为应用监听文件系统提供帮助
    mINotifyFd = inotify_init();
    //将设备节点的路径dev/input/设置到INotifyFd对象中去
    int result = inotify_add_watch(mINotifyFd, DEVICE_PATH, IN_DELETE | IN_CREATE);
    //通过epoll_ctl,epoll事件注册函数,将INotifyFd对象注册到mEpollFd中去,这样当设备节点有读写变化的时候就会通过mINotifyFd向上进行通知。
    struct epoll_event eventItem;
    memset(&eventItem, 0, sizeof(eventItem));
    eventItem.events = EPOLLIN;
    eventItem.data.u32 = EPOLL_ID_INOTIFY;
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mINotifyFd, &eventItem);
    //管道文件描述符,描述一个管道的读端文件描述符和写端文件描述符.当一个线程的消息队列没有消息需要处理时,它就会在这个管道的读端文件描述符上进行睡眠等待.直到其他线程通过这个管道的写端文件描述符来唤醒它为止.                                                                                                                                                                          
    int wakeFds[2];
    result = pipe(wakeFds);
    mWakeReadPipeFd = wakeFds[0];
    mWakeWritePipeFd = wakeFds[1];
    //设置管道文件描述符的新旗标
    result = fcntl(mWakeReadPipeFd, F_SETFL, O_NONBLOCK);
    result = fcntl(mWakeWritePipeFd, F_SETFL, O_NONBLOCK);

    eventItem.data.u32 = EPOLL_ID_WAKE;
    result = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, mWakeReadPipeFd, &eventItem);

    int major, minor;
    getLinuxRelease(&major, &minor);
    mUsingEpollWakeup = major > 3 || (major == 3 && minor >= 5);
}

epoll是一种I/O事件通知机制,是linux 内核同时监听多个输入输出源,在其中一个或多个输入输出源可用的时候返回,然后对其的进行读写操作的一个实现。


7368936-c868a5ee3b4cf9ff.png

NativeInputManager还新建了一个InputManager对象

/frameworks/native/services/inputflinger/InputManager.cpp
InputManager::InputManager(
        //eventhub是NativeInputManager新建的,两个policy是NativeInputManager自身。
        const sp<EventHubInterface>& eventHub,
        const sp<InputReaderPolicyInterface>& readerPolicy,
        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {  
    //这里新建了一个Dispatcher对象去分发事件,新建了一个Reader对象去读取事件
    mDispatcher = new InputDispatcher(dispatcherPolicy);
    mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
    initialize();
}

InputReader利用EventHub获取数据后,生成EventEntry事件,加入到InputDispatcher的mInboundQueue队列,再唤醒InputDispatcher线程。先看看initialize()函数干了什么事情,很简单的新建了mDispatcherThread和mReaderThread

void InputManager::initialize() {
    mReaderThread = new InputReaderThread(mReader);
    mDispatcherThread = new InputDispatcherThread(mDispatcher);
}

然后前面讲到了systemserver在创建inputmanagerservice的时候就已经调用了start函数,这里的start就是run了新建的这两个读取分发线程。

status_t InputManager::start() {
    status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
    if (result) {
        return result;
    }

    result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
    if (result) {
        mDispatcherThread->requestExit();
        return result;
    }
    return OK;
}

InputReaderThread的ThreadLoop就是调用mReader的loopOnce。

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

mReader是一个InputReaderInterface内部接口对象,InputReaderInterface是处理input event数据并将处理好的数据发送给input的listerner,接口里面可以做一些input设备信息、状态信息的查询,判断物理按键是否匹配framework按键列表key值;最重要的方法就是上面的loopOnce,这个方法只能在InputReaderThread里面调用,用来处理读取和处理从eventhub过来的信息。

sp<InputReaderInterface> mReader;
//无穷无尽的从eventhub里面读取数据并处理
class InputReaderThread : public Thread {
public:
    InputReaderThread(const sp<InputReaderInterface>& reader);
    virtual ~InputReaderThread();
private:
    sp<InputReaderInterface> mReader;
    virtual bool threadLoop();
};

loopOnce查看InputReader配置是否修改,如界面大小、方向、键盘布局重新加载、指针速度改变等、从EventHub读取事件,其中EVENT_BUFFER_SIZE = 256、处理事件、将事件传到InputDispatcher,getEvent是阻塞式的,只有有事件或者被唤醒的时候才会执行。


20170609101625473.png
void InputReader::loopOnce() {
···
//从EventHub里面通过getEvent获取事件,
size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
···
//如果count大于0就是代表有数据,就processEventsLocked处理
//原始输入事件、设备加载事件、设备卸载事件及FINISHED_DEVICE_SCAN事件
        if (count) {
            processEventsLocked(mEventBuffer, count);
        }
···
//回调回java层通知input设备状态发生改变
if (inputDevicesChanged) {
        mPolicy->notifyInputDevicesChanged(inputDevices);
}
···
//将数据送到listener,这里的listener就是diapatcher,为了防止死锁,flush操作放在了loopOnce的锁外面,因为dispatcher很有可能回调InputReader,如果也请求相同的锁对象就会死锁
mQueuedListener->flush();
}

processEventsLocked里会根据rawEvent的type进行事件的预处理,包含上面提到的四种操作,其中processEventsForDeviceLocked(deviceId, rawEvent, batchSize);会根据device id,先判断device会不会被忽略,然后不会被忽略就交给相应的设备进行处理,inputDevice会调用inputMapper进行处理

            for (size_t i = 0; i < numMappers; i++) {
                InputMapper* mapper = mMappers[i];
                mapper->process(rawEvent);
            }
···
//一共有这么多mapper
SwitchInputMapper 
VibratorInputMapper
KeyboardInputMapper
CursorInputMapper
TouchInputMapper
SingleTouchInputMapper
MultiTouchInputMapper
JoystickInputMapper 
···
5982616-ab16c91640ccbfa3.png

这里我们仅仅看看按键的mapper,调用getListener()->notifyKey(&args)调用的是QueuedInputListener的notifyKey函数,它里边有许多notifyXXX函数,做的事情都是将NotifyXXXArgs放入它的mArgsQueue队列中存储,等待处理。

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)) {
            int32_t keyCode;
            uint32_t flags;
            if (getEventHub()->mapKey(getDeviceId(), scanCode, usageCode, &keyCode, &flags)) {
                keyCode = AKEYCODE_UNKNOWN;
                flags = 0;
            }
            processKey(rawEvent->when, rawEvent->value != 0, keyCode, scanCode, flags);
        }
        break;
    }
    case EV_MSC: {
        if (rawEvent->code == MSC_SCAN) {
            mCurrentHidUsage = rawEvent->value;
        }
        break;
    }
    case EV_SYN: {
        if (rawEvent->code == SYN_REPORT) {
            mCurrentHidUsage = 0;
        }
    }
    }
}

processKey函数,当keydown,keyup的时候都会去找对应的keycode,scancode,还有down/up time,keyup的时候还要专门在map表里面清除key down的信息。

void KeyboardInputMapper::processKey(nsecs_t when, bool down, int32_t keyCode,
        int32_t scanCode, uint32_t policyFlags) {
        ···
        NotifyKeyArgs args(when, getDeviceId(), mSource, policyFlags,
            down ? AKEY_EVENT_ACTION_DOWN : AKEY_EVENT_ACTION_UP,
            AKEY_EVENT_FLAG_FROM_SYSTEM, keyCode, scanCode, newMetaState, downTime);
    getListener()->notifyKey(&args);
        ···
}

这里就通过getListener到InputDispacter里面notifyKey函数,mPolicy->interceptKeyBeforeQueueing(&event, /byref/ policyFlags);该函数最后会调用到java层的PhoneWindowManagerService函数,之后构造一个KeyEntry的对象,通过enqueueInboundEventLocked将按键事件入队列,入队成功,通过mLooper唤醒等待的InputDispatcher,进行事件分发。

    KeyEvent event;
    event.initialize(args->deviceId, args->source, args->action,
            flags, keyCode, args->scanCode, metaState, 0,
            args->downTime, args->eventTime);

    mPolicy->interceptKeyBeforeQueueing(&event, /*byref*/ policyFlags);
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();
        }

        int32_t repeatCount = 0;
        KeyEntry* newEntry = new KeyEntry(args->eventTime,
                args->deviceId, args->source, policyFlags,
                args->action, flags, keyCode, args->scanCode,
                metaState, repeatCount, args->downTime);

        needWake = enqueueInboundEventLocked(newEntry);
        mLock.unlock();
    } // release lock

    if (needWake) {
        mLooper->wake();
    }

现在看看mQueuedListener.flush是个什么操作,把刚才args队列里面的事件全部通知给InputDispatcher,去到dispatcher里面继续执行。

···
sp<QueuedInputListener> mQueuedListener;
···
//InputListenerInterface 是input用来通知InputListener输入事件的接口
class InputListenerInterface : public virtual RefBase {
protected:
    InputListenerInterface() { }
    virtual ~InputListenerInterface() { }

public:
    virtual void notifyConfigurationChanged(const NotifyConfigurationChangedArgs* args) = 0;
    virtual void notifyKey(const NotifyKeyArgs* args) = 0;
    virtual void notifyMotion(const NotifyMotionArgs* args) = 0;
    virtual void notifySwitch(const NotifySwitchArgs* args) = 0;
    virtual void notifyDeviceReset(const NotifyDeviceResetArgs* args) = 0;
};
···
//QueuedInputListener的构造函数就是新建一个这样的内部listener
QueuedInputListener::QueuedInputListener(const sp<InputListenerInterface>& innerListener) :
        mInnerListener(innerListener) {
}
···
void QueuedInputListener::flush() {
    size_t count = mArgsQueue.size();
    for (size_t i = 0; i < count; i++) {
        //NotifyArgs是所有input事件对象的父类
        NotifyArgs* args = mArgsQueue[i];
        args->notify(mInnerListener);
        delete args;
    }
    mArgsQueue.clear();
}
图片.png
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,616评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,020评论 3 387
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 159,078评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,040评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,154评论 6 385
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,265评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,298评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,072评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,491评论 1 306
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,795评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,970评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,654评论 4 337
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,272评论 3 318
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,985评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,223评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,815评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,852评论 2 351