likes
comments
collection
share

Android性能优化系列-腾讯matrix卡顿优化之TouchEventLagTracer源码分析

作者站长头像
站长
· 阅读数 98

前言

应用导致的卡顿问题,从源头上来讲,基本可以分为三种类型。

最后就是我们今天要分享的,点击事件导致的卡顿,这种类型的卡顿问题,前两种监控方案都无法覆盖到,所以需要有单独的方案进行监控。熟悉Android系统输入事件的读者应该清楚,当屏幕发生触摸事件时,是由系统native层的两个线程先进行处理的,InputReader线程负责从输入设备中读取输入事件,拿到事件后进行包装,再由InputDispatcher线程负责将事件分发给可以处理当前事件的窗口。这个分发的过程使用的socket进行通信,过程如下(图片来源于互联网):

Android性能优化系列-腾讯matrix卡顿优化之TouchEventLagTracer源码分析 所以一个点击事件从产生到完成的过程就可以从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
评论
请登录