Android 事件传递流程 - 基于WMS、IMS、ViewRootImpl等学习笔记

概述

在Android中,Touch事件的分发分服务端和应用端。在服务端由WindowManagerService(借助InputManagerService)负责采集和分发的,在应用端则是由ViewRootImpl(内部有一个mView变量指向View树的根,负责控制View树的UI绘制和事件消息的分发)负责分发的。

WMS在启动之后,经过逐层的调用,在Native层启动两个线程:InputReaderThread和InputDispachThread,前者用来读取输入事件,后者用来分发输入事件,输入事件经过Native层的层层传递,最终会传递到Java层的ViewRootImpl中,调用ViewPostImeInputState(ViewRootImpl的内部类)中的各个方法来分发事件,而Touch事件是在processPointerEvent方法进行分发的。

processPointerEvent方法中调用mView.processPointerEvent(event)方法,这里的mView就是在创建窗口后通过调用root,setView传进来的DecorView,而processPointerEvent方法会对event进行判断,如果是Touch事件,就调用dispatchTouchEvent将该事件分发给DecorView,这样Touch事件就传递到了View树中。

整体流程

    Android 事件传递流程 - 基于WMS、IMS、ViewRootImpl等学习笔记_第1张图片

  • 事件输入部分:当输入设备可用时,比如触屏,Linux内核会在/dev/input中创建对应的设备节点,输入事件所产生的原始信息会被Linux内核中的输入子系统采集,原始信息由Kernel Space的驱动层一直传递到User Space的设备节点

 Android的EventHub提供了getEvent和senEvent两个工具帮助开发者从设备节点读取和写入输入事件。

IMS所做的工作就是监听/dev/input下的所有的设备节点,当设备节点有数据时会将数据进行加工处理并找到合适的Window,将输入事件派发给他。

  • WMS处理部分:

                             Android 事件传递流程 - 基于WMS、IMS、ViewRootImpl等学习笔记_第2张图片

WMS的职责之一就是输入系统的中转站,WMS作为Window的管理者,会配合IMS将输入事件交由合适的Window来处理。

  • View处理部分:

这部分就是我们最输入的ViewGroup和View的事件处理、拦截等了。

事件的采集 -  IMS Get Events

WMS和IMS两个服务涉及到Android 事件的采集,因此这里需要说一下:

// 以下代码源于 - SystemServer的startOtherService方法
......
inputManager = new InputManagerService(context);

WindowManagerService.main(context, inputManager,
                    mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
                    !mFirstBoot, mOnlyCore, new PhoneWindowManager());
......
ServiceManager.addService(Context.INPUT_SERVICE, inputManager,
                    /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL);
......
inputManager.setWindowManagerCallbacks(wm.getInputMonitor());
inputManager.start();
......
---------------------------------------------------

 IMS所做的主要工作就是监听所有的事件输入设备节点,当设备节点有数据时,会将数据进行加工处理并找到合适的Window,将输入事件派发给它。WMS的职责之一就是输入系统的中转站,WMS作为Window的管理者,会配合IMS将输入事件交由合适的Window处理。

 public InputManagerService(Context context) {
        this.mContext = context;
        this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());
        ......
         
        mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());

        ...... 

        LocalServices.addService(InputManagerInternal.class, new LocalService());
}

---------------------------------------------------------------------------------
static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
        jobject serviceObj, jobject contextObj, jobject messageQueueObj) {
    ......    
    NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
            messageQueue->getLooper());//1
    ......
    return reinterpret_cast(im);
}

---------------------------------------------------------------------------------
NativeInputManager::NativeInputManager(jobject contextObj,
        jobject serviceObj, const sp& looper) :
        mLooper(looper), mInteractive(true) {
    ...
    sp eventHub = new EventHub();
    mInputManager = new InputManager(eventHub, this, this);
}

---------------------------------------------------------------------------------
InputManager::InputManager(
        const sp& eventHub,
        const sp& readerPolicy,
        const sp& dispatcherPolicy) {
    mDispatcher = new InputDispatcher(dispatcherPolicy);
    mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
    initialize();
}

---------------------------------------------------------------------------------
void InputManager::initialize() {
    mReaderThread = new InputReaderThread(mReader);
    mDispatcherThread = new InputDispatcherThread(mDispatcher);
}

