1. InputWindowInfo传递链路全景图
当我们点击手机屏幕时,系统如何精准找到目标窗口?这背后隐藏着一条横跨三个核心服务的协作链路。想象一下快递配送系统:WindowManager是仓库管理员,负责整理包裹信息;SurfaceFlinger是分拣中心,处理空间坐标转换;InputDispatcher则是快递员,最终将事件投递到正确地址。
这条链路的核心载体是InputWindowInfo对象,它像快递面单一样包含以下关键信息:
- 窗口边界(frameLeft/top/right/bottom):就像收件地址的门牌号
- 触摸区域(touchableRegion):类似"仅工作日配送"的特殊要求
- 层级关系(z-order):决定窗口堆叠顺序,好比快递的优先配送级别
- 输入通道(InputChannel):相当于收件人的联系方式
典型的传递场景发生在:
- 新Activity启动时WindowManager更新窗口树
- 窗口大小/位置发生变化时
- 输入法窗口弹出/隐藏时
- 屏幕旋转导致布局重构时
2. WindowManager的InputMonitor层
WindowManagerService(WMS)作为Android窗口系统的"大脑",其InputMonitor模块负责采集窗口信息。核心方法updateInputWindowsLw()就像仓库的库存盘点,会遍历所有WindowState:
// 简化后的关键调用链 void updateInputWindowsLw(boolean force) { scheduleUpdateInputWindows(); // 触发异步更新 } private class UpdateInputWindows implements Runnable { public void run() { mUpdateInputForAllWindowsConsumer.updateInputWindows(inDrag); } }实际开发中常见的调用栈包括:
- Activity启动:Task.onChildPositionChanged() → DisplayContent.layoutAndAssignWindowLayersIfNeeded()
- 窗口层级变化:TaskDisplayArea.positionChildTaskAt() → WindowContainer.positionChildAt()
- 界面渲染:RootWindowContainer.performSurfacePlacement()
遍历窗口时的关键过滤逻辑:
if (w.mWinAnimator.hasSurface()) { // 必须有Surface才处理 populateInputWindowHandle(inputWindowHandle, w); setInputWindowInfoIfNeeded(mInputTransaction, w.mWinAnimator.mSurfaceController.mSurfaceControl, inputWindowHandle); }这里有个性能优化点:通过mUpdateInputWindowsPending标志位避免重复调度,类似快递系统的"批量打包"策略。
3. Java到Native的数据转换
当数据到达SurfaceControl时,需要将Java层的InputWindowHandle转换为Native层对象。这个过程就像国际快递的报关手续:
// 转换关键步骤 static void nativeSetInputWindowInfo(JNIEnv* env, jclass clazz, jlong transactionObj, jlong nativeObject, jobject inputWindow) { sp<NativeInputWindowHandle> handle = android_view_InputWindowHandle_getHandle(env, inputWindow); handle->updateInfo(); // 数据拷贝到native结构体 auto transaction = reinterpret_cast<Transaction*>(transactionObj); transaction->setInputWindowInfo(ctrl, *handle->getInfo()); }updateInfo()方法如同海关的货物查验,会拷贝这些关键字段:
- 窗口token(IBinder对象,类似快递单号)
- 窗口名称(用于调试标识)
- 调度超时时间(dispatchingTimeout)
- 触摸区域坐标(frameLeft等)
这里容易出现跨语言调用的性能问题,Android通过JNI全局引用缓存来优化。
4. SurfaceFlinger的合成与同步
SurfaceFlinger作为合成引擎,其setInputWindowInfo()实现将数据绑定到Layer:
SurfaceComposerClient::Transaction& setInputWindowInfo( const sp<SurfaceControl>& sc, const WindowInfo& info) { layer_state_t* s = getLayerState(sc); s->windowInfoHandle = new WindowInfoHandle(info); // 绑定到图层状态 s->what |= layer_state_t::eInputInfoChanged; // 标记变更 return *this; }关键流程节点:
- 事务提交:通过
Transaction::apply()跨进程传递到SurfaceFlinger - 状态应用:在
setClientStateLocked()中更新Layer的输入信息 - 信息收集:遍历所有Layer构建WindowInfo列表
// 构建窗口信息的核心逻辑 void buildWindowInfos(std::vector<WindowInfo>& outWindowInfos) { mDrawingState.traverseInReverseZOrder([&](Layer* layer) { if (layer->needsInputInfo()) { outWindowInfos.push_back(layer->fillInputInfo(...)); } }); }实测发现一个坑:当屏幕旋转时,需要等待performSurfacePlacement完成后再更新输入信息,否则会出现坐标错乱。
5. InputDispatcher的事件分发
最终数据通过WindowInfosListenerInvoker到达InputDispatcher:
void InputDispatcher::onWindowInfosChanged( const vector<WindowInfo>& windowInfos, const vector<DisplayInfo>& displayInfos) { // 更新窗口句柄列表 setInputWindows(windowInfos, displayId); // 处理焦点变更 if (oldFocusedWindow != newFocusedWindow) { cancelEventsForWindow(oldFocusedWindow); // 取消旧窗口事件 } }InputDispatcher的核心处理逻辑:
- 有效性检查:过滤掉不可见/不可触摸的窗口
- 焦点管理:处理
FLAG_NOT_FOCUSABLE等标志位 - 触摸目标查找:通过
findTouchedWindowAtLocked()匹配坐标 - 间谍窗口:特殊处理
isSpy()窗口(如输入法)
一个典型的事件分发耗时分布:
- 20% 坐标变换计算
- 30% 窗口查找与验证
- 50% 跨进程通信开销
6. 跨进程通信优化实践
在开发TV launcher时,我们遇到过输入延迟问题。通过systrace分析发现瓶颈在IPC传递:
# 监控Binder调用的命令 adb shell su root cat /sys/kernel/debug/tracing/trace_pipe | grep Binder优化措施包括:
- 批量更新:合并多次窗口变更,减少IPC次数
- 数据精简:只传递变更字段(通过
isChanged()标志) - 异步处理:非关键路径使用
scheduleAnimation()
// 优化后的更新逻辑 if (!mUpdateInputWindowsImmediately) { mDisplayContent.getPendingTransaction().merge(mInputTransaction); mDisplayContent.scheduleAnimation(); // 延迟到VSync周期处理 }7. 调试技巧与问题定位
当遇到触摸事件异常时,可以这样排查:
- 查看当前窗口信息:
adb shell dumpsys input | grep -A 12 "Window"- 检查SurfaceFlinger状态:
adb shell dumpsys SurfaceFlinger --input- 关键日志过滤:
adb logcat -b events | grep "am_focused_activity"常见问题模式:
- 坐标偏移:检查DisplayTransform是否正确应用
- 事件丢失:确认InputChannel是否正常注册
- 响应延迟:检查窗口的
dispatchingTimeout设置
记得在WindowManager的InputMonitor.java中添加调试日志:
if (DEBUG_INPUT) { Slog.d(TAG, "Window " + name + " touchableRegion=" + region); }8. 线程模型与同步机制
整个传递链路涉及多线程协作:
- WMS线程:
WindowManagerThread处理窗口变更 - SF主线程:
android.display执行合成操作 - InputDispatcher线程:独立处理输入事件
关键同步点:
mGlobalLock:保护窗口树结构Transaction原子性:确保Surface状态一致InputChannel的线程安全:通过Looper机制
在自定义窗口时,需要注意:
// 正确做法:在ViewRootImpl的线程创建InputChannel new InputChannel(); // 错误做法:在任意线程创建会导致同步问题