Android触摸事件全过程分析

本文会分析触摸事件的产生 -> Activity.dispatchTouchEvent()整个过程。希望对于触摸事件的产生和系统处理过程有一个简单了解即可。

触摸事件的产生 : 触摸事件与中断

学习过Linux驱动程序编写的同学可能知道Linux是以中断的方式处理用户的输入事件。触摸事件其实是一种特殊的输入事件。它的处理方式与输入事件相同,只不过触摸事件的提供的信息要稍微复杂一些。

触摸事件产生的大致原理是:用户对硬件进行操作(触摸屏)会导致这个硬件产生对应的中断。该硬件的驱动程序会处理这个中断。不同的硬件驱动程序处理的方式不同,不过最终都是将数据处理后存放进对应的/dev/input/eventX文件中。所以硬件驱动程序完成了触摸事件的数据收集

/dev/input/eventX中的触摸事件是如何派发到Activity的呢?其实整个过程可以分为两个部分:一个是native(C++)层的处理、一个是java层的处理。我们先来看一下native层是如何处理的。

系统对触摸事件的处理

native层主要是通过下面3个组件来对触摸事件进行处理的,这3个组件都运行在系统服务中:

  • EventHub : 它的作用是监听、读取/dev/input目录下产生的新事件,并封装成RawEvent结构体供InputReader使用。
  • InputReader : 通过EventHub/dev/input节点获取事件信息,转换成EventEntry事件加入到InputDispatchermInboundQueue队列中。
  • InputDispatcher : 从mInboundQueue队列取出事件,转换成DispatchEntry事件加入到ConnectionoutboundQueue队列。然后使用InputChannel分发事件到java层

可以用下面这张图描述上面3个组件之间的逻辑:

InputChannel

我们可以简单的把它理解为一个socket, 即可以用来接收数据或者发送数据。一个Window会对应两个InputChannel,这两个InputChannel会相互通信。一个InputChannel会注册到InputDispatcher中, 称为serverChannel(服务端InputChannel)。另一个会保留在应用程序进程的Window中,称为clientChannel(客户端InputChannel)

下面来简要了解一下这两个InputChannel的创建过程,在Android的UI显示原理之Surface的创建一文中知道,一个应用程序的WindowWindowManagerService中会对应一个WindowState,WMS在创建WindowState时就会创建这两个InputChannel,下面分别看一下他们的创建过程。

服务端InputChannel的创建及注册

