你需要掌握的事件分发高阶知识

戳蓝字“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结构体,如下图所示。

640?wx_fmt=png

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

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,
                    00, 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, 00);
        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皮查伊“对决”美国国会


640?wx_fmt=png

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

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

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

相关文章

C语言数据类型 / 变量类型 - C语言零基础入门教程

目录 一.C 语言数据类型分类二.C 语言整形 1.无符号整形&#xff08;16 位系统&#xff09;2.有符号整形&#xff08;16 位系统&#xff09;3.int 和 long 区别4.超出取值范围的整形 三.C 语言浮点型 float/double 1.float&#xff08;单精度&#xff09;2.double&#xff08;…

【拨云见日】全面云化时代,如何选择适合自己的“云”?

戳蓝字“CSDN云计算”关注我们哦&#xff01;随着云计算技术的不断成熟&#xff0c;大数据、人工智能、区块链等技术的推陈出新&#xff0c;政府和企业级客户开始逐渐接受云计算。云计算不仅可以帮助政府客户解决运维人员能力不足、人手不足、安全保障等问题&#xff0c;也可以…

PWN题[强网先锋]orw超详细讲解(多解法)

知识点 构造一段shellcode的作用就是为了在缓冲区溢出时将shellcode的地址覆盖正常的返回地址。 \x00 截断符 shellcode里出现\x00就会从其截断&#xff0c;所以构造shellcode的时候要避免\x00 x64函数调用规则 x64机器在调用某个函数前&#xff0c;比如int func(int a, int…

资深程序员参加面试因穿着被认为是新手,拿下帽子后,被当场录取

戳蓝字“CSDN云计算”关注我们哦&#xff01;&#xff08;图自&#xff1a;微博冰原上空的飞燕&#xff09;IT程序猿 微博网友评论&#xff1a;嘉嘉大美人&#xff1a;这个发型&#xff0c;小公司都雇不起NanoNova&#xff1a;格子衫&#xff0c;脱发这些梗还要用多久贝贝呵呵哈…

BugkuCTF-Crypto题这不是md5

描  述: 666c61677b616537333538376261353662616566357d 知识点&#xff1a;16进制到文本字符串 66 6c 61 67 7b 61 65 37 33 35 38 37 62 61 35 36 62 61 65 66 35 7d 点击十六进制转字符&#xff1a;

为什么公司宁愿花25K重新招人,也不愿花20K留住老员工?

戳蓝字“CSDN云计算”关注我们哦&#xff01;身在职场&#xff0c;经常会暗自打听同事工资&#xff0c;尤其是得知身边新入职同事的工资居然比自己高&#xff0c;还高出一大截时&#xff0c;心里自然很不平衡&#xff0c;一心想要离职。 那么&#xff0c;为什么公司宁愿花高价招…

BugkuCTF-MISC题cisco(writeup)

解题准备 本题需要掌握AES加解密&#xff0c;cisco模拟器软件Cisco Packet Tracer 以及cisco交换机基本命令。 解题流程 cisco 描述: 密码是flag。 把cisco.zip解压后&#xff0c;得到2个txt文档。 解题思路 1.txt打开是乱码&#xff0c;先看看2.txt文件。 2.txt打开后得到…

20张图表达程序员的心酸

戳蓝字“CSDN云计算”关注我们哦&#xff01;「1」被老板委派接手刚刚离职同事的项目...「2」当他们要求我测试所有应用功能时「3」准备下班的时候,测试又提bug过來了… 「4」使用新框架却忘记阅读文档「5」测试实习生的代码「6」网络延迟的危害...「7」测试刚写完的代码「8」d…

BugkuCTF-MISC题convert

下载文件 打开文件&#xff0c;发现1和0总个数除以8余一个数 所以删除尾部的0&#xff0c;将1.txt改为1-删除尾部0.txt 八位二进制 对应 两个十六进制 先将其隔开 with open("1-删除尾部0.txt",r) as f:s f.read()for i in range(0,len(s),8):#print(s[i:i8],end&q…

程序员为什么想进大厂,看看年终奖你们就知道了

戳蓝字“CSDN云计算”关注我们哦&#xff01;2018年的最后一个月&#xff0c;一年的辛苦不易除了放假就盼着年终奖了&#xff0c;一直以来被外行人称为“钱多话少”的程序员&#xff0c;到了年底也都能拿到不少的年终奖&#xff0c;毕竟都是996拼出来的&#xff0c;那么国内的互…

