戳蓝字“CSDN云计算”关注我们哦!

系列前作

1. Android输入系统的事件传递流程和IMS的诞生

2. 只了解View的事件分发是不够的,来看下输入系统对事件的处理

1.InputReader的加工类型

在只了解View的事件分发是不够的,来看下输入系统对事件的处理这篇文章中,我们知道InputReader会对原始输入事件进行加工,如果事件的类型为按键类型的事件,就会调用如下一段代码。
frameworks/native/services/inputflinger/InputDispatcher.cpp

void InputDispatcher::notifyKey(const NotifyKeyArgs* args) {  ...    bool needWake;    {     ...    } // release lock    if (needWake) {        mLooper->wake();    }}

InputDispatcher的notifyKey方法用于唤醒InputDispatcherThread,它的参数NotifyKeyArgs是InputReader对按键类型的事件加工后得到的。
frameworks/native/services/inputflinger/InputListener.h

struct NotifyKeyArgs : public NotifyArgs {    nsecs_t eventTime;    int32_t deviceId;    uint32_t source;    uint32_t policyFlags;    int32_t action;    int32_t flags;    int32_t keyCode;    int32_t scanCode;    int32_t metaState;    nsecs_t downTime;    inline NotifyKeyArgs() { }    NotifyKeyArgs(nsecs_t eventTime, int32_t deviceId, uint32_t source, uint32_t policyFlags,            int32_t action, int32_t flags, int32_t keyCode, int32_t scanCode,            int32_t metaState, nsecs_t downTime);    NotifyKeyArgs(const NotifyKeyArgs& other);    virtual ~NotifyKeyArgs() { }    virtual void notify(const sp<InputListenerInterface>& listener) const;};

可以看到,NotifyKeyArgs结构体继承自NotifyArgs结构体,如下图所示。


NotifyArgs有三个子类,分别是NotifyKeyArgs、NotifyMotionArgs和NotifySwichArgs,这说明InputReader对原始输入事件加工后,最终会得出三种事件类型,分别是key事件、Motion事件和Swich事件,这些事件会交由InputDispatcher来进行分发,如下图所示。


2.InputDispatcher的分发过程

不同的事件类型有着不同的分发过程,其中Swich事件的处理是没有派发过程的,在InputDispatcher的notifySwitch函数中会将Swich事件交由InputDispatcherPolicy来处理。本系列文章一直讲解key事件相关,这次换一下,以Motion事件的分发过程来进行举例,对key事件分发事件有兴趣的可以自行去看源码,本质上都差不多。

2.1 唤醒InputDispatcherThread

InputDispatcher的notifyMotion函数用来唤醒InputDispatcherThread。
frameworks/native/services/inputflinger/InputDispatcher.cpp

void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {#if DEBUG_INBOUND_EVENT_DETAILS...#endif    //检查Motion事件的参数是否有效    if (!validateMotionEvent(args->action, args->actionButton,                args->pointerCount, args->pointerProperties)) {//1        return;    }    uint32_t policyFlags = args->policyFlags;    policyFlags |= POLICY_FLAG_TRUSTED;    mPolicy->interceptMotionBeforeQueueing(args->eventTime, /*byref*/ policyFlags);    bool needWake;    { // acquire lock        mLock.lock();        //Motion事件是否需要交由InputFilter过滤        if (shouldSendMotionToInputFilterLocked(args)) {//2            mLock.unlock();            MotionEvent event;            //初始化MotionEvent,将NotifyMotionArgs中的参数信息赋值给MotionEvent中的参数            event.initialize(args->deviceId, args->source, args->action, args->actionButton,                    args->flags, args->edgeFlags, args->metaState, args->buttonState,                    0, 0, args->xPrecision, args->yPrecision,                    args->downTime, args->eventTime,                    args->pointerCount, args->pointerProperties, args->pointerCoords);           //表示已经过滤了            policyFlags |= POLICY_FLAG_FILTERED;            //开始过滤,如果返回值为false,就会直接return,这次事件不再进行分发            if (!mPolicy->filterInputEvent(&event, policyFlags)) {//3                return; // event was consumed by the filter            }            mLock.lock();        }        /**        * 4         */        MotionEntry* newEntry = new MotionEntry(args->eventTime,                args->deviceId, args->source, policyFlags,                args->action, args->actionButton, args->flags,                args->metaState, args->buttonState,                args->edgeFlags, args->xPrecision, args->yPrecision, args->downTime,                args->displayId,                args->pointerCount, args->pointerProperties, args->pointerCoords, 0, 0);        needWake = enqueueInboundEventLocked(newEntry);//5        mLock.unlock();    } // release lock    if (needWake) {        mLooper->wake();//6    }}

注释1处用于检查Motion事件的参数是否有效,其内部会检查触控点的数量pointerCount是否在合理范围内(小于1或者大于16都是不合理的),以及触控点的ID是否在合理范围内(小于0或者大于31都是不合理的)。
注释2处如果Motion事件需要交由InputFilter过滤,就会初始化MotionEvent,其作用就是用NotifyMotionArgs中的事件参数信息构造一个MotionEvent,接着MotionEven会交给注释3处的方法进行过滤,如果返回值为false,这次Motion事件就会被忽略掉。
注释4处,用NotifyMotionArgs中的事件参数信息构造一个MotionEntry对象。注释5处将MotionEntry传入到enqueueInboundEventLocked函数中,其内部会将MotionEntry添加到InputDispatcher的mInboundQueue队列的末尾,并返回一个值needWake,代表InputDispatcherThread是否需要唤醒,如果需要唤醒就调用注释6处的代码来唤醒InputDispatcherThread。

2.2 InputDispatcher进行分发

InputDispatcherThread被唤醒后,会执行InputDispatcherThread的threadLoop函数:
frameworks/native/services/inputflinger/InputDispatcher.cpp

bool InputDispatcherThread::threadLoop() {    mDispatcher->dispatchOnce();    return true;}

threadLoop函数中只调用了InputDispatcher的dispatchOnce函数:
frameworks/native/services/inputflinger/InputDispatcher.cpp

void InputDispatcher::dispatchOnce() {    nsecs_t nextWakeupTime = LONG_LONG_MAX;    { // acquire lock        AutoMutex _l(mLock);        mDispatcherIsAliveCondition.broadcast();        if (!haveCommandsLocked()) {//1            dispatchOnceInnerLocked(&nextWakeupTime);//2        }        if (runCommandsLockedInterruptible()) {            nextWakeupTime = LONG_LONG_MIN;        }    } // release lock    nsecs_t currentTime = now();//3    int timeoutMillis = toMillisecondTimeoutDelay(currentTime, nextWakeupTime);//4    mLooper->pollOnce(timeoutMillis);}

注释1处用于检查InputDispatcher的缓存队列中是否有等待处理的命令,如果没有就会执行注释2处的dispatchOnceInnerLocked函数,用来将输入事件分发给合适的。注释3处获取当前的时间,结合注释4处,得出InputDispatcherThread需要睡眠的时间为timeoutMillis。最后调用Looper的pollOnce函数使InputDispatcherThread进入睡眠状态,并将它的最长的睡眠的时间设置为timeoutMillis。当有输入事件产生时,InputReader就会将睡眠状态的InputDispatcherThread
唤醒,InputDispatcher会重新开始分发输入事件。查看注释2处的dispatchOnceInnerLocked函数是如何进行事件分发的。
frameworks/native/services/inputflinger/InputDispatcher.cpp

void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {    ...    // 如果InputDispatcher被冻结,则不进行派发操作    if (mDispatchFrozen) {#if DEBUG_FOCUS        ALOGD("Dispatch frozen.  Waiting some more.");#endif        return;    }    //如果isAppSwitchDue为true,说明没有及时响应HOME键等操作   bool isAppSwitchDue = mAppSwitchDueTime <= currentTime;//1    if (mAppSwitchDueTime < *nextWakeupTime) {//2        *nextWakeupTime = mAppSwitchDueTime;    }   //如果还没有待分发的事件,去mInboundQueue中取出一个事件    if (! mPendingEvent) {        //如果mInboundQueue为空,并且没有待分发的事件,就return        if (mInboundQueue.isEmpty()) {            ...            if (!mPendingEvent) {                return;            }        } else {            //如果mInboundQueue不为空,取队列头部的EventEntry赋值给mPendingEvent             mPendingEvent = mInboundQueue.dequeueAtHead();            traceInboundQueueLengthLocked();        }        if (mPendingEvent->policyFlags & POLICY_FLAG_PASS_TO_USER) {            pokeUserActivityLocked(mPendingEvent);        }        resetANRTimeoutsLocked();    }    ALOG_ASSERT(mPendingEvent != NULL);    bool done = false;    DropReason dropReason = DROP_REASON_NOT_DROPPED;//3   ...    switch (mPendingEvent->type) {//4    ...    case EventEntry::TYPE_MOTION: {        MotionEntry* typedEntry = static_cast<MotionEntry*>(mPendingEvent);        //如果没有及时响应窗口切换操作        if (dropReason == DROP_REASON_NOT_DROPPED && isAppSwitchDue) {            dropReason = DROP_REASON_APP_SWITCH;        }        //事件过期        if (dropReason == DROP_REASON_NOT_DROPPED                && isStaleEventLocked(currentTime, typedEntry)) {            dropReason = DROP_REASON_STALE;        }        //阻碍其他窗口获取事件        if (dropReason == DROP_REASON_NOT_DROPPED && mNextUnblockedEvent) {            dropReason = DROP_REASON_BLOCKED;        }        done = dispatchMotionLocked(currentTime, typedEntry,                &dropReason, nextWakeupTime);//5        break;    }    default:        ALOG_ASSERT(false);        break;    }

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

InputDispatcher的dispatchOnceInnerLocked函数的代码比较长,这里截取了和Motion事件的分发相关的主要源码。主要做了以下几件事。

  1. InputDispatcher的冻结处理 
    如果当前InputDispatcher被冻结,则不进行派发操作,InputDispatcher有三种状态,分别是正常状态、冻结状态和禁用状态,可以通过InputDispatcher的setInputDispatchMode函数来设置。

  2. 窗口切换操作处理
    注释1处的mAppSwitchDueTime ,代表了App最近发生窗口切换操作时(比如按下Home键、挂断电话),该操作事件最迟的分发时间。如果这个时候,mAppSwitchDueTime小于等于当前系统时间,说明没有及时响应窗口切换操作,则isAppSwitchDue的值设置为true。
    注释2处,如果mAppSwitchDueTime小于nextWakeupTime(下一次InputDispatcher醒来的时间),就将mAppSwitchDueTime赋值给nextWakeupTime,这样当InputDispatcher处理完分发事件后,会第一时间处理窗口切换操作。

  3. 取出事件
    如果没有待分发的事件,就从mInboundQueue中取出一个事件,如果mInboundQueue为空,并且没有待分发的事件,就return,如果mInboundQueue不为空,取队列头部的EventEntry赋值给mPendingEvent,mPendingEvent的类型为EventEntry对象指针。

  4. 事件丢弃
    注释3处的dropReason代表了事件丢弃的原因,它的默认值为DROP_REASON_NOT_DROPPED,代表事件不被丢弃。
    注释4处根据mPendingEvent的type做区分处理,这里主要截取了对Motion类型的处理。经过过滤,会调用注释5处的dispatchMotionLocked函数为这个事件寻找合适的窗口。

  5. 后续处理
    如果注释5处的事件分发成功,则会在注释6处调用releasePendingEventLocked函数,其内部会将mPendingEvent的值设置为Null,并将mPendingEvent指向的对象内存释放掉。注释7处将nextWakeupTime的值设置为LONG_LONG_MIN,这是为了让InputDispatcher能够快速处理下一个分发事件。

后记

本文讲解了InputReader的加工类型和InputDispatcher的分发过程,由于文章篇幅的原因,InputDispatcher的分发过程还有一部分没有讲解,这一部分就是事件分发到目标窗口的过程,会在本系列的下一篇文章进行讲解。

文章转自公众号:刘舒望

— — — END — — —

1.微信群:

添加小编微信:color_ld,备注“进群+姓名+公司职位”即可,加入【云计算学习交流群】,和志同道合的朋友们共同打卡学习!

2.征稿:

投稿邮箱:liudan@csdn.net;微信号:color_ld。请备注投稿+姓名+公司职位。

推荐阅读

  • 程序员加班很严重吗?看看国外程序员怎么怼老板!

  • 锤子变天?| 畅言

  • 趣店斗鱼深陷裁员风波,程序员寒冬何去何从?| 畅言

  • 玩过音乐, "推过"嫩模, 以太坊大神人设崩塌, 有钱任性也抵不过区块链寒冬

  • 【BDTC 2018】PingCAP申砾:做一个真正通用的数据库产品

  • 谷歌搜索重返中国按下暂停键,CEO皮查伊“对决”美国国会

↓点击“阅读原文”,打开APP 阅读更顺畅

你需要掌握的事件分发高阶知识相关推荐

  1. 学习笔记:MySQL高阶知识体系(下)——索引、锁、日志、隔离级别与MVCC

    转载自https://www.ydlclass.com/doc21xnv/database/mysqladvance/mysqlAdvance2.html MySQL高阶知识体系(下) 6. 索引 6 ...

  2. 【高阶知识】用户态协议栈之Epoll实现原理

    Epoll 是 Linux IO 多路复用的管理机制.作为现在 Linux 平台高性能网络 IO 必要的组件.内核的实现可以参照:fs/eventpoll.c . 为什么需要自己实现 epoll 呢? ...

  3. 视频教程-2019 react入门至高阶实战,含react hooks-ReactJS

    2019 react入门至高阶实战,含react hooks 从事前端开发近5年时间,曾任职于丽珠集团等大型企业担任高级前端开发工程师职位,积累了很多大厂的前端开发经验. 目前处于创业期,正在筹备自己 ...

  4. js:如何监听history的pushState方法和replaceState方法。(高阶函数封装+自定义事件)

    出现原因: 想要监听路由变化就需要监听history的pushState和replaceState事件,但是原生并没有支持,此时,我们就得自己添加事件监听. 解决方法: 高阶函数封装自定义事件: co ...

  5. 吴秀波事件女主,被耽误的高阶玩家,一年12个月烧钱游遍全世界

    https://www.toutiao.com/a6650316699868332548/?tt_from=weixin&utm_campaign=client_share&wxsha ...

  6. Android工作经验6年,Android事件分发机制收藏这一篇就够了,分享PDF高清版

    前言 首先介绍一下自己,计算机水本,考研与我无缘.之前在帝都某公司算法部实习,公司算大公司吧,然而个人爱好偏开发,大二的时候写个一个app,主要是用各种框架. 饿了么Android岗一面 1:双亲委托 ...

  7. React系列---Redux高阶运用

    参考资料:<深入React技术栈> 高阶reducer 高阶函数是指将函数作为参数或返回值的函数,高阶reducer就是指将reducer作为参数或返回值的函数. 在Redux架构中,re ...

  8. 箭头函数的this指向谁_高阶函数

    NodeJS 系列文章,本篇是第一篇,首先,预计将后续高频使用逻辑串一遍,依次是高阶函数,promise以及事件机制.本篇主要是高阶函数. call.bind.apply call.apply 都是改 ...

  9. Android开发指南!Android事件分发机制收藏这一篇就够了,2年以上经验必看

    前言 现在的终端开发已经开始进入稳定期,在这个阶段大厂压力很大小厂更会收到挤压,人们使用的App越来越固定,即使是大厂,几年前平台级应用发个Push就能引流几十万,现在这些的作用也越来越小,特别到了今 ...

最新文章

  1. str_repeat() 函数
  2. 【java8】中stream的.findAny().orElse (null) 是什么意思?
  3. 高颜值的神经网络可视化工具:3D、彩色、可定制,还能可视化参数重要性 | 开源...
  4. mysql对所有列的数据进行修改6_MySQL的SQL语句 - 数据定义语句(6)- ALTER TABLE 语句 (3)...
  5. Java的系统Property
  6. python提供两个对象身份比较操作符_标准类型对象比较操作符
  7. windows服务定时重启软件的实现
  8. 华为HG8347R光猫 4台设备连接限制破解全过程
  9. Makefile 编写规则
  10. Nodejs ORM Prisma 介绍
  11. PDF页眉页脚删除用什么方法
  12. 【C语言】预处理的深入理解(第一期)
  13. 我 Spring Boot 贼 6,还有必要学 SpringMVC 么?
  14. MySQL中的max_connections和max_user_connections 及 MySQL服务器最大连接数的合理设置
  15. Win7/Win10双系统安装方法图文教程
  16. viper4android 系统io错误,golang配置信息库viper的使用
  17. 面试复试重点 算法与数据结构
  18. css grid 自动高度_10分钟理解CSS3 Grid布局
  19. JavaScript 获取上传文件的本地绝对路径
  20. 基于GBT28181:SIP协议组件开发-----------第五篇SIP注册流程eXosip2实现(二)

热门文章

  1. html中如何在标题中加样式,html title属性 样式
  2. python 知乎 合并 pdf_一键下载:将知乎专栏导出成电子书
  3. 做手游的计算机配置要求,原神pc配置要求高吗 最低什么配置能流畅运行​
  4. 知名教授:希望论文一作发Nature后去当公务员的那名学生能看到我的这篇文章...
  5. 公式之美:打通复杂思维的任督二脉
  6. 蚂蚁上市P8身价超亿,丢给我这几个牛逼的公众号
  7. 中科院等发布《2019研究前沿》
  8. 搜狐新闻推荐算法原理 | “呈现给你的,都是你所关心的”
  9. 真正聪明的人从来不自己做PPT,看完这篇就放假吧!
  10. 毕业大论文到底怎么写?