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,使循环线程再次挂起,等待下次事件重新工作。

相关推荐
l and1 小时前
Git 行尾换行符,导致无法进入游戏
android·git
程序媛小果1 小时前
基于Django+python的Python在线自主评测系统设计与实现
android·python·django
梁同学与Android1 小时前
Android --- 在AIDL进程间通信中,为什么使用RemoteCallbackList 代替 ArrayList?
android
Frank_HarmonyOS4 小时前
【无标题】Android消息机制
android
凯文的内存6 小时前
Android14 OTA升级速度过慢问题解决方案
android·ota·update engine·系统升级·virtual ab
VinRichard6 小时前
Android 常用三方库
android
Aileen_0v07 小时前
【玩转OCR | 腾讯云智能结构化OCR在图像增强与发票识别中的应用实践】
android·java·人工智能·云计算·ocr·腾讯云·玩转腾讯云ocr
Amd7949 小时前
PostgreSQL 数据库的启动与停止管理
postgresql·性能优化·数据库管理·故障处理·日常维护·启动数据库·停止数据库
江上清风山间明月10 小时前
Flutter DragTarget拖拽控件详解
android·flutter·ios·拖拽·dragtarget
debug_cat13 小时前
AndroidStudio Ladybug中编译完成apk之后定制名字kts复制到指定目录
android·android studio