[Input] App端消费事件流程

这是Android Input系列的第三篇文章,前面两篇的地址如下:

今天主要讲讲App端在收到事件之后,是如何消费这些事件的。

首先,我们看一个事件分发的典型Java堆栈:


image.png

可以看到,事件是从nativePollOnce分发出来的,调到了InputDispatcherReceiveronReceive方法中,然后再分发给ViewRootImpl去处理。

今天这篇文章,主要讲一下App端从socket中收到事件后,是怎样调度到InputDispatcherReceiver.onReceive方法的。下一篇文章,我们再讲后续ViewRootImpl的分发流程。

开始之前,先要要说明的是,接收事件的是App端的主线程,最后分发和处理事件,也是在主线程进行操作。

之前我们讲MessageQueue的时候说过,主线程会等待在epoll_wait方法,直到监听的端口有内容写入,才会被唤醒,继续执行下面的流程。更详细的内容,可以去看看我之前的文章从epoll机制看MessageQueue

点击事件的处理流程就是利用的epoll机制,就是我们常说的主线程的Looper机制,下面我们一起来详细看看源码。

epoll机制监听socketFd

由前面的分析知道,我们在创建了socket连接后,会创建一个WindowInputEventReceiver对象,并将客户端的InputChannel作为构造函数传入。下面我们就来看看WindowInputEventReceiver的构造方法。

final class WindowInputEventReceiver extends InputEventReceiver {
    //inputChannel是指socket客户端,Looper是指UI线程的Looper
    public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
        super(inputChannel, looper);
    }
}

WindowInputEventReceiver继承自InputEventReceiver

public InputEventReceiver(InputChannel inputChannel, Looper looper) {
     ...
     mInputChannel = inputChannel;
     mMessageQueue = looper.getQueue(); //UI线程消息队列
     mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),
             inputChannel, mMessageQueue);
 }

InputEventReceiver调用的是nativeInit方法,进行初始化

static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject inputChannelObj, jobject messageQueueObj) {
    sp<InputChannel> inputChannel = android_view_InputChannel_getInputChannel(env,
            inputChannelObj);
    //获取UI主线程的消息队列
    sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
    //创建NativeInputEventReceiver对象
    sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,
            receiverWeak, inputChannel, messageQueue);
    // 调用setFdEvents,将socket连接的fd添加到主线程Looper的监控中
    status_t status = receiver->initialize();
    return reinterpret_cast<jlong>(receiver.get());
}

nativeInit方法中,最终会调用setFdEvents方法,将socket连接的fd添加到主线程Looper的监控中。socket连接的fd通过InputChannel获取。

void NativeInputEventReceiver::setFdEvents(int events) {
  if (mFdEvents != events) {
      mFdEvents = events;
      int fd = mInputConsumer.getChannel()->getFd();
      if (events) {
          //将socket客户端的fd添加到主线程的消息池
          mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL);
      } else {
          mMessageQueue->getLooper()->removeFd(fd);
      }
  }
}  

addFd方法,就是通过epoll_ctl将fd加入监听,同时构造一个Request对象,将它加到mRequests队列中。

int Looper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data) {
    {
       // 构造request
        Request request;
        request.fd = fd;
        request.ident = ident;
        request.events = events;
        request.seq = mNextRequestSeq++;
        request.callback = callback; //是指nativeInputEventReceiver
        request.data = data;
        // 构造eventItem
        struct epoll_event eventItem;
        request.initEventItem(&eventItem);
        ssize_t requestIndex = mRequests.indexOfKey(fd);
        if (requestIndex < 0) {
            //通过epoll监听fd
            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_ADD, fd, & eventItem);
            //该fd的request加入到mRequests队列
            mRequests.add(fd, request); 
        } else {
            int epollResult = epoll_ctl(mEpollFd, EPOLL_CTL_MOD, fd, & eventItem);
            mRequests.replaceValueAt(requestIndex, request);
        }
    } 
    return 1;
}

我们来看看Request的结构:

  • fd:存的是socket通信的fd
  • ident:0
  • events:ALOOPER_EVENT_INPUT
  • callback:就是nativeInputEventReceiver

epoll_wait被唤醒

当监听的socket收到数据时,会从pollInner方法唤醒主线程Looper处理消息。

int Looper::pollInner(int timeoutMillis) {
    mPolling = true; //即将处于idle状态
    struct epoll_event eventItems[EPOLL_MAX_EVENTS]; //fd最大个数为16

    //等待事件发生或者超时,在nativeWake()方法,向管道写端写入字符;
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);
    mPolling = false; //不再处于idle状态
    //循环遍历,处理所有的事件
    for (int i = 0; i < eventCount; i++) {
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;
        // 如果是从wakeEventFd唤醒,表示MessageQueue有新消息了,会往这个fd写入
        if (fd == mWakeEventFd) {
            if (epollEvents & EPOLLIN) {
                // 这个方法会读取mWakeEventFd上的所有数据
                awoken(); 
            }
        } else {
            ssize_t requestIndex = mRequests.indexOfKey(fd);
            if (requestIndex >= 0) {
                //处理request,生成对应的reponse对象,push到mResponses数组
                pushResponse(events, mRequests.valueAt(requestIndex));
            }
        }
    }
