一、理论分析
从前面Android卡顿掉帧问题分析之原理篇文章中的分析我们知道,屏幕驱动上报的Input事件要经过框架的InputReader和InputDispatcher线程的读取与分发,然后通过Socket发送到应用进程中,再经过界面View控件树的层层分发后消费处理。这个过程中任意一个流程出现阻塞就能造成用户的触控操作得不到及时的响应,出现用户感知的系统反应延迟或卡顿现象。
二、典型案例分析
问题描述:微博应用界面手势滑动退出时,界面卡住几秒钟才有反应。
问题分析:结合Systrace工具分析问题原因如下:
input_jank.jpg
从以上问题Systrace的分析可以看出,退出应用界面时出现卡住几秒的原因:
首先看Systrace上system_server进程中标识Input事件分发的"iq"和“oq”队列的信息,可以看到框架InputDispatcher对事件的分发处理没有出现阻塞或漏报等异常情况;
从微博应用对应的“wq”队列可以看到,应用进程一直有没有完成处理消费的Input事件,说明问题在微博应用进程侧;
看Systrace上微博应用侧的信息,可以看到应用UI线程很长一段的deliverInputEvent的tag(大概持续2.5秒左右),且“aq:ime”队列中显示有持续很长一段时间的事件处理逻辑。说明应该是在应用进程UI线程中判断Input事件类型为返回键的Key事件,所以先交给输入法应用进行处理,但是输入法进程一直没有完成处理,导致应用UI线程一直处于等待状态无法及时处理此Input事件,说明问题出现在输入法应用侧;
然后我们用ps命令查看当前输入法进程信息,发现输入法进程(pid:5897)当前的状态为“D”,说明处于进程“冻结”的休眠状态,结合Systrace上显示的输入法进程的主线程信息显示,发现其确实是一直处于Sleeping状态。所以此问题出现的原因就是因为输入法进程被异常“冻结”导致的。和负责维护进程“冻结”功能的同事沟通并分析日志后发现,问题的原因是用户安装并设置了新的输入法应用,进程“冻结”模块没有成功识别,将其认定为普通后台进程所以进行了“冻结”从而导致了异常。
到此还剩一个疑问就是为什么输入法进程都被“冻结”而休眠了,应用UI线程在等待2.5秒后还是能继续往下处理Input事件,并成功退出应用Activity界面呢?原因就是应用UI线程进入等待输入法处理Input事件时会设置一个超时,这个时长就是2.5秒,如果超时后输入法进程还是没有处理完,就会强行结束等待的逻辑,由应用UI线程继续往下处理Input事件。相关源码如下所示:
/*frameworks/base/core/java/android/view/inputmethod/InputMethodManager.java*/
...
/**
* Timeout in milliseconds for delivering a key to an IME.
*/
static final long INPUT_METHOD_NOT_RESPONDING_TIMEOUT = 2500;
...
// Must be called on the main looper
int sendInputEventOnMainLooperLocked(PendingEvent p) {
if (mCurChannel != null) {
...
if (mCurSender.sendInputEvent(seq, event)) {
...
Message msg = mH.obtainMessage(MSG_TIMEOUT_INPUT_EVENT, seq, 0, p);
msg.setAsynchronous(true);
mH.sendMessageDelayed(msg, INPUT_METHOD_NOT_RESPONDING_TIMEOUT);//1.处理Input事件时设置一个2500ms的超时检查
return DISPATCH_IN_PROGRESS;
}
...
}
return DISPATCH_NOT_HANDLED;
}
...
case MSG_TIMEOUT_INPUT_EVENT: {
finishedInputEvent(msg.arg1, false, true);// 2.时间到之后如果输入法还是没有完成Input事件处理,则强行结束输入法对事件的处理逻辑
return;
}
...
三、优化思路
对用户触控事件的响应速度直接关系到用户对交互设备性能体验的感知,所以一直以来都是系统性能优化工作的重中之重。多年来谷歌,包括SOC厂商都有针对系统Input事件的处理流程作出优化,以提升触控响应速度。例如高通基线上在2018年左右就有一笔提交,优化应用进程侧的Input事件处理流程,大概思路就是识别应用UI线程中收到第一个ACTION_DOWN的Touch事件后,调用sendMessageAtFrontOfQueue接口在应用UI线程的消息队列的最前面插入一帧doFrame绘制任务,这样界面不用等待下一个Vsync的信号的到来就能直接上帧显示,从而减少整个Input触控事件的响应延迟。从Systrace上表现如下图所示:
国内各大手机厂商也有各自的优化方案。比如硬件上采用触控采样率更高的屏幕,屏幕触控采样率达到240HZ甚至480HZ。再比如监控到触控事件后提升CPU主频,提升触控事件处理相关线程和渲染线程的优先级等方式,从而优化事件触控响应速度。而对于应用APP开发者来说,需要做的就是避免在Input触控事件的分发处理流程中执行耗时操作,以免引起触控延迟或卡顿等性能问题。
整理来自:简书努比亚技术团队
更多framework干货,请关注下面“千里马学框架”