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

相关推荐
OkeyProxy3 小时前
設置Android設備全局代理
android·代理模式·proxy模式·代理服务器·海外ip代理
刘志辉4 小时前
vue传参方法
android·vue.js·flutter
前期后期6 小时前
Android OkHttp源码分析(一):为什么OkHttp的请求速度很快?为什么可以高扩展?为什么可以高并发
android·okhttp
轻口味9 小时前
Android应用性能优化
android
全职计算机毕业设计9 小时前
基于 UniApp 平台的学生闲置物品售卖小程序设计与实现
android·uni-app
dgiij10 小时前
AutoX.js向后端传输二进制数据
android·javascript·websocket·node.js·自动化
SevenUUp10 小时前
Android Manifest权限清单
android
高林雨露10 小时前
Android 检测图片抓拍, 聚焦图片后自动完成拍照,未对准图片的提示请将摄像头对准要拍照的图片
android·拍照抓拍
wilanzai11 小时前
Android View 的绘制流程
android
EterNity_TiMe_11 小时前
【Linux基础IO】深入Linux文件描述符与重定向:解锁高效IO操作的秘密
linux·运维·服务器·学习·性能优化·学习方法