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事件已经消费掉了,两者的时间相差过大时即说明产生了一次触摸事件的卡顿。

相关推荐
流浪00139 分钟前
Linux系统篇34——信号(六):信号处理期间又来了怎么办?——sigaction、sa_mask、可重入函数与 volatile
linux·运维·面试·操作系统·信号处理·信号
ssshooter1 小时前
Node.js 天生就是异步的,为什么还要消息队列?
javascript·后端·面试
邪修king2 小时前
Re:Linux系统篇(二十六):文件系统(二):Ext 文件系统底层详解:从 inode、块组到软硬链接,结合 Windows 讲透文件管理本质
android·java·linux
金虹桥程序员2 小时前
【动画】 📚 CSS 动画与 GPU 合成层优化全链路指南
前端·面试
又见情义2 小时前
RK3568 Android 13 版本号管理实战:基于 ROCKCHIP_BUILD_NUMBER 的定制化方案
android
RobinDevNotes2 小时前
用 Profile 揪出大模型训练性能瓶颈
python·性能优化
终端安全笔记2 小时前
iOS 27 强制 TLS 1.2:租赁设备的注册链路会在哪一环断
android·网络·安全·ios·智能手机
mmsx2 小时前
MapLibre 实战 11|用户说"我的地块丢了":一个 sealed class 图层模型,和四个让我重构三版的坑
android·前端·开源
ocean21033 小时前
2025-2026年C++专题大厂面试高频问题
开发语言·c++·面试
顶点多余4 小时前
9.15 面试问题总结(一面挂)
linux·面试·职场和发展