[Android] 输入系统(二)

在上一篇文章的最后,我们发现InputDispatcher是调用了InputChannel->sendMessage把键值发送出去,那么相应的,也有接收键值的地方。接收函数是InputChannel->receiveMessage。

在InputConsumer::consume内找到了receiveMessage,从类名能看出来发送端与接收端相当于生产者与消费者的关系。

status_t InputConsumer::consume(InputEventFactoryInterface* factory,bool consumeBatches, nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {// Receive a fresh message.status_t result = mChannel->receiveMessage(&mMsg);
}

 

receiveMessage内调用的是socket的接收函数recv

status_t InputChannel::receiveMessage(InputMessage* msg) {do {nRead = ::recv(mFd, msg, sizeof(InputMessage), MSG_DONTWAIT);} while (nRead == -1 && errno == EINTR);
}

 

 

事件接收端NativeInputEventReceiver

那么究竟是谁来消费这些事件呢,我们在NativeInputEventReceiver里面找到了答案。

在NativeInputEventReceiver内有个事件处理函数handleEvent,该函数是looperCallback的虚函数,NativeInputEventReceiver作为looperCallback的子类,自然有义务实现handleEvent这个函数。handleEvent就可以监听I/O事件。一旦有I/O事件,如上述的socket send事件,handleEvent就会被启动,进行后续的处理。

int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {status_t status = consumeEvents(env, false /*consumeBatches*/, -1, NULL);
}

 

既然有LooperCallback(NativeInputEventReceiver),必然会有Looper。虽然Looper不是本篇文章的研究对象,但是我们有必要理清下面的问题:

  • 究竟与NativeInputEventReceiver对应的这个Looper是什么?
  • 这个Looper是怎样与LooperCallback关联起来的呢?

   

实际上,一切起始于ViewRootImpl的setView方法:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
...//在这里传入了当前线程的Loopernew WindowInputEventReceiver(mInputChannel, Looper.myLooper());
...
}

 

InputEventReceiver作为WindowInputEventReceiver的子类,会一起被创建出来。在InputEventReceiver的构造方法中,会调用native方法nativeInit

public InputEventReceiver(InputChannel inputChannel, Looper looper) {mInputChannel = inputChannel;mMessageQueue = looper.getQueue();mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),inputChannel, mMessageQueue);
}

 

在NativeInputEventReceiver的nativeInit方法中,创建了NativeInputEventReceiver对象,并调用它的initialize方法

static jint nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak,jobject inputChannelObj, jobject messageQueueObj) {...sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,receiverWeak, inputChannel, messageQueue);status_t status = receiver->initialize();...
}

 

initialize方法只做了一件事,就是把NativeInputEventReceiver与Looper关联起来

status_t NativeInputEventReceiver::initialize() {setFdEvents(ALOOPER_EVENT_INPUT);return OK;
}void NativeInputEventReceiver::setFdEvents(int events) {if (mFdEvents != events) {mFdEvents = events;int fd = mInputConsumer.getChannel()->getFd();if (events) {mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL);} else {mMessageQueue->getLooper()->removeFd(fd);}}
}

Looper的方法addFd实现了关联Looper与LooperCallback(NativeInputEventReceiver)的功能,我们先来分析一下传给addFd的参数

  • fd,fd即inputChannel的socket fd,Looper会侦测该fd的状态
  • events,即传入的ALOOPER_EVENT_INPUT,只有fd的状态是INPUT的时候才会触发调用LooperCallback中的handleEvent方法
  • this,即NativeInputEventReceiver,当fd状态为Input时,NativeInputEventReceiver中的handleEvent方法会被调用

 

 

在consumeEvents内,我们能看到调用了InputConsume::consume来接收InputDispatcher发送过来的事件

status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {for (;;) {status_t status = mInputConsumer.consume(&mInputEventFactory,consumeBatches, frameTime, &seq, &inputEvent);}
}

 

 

输入事件在consumeEvents内将会被处理完成,其中包含了四个主要步骤:

  1. 获取输入事件
  2. 把输入事件转换成java也能处理的格式
  3. 输入事件分发到相应窗口去处理
  4. 处理结果反馈

 

 

1. 获取输入事件已在上面阐述过

 

2. 输入事件转换