Python | 7招教你识别一个网站是否是Django后台

戳蓝字“CSDN云计算”关注我们哦&#xff01;作者 l 上海小胖 来源 l Python专栏&#xff08;ID&#xff1a;xpchuiit&#xff09;转载请联系授权&#xff08;微信ID&#xff1a;pythonzhuanlan&#xff09;目录&#xff1a;1. 利用Debug模式异常页面判断2. 通过CSRF Token验…

BugkuCTF-MISC题Photo的自我

下载附件得到一个压缩包&#xff0c;压缩包里面是个文件夹&#xff0c;文件夹内有一个图片和两个加密的压缩包&#xff0c;先看看这个图片 能隐约发现右下角有信息藏着的&#xff0c;修改图片高度得到图片右下角的信息:B:One2021 图片右下角的信息露出来了&#xff1a;B:One2…

C语言变量声明和定义 - C语言零基础入门教程

目录 1.变量名的开头必须是字母或下划线&#xff0c;不能是数字2.变量名中的字母是区分大小写的3.变量名绝对不可以是 C 语言关键字4.变量名中不能有空格四.猜你喜欢 零基础 C/C 学习路线推荐 : C/C 学习目录 >> C 语言基础入门 **程序猿有一个梗&#xff1a;写代码代码…

我那么拼命,为什么还会被裁掉?

戳蓝字“CSDN云计算”关注我们哦&#xff01;本文转载自「程序人生」&#xff0c;搜索「coder_life」即可关注。我们那么努力&#xff0c;为什么还会被裁掉&#xff1f;昨晚下班经过北京望京地铁站&#xff0c;看到一位姑娘&#xff0c;在换乘通道里的消防栓上&#xff0c;打开…

BugkuCTF-MISC题Pokergame

补充&#xff1a; 用010hex打开zip文件。 把504B0304后的第3、4个byte改成0000 即将0900改为0000 而504B0102后的第5、6个byte不需改成0000即可破解伪加密。 解题 下载&#xff0c;解压 文件有hint.txt,king.jpg,kinglet.jpg,Poke.zip 通过binwalk -e 和foremost分别分离kin…

C语言格式控制符/占位符 - C语言零基础入门教程

目录 1.C 语言格式控制符/占位符2.猜你喜欢 零基础 C/C 学习路线推荐 : C/C 学习目录 >> C 语言基础入门 网络有这么一个段子&#xff1a; 如果说一个男生没对象 —单身狗&#xff1b;如果说一个女生没对象 — 狗不理&#xff1b; 1.C 语言格式控制符/占位符 不管是单…

Elastic:Beyond Search!

戳蓝字“CSDN云计算”关注我们哦&#xff01;作为后端的程序员&#xff0c;如果没有听说过Elastic及其产品Elasticsearch或者ELK&#xff0c;那实在是太Out了&#xff0c;这款分布式的实时搜索和分析引擎自从诞生以来&#xff0c;就受到了众多程序员的青睐&#xff0c;自其第一…

PWN题[强网先锋]no_output

知识点 strcpy(dest, src) strcpy 函数用于将指定长度的字符串复制到字符数组里 语法形式为&#xff1a;char *strcpy(char *dest, const char *src, int n)&#xff0c; 表示把src所指向的字符串里以src地址开始的前n个字节复制到dest所指的数组里&#xff0c;并返回被复制后…

上传自己的项目到github

思路&#xff1a;用秘钥将自己项目和github建立连接&#xff0c;实现上传 实现&#xff1a; 1.准备工具&#xff0c;下git&#xff0c;配置环境变量&#xff0c; 桌面右击出现 就ok&#xff0c;然后 cmd 输入git --version 说明安装和配置成功 2.qq邮箱和qq密码注册github&…

漫画:如何实现大整数相乘?(上)

戳蓝字“CSDN云计算”关注我们哦&#xff01;前一段时间&#xff0c;小灰发布了一篇有关大整数相加的漫画&#xff0c;没看过的小伙伴可以先看一看&#xff1a;漫画&#xff1a;如何实现大整数相加&#xff1f;那么&#xff0c;大整数相乘又是如何实现的呢&#xff1f;起初&…