Android 7系统输入(六)定制安卓系统的输入模块
系列目录:第一篇:从硬件到应用的事件旅程 | 第二篇:EventHub — 原始事件的采集者 | 第三篇:InputReader — 原始事件到Android事件的转换引擎 | 第四篇:InputDispatcher — 事件分发与ANR超时机制 | 第五篇:应用侧 — InputChannel、ViewRootImpl与事件消费 | 第六篇:定制安卓系统的输入模块一、为什么要定制输入模块在车载系统、工控设备、自动化测试等场景中,开发者经常需要实时监控用户的触摸行为。Android 原生的输入系统虽然功能完备,但它默认是"静默"的——事件从硬件一路流向应用,中间没有任何日志输出。当你遇到以下问题时,原生系统几乎帮不上忙:“用户说点了没反应,我看了 logcat,连点击事件有没有进系统都不知道”“焦点窗口到底是谁?为什么按键事件没有被当前应用收到?”“屏幕上这个按钮到底收到 DOWN 事件没有?UP 在哪里丢的?”这些问题都需要在输入系统的关键节点上插入定制日志。本篇基于前五篇文章对输入链路的完整分析,给出三个最实用的定制点。二、定制点总览三个定制点分布在输入链路的三个关键位置:┌─────────────────────────────────────────────────────────────────────┐ │ 输入系统定制点分布 │ ├───────────────┬───────────────────────┬─────────────────────────────┤ │ InputReader │ InputDispatcher │ 应用侧 (Java) │ │ │ │ │ │ 定制点① │ 定制点② 定制点③ │ 定制点② │ │ DOWN/UP │ 窗口名 焦点变更 │ 控件名 │ │ 事件日志 │ 打印 实时打印 │ 打印 │ └───────────────┴───────────────────────┴─────────────────────────────┘定制点位置目的改造行数① DOWN/UP 事件打印InputReader.cpp在事件源头过滤 MOVE,只打印 DOWN/UP~15 行② 窗口 + 控件打印InputDispatcher.cpp+ViewRootImpl.java知道点击事件最终被谁消费~20 行③ 焦点窗口变更打印InputDispatcher.cpp窗口切换时实时打印焦点信息~10 行三、定制点①:InputReader 中打印 DOWN/UP 事件3.1 为什么选这里回顾第三篇的分析,TouchInputMapper::dispatchTouches()是触摸事件从 RawEvent 加工为 Android MotionEvent 后的最后一个分发节点。在这里插入日志,可以拿到最完整的事件信息——动作类型、坐标、手指数量——同时避开了底层 RawEvent 的噪音。3.2 关键代码位置dispatchTouches()负责根据触点变化判定动作类型并分发:触点状态变化 分发的动作类型 ───────────── ───────────────── 新手指按下 (downIdBits) → ACTION_DOWN / ACTION_POINTER_DOWN 手指移动 (moveIdBits) → ACTION_MOVE 手指抬起 (upIdBits) → ACTION_UP / ACTION_POINTER_UP源码路径:frameworks/native/services/inputflinger/InputReader.cppvoidTouchInputMapper::dispatchTouches(nsecs_t when,uint32_tpolicyFlags){BitSet32 currentIdBits=mCurrentCookedState.cookedPointerData.touchingIdBits;BitSet32 lastIdBits=mLastCookedState.cookedPointerData.touchingIdBits;int32_tmetaState=getContext()-getGlobalMetaState();int32_tbuttonState=mCurrentCookedState.buttonState;if(currentIdBits==lastIdBits){if(!currentIdBits.isEmpty()){// ★ 纯 MOVE 事件——不需要打印,否则刷屏dispatchMotion(when,policyFlags,mSource,AMOTION_EVENT_ACTION_MOVE,0,0,metaState,buttonState,...);}}else{BitSet32upIdBits(lastIdBits.value~currentIdBits.value);BitSet32downIdBits(currentIdBits.value~lastIdBits.value);// ...// Dispatch pointer up events.while(!upIdBits.isEmpty()){uint32_tupId=upIdBits.clearFirstMarkedBit();dispatchMotion(when,policyFlags,mSource,AMOTION_EVENT_ACTION_POINTER_UP,0,0,metaState,buttonState,0,mLastCookedState.cookedPointerData.pointerProperties,mLastCookedState.cookedPointerData.pointerCoords,mLastCookedState.cookedPointerData.idToIndex,dispatchedIdBits,upId,mOrientedXPrecision,mOrientedYPrecision,mDownTime);dispatchedIdBits.clearBit(upId);}// ... MOVE 处理省略 ...// Dispatch pointer down events.while(!downIdBits.isEmpty()){uint32_tdownId=downIdBits.clearFirstMarkedBit();dispatchedIdBits.markBit(downId);if(dispatchedIdBits.count()==1){mDownTime=when;}dispatchMotion(when,policyFlags,mSource,AMOTION_EVENT_ACTION_POINTER_DOWN,0,0,metaState,buttonState,0,mCurrentCookedState.cookedPointerData.pointerProperties,mCurrentCookedState.cookedPointerData.pointerCoords,mCurrentCookedState.cookedPointerData.idToIndex,dispatchedIdBits,downId,mOrientedXPrecision,mOrientedYPrecision,mDownTime);}}}关键观察:dispatchTouches()已经替我们区分了 DOWN / MOVE / UP,只需要在 DOWN 和 UP 的分支中加日志,MOVE 分支完全不碰,天然不刷屏。3.3 定制代码在dispatchTouches()中增加两处ALOGD,分别打印 DOWN 和 UP 事件:源码路径:frameworks/native/services/inputflinger/InputReader.cppvoidTouchInputMapper::dispatchTouches(nsecs_t when,uint32_tpolicyFlags){BitSet32 currentIdBits=mCurrentCookedState.cookedPointerData.touchingIdBits;BitSet32 lastIdBits=mLastCookedState.cookedPointerData.touchingIdBits;// ... 省略 ...if(currentIdBits