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

相关推荐
Hello World......34 分钟前
互联网大厂Java面试:从Spring到微服务的全面探讨
java·spring boot·spring cloud·微服务·面试·技术栈·互联网大厂
apocelipes8 小时前
使用libdivide加速整数除法运算
c语言·c++·性能优化·linux编程
uwvwko10 小时前
BUUCTF——web刷题第一页题解
android·前端·数据库·php·web·ctf
fzxwl10 小时前
隆重推荐(Android 和 iOS)UI 自动化工具—Maestro
android·ui·ios
supingemail11 小时前
面试之 Java 新特性 一览表
java·面试·职场和发展
LittleLoveBoy12 小时前
踩坑:uiautomatorviewer.bat 打不开
android
Wannaer13 小时前
从 Vue3 回望 Vue2:性能优化内建化——从黑盒优化到可控编译
javascript·vue.js·性能优化
居然是阿宋13 小时前
Android核心系统服务:AMS、WMS、PMS 与 system_server 进程解析
android
霸王蟹13 小时前
React Fiber 架构深度解析:时间切片与性能优化的核心引擎
前端·笔记·react.js·性能优化·架构·前端框架
laowangpython13 小时前
MySQL基础面试通关秘籍(附高频考点解析)
数据库·mysql·其他·面试