从你触摸屏幕开始分析android触摸事件分发

本篇只会讲到触发ViewGroup的dispatchTouchEvent为止,因为接下去的一搜一大把,或者说有点基础的应该都了解。

  • 从触摸开始

    首先,请你打开命令行工具。输入adb shell进入到shell命令,然后输入getevent,会监听打印触摸屏幕的event信息。

    add device 1: /dev/input/event5
    name:     "msm8974-taiko-mtp-snd-card Headset Jack"
    add device 2: /dev/input/event4
    name:     "msm8974-taiko-mtp-snd-card Button Jack"
    add device 3: /dev/input/event3
    name:     "hs_detect"
    add device 4: /dev/input/event1
    name:     "touch_dev"
    add device 5: /dev/input/event0
    name:     "qpnp_pon"
    add device 6: /dev/input/event2
    name:     "gpio-keys"
    

    当你使出你的一阳指点击屏幕的时候,变回不断的去获取到你的点击事件,就像这样:

    /dev/input/event1: 0000 0000 00000000
    /dev/input/event1: 0003 0039 000005cf
    /dev/input/event1: 0003 0035 0000020b
    /dev/input/event1: 0003 0036 0000068d
    /dev/input/event1: 0000 0000 00000000
    /dev/input/event1: 0003 0036 0000068c
    /dev/input/event1: 0003 0030 00000005
    /dev/input/event1: 0000 0000 00000000
    /dev/input/event1: 0003 0039 ffffffff
    /dev/input/event1: 0000 0000 00000000
    

    这些操作全都是Linux Kernel去做的,只要你点击了屏幕了,硬件设备变回产生硬件终端,Kernel收到硬件终端之后,会对其进行加工,包装成event事件之后添加到/dev/input/目录下,就像如上所示的event1。

  • Android系统的监听

    Android会不断的去监控/dev/input/目录下的所有的设备节点,一旦发现有新的设备节点可读时就会立马读出事件并进行处理。

    • WMS

      而这里的复杂步骤涉及到frameWork层,我们就从WMS开始吧,
      先是有SystemServer启动的WMS。SystemServer.java的startOtherServices()

      wm = WindowManagerService.main(context, inputManager,
                    mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
                    !mFirstBoot, mOnlyCore);
      

      并且同样在这个方法中初始化了InputManagerService,掌管输入事件的服务。

      inputManager = new InputManagerService(context);
      

      我们看到WindowManagerService的main方法传入的就是这个inputManager。
      在InputManagerService的构造方法中,用到了

      mPtr = nativeInit(this, mContext, mHandler.getLooper().getQueue());
      

      native的方法,nativce层不是重点,我这边就快速的将过去了。
      frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp中(没有在本地编译过源码的同学可以去 http://androidxref.com/ 查看,基于当前最新的7.1.1)

      static jlong nativeInit(JNIEnv* env, jclass /* clazz */,
       jobject serviceObj, jobject contextObj, jobject messageQueueObj)     {
       sp<MessageQueue> messageQueue = android_os_MessageQueue_getMessageQueue(env, messageQueueObj);
       if (messageQueue == NULL) {
         jniThrowRuntimeException(env, "MessageQueue is not initialized.");
        return 0;
        }
        NativeInputManager* im = new NativeInputManager(contextObj, serviceObj,
           messageQueue->getLooper());
       im->incStrong(0);
       eturn reinterpret_cast<jlong>(im);
      }
      

      然后看内部类nativeInputManger

      NativeInputManager::NativeInputManager(jobject contextObj,
      ...
      sp<EventHub> eventHub = new EventHub();
      mInputManager = new InputManager(eventHub, this, this);
      }
      

      我们看到创建了一个EventHub类,并且将其交给InputManger并生成一个InputManger对象。
      /frameworks/native/services/inputflinger/InputManager.cpp

      InputManager::InputManager(
        const sp<EventHubInterface>& eventHub,
        const sp<InputReaderPolicyInterface>& readerPolicy,
        const sp<InputDispatcherPolicyInterface>& dispatcherPolicy) {
          mDispatcher = new InputDispatcher(dispatcherPolicy);
          mReader = new InputReader(eventHub, readerPolicy, mDispatcher);
          initialize();
      }
      

      一个分发对象,一个reader对象,并且调用initialize方法

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

      创建读线程和分发线程

      至此,所有的初始化先都ok了,在SystemServer.java,创建了InputManagerService之后没几行就调用了 inputManager.start();

      public void start() {
        ...
        nativeStart(mPtr);
        ...
      }
      

      又看到了native。。。来吧继续相当枯燥的native,我要快进了,我有点写的想吐。。
      frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp

      static void nativeStart(JNIEnv* env, jclass /* clazz */, jlong ptr) {
        NativeInputManager* im = reinterpret_cast<NativeInputManager*>(ptr);
      
        status_t result = im->getInputManager()->start();
        if (result) {
            jniThrowRuntimeException(env, "Input manager could not be started.");
        }
      }
      

      /frameworks/native/services/inputflinger/InputManager.cpp

      status_t InputManager::start() {
        status_t result = mDispatcherThread->run("InputDispatcher", PRIORITY_URGENT_DISPLAY);
        if (result) {
            ALOGE("Could not start InputDispatcher thread due to error %d.", result);
            return result;
        }
      
        result = mReaderThread->run("InputReader", PRIORITY_URGENT_DISPLAY);
        if (result) {
            ALOGE("Could not start InputReader thread due to error %d.", result);
      
            mDispatcherThread->requestExit();
            return result;
        }
      
        return OK;
      }
      

      启动了读线程和分发线程
      /frameworks/native/services/inputflinger/InputReader.cpp

      bool InputReaderThread::threadLoop() {
          mReader->loopOnce();
          return true;
      }
      void InputReader::loopOnce() {
          ...
          size_t count = mEventHub->getEvents(timeoutMillis, mEventBuffer, EVENT_BUFFER_SIZE);
         ...
      }  
      

      不断的loop去通过EventHub去getEvents(越来越偏了,getEvents不继续往下了,知道这个深度已经对于非framework工程师来说已经够了)
      在getEvents方法中去从dev/input/目录下读取设备节点并加工,并返回给InputReader进行处理。

      之后的处理过程以及一系列跳转也是相当复杂,由于本文的初衷并非详解最底层的东西,
      故而此处一并略过直接到底层将event回传给java层的最末
      
    • InputEventReceiver

      在此我们只需要知道由InputChannel构建起了UI进程和底层system_server进程的socket通道。
      最终会从NativeInputEventReceiver.cpp处调起InputEventReceiver的方法

          // Called from native code.
          @SuppressWarnings("unused")
          private void dispatchInputEvent(int seq, InputEvent event) {
              mSeqMap.put(event.getSequenceNumber(), seq);
              onInputEvent(event);
          }
      

      InputEventReceiver是个抽象类,我们在ViewRootImpl中定义了如下

      final class WindowInputEventReceiver extends InputEventReceiver {
        public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
            super(inputChannel, looper);
        }
      
        @Override
        public void onInputEvent(InputEvent event) {
            enqueueInputEvent(event, this, 0, true);
        }
      
        @Override
        public void onBatchedInputEventPending() {
            if (mUnbufferedInputDispatch) {
                super.onBatchedInputEventPending();
            } else {
                scheduleConsumeBatchedInput();
            }
        }
      
        @Override
        public void dispose() {
            unscheduleConsumeBatchedInput();
            super.dispose();
        }
        }
      

      那么我们就看看enqueueInputEvent到底做了些什么操作:

      void enqueueInputEvent(InputEvent event,
            InputEventReceiver receiver, int flags, boolean processImmediately) {
        ...
        if (processImmediately) {
            doProcessInputEvents();
        } else {
            scheduleProcessInputEvents();
        }
      }
      void doProcessInputEvents() {
          while (mPendingInputEventHead != null) {
              ...
              deliverInputEvent(q);
          }
          ...
      }
      private void deliverInputEvent(QueuedInputEvent q) {
         ...
         if (stage != null) {
             stage.deliver(q); 
         } else {
             finishInputEvent(q); 
         }
       }
      

      最终就在这个deliver方法中,而这个stage是个InputStage对象,这个类内部是链表结构,最终会将q分发到可以处理的窗口ViewPostImeInputStage,由它的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);
            ...
            return handled ? FINISH_HANDLED : FORWARD;
      }
      

      调用的view的dispatchPointerEvent方法:

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

      而我们知道,window的最底层的View就是DecorView,那么这个时候调用的应该就是DecorView的dispatchTouchEvent方法

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

      还记的callBack是谁么,在Activity的attach方法中,mWindow.setCallback(this);
      这个callback就是activity本身,所以我们要去Activity中查看它的dispatchTouchEvent方法

      public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
          }
      

      这里分了两步,先去getWindow().superDispatchTouchEvent(ev),这一步会从PhoneWindow->DecorView->ViewGroup(DecorView继承FrameLayout就是ViewGroup),最后实际上是触发了ViewGroup的dispatchTouchEvent方法,也就是activity会先将事件交给DecorView去处理,如果被消耗掉,就返回true。如果没有消耗这个事件,就回调Activity自己的onTouchEvent。

    • 总结

那么把上面的一大坨我们简略的来讲如下的流程:

  • 用户触摸屏幕产生设备节点中断并保存到/dev/input/目录下
  • 底层的EventHub监听目录,将事件读出并加工返回给随着WMS一起启动的底层的InputReader
  • InputReader处理加工之后交给InputDispatcher来进行分发,通过socket通知UI进程的InputEventReceiver接收到事件
  • InputEventReceiver将回调事件一步步传递给Activity来进行分发
  • Activity先将事件交给DecorView来进行处理,如果DecorView消耗则返回true,否则自己回调onTouchEvent方法

以上!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容