APM框架Matrix源码分析(十二)TouchEventLagTracer源码分析

Java层接收Touch事件的第一个方式是InputEventReceiver#dispatchInputEvent 是不经过Handler#dispatchMessage,也就是通过设置Printer监听卡顿的方式无法监控到Touch事件卡顿。

监控方案

关于事件处理全过程可以阅读:Input系统---事件处理全过程

用图总结交互过程:

  1. InputDispatcher线程调用publishKeyEvent向UI线程发送input事件
  2. UI线程收到该事件后,调用consumeEvents来处理该事件
  3. 当事件处理完毕,最终会调用sendFinishedSIgnal告知InputDispatcher事件已处理完成

InputReader(读取事件)和InputDispatch(分发事件)都是运行在system_server系统进程中,我们自己的app又运行在自己的进程,这里的跨进程通信用的是Socket。

以下源码以Android9.0为例:

cpp 复制代码
//path: /frameworks/native/libs/input/InputTransport.cpp

#include <sys/socket.h>

status_t InputPublisher::publishMotionEvent(
   //InputChannel通过socket向远端发送消息
	return mChannel->sendMessage(&msg);
}
 status_t InputChannel::sendMessage(const InputMessage* msg) {
    //Socket send函数,最终调到了sendto
    nWrite = ::send(mFd, msg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL);
 }
 

status_t InputPublisher::receiveFinishedSignal(uint32_t* outSeq, bool* outHandled) {
    //通过InputChannel接收socket消息
    status_t result = mChannel->receiveMessage(&msg);
}


status_t InputChannel::receiveMessage(InputMessage* msg) {
    //Socket recv函数,最终调到了recvfrom
    nRead = ::recv(mFd, msg, sizeof(InputMessage), MSG_DONTWAIT);
}

socket.h

cpp 复制代码
//socket发送
__socketcall ssize_t sendto(int __fd, const void* __buf, size_t __n, int __flags, const struct sockaddr* __dst_addr, socklen_t __dst_addr_length);
//socket接收
__socketcall ssize_t recvfrom(int __fd, void* __buf, size_t __n, int __flags, struct sockaddr* __src_addr, socklen_t* __src_addr_length);

InputTransport.cpp最终会编译成libinput.so

mk 复制代码
//path: /frameworks/native/libs/input/Android.bp

cc_library {
    //编译成libinput.so
    name: "libinput",
    target: {
       //针对Android环境进行编写的
       android: {
            srcs: [
                "InputTransport.cpp"
            ]
       }
    }
}

当调用recvfrom时说明应用端接收到了Touch事件,当调用sendto时,说明Touch事件已经被消费掉了,两者的差值就是Touch事件的处理时间。

所以通过xhookgithub.com/iqiyi/xHook ) 去hooklibinput.sorecvfromsendto函数。

再回到TouchEventLagTracer.java,onAlive会调用nativeInitTouchEventLagDetective方法进入native层进行监控。(方法在MatrixTracer.cc

cc 复制代码
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);
}

xhook_grouped_register最后一个参数表示 PLT 入口点的地址值将被保存在 original_sendtooriginal_recvfrom中,以便后面在不改变原逻辑的情况下处理自己的逻辑。

my_recvfrom

recvfrom 用于从指定的 socket 接收数据。其返回值具有以下含义:

  • 正整数:表示成功接收到的字节数
  • 0:连接关闭
  • -1:出现错误
cc 复制代码
ssize_t my_recvfrom(int sockfd, void *buf, size_t len, int flags,
                    struct sockaddr *src_addr, socklen_t *addrlen) {
    //调用原来的recvfrom完成系统调用
    long ret = original_recvfrom(sockfd, buf, len, flags, src_addr, addrlen);

    if (currentTouchFd == sockfd && inputHasSent && ret > VALIDATE_RET) {
        //应用端收到了Touch事件
        TouchEventTracer::touchRecv(sockfd);
    }

    if (currentTouchFd != sockfd) {
        TouchEventTracer::touchSendFinish(sockfd);
    }

    if (ret > 0) {
        currentTouchFd = sockfd;
    } else if (ret == 0) {
        TouchEventTracer::touchSendFinish(sockfd);
    }
    return ret;
}

my_sendto