WindowManagerService.java

 public int addWindow(Session session...) {
        ...
        WindowState win = new WindowState(this, session, client, token,
            attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
        ...
        final boolean openInputChannels = (outInputChannel != null && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
        if  (openInputChannels) {
            win.openInputChannel(outInputChannel);
        }
    ...
}

void openInputChannel(InputChannel outInputChannel) { //这个 outInputChannel 其实是应用程序获取的inputchannel,它其实就是 inputChannels[1];
    InputChannel[] inputChannels = InputChannel.openInputChannelPair(makeInputChannelName()); //通过native创建了两个InputChannel,实际上是创建了两个socket
    mInputChannel = inputChannels[0]; // 这里将服务端的inputChannel保存在了WindowState中
    mClientChannel = inputChannels[1];
    ....
    mService.mInputManager.registerInputChannel(mInputChannel, mInputWindowHandle);
}

registerInputChannel(..);实际上就是把服务端InputChannel注册到了InputDispatcher中。上图中的InputChannel其实就是在创建一个WindowState时注册的。来看一下InputDispatcher中注册InputChannel都干了什么:

InputDispatcher.cpp

status_t InputDispatcher::registerInputChannel(const sp<InputChannel>& inputChannel,const sp<InputWindowHandle>& inputWindowHandle, bool monitor) {

    sp<Connection> connection = new Connection(inputChannel, inputWindowHandle, monitor); //利用 inputChannel 创建了一个 connection,简单的理解为socket的链接。

    int fd = inputChannel->getFd();
    mConnectionsByFd.add(fd, connection);

    //把这个 inputChannel 的 fd添加到 Looper中
    mLooper->addFd(fd, 0, ALOOPER_EVENT_INPUT, handleReceiveCallback, this);

    mLooper->wake();

    return OK;
}

即利用InputChannel创建了一个Connection(InputDispatcher会通过这个Connection来向InputChannel发射数据),并且把这个InputChannel添加到mLooper中。

那这里这个mLooper是什么呢?是UI线程的那个Looper吗?这部分我们后面再看,我们先来看一下客户端InputChannel的相关过程。

客户端InputChannel的相关逻辑

客户端(应用程序)Window是如何通过InputChannel来接收触摸事件的呢?上面WindowState.openInputChannel()方法创建完InputChannel后会走到下面的代码:

ViewRootImpl.java

if (mInputChannel != null) { // mInputChannel 即为前面创建的 client inputchannel
    mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, Looper.myLooper());
}

这里的new了一个WindowInputEventReceiver,它继承自InputEventReceiver,看一下它的初始化过程:

InputEventReceiver.java

public InputEventReceiver(InputChannel inputChannel, Looper looper) {
    ...
    mInputChannel = inputChannel;
    mMessageQueue = looper.getQueue();
    mReceiverPtr = nativeInit(new WeakReference<InputEventReceiver>(this),inputChannel, mMessageQueue);
    ...
}

static jlong nativeInit(JNIEnv* env, jclass clazz, jobject receiverWeak, jobject inputChannelObj, jobject messageQueueObj) {
    ...
    sp<NativeInputEventReceiver> receiver = new NativeInputEventReceiver(env,receiverWeak, inputChannel, messageQueue);
    status_t status = receiver->initialize();
    ...
}

即主要初始化了NativeInputEventReceiver ,它的initialize()调用了setFdEvents():

android_view_InputEventReceiver.cpp

void NativeInputEventReceiver::setFdEvents(int events) {
    ...
    int fd = mInputConsumer.getChannel()->getFd(); // 这个fd 就是客户端的 InputChannel 的 Connection
    ...
    mMessageQueue->getLooper()->addFd(fd, 0, events, this, NULL);
}

这里将客户端的InputChannel的 Connection Fd加入到了Native Looper(下面会分析它)中。看一下addFd:

int Looper::addFd(int fd, int ident, int events, const sp<LooperCallback>& callback, void* data) {
    Request request;
    request.fd = fd;
    request.callback = callback;
    request.events = events;
    ...
    mRequests.add(fd, request);
}

这里就是利用fd来构造了一个Request注意 :这里的callback就是NativeInputEventReceiver

OK,到这里我们就看完了客户端的InputChannel的初始化。并且还知道 Looper中是持有着客户端InputChannel服务端InputChannelConnection

那么就继续来看一下上面提到的native消息队列Native Looper,它有什么作用。

Android Native 消息循环

我们知道LooperMessageQueue中不断获取消息并处理消息。其实在MessageQueue创建时还创建了一个native的消息队列。InputDispatcher派发的触摸事件就会放到这个消息队列中等待执行。先来看一下这个消息队列的创建:

//MessageQueue.java
MessageQueue(boolean quitAllowed) {
    mQuitAllowed = quitAllowed;
    mPtr = nativeInit();
}

//android_os_MessageQueue.cpp
static jlong android_os_MessageQueue_nativeInit(JNIEnv* env, jclass clazz) {
    NativeMessageQueue* nativeMessageQueue = new NativeMessageQueue();
    ...
    nativeMessageQueue->incStrong(env);
    return reinterpret_cast<jlong>(nativeMessageQueue);
}

//android_os_MessageQueue.cpp
NativeMessageQueue::NativeMessageQueue() : mPollEnv(NULL), mPollObj(NULL), mExceptionObj(NULL) {
    mLooper = Looper::getForThread();  // 其实就是主线程的Looper
    if (mLooper == NULL) {
        mLooper = new Looper(false);
        Looper::setForThread(mLooper);
    }
}

即创建了一个NativeMessageQueueLooper在循环读取MessageQueue中的消息的同时其实也读取了NativeMessageQueue中的消息:

Looper.java

public static void loop() {
    final Looper me = myLooper();
    ...
    final MessageQueue queue = me.mQueue;
    ...
    for (;;) {
        Message msg = queue.next(); // might block
        ...
    }
}

Message next() {
    ....
    for (;;) {
        ...
        nativePollOnce(ptr, nextPollTimeoutMillis);
        ...
    }
}

即调用到了nativePollOnce()方法。在这个方法中会读取Server InputChannel发送的触摸事件(怎么发送的后面会讲到)。这个方法最终调用到Looper.pollInner()

int Looper::pollInner(int timeoutMillis) {
    ...
    struct epoll_event eventItems[EPOLL_MAX_EVENTS];
    int eventCount = epoll_wait(mEpollFd, eventItems, EPOLL_MAX_EVENTS, timeoutMillis);  // 阻塞读取event, 并保存到eventItems
    ...

    for (int i = 0; i < eventCount; i++) { //依次处理每一个读取到的event
        int fd = eventItems[i].data.fd;
        uint32_t epollEvents = eventItems[i].events;

        ...
        ssize_t requestIndex = mRequests.indexOfKey(fd);
        ...
        pushResponse(events, mRequests.valueAt(requestIndex));
    }
}

pollInner会调用pushResponse来依次处理每一个Event。这里的mRequests.valueAt(requestIndex)就是前面客户端的InputChannel注册时的一些信息。pushResponse会回调到NativeInputEventReceiver.handleEvent()

InputDispatcher通过服务端InputChannel发送触摸事件

上面我们知道了客户端会通过Looper不断处理NativeMessageQueue中的消息,那触摸事件的消息是如何发送到NativeMessageQueue的呢?其实触摸原始事件是通过建立好的InputChannel.sendMessage()来发送的:

status_t InputChannel::sendMessage(const InputMessage* msg) {
    size_t msgLength = msg->size();
    ssize_t nWrite;
    do {
        nWrite = ::send(mFd, msg, msgLength, MSG_DONTWAIT | MSG_NOSIGNAL); //向socket中写入数据
    } while (nWrite == -1 && errno == EINTR);
    ...
    return OK;
}

这个方法是InputDispatcher调用的。上面pollInner会因为InputChannel.sendMessage()发送的数据而被唤醒。进而调用request中的NativeInputEventReceiverhandleEvent()方法,参数就是我们接收到的事件信息与数据。

上面整个过程可以用下图表示:

其实上面整个过程是利用Socket完成了数据的跨进程通信(InputDispatcher->NativeMessageQueue)。Socket阻塞/通知机制在这里是十分高效的。NativeMessageQueue/Looper的主要作用是监听InputDispatcher服务端InputChannel发送的触摸数据。然后把这些数据通过NativeInputEventReceiver.handleEvent()回调到客户端。

NativeInputEventReceiver.handleEvent()

android_view_NativeInputEventReceiver.cpp

int NativeInputEventReceiver::handleEvent(int receiveFd, int events, void* data) {
     ...
    if (events & ALOOPER_EVENT_INPUT) {
        JNIEnv* env = AndroidRuntime::getJNIEnv();
        status_t status = consumeEvents(env, false /*consumeBatches*/, -1, NULL);
        mMessageQueue->raiseAndClearException(env, "handleReceiveCallback");
        return status == OK || status == NO_MEMORY ? 1 : 0;
    }
    ...
    return 1;
}

即主要通过consumeEvents()来处理这个事件:

status_t NativeInputEventReceiver::consumeEvents(JNIEnv* env,...)
{

    ...
    InputEvent* inputEvent;
    status_t status = mInputConsumer.consume(&mInputEventFactory,consumeBatches, frameTime, &seq, &inputEvent);

    jobject inputEventObj;
    ...
    switch (inputEvent->getType()) {
        ...
        case AINPUT_EVENT_TYPE_MOTION: {
            MotionEvent* motionEvent = static_cast<MotionEvent*>(inputEvent); // MotionEvent的产生
            inputEventObj = android_view_MotionEvent_obtainAsCopy(env, motionEvent);
            break;
        }
    }

    if (inputEventObj) {
        env->CallVoidMethod(receiverObj.get(),
                gInputEventReceiverClassInfo.dispatchInputEvent, seq, inputEventObj,
                displayId);
    }
}

}

