ViewRootImpl源碼分析事件分發(fā)

前面講到View與WindowManager與ViewRootImpl中只講到了ViewRootImpl是如何觸發(fā)View的繪制的操软,但ViewRootImpl的功能可不只是繪制而已,本篇文章最主要介紹ViewRootImpl的事件分發(fā)功能。當(dāng)然,對(duì)于事件分發(fā)劈榨,大家肯定很熟悉销睁,但是大家平常所看到的事件分發(fā)機(jī)制則是在Activity得到觸摸事件后的,然后再傳遞給View處理浩习,那么到底是誰傳遞給Activity觸摸事件的呢?

ViewRootImpl事件分發(fā)

前面只是介紹到了ViewRootImpl的繪制功能济丘,當(dāng)然谱秽,你在看ViewRootImpl源碼的時(shí)候會(huì)看到很多很多Event之類的,沒錯(cuò)ViewRootImpl也是有事件分發(fā)的摹迷。要知道疟赊,當(dāng)用戶點(diǎn)擊屏幕產(chǎn)生一個(gè)觸摸行為,這個(gè)觸摸行為則是通過底層硬件來傳遞捕獲峡碉,然后交給ViewRootImpl近哟,接著將事件傳遞給DecorView,而DecorView再交給PhoneWindow鲫寄,PhoneWindow再交給Activity吉执,然后接下來就是我們常見的View事件分發(fā)了。

**硬件 -> ViewRootImpl -> DecorView -> PhoneWindow -> Activity **

我們從ViewRootImpl開始看下具體的事件分發(fā)過程
首先會(huì)到ViewRootImpl的dispatchInputEvent

    public void dispatchInputEvent(InputEvent event, InputEventReceiver receiver) {
        SomeArgs args = SomeArgs.obtain();
        args.arg1 = event;
        args.arg2 = receiver;
        Message msg = mHandler.obtainMessage(MSG_DISPATCH_INPUT_EVENT, args);
        msg.setAsynchronous(true);
        mHandler.sendMessage(msg);
    }

這里有兩個(gè)參數(shù)地来,InputEvent和InputEventReceiver

  • InputEvent:輸入事件的基類戳玫,它有兩個(gè)子類,分別是KeyEvent,MotionEvent對(duì)應(yīng)鍵盤輸入事件和屏幕觸摸事件未斑,這兩個(gè)也是我們比較熟悉的了量九。

  • InputEventReceiver:為應(yīng)用程序提供了一個(gè)低級(jí)的機(jī)制來接收輸入事件。也就是用來接收輸入事件的颂碧,然后交給ViewRootImpl的dispatchInputEvent去分發(fā)荠列。

