
15.1 InputFilter系统级的事件过滤器先说说InputFilter。它是个什么东西说白了它就是一个可以插入到事件分发管道里的过滤器。你想想看所有触摸事件、按键事件在到达目标窗口之前都会先经过它手里过一遍。核心要点InputFilter运行在system_server进程里它有权决定事件是放行、修改还是直接吃掉。我记得在做一个安全项目时需要拦截所有应用的截屏按键。当时我就在想如果每个应用都去监听按键那太不靠谱了。后来发现InputFilter正好干这个——在系统层面统一拦截。来看一下它的工作流程// InputFilter.java 核心方法 Override public void filterInputEvent(InputEvent event, int policyFlags) { if (event instanceof KeyEvent) { KeyEvent keyEvent (KeyEvent) event; // 我习惯在这里先判断事件类型 if (keyEvent.getKeyCode() KeyEvent.KEYCODE_VOLUME_UP) { // 直接吃掉不让往下传 return; // 不调用super事件就消失了 } // 或者修改事件 KeyEvent modifiedEvent KeyEvent.changeFlags(keyEvent, keyEvent.getFlags() | KeyEvent.FLAG_CANCELED); super.filterInputEvent(modifiedEvent, policyFlags); return; } super.filterInputEvent(event, policyFlags); }这里有个坑我曾经踩过如果你在filterInputEvent里不调用super.filterInputEvent()事件就被你“私吞”了。系统不会报错但所有窗口都收不到这个事件。调试时我找了半天还以为是驱动问题。警告InputFilter的修改会影响所有应用。如果你不小心把HOME键给过滤了用户就回不了桌面了。我曾经在测试机上干过这事最后只能adb重启。15.2 InputMonitor窗口级的监控者接下来是InputMonitor。这个家伙和InputFilter不同它不修改事件它只偷听。你想想看有时候我们想知道某个窗口收到了什么事件但又不想影响它正常处理怎么办InputMonitor就是干这个的。我个人习惯把InputMonitor理解成“事件窃听器”。它注册到InputDispatcher里当事件被派发给目标窗口时它会收到一份拷贝。注意是拷贝不是原件。特性InputFilterInputMonitor运行位置system_serversystem_server能否修改事件能不能能否拦截事件能不传递即可不能典型用途系统级按键屏蔽、手势修改录屏、无障碍、调试为什么会需要InputMonitor举个例子录屏应用需要记录用户的触摸轨迹。如果它自己去监听每个窗口的触摸事件那得侵入多少代码有了InputMonitor它只需要在系统注册一个监控器所有事件都会“路过”它这里。15.3 事件窃听的实现原理咱们深入一点看看InputMonitor是怎么“偷听”的。在InputDispatcher的源码里有一个关键的数据结构// InputDispatcher.cpp class InputDispatcher : public Thread { // 监控器列表 VectorspInputMonitor mMonitors; void dispatchOnce() { // ... 获取事件 for (const auto monitor : mMonitors) { // 给每个监控器发一份拷贝 monitor-notifyInputEvent(event); } // 然后才派发给目标窗口 dispatchToTarget(event, target); } };看到没先通知监控器再派发给窗口。这个顺序很重要。监控器收到的是事件拷贝所以它修改不了原始事件。但如果你在监控器里做了一些耗时操作比如写日志、网络请求那就会拖慢整个事件分发。提示我曾经在InputMonitor的回调里做了文件IO操作结果导致触摸明显卡顿。后来改成异步处理问题就解决了。记住监控器回调里千万别做耗时操作。15.4 实战如何实现一个自定义拦截器好了理论说完了咱们来点实际的。假设你现在需要实现一个功能在游戏模式下屏蔽所有虚拟按键。你会怎么做嗯我的做法是写一个自定义InputFilterpublic class GameModeInputFilter extends InputFilter { private boolean mGameModeEnabled false; public GameModeInputFilter(InputFilterHost host) { super(host); } Override public void filterInputEvent(InputEvent event, int policyFlags) { if (!mGameModeEnabled) { super.filterInputEvent(event, policyFlags); return; } if (event instanceof KeyEvent) { KeyEvent keyEvent (KeyEvent) event; int keyCode keyEvent.getKeyCode(); // 虚拟按键列表返回、菜单、主页、最近任务 if (keyCode KeyEvent.KEYCODE_BACK || keyCode KeyEvent.KEYCODE_MENU || keyCode KeyEvent.KEYCODE_HOME || keyCode KeyEvent.KEYCODE_APP_SWITCH) { // 直接吃掉不传递 return; } } // 其他事件正常放行 super.filterInputEvent(event, policyFlags); } public void setGameMode(boolean enabled) { mGameModeEnabled enabled; } }然后通过InputManager注册InputManager im (InputManager) getSystemService(Context.INPUT_SERVICE); im.registerInputFilter(new GameModeInputFilter(host));这里要注意注册InputFilter需要系统权限。普通应用是调不了这个API的。我记得当时为了在普通应用里实现类似效果只能走无障碍服务那条路但效果差很多。15.5 两者的协作与区别最后总结一下。InputFilter和InputMonitor虽然都叫拦截器但定位完全不同InputFilter像门卫能决定谁进谁出还能给人换衣服修改事件。InputMonitor像监控摄像头只看不碰记录一切。在实际项目中我建议你这样选择需要阻止或修改事件 → 用InputFilter只需要观察或记录事件 → 用InputMonitor两者都用可以但要注意顺序InputFilter先处理InputMonitor后观察。避坑指南我曾经在同一个项目里同时用了InputFilter和InputMonitor结果InputFilter把事件拦截了InputMonitor就收不到。因为InputMonitor是在事件被派发后才通知的。如果你既想拦截又想监控记得在InputFilter里手动通知监控器。好了这一章的内容就到这里。拦截器这部分虽然代码量不大但理解透彻了对掌握整个Input系统的灵活性很有帮助。