事件分发原理

1.概述

上一篇文章View工作原理分析了在Activity启动之后界面是怎么工作起来的,怎么执行到View的测量、布局、绘制流程来展示到屏幕上的,此时App的界面已经可以进行交互。与app的交互往往是通过触摸手机屏幕来触发的,这里就涉及到了屏幕触摸事件的触发和分发原理。

2.分发过程

整个事件的触发和分发流程涉及到很多的相关知识点,比如进程间通信、Binder、Socket和很多Native(c++)代码相关的流程。文章主要来分析Java代码上的相关流程,Native上的流程会大体带过。

2.1InputManagerService

从触摸手机屏幕使硬件层驱动接收到触摸信号,到由Linux内核层发送触摸事件的流程这里就不进行分析。首先从InputManagerService开始说起,InputManagerService是随着system_server进程的启动而启动,对system_server启动感兴趣的可以看SystemServer工作流程这篇文章。

public InputManagerService(Context context) {
    //上下文
    this.mContext = context; 
    //接收事件的Handler,运行在线程"android.display"
    this.mHandler = new InputManagerHandler(DisplayThread.get().getLooper());
    ....
    //绑定Native对象并获取地址mPtr
    mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
    ....
    //注册服务到ServiceManager
    LocalServices.addService(InputManagerInternal.class, new LocalService());
}
复制代码

构造方法的只要工作是创建了处理事件的Handler与"android.display"线程的Looper进行绑定,DisplayThread其实是一个单例的HandlerThread对象。然后进行Native对象的绑定获取到Native对象的地址信息。

public void start() {
    //绑定Native对象
    nativeStart(mPtr);
    ....
    //注册几种观察者
    registerPointerSpeedSettingObserver();
    registerShowTouchesSettingObserver();
    registerAccessibilityLargePointerSettingObserver();
    mContext.registerReceiver(new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            //更新设置
            updatePointerSpeedFromSettings();
            updateShowTouchesFromSettings();
            updateAccessibilityLargePointerFromSettings();
        }
    }, new IntentFilter(Intent.ACTION_USER_SWITCHED), null, mHandler);
    //更新设置
    updatePointerSpeedFromSettings();
    updateShowTouchesFromSettings();
    updateAccessibilityLargePointerFromSettings();
}
复制代码

可以看到InputManagerService的start方法最核心的地方是跟Native做绑定,整体InputManagerService的工作流程大部分逻辑其实是native层来实现的,包括事件的处理线程、事件管理等方面。这篇文章有详细的流程上的分析Input系统—启动篇,这里需要提一下InputManagerService的registerInputChannel方法,下文中的流程会涉及到这里。

public void registerInputChannel(InputChannel inputChannel,
        InputWindowHandle inputWindowHandle) {
    //绑定InputChannel
    nativeRegisterInputChannel(mPtr, inputChannel, inputWindowHandle, false);
}
复制代码

2.2ViewRootImpl

事件分发这里又涉及到了ViewRootImpl,可见ViewRootImpl不单单负责View的测量、布局和绘制,触摸事件的分发流程也有参与。

public ViewRootImpl(Context context, Display display) {
    ....
    //触摸事件相关流程的关键代码,获取IWindowSession的代理类
    mWindowSession = WindowManagerGlobal.getWindowSession();
    ....
}
WindowManagerGlobal#getWindowSession
public static IWindowSession getWindowSession() {
    synchronized (WindowManagerGlobal.class) {
        if (sWindowSession == null) {
            try {
                //获取IMS的代理类
                InputMethodManager imm = InputMethodManager.getInstance();
                //获取WMS的代理类
                IWindowManager windowManager = getWindowManagerService();
                //通过Binder调用获取Session代理对象
                sWindowSession = windowManager.openSession(
                        new IWindowSessionCallback.Stub() {
                            @Override
                            public void onAnimatorScaleChanged(float scale) {
                                ValueAnimator.setDurationScale(scale);
                            }
                        },
                        imm.getClient(), imm.getInputContext());
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        return sWindowSession;
    }
}

WindowManagerService#openSession
public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
        IInputContext inputContext) {
    ....
    //创建Session对象
    Session session = new Session(this, callback, client, inputContext);
    return session;
}
复制代码

在ViewRootImpl的构造方法中获取了WindowManagerServicesession代理对象。

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
            ....
            if ((mWindowAttributes.inputFeatures
                    & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                //创建InputChannel     
                mInputChannel = new InputChannel();
            }
            ....
            try {
                ....
                // 通过Binder在SystemServer进程中完成InputChannel的注册
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                        mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
            } 
            ....
            if (mInputChannel != null) {
                if (mInputQueueCallback != null) {
                    mInputQueue = new InputQueue();
                    mInputQueueCallback.onInputQueueCreated(mInputQueue);
                }
                //创建WindowInputEventReceiver对象
                mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                        Looper.myLooper());
            }
            ....
            //组装处理事件的责任链
           mSyntheticInputStage = new SyntheticInputStage();
           InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
           InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
        "aq:native-post-ime:" + counterSuffix);
           InputStage earlyPostImeStage = new     EarlyPostImeInputStage(nativePostImeStage);
           InputStage imeStage = new ImeInputStage(earlyPostImeStage,
        "aq:ime:" + counterSuffix);
           InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
           InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
        "aq:native-pre-ime:" + counterSuffix);
          ....
        }
    }
}
复制代码