上面代碼可以看到輸入事件接收器通過Handler切換到UI線程中。

   final class ViewRootHandler extends Handler {
        ...
        @Override
        public void handleMessage(Message msg) {
            switch (msg.what) {
                 ...
                case MSG_DISPATCH_INPUT_EVENT: {
                    SomeArgs args = (SomeArgs)msg.obj;
                    InputEvent event = (InputEvent)args.arg1;
                    InputEventReceiver receiver = (InputEventReceiver)args.arg2;
                    enqueueInputEvent(event, receiver, 0, true);
                    args.recycle();
                } break;
                ...
            }
        }

轉(zhuǎn)發(fā)到UI線程后载城,調(diào)用到enqueueInputEvent

  void enqueueInputEvent(InputEvent event,
            InputEventReceiver receiver, int flags, boolean processImmediately) {
        adjustInputEventForCompatibility(event);
        //將當(dāng)前輸入事件加入隊(duì)列中排列等候執(zhí)行
        QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
        //輸入事件添加進(jìn)隊(duì)列后肌似,加入輸入事件的默認(rèn)尾部
        QueuedInputEvent last = mPendingInputEventTail;
        if (last == null) {
            mPendingInputEventHead = q;
            mPendingInputEventTail = q;
        } else {
            last.mNext = q;
            mPendingInputEventTail = q;
        }
        //隊(duì)列計(jì)數(shù)
        mPendingInputEventCount += 1;
        ...
        //processImmediately則是判斷是同步還是異步,前面我們?cè)趆andler中調(diào)用的诉瓦,因?yàn)槭窃赨I線程川队,肯定是同步的力细,所以傳遞了參數(shù)是true,如果是異步固额,則調(diào)用到scheduleProcessInputEvents()
        if (processImmediately) {
            doProcessInputEvents();
        } else {
            scheduleProcessInputEvents();
        }
    }

可以看到enqueueInputEvent將當(dāng)前的輸入事件加入隊(duì)列中眠蚂,QueuedInputEvent相當(dāng)于一個(gè)鏈表,
可以看到里面成員變量有next,用來鏈接下一個(gè)成員

  private static final class QueuedInputEvent {
        ...
        public QueuedInputEvent mNext;
        public InputEvent mEvent;
        public InputEventReceiver mReceiver;
        ...
}

而obtainQueuedInputEvent則是為當(dāng)前的輸入事件構(gòu)建一個(gè)鏈表結(jié)構(gòu)斗躏,然后鏈接到之前隊(duì)列的尾部

    private QueuedInputEvent obtainQueuedInputEvent(InputEvent event,
            InputEventReceiver receiver, int flags) {
        QueuedInputEvent q = mQueuedInputEventPool;
        if (q != null) {
            mQueuedInputEventPoolSize -= 1;
            mQueuedInputEventPool = q.mNext;
            q.mNext = null;
        } else {
            q = new QueuedInputEvent();
        }

        q.mEvent = event;
        q.mReceiver = receiver;
        q.mFlags = flags;
        return q;
    }

接著到了

        if (processImmediately) {
            doProcessInputEvents();
        } else {
            scheduleProcessInputEvents();
        }

processImmediately則是判斷是同步還是異步逝慧,前面我們?cè)趆andler中調(diào)用的,因?yàn)槭窃赨I線程啄糙,肯定是同步的笛臣,所以傳遞了參數(shù)是true,如果是異步隧饼,則調(diào)用到scheduleProcessInputEvents()

   private void scheduleProcessInputEvents() {
        if (!mProcessInputEventsScheduled) {
            mProcessInputEventsScheduled = true;
            Message msg = mHandler.obtainMessage(MSG_PROCESS_INPUT_EVENTS);
            msg.setAsynchronous(true);
            mHandler.sendMessage(msg);
        }
    }

Handler中

  case MSG_PROCESS_INPUT_EVENTS:
                mProcessInputEventsScheduled = false;
                doProcessInputEvents();
                break;

可以看到最后還是調(diào)用到了doProcessInputEvents沈堡。

void doProcessInputEvents() {
        //循環(huán)取出隊(duì)列中的輸入事件
        while (mPendingInputEventHead != null) {
            QueuedInputEvent q = mPendingInputEventHead;
            mPendingInputEventHead = q.mNext;
            ...
            mPendingInputEventCount -= 1;
            ...
            //分發(fā)處理
            deliverInputEvent(q);
        }

        //處理完所有輸入事件,清楚標(biāo)志
        if (mProcessInputEventsScheduled) {
            mProcessInputEventsScheduled = false;
            mHandler.removeMessages(MSG_PROCESS_INPUT_EVENTS);
        }
    }

可以看到該方法是用來循環(huán)獲取隊(duì)列中的輸入事件
接著進(jìn)行分發(fā)處理deliverInputEvent(q)

 private void deliverInputEvent(QueuedInputEvent q) {
        //校驗(yàn)輸入事件
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
        }

        InputStage stage;
        if (q.shouldSendToSynthesizer()) {
            stage = mSyntheticInputStage;
        } else {
            stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
        }

        if (stage != null) {
            stage.deliver(q);
        } else {
            finishInputEvent(q);
        }
    }

這里InputStage則是一個(gè)實(shí)現(xiàn)處理輸入事件責(zé)任的階段燕雁,它是一個(gè)基類诞丽,也就是說InputStage提供一系列處理輸入事件的方法,也可以轉(zhuǎn)發(fā)給其他事件處理拐格,而具體的處理則是看它的實(shí)現(xiàn)類僧免。每種InputStage可以處理一定的事件類型,比如AsyncInputStage禁荒、ViewPreImeInputStage猬膨、ViewPostImeInputStage等角撞。當(dāng)一個(gè)InputEvent到來時(shí)呛伴,ViewRootImpl會(huì)尋找合適它的InputStage來處理。
InputStage的處理情況為谒所,會(huì)先調(diào)用deliver開始處理

InputState

最終的事件分發(fā)處理則是在apply方法里的onProcess方法热康。

對(duì)于點(diǎn)擊事件來說,InputState的子類ViewPostImeInputStage可以處理它劣领,我們看下ViewPostImeInputStage的onProcess

 @Override
        protected int onProcess(QueuedInputEvent q) {
            if (q.mEvent instanceof KeyEvent) {
                return processKeyEvent(q);
            } else {
                // If delivering a new non-key event, make sure the window is
                // now allowed to start updating.
                handleDispatchWindowAnimationStopped();
                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);
                }
            }
        }

