Principio de distribución de eventos

1. Información general

En el artículo anterior, el principio de funcionamiento de View analizó cómo funciona la interfaz después de que se inicia la Actividad y cómo realizar el proceso de medición, diseño y dibujo de la Vista para mostrarla en la pantalla. la aplicación ya puede interactuar. La interacción con la aplicación a menudo se desencadena al tocar la pantalla del teléfono móvil, lo que involucra los principios de activación y distribución de los eventos de toque de pantalla.

2. Proceso de distribución

El proceso de activación y distribución de todo el evento implica una gran cantidad de puntos de conocimiento relacionados, como la comunicación entre procesos, Binder, Socket y muchos procesos relacionados con el código nativo (c++). El artículo analiza principalmente los procesos relevantes en el código Java, y en general se tomarán los procesos en Native.

2.1 Servicio de administrador de entrada

El proceso desde tocar la pantalla del teléfono móvil para hacer que el controlador de la capa de hardware reciba la señal táctil hasta enviar el evento táctil por la capa del kernel de Linux no se analizará aquí. Comencemos con InputManagerService. InputManagerService se inicia con el inicio del proceso system_server. Si está interesado en el inicio de system_server, puede leer el artículo SystemServer Workflow .

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());
}
复制代码

Siempre que el método de construcción sea crear un controlador que maneje eventos y vincularlo al Looper del subproceso "android.display", DisplayThread es en realidad un objeto HandlerThread único. Luego vincule el objeto nativo para obtener la información de la dirección del objeto nativo.

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…

Supongo que te gusta

Origin juejin.im/post/7105380689503059975
Recomendado
Clasificación