setView方法的流程里创建了InputChannel对象然后将InputChannel对象注册给WMS,最后创建WindowInputEventReceiver来接收处理事件。Session的addToDisplay方法最终会调用到WindowManagerServiceaddWindow方法。

public int addWindow(Session session, IWindow client, int seq,
        LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
        Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
        DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) {
        ....
        //创建WindowState对象
        final WindowState win = new WindowState(this, session, client, token, parentWindow,
                appOp[0], seq, attrs, viewVisibility, session.mUid,
                session.mCanAddInternalSystemWindow);
        ....
        
        final boolean openInputChannels = (outInputChannel != null
                && (attrs.inputFeatures & INPUT_FEATURE_NO_INPUT_CHANNEL) == 0);
        if  (openInputChannels) {
            //WindowState对象绑定InputChannel
            win.openInputChannel(outInputChannel);
        }
        ....
}

WindowState#openInputChannel
void openInputChannel(InputChannel outInputChannel) {
    ....
    //根据WindowState的HashCode以及Tag来生成InputChannel名称
    String name = getName();
    //创建一对InputChannel[见小节2.6]
    InputChannel[] inputChannels = InputChannel.openInputChannelPair(name);
    //第一个是服务端Channel
    mInputChannel = inputChannels[0];
    //第二个是客户端Channel
    mClientChannel = inputChannels[1];
    //将socket服务端Channel保存到WindowState的mInputChannel
    mInputWindowHandle.inputChannel = inputChannels[0];
    if (outInputChannel != null) {
        //socket客户端Channel传递给outInputChannel
        mClientChannel.transferTo(outInputChannel);
        mClientChannel.dispose();
        mClientChannel = null;
    } else {
      ....
    }
    //利用socket服务端Channel作为参数注册到IMS
    mService.mInputManager.registerInputChannel(mInputChannel, mInputWindowHandle);
}
复制代码

addWindow方法会创建WindowState,然后InputChannel会根据WindowState生成的name创建两个InputChannel对象,一个作为客户端、一个作为服务端。服务端保存到WindowState的mInputChannel并注册到IMS,客户端传递给outInputChannel,最终传递给ViewRootImpl的mInputChannel,触摸事件就可以通过mInputChannel来到ViewRootImplWindowInputEventReceiver中处理了。整体的流程可以归纳为两部分:
1)创建socket pair,作为InputChannel,一个作为服务端,一个作为客户端。
2) IMS.registerInputChannel()注册InputChannel,监听socket服务端。

2.3WindowInputEventReceiver

在前面ViewRootImpl的setView方法中创建了WindowInputEventReceiver对象,WindowInputEventReceiver用来接收事件和分发事件。

final class WindowInputEventReceiver extends InputEventReceiver {
    public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
        //构造方法传入了客户端InputChannel
        super(inputChannel, looper);
    }
    @Override
    public void onInputEvent(InputEvent event, int displayId) {
        //InputEvent事件会回调到onInputEvent方法,此处将输入事件加入队列,最后会调用到ViewRootImpl的enqueueInputEvent方法
        enqueueInputEvent(event, this, 0, true);
    }
    @Override
    public void onBatchedInputEventPending() {
        if (mUnbufferedInputDispatch) {
            super.onBatchedInputEventPending();
        } else {
            scheduleConsumeBatchedInput();
        }
    }
    @Override
    public void dispose() {
        unscheduleConsumeBatchedInput();
        super.dispose();
    }
}

