Java层接收Touch事件的第一个方式是InputEventReceiver#dispatchInputEvent
是不经过Handler#dispatchMessage
,也就是通过设置Printer监听卡顿的方式无法监控到Touch事件卡顿。
监控方案
关于事件处理全过程可以阅读:Input系统---事件处理全过程
用图总结交互过程:
- InputDispatcher线程调用publishKeyEvent向UI线程发送input事件
- UI线程收到该事件后,调用consumeEvents来处理该事件
- 当事件处理完毕,最终会调用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事件的处理时间。
所以通过xhook
( github.com/iqiyi/xHook ) 去hooklibinput.so
的recvfrom
和sendto
函数。
再回到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_sendto
和original_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,使循环线程再次挂起,等待下次事件重新工作。