以Key为例,输入事件只是把事件内部的成员拆分,然后通过JNI调用java的构造函数来生成相应的java event对象,后面的事件处理都在java层

            jobject inputEventObj;switch (inputEvent->getType()) {case AINPUT_EVENT_TYPE_KEY:inputEventObj = android_view_KeyEvent_fromNative(env,static_cast<KeyEvent*>(inputEvent));break;// ----------------------------------------------------------------------------jobject android_view_KeyEvent_fromNative(JNIEnv* env, const KeyEvent* event) {jobject eventObj = env->CallStaticObjectMethod(gKeyEventClassInfo.clazz,gKeyEventClassInfo.obtain,nanoseconds_to_milliseconds(event->getDownTime()),nanoseconds_to_milliseconds(event->getEventTime()),event->getAction(),event->getKeyCode(),event->getRepeatCount(),event->getMetaState(),event->getDeviceId(),event->getScanCode(),event->getFlags(),event->getSource(),NULL);if (env->ExceptionCheck()) {ALOGE("An exception occurred while obtaining a key event.");LOGE_EX(env);env->ExceptionClear();return NULL;}return eventObj;
}public static KeyEvent obtain(long downTime, long eventTime, int action,int code, int repeat, int metaState,int deviceId, int scancode, int flags, int source, String characters) {KeyEvent ev = obtain();ev.mDownTime = downTime;ev.mEventTime = eventTime;ev.mAction = action;ev.mKeyCode = code;ev.mRepeatCount = repeat;ev.mMetaState = metaState;ev.mDeviceId = deviceId;ev.mScanCode = scancode;ev.mFlags = flags;ev.mSource = source;ev.mCharacters = characters;return ev;}

 

 

 

3.输入事件分发

这里是在java层的事件分发,最终目的是为了调用到窗口的onTouch这类回调函数。

                env->CallVoidMethod(receiverObj.get(),gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);

 

 

还记得上面InputEventReceiver初始化时的流程吗?是通过setView--->new WindowInputEventReceiver--->new InputEventReceiver--->new NativeInputEventReceiver这样一步一步创建的。

通过上述的JNI调用,会调用到WindowInputEventReceiver的dispatchInputEvent方法,不过由于WindowInputEventReceiver并没有自己实现这个方法,因此会调用父类InputEventReceiver::dispatchInputEvent,内部会真正调用到WindowInputEventReceiver::onInputEvent

    public void dispatchInputEvent(InputEvent event) {onInputEvent(event);}

 

在onInputEvent内,转到了ViewRootImpl这边进行处理

public void onInputEvent(InputEvent event) {enqueueInputEvent(event, this, 0, true);
}void enqueueInputEvent(InputEvent event,InputEventReceiver receiver, int flags, boolean processImmediately) {doProcessInputEvents();
}

 

