本文摘要:
1,CursorInputMapper.sync内容
不要考虑你是什么人应该做什么, 而是你选择怎么做,才决定了你是什么样的人 --By fanfan
1概要接上文,继续分析来看当获取到鼠标事件后,InputReader的处理.也就是CursorInputMapper.sync所处理的内容.
代码还挺多,大致看下,sync做了三件事儿
-
event信息获取:上文说event有三大类,所以信息获取是指按键相关(WhichKey哪一个按键,StateOfKey按键状态),滚轮滚动WheelScroll数据,鼠标移动move数据
-
event数据调整:按键相关的数据没什么需要调整的,但是move和scroll的数据需要根据speed来调整
-
event重新组装交给dispatcher:虚拟出inputevent事件,交给InputDispathcer,之后会分发给应用处理
但是如果是针对鼠标的话,就会又多了一个事件,因为鼠标的光标的移动和绘制不是应用控制的.
所以,也就是说:鼠标的move和button时图标的绘制需要在framework进行一个处理.
针对鼠标来说,本文需要分析的就是四个问题了.
但其实,归根结底,就是两个问题,一个是event的调整,一个是event的处理,包括交给dispatcher分发和控制pointer移动
2event调整这个必须要结合上一文中的process方法来看了.event信息获取包括三个
-
按键key
-
鼠标移动move
-
滚轮滚动scroll
01
第一,key按键相关
WhichKey哪一个按键
针对鼠标所考虑到的按键有三个:left/right/middle.根据buttonState来获取到是哪个按键
1int32_t currentButtonState = mCursorButtonAccumulator.getButtonState();
在确定是哪个按键之后,接下来就是判断按键是不是鼠标按键了
btnState
1bool down = isPointerDown(currentButtonState);
isPointerDown方法也很简单,会过滤三个值:left/right/middle.
当且仅当buttonState符合其中一个时,才代表是按键按下.
也就是说,只要buttonState符合其中一个值,就代表是down.
那如何实现按键up呢?这也是涉及到一个虚拟输入事件的问题了.
如果你想实现一个按键的down和up事件,那么在down时要传入对应key值代表哪一个按键按下
在Up时要传入0代表没有按键按下也就是up了.
目前为止,可以确定按键的down和up了.
代码中除了down和up,还有一组button相关的状态:press和released.
这个和down/up有什么区别呢?看下press/release是怎么定义的?
1//如果当前
2int32_t buttonsPressed = currentButtonState & ~lastButtonState;
3int32_t buttonsReleased = lastButtonState & ~currentButtonState;
从代码可以总结出press的含义:只有当前的btnState和上一次的btnState不同时,才表示press.
也就是btn的state发生了改变,并且是由false->true的改变
也就是说press和按下后的第一个down事件是一致的.
但如果是长按,那么down一直是true,而press却只有在第一个down时才为true.
同理,release的含义以及和up的区别就不再介绍.
02第二,move移动
鼠标的移动距离对应到手机平面就是x,y方向上的偏移relativeX,relativeY.这个值跟什么有关系?
首先输入设备会输入一组偏移量的值getRelative,那么如何和屏幕上移动的距离对应呢?就需要按照一定的比例移动.
1float deltaX = mCursorMotionAccumulator.getRelativeX() * mXScale;
2float deltaY = mCursorMotionAccumulator.getRelativeY() * mYScale;
这里的mXscale|mYscale就是对应的比例.什么意思?
scale是说鼠标设备移动1尺寸,对应到屏幕上光标要移动的距离,也就是相当于地图上的图例,地图一公里相当于实际多少公里.
到此,就可以得到屏幕上的光标需要移动的距离参数
但是,仅仅到这儿还不够,我们所使用的手机基本上都是长方形状.而目前这组偏移量的值的x,y是针对鼠标来说的.
对鼠标来说,x方向偏移量就是鼠标水平移动,y方向偏移量就是鼠标垂直移动.
可对于手机来说怎么处理?也就是说如何把鼠标设备上的x|y方向上的移动,映射到手机的x|y方向上?这个要看手机是横屏还是竖屏了
1static void rotateDelta(int32_t orientation, float* deltaX, float* deltaY) {
2 float temp;
3 switch (orientation) {
4 case DISPLAY_ORIENTATION_90:
5 temp = *deltaX;
6 *deltaX = *deltaY;
7 *deltaY = -temp;
8 break;
9
10 case DISPLAY_ORIENTATION_180:
11 *deltaX = -*deltaX;
12 *deltaY = -*deltaY;
13 break;
14
15 case DISPLAY_ORIENTATION_270:
16 temp = *deltaX;
17 *deltaX = -*deltaY;
18 *deltaY = temp;
19 break;
20 }
21}
这个也很好理解,rotateDelta代码也很简单.是根据手机屏幕方向来调整对应的偏移量.
如果竖屏0度(手机坐标远点开始于左上角,向右为x正轴,向下为y正轴),也就是默认情况下,手机的坐标轴与鼠标设备坐标轴对应
如果是竖屏180度,也就相当于坐标轴顺时针旋转180度,那么此时手机的y的负半轴对应鼠标y的正半轴,手机x的负半轴对应鼠标x的负半轴.
所以手机x轴偏移量等于负的鼠标设备x轴偏移量.手机y轴偏移量则是负的鼠标设备y轴的偏移量
如果横屏90度,也就相当于坐标轴顺时针旋转90度,那么此时手机的y的负半轴对应鼠标x正半轴,手机的x正半轴对应鼠标y正半轴.
所以手机x轴偏移量等于鼠标设备y轴偏移量.而手机y轴偏移量则是负的鼠标设备x轴的偏移量
如果横屏270度,相当于坐标轴顺时针旋转270度,那么此时手机的y正半轴对应鼠标x正半轴方向,手机的x负半轴轴对应鼠标y正半轴.
所以手机x轴偏移量等于负的鼠标设备y轴偏移量,而手机y轴偏移量则等于鼠标设备x轴的偏移量.
这么多字,都快晕了,直接看图吧.
到这一步,手机和鼠标坐标轴方向上的偏移量已经一一对应了.接下来其实完全可以在手机上移动光标了.
但是代码还做了一层处理,算上了光标的移动速度,滚轮滚动时同样会计算速度
所以分析完scroll之后,再来分析是如何获取speed以及如何影响偏移量的
03第三,scroll滚动
scroll的方向有两个,一个是竖直scroll一个是水平scroll.这个跟手机的坐标轴方向没关系
1float vscroll = mCursorScrollAccumulator.getRelativeVWheel();
2float hscroll = mCursorScrollAccumulator.getRelativeHWheel();
某一个event中,vscroll和hscroll是不会同时存在的.
上一篇文章也看到过,scroll时值是固定的.接下来就是针对speed的处理了.
手机的move和scroll的距离,除了直接从鼠标的偏移距离映射过来之外,还和一个speed有关系,也就是说关系如下
那这就奇怪了,地图距离*图例不就是实际距离吗?为什么还有speed?其实相当于,不同的速度对应不同的比例,归根结底手机距离=鼠标距离*scale比例接下来就是一个关键难点:speed的计算手机的距离=鼠标距离*scale比例*speed速度
1mWheelYVelocityControl.move(when, NULL, &vscroll);
2mWheelXVelocityControl.move(when, &hscroll, NULL);
3mPointerVelocityControl.move(when, &deltaX, &deltaY);
speed是通过VelocityControl.move进行计算.这个目前涉及到矩阵算法问题,感兴趣的可以看下,具体涉及到VelocityControl和VelocityTracher.
到目前为止,event信息的获取基本已经转换成了与手机对应的值,接下来就是处理了
3event的处理鼠标的绘制和移动是由系统处理的,不需要分发给应用控制.
所以移动鼠标时,一个是需要完成光标的移动,一个是需要分发给应用.
01先来看光标的移动
1if (moved) {
2 mPointerController->move(deltaX, deltaY);
3}
这么简单的咧,直接交给pointerControll去move,数据也不再进行处理.也不能这么说,还是有个min和max的限制的.
ok,暂时遗留第二问,从这儿之后的鼠标move就是和绘制相关了.
02
02除了需要单独绘制鼠标,还需要构造motionEvent事件交给dispatcher.
如何构造?event事件类型有很多,先拿一个举例
1 // Send scroll events.
2 if (scrolled) {
3 pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_VSCROLL, vscroll);
4 pointerCoords.setAxisValue(AMOTION_EVENT_AXIS_HSCROLL, hscroll);
5
6 NotifyMotionArgs scrollArgs(when, getDeviceId(), mSource, policyFlags,
7 AMOTION_EVENT_ACTION_SCROLL, 0, 0, metaState, currentButtonState,
8 AMOTION_EVENT_EDGE_FLAG_NONE,
9 displayId, /* deviceTimestamp */ 0, 1, &pointerProperties, &pointerCoords,
10 mXPrecision, mYPrecision, downTime);
11 getListener()->notifyMotion(&scrollArgs);
12}
第一步,构造NotifyMotionArgs参数
虽然主要是分析代码流程,但是看到这么多不认识的参数还是忍不住想要分析一下.
NotifyMotionArgs这家伙一下子一二十个参数,到底要干啥,我也真心奇怪用得了那么多吗?有那么多输入事件需要区分吗?难道必须要这么多参数才能将不同的事件区分开来吗?
直接拿构造方法看一下都是什么东西
1NotifyMotionArgs::NotifyMotionArgs(nsecs_t eventTime, int32_t deviceId, uint32_t source,
2 uint32_t policyFlags,
3 int32_t action, int32_t actionButton, int32_t flags, int32_t metaState,
4 int32_t buttonState, int32_t edgeFlags, int32_t displayId, uint32_t pointerCount,
5 const PointerProperties* pointerProperties, const PointerCoords* pointerCoords,
6 float xPrecision, float yPrecision, nsecs_t downTime)
7.....
8}
如果你觉得看c++代码不太方便,可以直接去MotionEvet.java文件,查看这些参数说明
-
eventTime:ns级别,指示本次event发生的时间戳
-
deviceId:输入设备的uid唯一标识,用于识别是哪个设备
-
source:表明设备类型,究竟是属于鼠标类还是键盘类等
-
policyFlags:这个参数取值和WindowManagerPolicy中的flag对应,比如flag_wake,表示event来临时唤醒屏幕
-
action:事件的action,准确来说是当前event对应的动作
-
actionButton:暂时未知
-
flags:暂时未知
-
metaState:key相关的状态,这个一般都默认为0,只有keyBoard类型的输入设备,会记录key的状态
-
buttonState:拿鼠标来说,可以指明按下的是left/right/middle哪一个按键
-
edgeFlags:这个边界标识,具体何用暂时不明.但看了下取值有五个,具体取值见下文,想起来以前工程测试有接触过,测试手机屏幕四周的触摸事件,验证手机屏幕边界是否响应正常
-
displayId:跟输出设备有关,什么意思?显示屏的id,也就是显示屏有可能不一致,比如手机连接显示屏,此时有两个display
-
pointerCount:event中坐标点的数量
-
pointerProperties:坐标点的属性,包含pointer的唯一表示id
-
pointerCoords:坐标点的值,拿鼠标来说,会有当前坐标点,以及索要偏移的偏移量的大小
-
xPrecision/yPrecision:坐标轴上的精度,用处暂时不明
-
downTime:记录上一次down事件的时间戳
edgeflags的取值有五个:分别是
屏幕上方边界事件AMOTION_EVENT_EDGE_FLAG_TOP(0x01),屏幕下方边界事件,屏幕左方和屏幕右方以及不限制边界.
值的定义在input.h文件中,目前猜测用于工程测试,测试手机屏幕的性能,也就是测试手机四周是否能够正常响应输入事件.
第二步,notifyMotion通知listener将event插入队列
这个getListener是什么?
是一个QueuedInputListener.在构造reader对象时传入的InputDispatcher.并且把inputDispatcher作为一个innerListener交给了这个QueuedInputListener.
所以,参数构造完毕后会触发listener的notifyMotion.
1void QueuedInputListener::notifyMotion(const NotifyMotionArgs* args) {
2 mArgsQueue.push(new NotifyMotionArgs(*args));
3}
也很简单,就是向队列中push一个数据.到此,InputMapper的处理就完成了
接着回到loopOnce方法中.会触发listener的flush方法.该方法会将队列中的数据notify给InputDispatcher.
这里涉及到两个listener(QueueInputListener和InputDispatcher),简单介绍一下
首先,两个listener的来源:dispatcher是在InputManager中构造reader时传入的dispatcher对象.
而queue是属于reader的一个成员,queue中会嵌套一个InputListener,也就是会把dipatcher交给queue来支配.
两个listenenr作用:当reader的event发生改变时,queueListener会把event存入队列并且通知给dispatcher,之后dispatcher就负责分发了
也就是说reader和dispatcher之间还有一个纽带,那就是queueListener,至于为何这么设计?
猜测应该是因为reader负责读取events并交给对应listener的队列存储. dispatcher负责分发events
如果没有queueListener存在,那么相当于用于存储event的队列和用于分发event的队列是同一个
既然是同一个队列操作,那么一定是需要线程同步和线程安全的
不论是reader线程还是dispatcher线程,都是需要loop并且处于阻塞的,reader和dispatcher两者之间的操作会相互影响
相比较来看,现在的这种设计模式就避免了这种尴尬:reader读取events,之后交给queueListener就不再过问,继续loop自己的线程.
queue也会去通知dispatcher去刷新存储队列,dispatcher会异步分发事件.
以上是我自己的揣测,至于google大佬心里怎么想的,以及为什么这么设计,估计肯定是不会这么简单的了.
不废话了,接着看吧
紧接着就是dispatcher的处理了.在notifyMotion中会构造motionEvent事件,并插入到队列之中,等待分发
1 void InputDispatcher::notifyMotion(const NotifyMotionArgs* args) {
2 ....
3
4 ...
5 //在插入队列之前先交给inputManager进行一个处理
6 mPolicy->interceptMotionBeforeQueueing(args->eventTime, /*byref*/ policyFlags);
7 ...
8 // Just enqueue a new motion event.
9 MotionEntry* newEntry = new MotionEntry(args->eventTime,
10 args->deviceId, args->source, policyFlags,
11 args->action, args->actionButton, args->flags,
12 args->metaState, args->buttonState,
13 args->edgeFlags, args->xPrecision, args->yPrecision, args->downTime,
14 args->displayId,
15 args->pointerCount, args->pointerProperties, args->pointerCoords, 0, 0);
16 needWake = enqueueInboundEventLocked(newEntry);
17...
18}
19
20//插入队列
21bool InputDispatcher::enqueueInboundEventLocked(EventEntry* entry) {
22 ...
23 mInboundQueue.enqueueAtTail(entry);
24 ...
25}
其实看到这儿,我差点儿蒙住了,没看到有事件的分发啊,只看到了event插入队列就完事儿了
还记得InputManager开启的dispatcher线程吗?开始起作用了,线程的作用就是一直从队列中取event
1void InputDispatcher::dispatchOnce() {
2 ...
3 dispatchOnceInnerLocked(&nextWakeupTime);
4 ...
5}
6
7void InputDispatcher::dispatchOnceInnerLocked(nsecs_t* nextWakeupTime) {
8 ...
9 // Inbound queue has at least one entry.
10 mPendingEvent = mInboundQueue.dequeueAtHead();
11 ...
12}
以key分发为例,继续分析代码就会发现,从dispatcher分发给IMS,而在IMS中注册了windowManagerCallBack监听(这段代码在SystemServer中,ims和wms绑定了起来)。
而最终会触发WMS中的policy对象也就是PhoneWindowManager的interceptKeyBeforeQueueing,接下来就是PWM的处理了
ok,之后就开始根据参数进行对应分发了.流程就是这么个流程,相当于基本把树干找到了,细枝末节更多的就可以针对性分析了
总结下来,本文讲述reader读取到event之后的处理以及dispatcher对于event的分发
忙了好多天, 回头发现前段儿时间的笔记, 突然就踏实了
庆幸, 我还在写, 很乐意写......
原创声明
本文为作者原创文章,未经本人同意,禁止转载和挪作他用
https://mp.weixin.qq.com/s/dOiDRHr5TzhF9wv6p97W5A