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

这是性能优化系列之matrix框架的第14篇文章,我将在性能优化专栏中对matrix apm框架做一个全面的代码分析,性能优化是Android高级工程师必知必会的点,也是面试过程中的高频题目,对性能优化感兴趣的小伙伴可以去我主页查看所有关于matrix的分享。

前言

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

首先是Message的生产与消费过程中产生的卡顿,此类型的卡顿监控方案实现来源于消息队列中的Printer机制,通过给Looper设置一个Printer对象从而获取到每个消息执行的耗时情况,再结合方法执行的耗时信息定位卡顿,它监控的是dispatchMessage方法执行的耗时-Android性能优化系列-腾讯matrix-TracePlugin卡顿优化之LooperMonitor源码分析

其次是我们上一篇分享的,IdleHandler导致的卡顿类型,这种卡顿发生在消息队列空闲时,它的监控方案是通过hook拿到每个IdleHandler执行queueIdle的时机,然后做处理,它发生在MessageQueue的next方法拿不到消息的情况下,与第一种消息执行时的时机互为补充-Android性能优化系列-腾讯matrix卡顿优化之IdleHandlerLagTracer源码分析

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

所以一个点击事件从产生到完成的过程就可以从socket的发送与接收入手。通过hook到libinput.so中的recvfrom和sendto方法就可以实现对点击事件的卡顿监控,matrix中的TouchEventLagTracer就是按照这个思路实现的。 。

接下来我们进入代码分析,同样还是这几个关键入口:

  • 构造方法
  • onStartTrace
  • onStopTrace

构造方法

arduino 复制代码
public TouchEventLagTracer(TraceConfig config) {
    traceConfig = config;
}

onStartTrace

onStartTrace会调用到onAlive方法。onAlive方法中判断如果开启了TouchEventTrace开关,则调用nativeInitTouchEventLagDetective方法进入native层初始化监控,我们前边提到了的hook socket的方案,就是在这里进行的处理。

scss 复制代码
@Override
public synchronized void onAlive() {
    super.onAlive();
    if (traceConfig.isTouchEventTraceEnable()) {
        nativeInitTouchEventLagDetective(traceConfig.touchEventLagThreshold);
    }
}

nativeInitTouchEventLagDetective方法对应的native层的实现在MatrixTracer.cc中,我们进入这个类找到它。可以看到这里hook了libinput.so的三个方法:__sendto_chk,sendto,recvfrom。

scss 复制代码
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方法。

arduino 复制代码
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成对出现,标志着一次触摸事件的完成。

arduino 复制代码
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

ini 复制代码
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方法获取到当前堆栈的信息。

scss 复制代码
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,超过了之后就会记录堆栈信息。

ini 复制代码
void TouchEventTracer::touchRecv(int fd) {
    currentFd = fd;
    lagFd = 0;
    if (lagFd == fd) {
        lastRecvTouchEventTimeStamp = 0;
    } else {
        //更新当前时间值
        lastRecvTouchEventTimeStamp = time(nullptr);
        queueMutex.unlock();
    }
}

touchSendFinish

这个方法被调用说明触摸事件已经被消费完成,假如此时循环线程中判断时长超过了设定阈值,那么卡顿发生,则lagFd == fd条件满足,调用Java层上报卡顿信息。

ini 复制代码
void TouchEventTracer::touchSendFinish(int fd) {
    if (lagFd == fd) {
        //上报信息
        reportLag();
    }
    lagFd = 0;
    //更新为0
    lastRecvTouchEventTimeStamp = 0;
    startDetect = true;
}

onStopTrace

onStopTrace会调用到onDead方法,可以看到这里没做什么逻辑。

typescript 复制代码
@Override
public void onDead() {
    super.onDead();
}

总结

总结一下,触摸事件的卡顿监控实现逻辑很明确,就是通过hook底层的socket发送的sendto和recvfrom方法,从而实现对一次触摸事件执行消耗时长的监控。当调用到了recvfrom方法时,说明应用接收到了Touch事件,当调用到了sendto方法时,说明这个Touch事件已经消费掉了,两者的时间相差过大时即说明产生了一次触摸事件的卡顿。

相关推荐
爱学习的茄子23 分钟前
React Hooks进阶:从0到1打造高性能Todo应用
前端·react.js·面试
Edingbrugh.南空1 小时前
操作系统级TCP性能优化:高并发场景下的内核参数调优实践
网络协议·tcp/ip·性能优化
Edingbrugh.南空2 小时前
ClickHouse 全生命周期性能优化
clickhouse·性能优化
xiangzhihong82 小时前
使用Universal Links与Android App Links实现网页无缝跳转至应用
android·ios
车载应用猿3 小时前
基于Android14的CarService 启动流程分析
android
天涯学馆3 小时前
网站秒变 App!手把手教你搞定 PWA
前端·javascript·面试
没有了遇见4 小时前
Android 渐变色实现总结
android
UrbanJazzerati4 小时前
使用 Thunder Client 调用 Salesforce API 的完整指南
面试·visual studio code
bo521004 小时前
浏览器渲染机制详解(包含渲染流程、树结构、异步js)
前端·面试·浏览器
呆呆的心4 小时前
大厂面试官都在问的 WEUI Uploader,源码里藏了多少干货?🤔
前端·微信·面试