Android性能优化系列-腾讯matrix卡顿优化之TouchEventLagTracer源码分析
前言
应用导致的卡顿问题,从源头上来讲,基本可以分为三种类型。
最后就是我们今天要分享的,点击事件导致的卡顿,这种类型的卡顿问题,前两种监控方案都无法覆盖到,所以需要有单独的方案进行监控。熟悉Android系统输入事件的读者应该清楚,当屏幕发生触摸事件时,是由系统native层的两个线程先进行处理的,InputReader线程负责从输入设备中读取输入事件,拿到事件后进行包装,再由InputDispatcher线程负责将事件分发给可以处理当前事件的窗口。这个分发的过程使用的socket进行通信,过程如下(图片来源于互联网):
所以一个点击事件从产生到完成的过程就可以从socket的发送与接收入手。通过hook到libinput.so中的recvfrom和sendto方法就可以实现对点击事件的卡顿监控,matrix中的TouchEventLagTracer就是按照这个思路实现的。
。
接下来我们进入代码分析,同样还是这几个关键入口:
- 构造方法
- onStartTrace
- onStopTrace
构造方法
public TouchEventLagTracer(TraceConfig config) {
traceConfig = config;
}
onStartTrace
onStartTrace会调用到onAlive方法。onAlive方法中判断如果开启了TouchEventTrace开关,则调用nativeInitTouchEventLagDetective方法进入native层初始化监控,我们前边提到了的hook socket的方案,就是在这里进行的处理。
@Override
public synchronized void onAlive() {
super.onAlive();
if (traceConfig.isTouchEventTraceEnable()) {
nativeInitTouchEventLagDetective(traceConfig.touchEventLagThreshold);
}
}
nativeInitTouchEventLagDetective方法对应的native层的实现在MatrixTracer.cc中,我们进入这个类找到它。可以看到这里hook了libinput.so的三个方法:__sendto_chk,sendto,recvfrom。
static void nativeInitTouchEventLagDetective(JNIEnv *env, jclass, jint threshold) {
//hook了libinput.so的三个方法:__sendto_chk,sendto,recvfrom。
xhook_grouped_register(HOOK_REQUEST_GROUPID_TOUCH_EVENT_TRACE, ".*libinput\\.so$", "__sendto_chk",
(void *) my_sendto, (void **) (&original_sendto));
xhook_grouped_register(HOOK_REQUEST_GROUPID_TOUCH_EVENT_TRACE, ".*libinput\\.so$", "sendto",
(void *) my_sendto, (void **) (&original_sendto));
xhook_grouped_register(HOOK_REQUEST_GROUPID_TOUCH_EVENT_TRACE, ".*libinput\\.so$", "recvfrom",
(void *) my_recvfrom, (void **) (&original_recvfrom));
xhook_refresh(true);
//启动子线程,传入阈值,超过阈值则认为发生了卡顿
TouchEventTracer::start(threshold);
}
接下来的关键点就是这三个了。我们先看看两个hook点做了什么。
- my_sendto
- my_recvfrom
- TouchEventTracer
my_recvfrom
拦截到recvfrom方法后,先调用原来的额recefrom方法完成系统调用,然后判断当条件满足时,执行TouchEventTracer的touchRecv方法。
ssize_t my_recvfrom(int sockfd, void *buf, size_t len, int flags,
struct sockaddr *src_addr, socklen_t *addrlen) {
long ret = original_recvfrom(sockfd, buf, len, flags, src_addr, addrlen);
if (currentTouchFd == sockfd && inputHasSent && ret > VALIDATE_RET) {
TouchEventTracer::touchRecv(sockfd);
}
return ret;
}
my_sendto
拦截到sendto方法,调用系统的sendto完成调用,后边才是关键,将标记为inputHasSent设置为true,然后调用TouchEventTracer的touchSendFinish方法,此方法和touchRecv成对出现,标志着一次触摸事件的完成。
ssize_t my_sendto(int sockfd, const void *buf, size_t len, int flags,
const struct sockaddr *dest_addr, socklen_t addrlen) {
//拦截到sendto方法,调用系统的sendto完成调用
long ret = original_sendto(sockfd, buf, len, flags, dest_addr, addrlen);
if (ret >= 0) {
inputHasSent = true;
TouchEventTracer::touchSendFinish(sockfd);
}
return ret;
}
既然发送和接收的处理都在TouchEventTracer中,那么我们将目光转向TouchEventTracer这个类,看看它的内部实现。
TouchEventTracer
在前边初始化hook的时候,先调用了start方法开启线程,线程开启了一个无限循环recvQueueLooper。
start
void TouchEventTracer::start(int threshold) {
LAG_THRESHOLD = threshold / 1000;
loopRunning = true;
thread recvThread = thread(recvQueueLooper);
recvThread.detach();
}
recvQueueLooper是一个无限循环,它会不断的去检测lastRecvTouchEventTimeStamp这个值,当这个值与当前时间间隔的差值超过我们设置的阈值LAG_THRESHOLD后,就认为发生了卡顿,然后调用onTouchEventLagDumpTrace方法,onTouchEventLagDumpTrace方法会回调到Java层TouchEventLagTracer中的onTouchEventLagDumpTrace方法获取到当前堆栈的信息。
void recvQueueLooper() {
queueMutex.lock();
while (loopRunning) {
if (time(nullptr) - lastRecvTouchEventTimeStampNow >= LAG_THRESHOLD && startDetect) {
lagFd = currentFd;
//回到Java层获取堆栈信息
onTouchEventLagDumpTrace(currentFd);
queueMutex.lock();
}
}
}
上边我们提到了recvQueueLooper会不断的去检测lastRecvTouchEventTimeStamp这个值,那么lastRecvTouchEventTimeStamp这个值来自哪?接下来我们看下TouchEventTracer的另外两个方法实现。
touchRecv
这个方法被调用说明应用收到了触摸事件,在这个方法中更新了lastRecvTouchEventTimeStamp的值,这里更新值之后,因为此时循环线程一直在不断的检测当前时间和lastRecvTouchEventTimeStamp的差值是否超过LAG_THRESHOLD,超过了之后就会记录堆栈信息。
void TouchEventTracer::touchRecv(int fd) {
currentFd = fd;
lagFd = 0;
if (lagFd == fd) {
lastRecvTouchEventTimeStamp = 0;
} else {
//更新当前时间值
lastRecvTouchEventTimeStamp = time(nullptr);
queueMutex.unlock();
}
}
touchSendFinish
这个方法被调用说明触摸事件已经被消费完成,假如此时循环线程中判断时长超过了设定阈值,那么卡顿发生,则lagFd == fd条件满足,调用Java层上报卡顿信息。
void TouchEventTracer::touchSendFinish(int fd) {
if (lagFd == fd) {
//上报信息
reportLag();
}
lagFd = 0;
//更新为0
lastRecvTouchEventTimeStamp = 0;
startDetect = true;
}
onStopTrace
onStopTrace会调用到onDead方法,可以看到这里没做什么逻辑。
@Override
public void onDead() {
super.onDead();
}
总结
总结一下,触摸事件的卡顿监控实现逻辑很明确,就是通过hook底层的socket发送的sendto和recvfrom方法,从而实现对一次触摸事件执行消耗时长的监控。当调用到了recvfrom方法时,说明应用接收到了Touch事件,当调用到了sendto方法时,说明这个Touch事件已经消费掉了,两者的时间相差过大时即说明产生了一次触摸事件的卡顿。
转载自:https://juejin.cn/post/7285518866947014716