由于事件队列内会包含多个事件,因此在doProcessInputEvent时,需要分别对所有的事件都进行分发

    void doProcessInputEvents() {// Deliver all pending input events in the queue.while (mPendingInputEventHead != null) {QueuedInputEvent q = mPendingInputEventHead;mPendingInputEventHead = q.mNext;if (mPendingInputEventHead == null) {mPendingInputEventTail = null;}q.mNext = null;mPendingInputEventCount -= 1;deliverInputEvent(q);}}

 

deliverInputEvent会调用到InputState的deliver方法

        public final void deliver(QueuedInputEvent q) {if ((q.mFlags & QueuedInputEvent.FLAG_FINISHED) != 0) {forward(q);} else if (shouldDropInputEvent(q)) {finish(q, false);} else {apply(q, onProcess(q));}}

由于一开始我们的事件还没有完成,因此不会带上FLAG_FINISHED,而且我们的事件时一般事件,并不会被丢弃,因此会走apply分支。

 

 

首先会调用onProcess处理事件

        protected int onProcess(QueuedInputEvent q) {if (q.mEvent instanceof KeyEvent) {return processKeyEvent(q);} else {// If delivering a new non-key event, make sure the window is// now allowed to start updating.handleDispatchDoneAnimating();final int source = q.mEvent.getSource();if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {return processPointerEvent(q);} else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {return processTrackballEvent(q);} else {return processGenericMotionEvent(q);}}}

 

 

以Key为例,我们会调用到processKeyEvent

        private int processKeyEvent(QueuedInputEvent q) {// Deliver the key to the view hierarchy.if (mView.dispatchKeyEvent(event)) {return FINISH_HANDLED;}}

 

然后调用了View类的dispatchKeyEvent方法,最终会调用到onKey这个回调函数

    public boolean dispatchKeyEvent(KeyEvent event) {// Give any attached key listener a first crack at the event.//noinspection SimplifiableIfStatementListenerInfo li = mListenerInfo;if (li != null && li.mOnKeyListener != null && (mViewFlags & ENABLED_MASK) == ENABLED&& li.mOnKeyListener.onKey(this, event.getKeyCode(), event)) {return true;}}

 

 

4. 处理结果反馈

然后还剩下apply这个方法需要分析。如果onProcess正常处理完成后,会返回FINISH_HANDLED,否则返回FINISHED_NOT_NHANDLED。

        protected void apply(QueuedInputEvent q, int result) {if (result == FORWARD) {forward(q);} else if (result == FINISH_HANDLED) {finish(q, true);} else if (result == FINISH_NOT_HANDLED) {finish(q, false);} else {throw new IllegalArgumentException("Invalid result: " + result);}}protected void finish(QueuedInputEvent q, boolean handled) {q.mFlags |= QueuedInputEvent.FLAG_FINISHED;if (handled) {q.mFlags |= QueuedInputEvent.FLAG_FINISHED_HANDLED;}forward(q);}protected void forward(QueuedInputEvent q) {onDeliverToNext(q);}protected void onDeliverToNext(QueuedInputEvent q) {if (mNext != null) {mNext.deliver(q);} else {finishInputEvent(q);}}private void finishInputEvent(QueuedInputEvent q) {if (q.mReceiver != null) {boolean handled = (q.mFlags & QueuedInputEvent.FLAG_FINISHED_HANDLED) != 0;q.mReceiver.finishInputEvent(q.mEvent, handled);} else {q.mEvent.recycleIfNeededAfterDispatch();}recycleQueuedInputEvent(q);}

     

 

 

mReceiver.finishInputEvent就是NativeInputEvent的finishInputEvent

status_t NativeInputEventReceiver::finishInputEvent(uint32_t seq, bool handled) {status_t status = mInputConsumer.sendFinishedSignal(seq, handled);
}status_t InputConsumer::sendFinishedSignal(uint32_t seq, bool handled) {while (!status && chainIndex-- > 0) {status = sendUnchainedFinishedSignal(chainSeqs[chainIndex], handled);}
}status_t InputConsumer::sendUnchainedFinishedSignal(uint32_t seq, bool handled) {InputMessage msg;msg.header.type = InputMessage::TYPE_FINISHED;msg.body.finished.seq = seq;msg.body.finished.handled = handled;return mChannel->sendMessage(&msg);
}

最后也是调用sendMessage把消息反馈给InputDispatcher。

到这里,上层的处理已经完成,接下来就是InputDispatcher的反馈处理。

 

 

InputDispatcher反馈处理

反馈处理在handleReceiveCallback中进行,其中包含两个部分:

  1. 接收反馈消息
  2. 处理反馈消息
int InputDispatcher::handleReceiveCallback(int fd, int events, void* data) {for (;;) {uint32_t seq;bool handled;status = connection->inputPublisher.receiveFinishedSignal(&seq, &handled);if (status) {break;}d->finishDispatchCycleLocked(currentTime, connection, seq, handled);gotOne = true;}
}

 

 

1. 接收反馈消息

接收反馈消息是调用的inputPublisher的receiveFinishedSignal方法,内部还是调用了mChannel->receiveMessage

status_t InputPublisher::receiveFinishedSignal(uint32_t* outSeq, bool* outHandled) {status_t result = mChannel->receiveMessage(&msg);}

 

 

2. 处理反馈消息

处理反馈消息是调用了finishDispatchCycleLocked。

void InputDispatcher::finishDispatchCycleLocked(nsecs_t currentTime,const sp<Connection>& connection, uint32_t seq, bool handled) {// Notify other system components and prepare to start the next dispatch cycle.onDispatchCycleFinishedLocked(currentTime, connection, seq, handled);
}

 

void InputDispatcher::onDispatchCycleFinishedLocked(nsecs_t currentTime, const sp<Connection>& connection, uint32_t seq, bool handled) {CommandEntry* commandEntry = postCommandLocked(& InputDispatcher::doDispatchCycleFinishedLockedInterruptible);}

 

 

postCommandLocked其实也是发送消息给InputDispatcherThread,那么在分发线程下一次处理消息的时候会首先处理doDispatchCycleFinishedLockedInterruptible。

doDispatchCycleFinishedLockedInterruptible是实际上反馈进行处理的地方,其中包含了下面几个处理步骤:

  1. 从waitQueue中取出所反馈的事件
  2. 事件是否处理超时,如果是则做超时处理
  3. 从waitQueue中删除所反馈的事件
  4. 立刻展开下一次的outboundQueue事件监听

 

void InputDispatcher::doDispatchCycleFinishedLockedInterruptible(CommandEntry* commandEntry) {// Handle post-event policy actions.DispatchEntry* dispatchEntry = connection->findWaitQueueEntry(seq);if (eventDuration > SLOW_EVENT_PROCESSING_WARNING_TIMEOUT) {String8 msg;msg.appendFormat("Window '%s' spent %0.1fms processing the last input event: ",connection->getWindowName(), eventDuration * 0.000001f);dispatchEntry->eventEntry->appendDescription(msg);ALOGI("%s", msg.string());}if (dispatchEntry == connection->findWaitQueueEntry(seq)) {connection->waitQueue.dequeue(dispatchEntry);}// Start the next dispatch cycle for this connection.startDispatchCycleLocked(now(), connection);}
}

 

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

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

相关文章

TCP协议和套接字

一、TCP通信概述&#xff0c;逻辑连接就是三次握手 二、客户端和服务端实现TCP协议通信基本步骤 1、客户端套接字对象 Socket 2、服务端套接字ServerSocket 客户端补充完整代码&#xff1a;除了创建各自的Socket对象有关代码&#xff0c;其他代码一样&#xff0c;就输出流的输出…

齐博V7仿爱丽图库模板(含齐博图库V1.0模板)

齐博模板图片模板 本齐博模板包含两个版本&#xff0c;分别适用于齐博V7整站的图片模型模板和齐博图库程序V1版。 1、图片独立频道页设置 整站后台添加独立频道页面名称&#xff1a;图片专题 网页头部模板&#xff1a;************************ 网页中间模板&#xff1a;******…

信息安全官谁:逼近的挑战,你准备好了吗?

作为一个首席信息安全官&#xff08;CISO&#xff09;并不easy&#xff0c;一方面要时刻面对董事会提出的难题。还有一方面在处理解决公司的安全威胁时&#xff0c;又不能超出预算范围。然而首席信息安全官的资源保持不变时&#xff0c;威胁环境本身却是千变万化、日新月异。因…

【hash】Seek the Name, Seek the Fame

【哈希和哈希表】Seek the Name, Seek the Fame 题目描述 The little cat is so famous, that many couples tramp over hill and dale to Byteland, and asked the little cat to give names to their newly-born babies. They seek the name, and at the same time seek the …

WAP开发笔记(1)-.net移动页面中html控件不能直接显示的解决

最近这几天做了一些.NET移动控件的应用开发&#xff0c;与普通的asp.net页面比起来还是有点差别的。在.net移动页面中是不能直接使用普通的html控件的&#xff0c;这样给开发带来许多的不方便&#xff0c;因为感觉.net移动控件有很多功能都不能实现&#xff08;也可能是我不太熟…

BufferedInputStream学习笔记

【本文转载于http://icanfly.iteye.com/blog/1207397】 BufferedInputStream是一个带有缓冲区域的InputStream,它的继承体系如下&#xff1a; InputStream |__FilterInputStream |__BufferedInputStream首先了解一下FilterInputStream&#xff1a; FilterInputStrea…

文件上传案例——客户端和服务端套接字

一、文件上传原理 文件上传下载就是反复的输入流和输出流的read和wirte方法&#xff08;反复的内存和硬盘的交互&#xff09;&#xff1b; 二、实现 1、客户端实现&#xff1a; 2、服务端实现&#xff1a; 3、解决客户端和服务端两个程序在完成上传下载之后没有停止 原因是whil…

大家一起来博皮——2:液态布局和固态布局,页面框架篇

大家一起来博皮虽然博客园的皮肤很多&#xff0c;而且很漂亮。但是那些自己想更“个性化”自己博客皮肤的朋友&#xff0c;对博客园的皮肤模板还是颇多不满&#xff0c;认为皮肤的结构过于混乱&#xff0c;css样式难以掌控。针对这种情况&#xff0c;博客园开发团队在2007年底&…

Linux配置 DNS and BIND服务配置详解--缓存服务器配置 正反向解析配置

一、DNS简介一、DNS简介 DNS是计算机域名系统 (Domain Name System 或Domain Name Service) 的缩写&#xff0c;它是由域名解析器和域名服务器组成的。域名服务器是指保存有该网络中所有主机的域名和对应IP地址&#xff0c;并具有将域名转换为IP地址功能的服务器。其中域名必…

我的博客网站开发6——博文关键字搜索

在页面中&#xff0c;用户可以通过关键字的搜索功能搜索博文。可以实现类似百度和Google的页面搜索功能&#xff0c;可实现多个关键字的搜索。搜索后&#xff0c;在搜索的结果中有关键字的高亮度的提示如&#xff1a; 在搜索的结果页面&#xff0c;模仿Google的搜索页面的快照功…

shell 函数定义和调用

为什么80%的码农都做不了架构师&#xff1f;>>> 一. 函数定义 语法&#xff1a; [function] functionname[()]{action;[return int;] } 说明&#xff1a; 1、可以带function fun() 定义&#xff0c;也可以直接fun() 定义,不带任何参数。 2、参数返回&#xff0c;可…

Nhibernate代码生成器v2.1中文版

Nhibernate代码生成器v2.1中文版(转发)下载转载于:https://www.cnblogs.com/hakuci/archive/2008/03/15/1106802.html

Head First设计模式读书笔记——策略模式

问题描述&#xff1a; 目前的任务是实现一个FPS类游戏的各种角色&#xff08;友军、敌军、平民和狗、猫、鸭子等动物&#xff09;以及他们的各种行为&#xff08;攻击、游泳等&#xff09;。 设计方案一 很简单&#xff0c;只要实现一个角色超类&#xff0c;将角色的各种行为放…

centos+bond+bridge+docker(ssh容器)固定ip实现测试环境(一)

硬件&#xff1a;R730交换机&#xff1a;H3C Switch S5120-28P-SI系统&#xff1a;centos7#nmtuihttp://568273240.blog.51cto.com/802.3ad为LACP模式交换机部分&#xff1a;# systemctl restart network可以多重启几遍试下。http://568273240.blog.51cto.com/注意&#xff1a;…

简单线性回归算法

为什么80%的码农都做不了架构师&#xff1f;>>> /*** 简单线性回归算法* param array y轴数据* param array x轴数据* returns array(slope,intercept,r2)*/ function linearRegression(y, x) {var lr {};var n y.length;var sum_x 0;var sum_y 0;var sum_xy …

模拟BS服务器

一、模拟BS服务器分析 二、BS模拟服务器代码实现 图片都是单独请求&#xff0c;后台单独线程&#xff0c;这边是通过构造方法传入的Runable接口的实现类匿名对象创建线程&#xff1b; 创建本地输入流读取到网络输出流传过来的信息再放到网络输出流中返回&#xff1b; 转载于:ht…

不要62

题目 试题描述杭州人称那些傻乎乎粘嗒嗒的人为 62&#xff08;音&#xff1a;laoer&#xff09;。杭州交通管理局经常会扩充一些的士车牌照&#xff0c;新近出来一个好消息&#xff0c;以后上牌照&#xff0c;不再含有不吉利的数字了&#xff0c;这样一来&#xff0c;就可以消除…

CentOS 7 下的 Firewall

CentOS 7 默认实用的用Firewalld作为防火墙&#xff0c;摒弃了原先的iptables。但是内核还是使用iptable作为管理参考文档https://access.redhat.com/documentation/zh-CN/Red_Hat_Enterprise_Linux/7/html/Security_Guide/sec-Using_Firewalls.htmlhttp://www.myhome.net.tw/2…

POJ 1091(数论)

题目大意是给定两个整数n和m&#xff0c;求出长度为n1满足条件的数列data的个数&#xff0c;数列的要求下&#xff1a;1&#xff09;1<data[i]<m,for1<i<n2)data[n1]m;3&#xff09;这个n1个数满足&#xff1a;存在x1,x2,...,xn,xn1,满足x1*data[1]x2*data[2]...x(…

没有动任何配置文件,今天就出现了修改的JSP内容在页面不体现。依然是老的页面内容...

2019独角兽企业重金招聘Python工程师标准>>> 现象&#xff1a; 没有动任何配置文件&#xff0c;今天就出现了修改的JSP内容在页面不体现。依然是老的页面内容 问题分析: 既然可以用过&#xff0c;所以系统应该是检测文件更新的&#xff0c;之所以不重新编译JSP&…