cc 复制代码
ssize_t my_sendto(int sockfd, const void *buf, size_t len, int flags,
                  const struct sockaddr *dest_addr, socklen_t addrlen) {
    //调用原来的sendto完成系统调用
    long ret = original_sendto(sockfd, buf, len, flags, dest_addr, addrlen);
    if (ret >= 0) {
        inputHasSent = true;
        //Touch事件已被消费
        TouchEventTracer::touchSendFinish(sockfd);
    }
    return ret;
}

接着看下TouchEventTracer

TouchEventTracer

start
cc 复制代码
    void TouchEventTracer::start(int threshold) {
        //卡顿阈值
        LAG_THRESHOLD = threshold / 1000;
        loopRunning = true;
        //启动一个线程
        thread recvThread = thread(recvQueueLooper);
        recvThread.detach();
    }
    
    
    void recvQueueLooper() {
        unique_lock lk(queueMutex);
        //子线程循环
        while (loopRunning) {
            //lastRecvTouchEventTimeStamp为0表示没有事件或者事件结束
            if (lastRecvTouchEventTimeStamp == 0) {
                //等待被唤醒
                cv.wait(lk);
            } else {
                long lastRecvTouchEventTimeStampNow = lastRecvTouchEventTimeStamp;
                if (lastRecvTouchEventTimeStampNow <= 0) {
                    continue;
                }
                //当前时间与收到Touch事件时间lastRecvTouchEventTimeStamp差值>=阈值,则认为是卡顿
                if (time(nullptr) - lastRecvTouchEventTimeStampNow >= LAG_THRESHOLD && startDetect) {
                    //记录卡顿sockfd
                    lagFd = currentFd;
                    //调到java层获取堆栈信息
                    onTouchEventLagDumpTrace(currentFd);
                    //等待被唤醒
                    cv.wait(lk);
                }
            }
        }
    }

start的时候开启了一个循环子线程,会根据当前时间和收到Touch事件的时间差>=卡顿阈值,记录为卡顿,并获取堆栈信息。没有事件、事件结束、满足卡顿条件就会挂起等待唤醒。

lastRecvTouchEventTimeStamp是在哪里更新的呢?

什么时候唤醒继续循环呢?

继续往下看

touchRecv
cc 复制代码
    void TouchEventTracer::touchRecv(int fd) {
        currentFd = fd;
        lagFd = 0;
        if (lagFd == fd) {
            lastRecvTouchEventTimeStamp = 0;
        } else {
            //app收到Touch事件时更新lastRecvTouchEventTimeStamp
            lastRecvTouchEventTimeStamp = time(nullptr);
            //并唤醒上面循环线程
            cv.notify_one();
        }
    }

接收到Touch事件则更新lastRecvTouchEventTimeStamp,并通知循环线程可以计算是否发生了卡顿。

touchSendFinish
cc 复制代码
    void TouchEventTracer::touchSendFinish(int fd) {
        if (lagFd == fd) {
            //事件结束发现是卡顿事件则上报
            reportLag();
        }
        lagFd = 0;
        //事件结束lastRecvTouchEventTimeStamp置为0
        lastRecvTouchEventTimeStamp = 0;
        startDetect = true;
    }

事件消费完毕发现是卡顿事件则上报,并将lastRecvTouchEventTimeStamp置为0,使循环线程再次挂起,等待下次事件重新工作。

相关推荐
EterNity_TiMe_2 小时前
【论文复现】STM32设计的物联网智能鱼缸
stm32·单片机·嵌入式硬件·物联网·学习·性能优化
烬奇小云3 小时前
认识一下Unicorn
android·python·安全·系统安全
saturday-yh3 小时前
性能优化、安全
前端·面试·性能优化
青云交13 小时前
大数据新视界 -- 大数据大厂之 Impala 性能优化:基于数据特征的存储格式选择(上)(19/30)
大数据·性能优化·金融数据·impala·存储格式选择·数据特征·社交媒体数据
顾北川_野15 小时前
Android 进入浏览器下载应用,下载的是bin文件无法安装,应为apk文件
android
CYRUS STUDIO15 小时前
Android 下内联汇编,Android Studio 汇编开发
android·汇编·arm开发·android studio·arm
右手吉他15 小时前
Android ANR分析总结
android
PenguinLetsGo17 小时前
关于 Android15 GKI2407R40 导致梆梆加固软件崩溃
android·linux
数据智能老司机17 小时前
Rust原子和锁——Rust 并发基础
性能优化·rust·编程语言
杨武博19 小时前
音频格式转换
android·音视频