这个方法的主要处理是:

  1. mInputConsumer.consume()会调用到mChannel->receiveMessage(&mMsg);,mChannel其实就是客户端InputChannel,它通过socket接收服务端InputChannel的消息。这个消息其实就是触摸事件。
  2. 产生MotionEvent对象inputEventObj,这个对象可以通过jni调用
  3. 调用jni方法gInputEventReceiverClassInfo.dispatchInputEvent()

其实gInputEventReceiverClassInfo.dispatchInputEvent()最终调用到java层InputEventReceiver.dispatchInputEvent(), 这个方法是java层分发触摸事件的开始。

InputEventReceiver的dispatchInputEvent()

InputEventReceiver.java

private void dispatchInputEvent(int seq, InputEvent event) {
    mSeqMap.put(event.getSequenceNumber(), seq);
    onInputEvent(event);
}

InputEventReceiver是一个抽象类,它在java层的实现是ViewRootImpl.WindowInputEventReceiver,它复写了onInputEvent():

@Override
public void onInputEvent(InputEvent event) {
    enqueueInputEvent(event, this, 0, true);
}

enqueueInputEvent()最终会调用deliverInputEvent()处理事件:

private void deliverInputEvent(QueuedInputEvent q) {
    ...
    InputStage stage;
    if (q.shouldSendToSynthesizer()) {
        stage = mSyntheticInputStage;
    } else {
        stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
    }

    if (stage != null) {
        stage.deliver(q);
    } else {
        finishInputEvent(q);
    }
}