在ViewPostImeInputStage里面有判斷是鍵盤事件還是觸摸事件姐军,這里我們只看觸摸事件,調(diào)用到了
processPointerEvent(q)方法

private int processPointerEvent(QueuedInputEvent q) {
            final MotionEvent event = (MotionEvent)q.mEvent;

            mAttachInfo.mUnbufferedDispatchRequested = false;
            boolean handled = mView.dispatchPointerEvent(event);
            ...
            return handled ? FINISH_HANDLED : FORWARD;
        }

而這里調(diào)用到了mView.dispatchPointerEvent尖淘,這里的mView就是DecorView奕锌,細(xì)心的話可能就知道m(xù)View是在setView的時(shí)候賦值的

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;
                ...
       }
}

再看View的dispatchPointerEvent

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

可以看到,這里會(huì)再判斷當(dāng)前事件是否是觸摸事件村生,如果是則調(diào)用了dispatchTouchEvent進(jìn)行分發(fā)惊暴,
而我們的DecorView是繼承于View的,根據(jù)多態(tài)趁桃,我們看下DecorView對(duì)dispatchTouchEvent的重寫

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

可以看到辽话,這里調(diào)用了callback.dispatchTouchEvent肄鸽,Callback是Window里面的一個(gè)接口

   public interface Callback {
        ...
        public boolean dispatchKeyEvent(KeyEvent event);
        ...
        public boolean dispatchTouchEvent(MotionEvent event);
   }

既然是callback調(diào)用了,那么到底是誰實(shí)現(xiàn)了Callback接口去調(diào)用的油啤?
正是Activity

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback, KeyEvent.Callback,
        OnCreateContextMenuListener, ComponentCallbacks2,
        Window.OnWindowDismissedCallback {
        ..
}

而在Activity的attach方法中

  final void attach(Context context, ActivityThread aThread,
            Instrumentation instr, IBinder token, int ident,
            Application application, Intent intent, ActivityInfo info,
            CharSequence title, Activity parent, String id,
            NonConfigurationInstances lastNonConfigurationInstances,
            Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
        ...
        mWindow = new PhoneWindow(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
  }

PhoneWindow通過設(shè)置setCallback將Callback設(shè)置為this也就是Activity典徘。到這里,點(diǎn)擊事件就傳到了Activity益咬,之后的事件分發(fā)就是大家比較熟悉的了逮诲。

小結(jié)

ViewRootImpl事件分發(fā)流程圖

ViewRootImpl事件分發(fā)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市础废,隨后出現(xiàn)的幾起案子汛骂,更是在濱河造成了極大的恐慌,老刑警劉巖评腺,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件帘瞭,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡蒿讥,警方通過查閱死者的電腦和手機(jī)蝶念,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來芋绸,“玉大人媒殉,你說我怎么就攤上這事∷ち玻” “怎么了廷蓉?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)马昙。 經(jīng)常有香客問我桃犬,道長(zhǎng),這世上最難降的妖魔是什么行楞? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任攒暇,我火速辦了婚禮,結(jié)果婚禮上子房,老公的妹妹穿的比我還像新娘形用。我一直安慰自己,他們只是感情好证杭,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布田度。 她就那樣靜靜地躺著,像睡著了一般解愤。 火紅的嫁衣襯著肌膚如雪镇饺。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天琢歇,我揣著相機(jī)與錄音兰怠,去河邊找鬼梦鉴。 笑死,一個(gè)胖子當(dāng)著我的面吹牛揭保,可吹牛的內(nèi)容都是我干的肥橙。 我是一名探鬼主播,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼秸侣,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼存筏!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起味榛,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤椭坚,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后搏色,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體善茎,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年频轿,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了垂涯。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡航邢,死狀恐怖耕赘,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情膳殷,我是刑警寧澤操骡,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布,位于F島的核電站赚窃,受9級(jí)特大地震影響册招,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜考榨,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一跨细、第九天 我趴在偏房一處隱蔽的房頂上張望鹦倚。 院中可真熱鬧河质,春花似錦、人聲如沸震叙。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽媒楼。三九已至乐尊,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間划址,已是汗流浹背扔嵌。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國(guó)打工限府, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人痢缎。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓胁勺,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親独旷。 傳聞我的和親對(duì)象是個(gè)殘疾皇子署穗,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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