下图可以很明了的看出IMS的事件采集过程:

                      Android 事件传递流程 - 基于WMS、IMS、ViewRootImpl等学习笔记_第3张图片

  • EventHub:通过Linux内核的INotify与Epoll控制监听设备节点,通过EventHub的getEvent函数读取设备节点的原始输入事件
  • InputManager中创建了InputReader和InputDispatcher,其中InputReader会不断的循环读取EventHub中的原始输入事件,将这些原始输入事件进行加工后交由InputDispatcher,InputDispatcher中保存了WMS中的所有的Window信息(WMS会将窗口的信息实时的更新到InputDispatcher中),这样InputDispatcher就可以将输入事件派发给合适的Window。由于InputDispatcher和InputReader都是耗时的,因此单独创建了InputDispatcherThread和InputReaderThread。

中转 - WMS

WMS作为输入系统的中转站,持有IMS的引用,ViewRootImpl通过WMS调用IMS的相关方法获取InputChannel.

关于InputChannel:

Android 事件传递流程 - 基于WMS、IMS、ViewRootImpl等学习笔记_第4张图片

这里需要特别注意的是,Touch事件从server端传递到client端采用的IPC方式并不是Binder,而是共享内存和管道,至于为什么不采用Binder,应该是共享内存的效率更高,而管道(注意,两个管道,分别负责不同方向的读和写)只负责通知是否有事件发生,传递的只是一个很简单的字符串,因此并不会太多地影响到IPC的效率。

在sever端中,InputReader和InputDispatcher是native 层的两个线程,前者不断地从EventHub中读取事件(包括所有的事件,对不同的事件会做判断处理),后者则不断地分发InputReader读取到的事件。而实际的分发操作时在InputPublish中进行的,它里面保存的有一个指向server端InputChannel端的指针,和一个指向ShareMemory(共享内存)的指针,当有事件要分发时,它会将事件写入到ShareMemory中,并且传递一个特定的字符串给InputChannel,由inutChannel将该字符串写入到管道中。在client端,一旦InputChannel从管道中读取到有事件分发过来,便会通知InPutConsumer从ShareMemory中读取具体的事件,并传递到framework层的InputQueue中。同样,一旦事件消费完毕,client端会通过管道告诉server端,事件已经消费完毕,流程与上面的似。

另外,顺便说下这里的两个InputChannel,这两个InputChannel是在native 层的,他们在framework层各自有一个对应的InputChannel类,对于这两个framework层的InputChannel,client端的是在ViewRootImpl中的setView中new出来的,但是并未做任何初始化操作(真正的初始化操作是跟server端的一起在WMS中执行的),也就是构造方法里面为空。

if ((mWindowAttributes.inputFeatures
                        & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
  mInputChannel = new InputChannel();
}

server端的InputChannel虽然是在server端创建的,但其创建过程是在client端发起的,ViewRootImpl中有server端的Session的代理,同样是setview方法中,通过Session代理执行server端Session的addToDisplay方法,该方法接受了client端的InputChannel方法:

            try {
                     
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);
                } catch (RemoteException e) {
                    
                } 

addToDisplay方法在Session类中,它会调用WindowManagerService的addWindow方法,而两个InputChannel的初始化操作都是在这里面的这这段代码中进行的。

    public int addWindow(Session session, IWindow client, int seq,
            WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel) {
            ......
            if  (openInputChannels) {
                win.openInputChannel(outInputChannel);
            }
            ......

    }


void openInputChannel(InputChannel outInputChannel) {
        if (mInputChannel != null) {
            throw new IllegalStateException("Window already has an input channel.");
        }
        String name = getName();
        InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
        mInputChannel = inputChannels[0];
        mClientChannel = inputChannels[1];
        mInputWindowHandle.inputChannel = inputChannels[0];
        if (outInputChannel != null) {
            mClientChannel.transferTo(outInputChannel);
            mClientChannel.dispose();
            mClientChannel = null;
        } else {
            // If the window died visible, we setup a dummy input channel, so that taps
            // can still detected by input monitor channel, and we can relaunch the app.
            // Create dummy event receiver that simply reports all events as handled.
            mDeadWindowEventReceiver = new DeadWindowEventReceiver(mClientChannel);
        }
        // 此处的mService就是WMS
        mService.mInputManager.registerInputChannel(mInputChannel, mInputWindowHandle);
    }

