从你触摸屏幕开始分析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方法

以上!

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容