從你觸摸屏幕開始分析android觸摸事件分發(fā)

本篇只會講到觸發(fā)ViewGroup的dispatchTouchEvent為止,因為接下去的一搜一大把,或者說有點基礎(chǔ)的應(yīng)該都了解。

  • 從觸摸開始

    首先腐晾,請你打開命令行工具叉弦。輸入adb shell進(jìn)入到shell命令,然后輸入getevent藻糖,會監(jiān)聽打印觸摸屏幕的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"
    

    當(dāng)你使出你的一陽指點擊屏幕的時候,變回不斷的去獲取到你的點擊事件巨柒,就像這樣:

    /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去做的樱拴,只要你點擊了屏幕了,硬件設(shè)備變回產(chǎn)生硬件終端洋满,Kernel收到硬件終端之后晶乔,會對其進(jìn)行加工,包裝成event事件之后添加到/dev/input/目錄下牺勾,就像如上所示的event1瘪弓。

  • Android系統(tǒng)的監(jiān)聽

    Android會不斷的去監(jiān)控/dev/input/目錄下的所有的設(shè)備節(jié)點,一旦發(fā)現(xiàn)有新的設(shè)備節(jié)點可讀時就會立馬讀出事件并進(jìn)行處理禽最。

    • WMS

      而這里的復(fù)雜步驟涉及到frameWork層腺怯,我們就從WMS開始吧,
      先是有SystemServer啟動的WMS川无。SystemServer.java的startOtherServices()

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

      并且同樣在這個方法中初始化了InputManagerService呛占,掌管輸入事件的服務(wù)。

      inputManager = new InputManagerService(context);
      

      我們看到WindowManagerService的main方法傳入的就是這個inputManager懦趋。
      在InputManagerService的構(gòu)造方法中晾虑,用到了

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

      native的方法,nativce層不是重點仅叫,我這邊就快速的將過去了帜篇。
      frameworks/base/services/core/jni/com_android_server_input_InputManagerService.cpp中(沒有在本地編譯過源碼的同學(xué)可以去 http://androidxref.com/ 查看,基于當(dāng)前最新的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);
      }
      

      然后看內(nèi)部類nativeInputManger

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

      我們看到創(chuàng)建了一個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();
      }
      

      一個分發(fā)對象,一個reader對象坎缭,并且調(diào)用initialize方法

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

      創(chuàng)建讀線程和分發(fā)線程

      至此竟痰,所有的初始化先都o(jì)k了,在SystemServer.java掏呼,創(chuàng)建了InputManagerService之后沒幾行就調(diào)用了 inputManager.start();坏快,

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

      又看到了native。憎夷。莽鸿。來吧繼續(xù)相當(dāng)枯燥的native,我要快進(jìn)了拾给,我有點寫的想吐祥得。臼予。
      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;
      }
      

      啟動了讀線程和分發(fā)線程
      /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不繼續(xù)往下了啃沪,知道這個深度已經(jīng)對于非framework工程師來說已經(jīng)夠了)
      在getEvents方法中去從dev/input/目錄下讀取設(shè)備節(jié)點并加工,并返回給InputReader進(jìn)行處理窄锅。

      之后的處理過程以及一系列跳轉(zhuǎn)也是相當(dāng)復(fù)雜创千,由于本文的初衷并非詳解最底層的東西,
      故而此處一并略過直接到底層將event回傳給java層的最末
      
    • InputEventReceiver

      在此我們只需要知道由InputChannel構(gòu)建起了UI進(jìn)程和底層system_server進(jìn)程的socket通道入偷。
      最終會從NativeInputEventReceiver.cpp處調(diào)起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對象疏之,這個類內(nèi)部是鏈表結(jié)構(gòu)殿雪,最終會將q分發(fā)到可以處理的窗口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;
      }
      

      調(diào)用的view的dispatchPointerEvent方法:

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

      而我們知道,window的最底層的View就是DecorView锋爪,那么這個時候調(diào)用的應(yīng)該就是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),最后實際上是觸發(fā)了ViewGroup的dispatchTouchEvent方法拯爽,也就是activity會先將事件交給DecorView去處理索抓,如果被消耗掉,就返回true毯炮。如果沒有消耗這個事件逼肯,就回調(diào)Activity自己的onTouchEvent。

    • 總結(jié)

那么把上面的一大坨我們簡略的來講如下的流程:

  • 用戶觸摸屏幕產(chǎn)生設(shè)備節(jié)點中斷并保存到/dev/input/目錄下
  • 底層的EventHub監(jiān)聽目錄桃煎,將事件讀出并加工返回給隨著WMS一起啟動的底層的InputReader
  • InputReader處理加工之后交給InputDispatcher來進(jìn)行分發(fā)篮幢,通過socket通知UI進(jìn)程的InputEventReceiver接收到事件
  • InputEventReceiver將回調(diào)事件一步步傳遞給Activity來進(jìn)行分發(fā)
  • Activity先將事件交給DecorView來進(jìn)行處理,如果DecorView消耗則返回true为迈,否則自己回調(diào)onTouchEvent方法

以上洲拇!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市曲尸,隨后出現(xiàn)的幾起案子赋续,更是在濱河造成了極大的恐慌,老刑警劉巖另患,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件纽乱,死亡現(xiàn)場離奇詭異,居然都是意外死亡昆箕,警方通過查閱死者的電腦和手機(jī)鸦列,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進(jìn)店門租冠,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人薯嗤,你說我怎么就攤上這事顽爹。” “怎么了骆姐?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵镜粤,是天一觀的道長。 經(jīng)常有香客問我玻褪,道長肉渴,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任带射,我火速辦了婚禮同规,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘窟社。我一直安慰自己券勺,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布灿里。 她就那樣靜靜地躺著朱灿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪钠四。 梳的紋絲不亂的頭發(fā)上盗扒,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天,我揣著相機(jī)與錄音缀去,去河邊找鬼侣灶。 笑死,一個胖子當(dāng)著我的面吹牛缕碎,可吹牛的內(nèi)容都是我干的褥影。 我是一名探鬼主播,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼咏雌,長吁一口氣:“原來是場噩夢啊……” “哼凡怎!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起赊抖,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤统倒,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后氛雪,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體房匆,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了浴鸿。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片井氢。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖岳链,靈堂內(nèi)的尸體忽然破棺而出花竞,到底是詐尸還是另有隱情,我是刑警寧澤掸哑,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布约急,位于F島的核電站,受9級特大地震影響举户,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜遍烦,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一俭嘁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧服猪,春花似錦供填、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至膳帕,卻和暖如春粘捎,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背危彩。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工攒磨, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人汤徽。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓娩缰,卻偏偏與公主長得像,于是被迫代替她去往敵國和親谒府。 傳聞我的和親對象是個殘疾皇子拼坎,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容