Done: ;
    //处理带有Callback()方法的Response事件,执行Reponse相应的回调方法
    for (size_t i = 0; i < mResponses.size(); i++) {
        Response& response = mResponses.editItemAt(i);
        if (response.request.ident == POLL_CALLBACK) {
            int fd = response.request.fd;
            int events = response.events;
            void* data = response.request.data;
            // 处理请求的回调方法
            int callbackResult = response.request.callback->handleEvent(fd, events, data);
            // 正常处理事件会返回1,如果返回0,表示窗口被移除
            if (callbackResult == 0) {
                removeFd(fd, response.request.seq); //移除fd
            }
            response.request.callback.clear(); //清除reponse引用的回调方法
            result = POLL_CALLBACK;  // 发生回调
        }
    }
    return result;
}

这个方法的流程:

  • 获取唤醒的fd和events,从mRequest中找到对应fd的request
  • 将events和request封装成一个response对象,然后将他加到mResponses数组中
  • 循环处理mResponses数组中的所有response,调用request.callback->handleEvent

这个方法会调到request.callback->handleEvent,也就是NativeInputEventReceiverhandleEvent方法。这个方法主要调用了consumeEvents,所以我们直接看ConsumeEvents方法。

读取并消费所有的message

status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,
        bool consumeBatches, nsecs_t frameTime, bool* outConsumedBatch) {
    // 循环消费所有的消息
    for (;;) {
        status_t status = mInputConsumer.consume(&mInputEventFactory,
                consumeBatches, frameTime, &seq, &inputEvent);
            if (inputEventObj) {
                //执行Java层的InputEventReceiver.dispachInputEvent
                env->CallVoidMethod(receiverObj.get(),
                        gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj);
            } else {
                skipCallbacks = true;
            }
        }
        if (skipCallbacks) {
            //发生异常,则直接向InputDispatcher线程发送完成信号。
            mInputConsumer.sendFinishedSignal(seq, false);
        }
    }
}

循环读取所有的消息,并且调用Java层的InputEventReceiver.dispatchInputEvent方法处理事件。

status_t InputConsumer::consume(InputEventFactoryInterface* factory,
        bool consumeBatches, nsecs_t frameTime, uint32_t* outSeq, InputEvent** outEvent) {
    //循环遍历所有的Event
    while (!*outEvent) {
        if (mMsgDeferred) {
            mMsgDeferred = false; //上一次没有处理的消息
        } else {
            // 通过InputChannel接收一条消息
            status_t result = mChannel->receiveMessage(&mMsg);
            if (result) {
                if (consumeBatches || result != WOULD_BLOCK) {
                    result = consumeBatch(factory, frameTime, outSeq, outEvent);
                }
            }
        }
        }
    }
    return OK;
}

单条消息,调用InputChannel的receiveMessage读取。

status_t InputChannel::receiveMessage(InputMessage* msg) {
    ssize_t nRead;
    do {
        //读取InputDispatcher发送过来的消息
        nRead = ::recv(mFd, msg, sizeof(InputMessage), MSG_DONTWAIT);
    } while (nRead == -1 && errno == EINTR);
    return OK;
}

Java端收到事件后,会回调到WindowInputEventReceiveronInputEvent方法中,处理事件。

发送处理完信号

当事件处理完后,会调用finishInputEvent方法,将处理完的结果返回给系统。

从Java端的InputEventReceiver开始。

public final void finishInputEvent(InputEvent event, boolean handled) {
            int seq = mSeqMap.valueAt(index);
            mSeqMap.removeAt(index);
            // 调用native的方法
            nativeFinishInputEvent(mReceiverPtr, seq, handled);
}

调用到NativeInputEventReceiver,最终调用到sendFinishedSignal,然后调用到sendUnchainedFinishedSignal

status_t InputConsumer::sendUnchainedFinishedSignal(uint32_t seq, bool handled) {
    // 组装一个finished的信号,通过InputChannel的socket发送给系统
    InputMessage msg;
    msg.header.type = InputMessage::TYPE_FINISHED;
    msg.body.finished.seq = seq;
    msg.body.finished.handled = handled;
    return mChannel->sendMessage(&msg);
}

总结

App端消费事件的流程如下:

  • 在创建完socketpair后,App端会用mInputChannel创建一个WindowInputEventReceiver对象,并且注册对socket fd的监听。
  • socket fd上有输入写入时(即有事件时),会唤醒主线程
  • 主线程循环读取socket fd上的InputMessage,然后将message发送给Java层的InputEventReceiver去处理
  • 处理完之后,组装一个finished的信号,通过mInputChannel发送给system_server
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 196,099评论 5 462
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,473评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 143,229评论 0 325
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,570评论 1 267
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,427评论 5 358
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,335评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,737评论 3 386
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,392评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,693评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,730评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,512评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,349评论 3 314
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,750评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,017评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,290评论 1 251
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,706评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,904评论 2 335

推荐阅读更多精彩内容