这里的InputChannel.openInputChannelPair方法会在native层创建两个InputChannel,也就是我们上面看到的那两个,并返回对应的framework层InputChannel的两个对象,保存在iputChannels数组中,其中一个保留字server端,一个通过transferTo方法返回到client 端。

因此,借助InputChannel,事件就被传递到了Framework层了,下面分析下Framework层ViewRootImpl的事件传递。

事件传递 - ViewRootImpl

         Android 事件传递流程 - 基于WMS、IMS、ViewRootImpl等学习笔记_第5张图片

源码品尝:

final class ViewPostImeInputStage extends InputStage {
    public ViewPostImeInputStage(InputStage next) {
        super(next);
    }

    @Override
    protected int onProcess(QueuedInputEvent q) {
        if (q.mEvent instanceof KeyEvent) {
            return processKeyEvent(q);
        } else {
            final int source = q.mEvent.getSource();
            if ((source & InputDevice.SOURCE_CLASS_POINTER) != 0) {
                return processPointerEvent(q);
            } else if ((source & InputDevice.SOURCE_CLASS_TRACKBALL) != 0) {
                return processTrackballEvent(q);
            } else {
                return processGenericMotionEvent(q);
            }
        }
    }

processPointerEvent源码如下:

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

            .......
            boolean handled = mView.dispatchPointerEvent(event);
            .......
            if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
                mUnbufferedInputDispatch = true;
                if (mConsumeBatchedInputScheduled) {
                    scheduleConsumeBatchedInputImmediately();
                }
            }
            return handled ? FINISH_HANDLED : FORWARD;
        }

注意:这里的mView就是我们熟悉的DecorView。因此,Touch事件的传递自然是先从ViewRootImpl传递到DecorView中。

查看DecorView的dispatchPointerEvent方法:

由于DecorView中没有dispatchPointerEvent,其父类View中有此方法,因此会调用View的dispatchPointerEvent方法:
public final boolean dispatchPointerEvent(MotionEvent event) {
        if (event.isTouchEvent()) {
            return dispatchTouchEvent(event);
        } else {
            return dispatchGenericMotionEvent(event);
        }
}
按照多态的特性,dispatchTouchEvent方法是DecorView的dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
        final Window.Callback cb = mWindow.getCallback();
        return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
                ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
    }

cb是啥?还记得Activity的attach方法中,为PhoneWindow设置的CallBack,此处的cb就是Activity。

Touch事件从IMS到ViewRootImpl的传递:从底层收集上来

硬件产生触屏事件 -> Native(EventHub、InputReader/Dispatcher、InputManager) -> IMS --采集完成--> ViewRootImpl -> DecorView -> PhoneWindow -> 事件传递到Activity

---------------------------------------------------------------分割线----------------------------------------------------------------------------

Touch事件在View树中的分发:从上向下传递

Activity -> PhoneWindow -> DecorView -> ViewGroup事件传递

Activity的dispatchTouch Event方法:

  public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();//默认是空实现
        }
        if (getWindow().superDispatchTouchEvent(ev)) {// PhoneWindow的superDispatchTouchEvent方法
            return true;
        }
        return onTouchEvent(ev);
    }

可以看到,如果getWindow().superDispatchTouchEvent(ev)返回false,则Activity的onTouchEvent方法会被调用,也就说说该事件没有被任何子View处理的情况下,事件会由Activity来处理。

PhoneWindow的superDispatchTouchEvent方法:

public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

mDecor是DecorView类型的变量,因此查看DecorView的superDispatchTouchEvent方法:

public boolean superDispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }

DecorView的父View是FrameLayout,因此走到了ViewGroup的dispatchTouchEvent方法流程中了。

学习资料来源:

https://blog.csdn.net/ns_code/article/details/49848801

https://blog.csdn.net/singwhatiwanna/article/details/50775201

http://liuwangshu.cn/tags/Android%E8%BE%93%E5%85%A5%E7%B3%BB%E7%BB%9F/

你可能感兴趣的:(Android)