2. 延迟的构成:从物理触摸到屏幕反馈的完整链路

大家好,我是你们的老朋友。上一章我们聊了延迟为什么是「玄学」,这一章咱们来点实在的——把这条链路拆开看看。

你想想看,你用手指戳了一下屏幕,到界面给你反馈,中间到底经历了什么?

我习惯把这整个过程比作一场接力赛。每个环节都有自己的任务,也都有自己的「拖延症」。咱们一个一个来看。

2.1 第一棒:触摸屏的物理捕获

一切从你的指尖开始。你戳下去的那一刻,触摸屏的传感器就开始工作了。

目前主流的是电容式触摸屏。你的手指和屏幕之间会形成一个微小的电容变化。这个变化有多小?大概在皮法(pF)级别。触摸控制器(Touch Controller,简称TC)需要不停地扫描这些变化。

这里有个关键参数:扫描频率。

我见过不少项目,为了省电把扫描频率降到 60Hz。这意味着每 16.6 毫秒才采样一次。你想想看,如果你手指点下去刚好错过了一次采样,那就要等下一个周期。这 16 毫秒的延迟,就这么白白浪费了。

核心指标:触摸屏的扫描频率决定了物理捕获的延迟下限。我个人建议,车载场景至少要做到 120Hz 的扫描频率,也就是 8.3 毫秒一次。

2.2 第二棒:驱动层的「翻译」与「上报」

触摸控制器拿到了原始数据,接下来要交给驱动。驱动干两件事:

  1. 滤波与去抖: 原始信号是有噪声的。驱动要做一些算法处理,比如卡尔曼滤波,把毛刺去掉。但滤波太强了,反而会引入延迟。这是个取舍问题。
  2. 坐标计算与上报: 把电容变化量换算成 (x, y) 坐标,然后通过 I2C 或 SPI 总线报给系统。

我曾经在一个项目里踩过坑。驱动里为了「更平滑」,加了一个滑动平均滤波器,窗口大小设成了 5。结果就是,手指已经停下来了,坐标还在「飘」。用户感觉就是「粘手」。后来我把窗口改成了 3,延迟降了 30%,手感立马好了。

避坑指南: 驱动层的滤波算法,能少用就少用。如果非要用,优先考虑一阶低通滤波,计算量小,延迟可控。

2.3 第三棒:系统层的「事件分发」

驱动把坐标报上来了,接下来是系统的事。在 Android 或 Linux 系统里,这涉及到 Input 子系统。

流程大概是这样的:

  • 驱动上报事件到 /dev/input/eventX 节点。
  • 系统层的 InputReader 线程读取事件。
  • InputDispatcher 线程把事件分发给当前焦点的应用。

这里最容易出问题的是什么呢?线程调度。

系统里有很多线程在跑。如果 InputReader 线程的优先级不够高,它可能被其他任务抢占。比如后台在下载地图数据,CPU 忙不过来,你的触摸事件就得排队等着。

我记得有一次,用户反馈说「偶尔卡一下」。查了半天,发现是系统的一个后台服务在做垃圾回收(GC),把 Input 线程给阻塞了。后来我们把 Input 线程的优先级调高,问题就解决了。

注意: 系统层的延迟往往是「偶发」的,很难复现。但一旦出现,用户感知非常明显。建议在系统层做事件时间戳打点,方便排查。

2.4 第四棒:应用层的「业务逻辑」

事件终于到了应用手里。应用要做什么?

  • 判断你点的是哪个控件(HitTest)。
  • 执行对应的业务逻辑(比如切换页面、播放音乐)。
  • 更新界面状态(比如按钮高亮)。

这一棒的延迟,往往是最容易被忽视的。为什么?因为很多开发者觉得「业务逻辑很快啊,不就是几行代码吗?」

但实际情况是,业务逻辑里可能藏着「定时炸弹」。比如:

  • 在触摸事件回调里做了网络请求。
  • 做了复杂的字符串拼接或 JSON 解析。
  • 调用了 Thread.sleep() 或者同步锁。

我见过最离谱的一个案例,是某个应用在 onTouchEvent 里直接写文件操作。结果文件系统 I/O 阻塞了 200 毫秒。用户点下去,界面要等 200 毫秒才有反应。这谁能忍?

铁律: 触摸事件的回调里,绝对不要做任何 I/O 操作、网络请求、或者耗时计算。只做最轻量级的状态更新。

2.5 第五棒:渲染层的「合成与绘制」

应用更新了界面状态,接下来要交给渲染管线。这里涉及三个关键步骤:

步骤 说明 典型延迟
Measure / Layout 重新计算控件的大小和位置 1-5 ms
Draw 执行 onDraw 方法,生成绘制指令 2-10 ms
RenderThread 把绘制指令提交给 GPU 1-3 ms

这里有个概念叫「垂直同步」(VSync)。系统每 16.6 毫秒(60Hz)发出一个 VSync 信号,告诉渲染管线「可以开始下一帧了」。

如果你的应用没能在 VSync 到来之前完成渲染,那就要等下一个 VSync。这一等,就是 16.6 毫秒。这就是所谓的「掉帧」。

说白了,渲染层的延迟,很大程度上取决于你的布局复杂度和 GPU 的负载。

我的习惯: 在开发阶段,我会打开系统的「Profile GPU Rendering」工具,看看每一帧的渲染时间。如果超过 16 毫秒,就要开始优化了。

2.6 第六棒:显示层的「像素刷新」

最后一步,GPU 渲染好的图像,要通过显示接口(比如 LVDS、MIPI DSI)送到屏幕面板上。

屏幕面板本身也有延迟。这个延迟取决于面板的响应时间(Response Time)和刷新率。

  • 普通 LCD 面板:响应时间约 10-25 毫秒。
  • 高端 LCD 或 OLED 面板:响应时间可以做到 1-5 毫秒。

另外,还有一个容易被忽略的点:显示缓冲区(Display Buffer)。

系统通常会使用双缓冲或三缓冲机制。这意味着,GPU 渲染完一帧后,要等屏幕把当前帧显示完,才能把新帧推上去。这个等待时间,最多可能是一个刷新周期(16.6 毫秒)。

嗯,这里要注意。有些车载屏幕为了降低成本,用的是单缓冲。单缓冲虽然延迟低,但会有「撕裂」现象。所以,双缓冲是底线。

2.7 总结:延迟的「总账本」

咱们把每一棒的延迟加起来,看看总延迟大概是多少:

环节 典型延迟(毫秒) 优化空间
触摸屏扫描 8.3 (120Hz) 提高扫描频率
驱动滤波与上报 2-5 简化滤波算法
系统事件分发 1-3 提高线程优先级
应用业务逻辑 1-10 避免耗时操作
渲染管线 5-16 优化布局、减少重绘
显示刷新 8-16 使用高刷屏、低响应面板
总计 25-58

你看,从你手指戳下去到屏幕反馈,最快也要 25 毫秒,最慢可能接近 60 毫秒。这还是在理想情况下。

所以,当你听到有人说「我们的系统延迟只有 10 毫秒」时,你可以礼貌地笑一笑。因为物理定律摆在那里,10 毫秒连触摸屏扫描都不够。

下一章,咱们聊聊怎么测量这些延迟。毕竟,没有测量就没有优化。