ViewRootImpl#enqueueInputEvent
void enqueueInputEvent(InputEvent event,
        InputEventReceiver receiver, int flags, boolean processImmediately) {
    ....
    if (processImmediately) {
        //方法会调用到这里
        doProcessInputEvents();
    } else {
        scheduleProcessInputEvents();
    }
}
ViewRootImpl#doProcessInputEvents
void doProcessInputEvents() {
     ....
     //关键代码
     deliverInputEvent(q);
     ....
}
ViewRootImpl#deliverInputEvent
private void deliverInputEvent(QueuedInputEvent q) {
    ....
    InputStage stage; //处理输入事件
    if (q.shouldSendToSynthesizer()) {
        stage = mSyntheticInputStage;
    } else {
        stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
    }

    if (q.mEvent instanceof KeyEvent) {
        mUnhandledKeyManager.preDispatch((KeyEvent) q.mEvent);
    }

    if (stage != null) {
        handleWindowFocusChanged();
        stage.deliver(q);  //事件的责任链调用
    } else {
        finishInputEvent(q);
    }
}
复制代码

InputStage是处理输入的责任链,在调用deliver时会遍历责任链传递事件,事件分发完成后会调用finishInputEvent,告知SystemServer进程的InputDispatcher线程,最终将该事件移除,完成此次事件的分发消费。InputStage有多个处理类型的子类,这里主要看ViewPostImeInputStage类,这个是处理事件分发到View的相关逻辑。

  • SyntheticInputStage。综合处理事件阶段,比如处理导航面板、操作杆等事件。
  • ViewPostImeInputStage。视图输入处理阶段,比如按键、手指触摸等运动事件,我们熟知的view事件分发就发生在这个阶段。
  • NativePostImeInputStage。本地方法处理阶段,主要构建了可延迟的队列。
  • EarlyPostImeInputStage。输入法早期处理阶段。
  • ImeInputStage。输入法事件处理阶段,处理输入法字符。
  • ViewPreImeInputStage。视图预处理输入法事件阶段,调用视图viewdispatchKeyEventPreIme方法。
  • NativePreImeInputStage。本地方法预处理输入法事件阶段。
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);
            }
        }
    }
    ```
   private int processPointerEvent(QueuedInputEvent q) {
       final MotionEvent event = (MotionEvent)q.mEvent;
       ....
       //mView就是DecorView,流程就真正来到了View的dispatchPointerEvent
       boolean handled = mView.dispatchPointerEvent(event);
       maybeUpdatePointerIcon(event);
       maybeUpdateTooltip(event);
       mAttachInfo.mHandlingPointerEvent = false;
       if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
          mUnbufferedInputDispatch = true;
          if (mConsumeBatchedInputScheduled) {
             scheduleConsumeBatchedInputImmediately();
          }
     }
       return handled ? FINISH_HANDLED : FORWARD;
   }
}

View#dispatchPointerEvent
public final boolean dispatchPointerEvent(MotionEvent event) {
    if (event.isTouchEvent()) {
        //触摸事件分发,调用到DecorView的方法
        return dispatchTouchEvent(event);
    } else {
        return dispatchGenericMotionEvent(event);
    }
}
DecorView#dispatchTouchEvent
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    //activity就是DecorView的callBack
    final Window.Callback cb = mWindow.getCallback(); 
    return cb != null && !mWindow.isDestroyed() && mFeatureId < 0
            ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev);
}
复制代码

至此为止,屏幕触摸事件就彻底来到了Activity的DecorView中,之后的流程就是常见的事件分发流程了。

3.总结

整个触摸事件的分发流程可以大体分为几个部分:
1)触摸手机屏幕使硬件层驱动接收到触摸信号,由Linux内核层发送触摸事件的Event
2)InputManagerService通过InputReader线程接收硬件层的Event,并采用 InputDispatcher线程进行分发(InputReaderInputDispatcher都是native线程)。
3)InputManagerService分发出的事件交给InputChannel进行分发,分发的过程涉及进程间的通信,采用的是Socket,这个过程中又经过了WindowManagerService
4)ViewRootImpl通过WindowInputEventReceiver接收InputChannel传过来的InputEvent
5)ViewRootImpl将接收的InputEvent发送给DecorView
6)DecorView将事件发送给ActivityActivity将事件发送给PhoneWindowPhoneWindow又将事件发送回DecorView
经过上述6个步骤事件就来到了我们App的界面层了,之后的事件分发就是我们常见的ViewGroup->View的流程了。

参考文章
juejin.cn/post/684490…
juejin.cn/post/696548…

猜你喜欢

转载自juejin.im/post/7105380689503059975