InputStage可以理解为处理事件过程中的一步,多个InputStage可以组成一个处理流程,他们的组织形式类似于一个链表。看一下它的类组成应该就能猜到个大概逻辑:

abstract class InputStage {
    private final InputStage mNext;

    ...
    protected void onDeliverToNext(QueuedInputEvent q) {
        if (mNext != null) {
            mNext.deliver(q);
        } else {
            finishInputEvent(q);
        }
    }
    ...
    protected int onProcess(QueuedInputEvent q) {
        return FORWARD;
    }
}

事件QueuedInputEvent最终会由ViewPostImeInputStage处理,它的onProcess()会调用到processPointerEvent:

private int processPointerEvent(QueuedInputEvent q) {
    final MotionEvent event = (MotionEvent)q.mEvent;

    final View eventTarget = (event.isFromSource(InputDevice.SOURCE_MOUSE) && mCapturingView != null) ? mCapturingView : mView;

    boolean handled = eventTarget.dispatchPointerEvent(event);
    ...
}

这里的eventTarget(View)其实就是DecorView,即回调到了DecorView.dispatchPointerEvent():

View.java

public final boolean dispatchPointerEvent(MotionEvent event) {
    if (event.isTouchEvent()) {
        return dispatchTouchEvent(event);
    } else {
        return dispatchGenericMotionEvent(event);
    }
}

DecorView.java

public boolean dispatchTouchEvent(MotionEvent ev) {
    final Window.Callback cb = mWindow.getCallback();
    return cb != null && !mWindow.isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}

这里的Window.Callback其实就是Activity:

public class Activity extends ContextThemeWrapper implements Window.Callback,...{

即回调到Activity.dispatchTouchEvent()。到这里就回到的我们常分析Android事件分发机制。这些内容会在下一篇文章来看一下。

阅读全文
下载说明:
1、本站所有资源均从互联网上收集整理而来,仅供学习交流之用,因此不包含技术服务请大家谅解!
2、本站不提供任何实质性的付费和支付资源,所有需要积分下载的资源均为网站运营赞助费用或者线下劳务费用!
3、本站所有资源仅用于学习及研究使用,您必须在下载后的24小时内删除所下载资源,切勿用于商业用途,否则由此引发的法律纠纷及连带责任本站和发布者概不承担!
4、本站站内提供的所有可下载资源,本站保证未做任何负面改动(不包含修复bug和完善功能等正面优化或二次开发),但本站不保证资源的准确性、安全性和完整性,用户下载后自行斟酌,我们以交流学习为目的,并不是所有的源码都100%无错或无bug!如有链接无法下载、失效或广告,请联系客服处理!
5、本站资源除标明原创外均来自网络整理,版权归原作者或本站特约原创作者所有,如侵犯到您的合法权益,请立即告知本站,本站将及时予与删除并致以最深的歉意!
6、如果您也有好的资源或教程,您可以投稿发布,成功分享后有站币奖励和额外收入!
7、如果您喜欢该资源,请支持官方正版资源,以得到更好的正版服务!
8、请您认真阅读上述内容,注册本站用户或下载本站资源即您同意上述内容!
原文链接:https://www.dandroid.cn/archives/18916,转载请注明出处。
0

评论0

显示验证码
没有账号?注册  忘记密码?