從底層源碼分析Android事件傳遞機(jī)制

點(diǎn)擊一下告訴你什么是事件分發(fā)包蓝,知其然知其所以然彪置,所以這邊我篇我們就對源碼展開分析县耽!
我們知道事件都是由Activity往下面分發(fā)的!但是Activity.dispatchTouchEvent()又是從被哪里調(diào)用的呢匈勋?好奇不好奇监憎?

1.首先從手指觸摸到屏幕開始

我們知道Android是基于Linux系統(tǒng)的绞呈。當(dāng)輸入設(shè)備可用時(shí)(這里的輸入設(shè)備包括很多設(shè)備贸人,比如觸摸屏和鍵盤是Android最普遍也是最標(biāo)準(zhǔn)的輸入設(shè)備,另外它還包括外接的游戲手柄佃声、鼠標(biāo)等)艺智,Linux內(nèi)核會為輸入設(shè)置創(chuàng)建對應(yīng)的設(shè)備節(jié)點(diǎn)。當(dāng)輸入設(shè)備不可用時(shí)圾亏,就把對應(yīng)的設(shè)備節(jié)點(diǎn)刪除十拣,這也是如果我們的屏幕意外摔碎了或者其他原因?qū)е掠|摸屏幕不可用時(shí)觸摸沒有反應(yīng)的根本原因封拧。當(dāng)我們的輸入設(shè)備可用時(shí)(我們這里只來講解觸摸屏),我們對觸摸屏進(jìn)行操作時(shí)夭问,Linux就會收到相應(yīng)的硬件中斷泽西,然后將中斷加工成原始的輸入事件并寫入相應(yīng)的設(shè)備節(jié)點(diǎn)中。而我們的Android 輸入系統(tǒng)所做的事情概括起來說就是**監(jiān)控這些設(shè)備節(jié)點(diǎn)缰趋,當(dāng)某個(gè)設(shè)備節(jié)點(diǎn)有數(shù)據(jù)可讀時(shí)捧杉,將數(shù)據(jù)讀出并進(jìn)行一系列的翻譯加工,然后在所有的窗口中找到合適的事件接收者秘血,并派發(fā)給它味抖。
手指進(jìn)行一系列操作(這里指的是手指的移動,這一步可能沒有)
手指抬起或者因其他其他原因(突然間來了個(gè)電話之類的)導(dǎo)致事件結(jié)束

上面我們說到了Android 輸入系統(tǒng)所做的事情概括起來說就是監(jiān)控設(shè)備節(jié)點(diǎn)灰粮,當(dāng)某個(gè)設(shè)備節(jié)點(diǎn)有數(shù)據(jù)可讀時(shí)仔涩,將數(shù)據(jù)讀出并進(jìn)行一系列的翻譯加工,然后在所有的窗口中找到合適的事件接收者谋竖,并派發(fā)給它红柱。那么它是如何做的呢,蓖乘,我們來具體分析一下锤悄。Android 的輸入系統(tǒng)InputManagerService(以下簡稱為IMS)作為系統(tǒng)服務(wù),它像其他系統(tǒng)服務(wù)一樣在SystemServer進(jìn)程中創(chuàng)建嘉抒。
Linux會為所有可用的輸入設(shè)備在/dev/input目錄在建立event0~n或者其他名稱的設(shè)備節(jié)點(diǎn)零聚,Android輸入系統(tǒng)會監(jiān)控這些設(shè)備節(jié)點(diǎn),具體是通過INotify和Epoll機(jī)制來進(jìn)行監(jiān)控些侍。而不是通過一個(gè)線程進(jìn)行輪詢查詢隶症。

INotify機(jī)制

INotify是Linux內(nèi)核提供的一種文件系統(tǒng)變化通知機(jī)制。它可以為應(yīng)用程序監(jiān)控文件系統(tǒng)的變化岗宣,如文件的新建蚂会,刪除等。
//創(chuàng)建INotify對象耗式,并用描述符inotifyFd 描述它
int inotifyFd = inotify_init();
/*
添加監(jiān)聽
inotify_add_watch函數(shù)參數(shù)說明
inotifyFd:上面建立的INotify對象的描述符胁住,當(dāng)監(jiān)聽的目錄或文件發(fā)生變化時(shí)記錄在INotify對象
“/dev/input”:被監(jiān)聽的文件或者目錄
IN_CREATE | IN_DELETE:事件類型
綜合起來下面的代碼表示的意思就是當(dāng)“/dev/input”下發(fā)生IN_CREATE | IN_DELETE(創(chuàng)建或者刪除)時(shí)即把這個(gè)事件寫入到INotify對象中
*/
int wd = inotify_add_watch(inotifyFd, "/dev/input", IN_CREATE|IN_DELETE )

Epoll機(jī)制

在上述INotify機(jī)制中我們知道了我們只需關(guān)心inotifyFd這個(gè)描述符就行了,可是事件是隨機(jī)發(fā)生的刊咳,我們也不會本末倒置的采用輪詢的方式輪詢這個(gè)描述符彪见,因?yàn)槿绻@樣做的話會浪費(fèi)大量系統(tǒng)資源。這時(shí)候我們Linux的另一個(gè)機(jī)制就派上用場了娱挨,即Epoll機(jī)制余指。Epoll機(jī)制簡單的說就是使用一次等待來獲取多個(gè)描述的可讀或者可寫狀態(tài)。這樣我們不必對每一個(gè)描述符創(chuàng)建獨(dú)立的線程進(jìn)行阻塞讀取跷坝,在避免了資源浪費(fèi)的同時(shí)獲得較快的相應(yīng)速度酵镜。
至此原始輸入事件已經(jīng)讀取完畢碉碉,Android輸入系統(tǒng)對原始輸入事件進(jìn)行翻譯加工以及派發(fā)的詳細(xì)過程很復(fù)雜。我們這里只分析其中一部分——IMS與窗口笋婿。

Avtivity,Window,PhoneWindow,以及ViewRootImpl之間的聯(lián)系

上文中我們也說到了IMS會在所有的窗口中找到合適的事件接收者誉裆。IMS是運(yùn)行在SystemServer進(jìn)程中,而我們的窗口呢缸濒,是在我們的應(yīng)用進(jìn)程中。這就引出了我們在上文中留下的懸念

// ② 初始化mInputChanel粱腻。InputChannel是窗口接收來自InputDispatcher的輸入事件的管道庇配。這部分內(nèi)容我們將在下一篇介紹。
  if ((mWindowAttributes.inputFeatures
          & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
      mInputChannel = new InputChannel();
  }
  ...
  ...
// ③ 如果mInputChannel不為空绍些,則創(chuàng)建mInputEventReceiver用于接收輸入事件捞慌。
  if (mInputChannel != null) {
      mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
              Looper.myLooper());
  }
InputChannel

InputChannel的本質(zhì)是一對SocketPair(非網(wǎng)絡(luò)套接字)。套接字可以用于網(wǎng)絡(luò)通信柬批,也可以用于本機(jī)內(nèi)的進(jìn)程通信啸澡。進(jìn)程間通信的一種方式

public final class InputChannel implements Parcelable {
    private static final String TAG = "InputChannel";
    
    private static final boolean DEBUG = false;
    
    public static final Parcelable.Creator<InputChannel> CREATOR
            = new Parcelable.Creator<InputChannel>() {
        public InputChannel createFromParcel(Parcel source) {
            InputChannel result = new InputChannel();
            result.readFromParcel(source);
            return result;
        }
        
        public InputChannel[] newArray(int size) {
            return new InputChannel[size];
        }
    };
    @SuppressWarnings("unused")
    private long mPtr; // used by native code
    private static native InputChannel[] nativeOpenInputChannelPair(String name);
    private native void nativeDispose(boolean finalized);
    private native void nativeTransferTo(InputChannel other);
    private native void nativeReadFromParcel(Parcel parcel);
    private native void nativeWriteToParcel(Parcel parcel);
    private native void nativeDup(InputChannel target);
    
    private native String nativeGetName();
}
WindowInputEventReceiver

得到InputChannel后,便用它創(chuàng)建WindowInputEventReceiver氮帐,WindowInputEventReceiver繼承于InputEventReceiver嗅虏,InputEventReceiver對象可以接收來自InputChannel的輸入事件,并觸發(fā)其onInputEvent方法的回調(diào)上沐。我們這里的是WindowInputEventReceiver皮服,所以我們來看一下這個(gè)類

final class WindowInputEventReceiver extends InputEventReceiver {
        public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) {
            super(inputChannel, looper);
        }

        @Override
        public void onInputEvent(InputEvent event, int displayId) {
            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) {
      ...
    //① 將InputEvent對應(yīng)的InputEventReceiver封裝為一個(gè)QueuedInputEvent 
    QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
    //② 將新建的QueuedInputEvent 追加到mPendingInputEventTail所表示的一個(gè)單向鏈表中
    QueuedInputEvent last = mPendingInputEventTail;
    if (last == null) {
        mPendingInputEventHead = q;
        mPendingInputEventTail = q;
    } else {
        last.mNext = q;
        mPendingInputEventTail = q;
    }
    mPendingInputEventCount += 1;
   

    if (processImmediately) {
        //③ 如果第三個(gè)參數(shù)為true,則直接在當(dāng)前線程中開始對輸入事件的處理工作
        doProcessInputEvents();
    } else {
        //④ 否則將處理事件的請求發(fā)送給主線程的Handler参咙,隨后進(jìn)行處理
        scheduleProcessInputEvents();

    }
doProcessInputEvents
void doProcessInputEvents() {
       //遍歷整個(gè)輸入事件隊(duì)列龄广,并逐一處理
    while (mPendingInputEventHead != null) {
        QueuedInputEvent q = mPendingInputEventHead;
        mPendingInputEventHead = q.mNext;
        if (mPendingInputEventHead == null) {
            mPendingInputEventTail = null;
        }
        q.mNext = null;

        mPendingInputEventCount -= 1;
        Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName,
                mPendingInputEventCount);

        long eventTime = q.mEvent.getEventTimeNano();
        long oldestEventTime = eventTime;
        if (q.mEvent instanceof MotionEvent) {
            MotionEvent me = (MotionEvent)q.mEvent;
            if (me.getHistorySize() > 0) {
                oldestEventTime = me.getHistoricalEventTimeNano(0);
            }
        }
        mChoreographer.mFrameInfo.updateInputEventTime(eventTime, oldestEventTime);
        deliverInputEvent()//方法會將完成單個(gè)事件的整個(gè)處理流程
deliverInputEvent
   private void deliverInputEvent(QueuedInputEvent q) {
        Trace.asyncTraceBegin(Trace.TRACE_TAG_VIEW, "deliverInputEvent",
                q.mEvent.getSequenceNumber());
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onInputEvent(q.mEvent, 0);
        }

        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);
        }
    }
new ViewPostImeInputStage(mSyntheticInputStage);
從這個(gè)方法processPointerEvent將事件傳遞給了View樹的根節(jié)點(diǎn),調(diào)用了mView.dispatchPointerEvent(event);
private int processPointerEvent(QueuedInputEvent q) {
        final MotionEvent event = (MotionEvent)q.mEvent;
        mAttachInfo.mUnbufferedDispatchRequested = false;
        mAttachInfo.mHandlingPointerEvent = true;
        // 此時(shí)ViewRootImpl會將事件的處理權(quán)移交給View樹的根節(jié)點(diǎn)蕴侧,調(diào)用dispatchPointerEvent函數(shù)  
        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;
    }

這里肯定有疑問了择同,為什么會傳到mView.dispatchPointerEvent(event),不應(yīng)該先到Activity.dispatchPointerEvent(event)?

DecorView.dispatchTouchEvent(MotionEvent ev)

    @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);
    }

Window.Callback cb = mWindow.getCallback();這個(gè)是Activity里面實(shí)現(xiàn)的

 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,
            Window window, ActivityConfigCallback activityConfigCallback) {
        mWindow = new PhoneWindow(this, window, activityConfigCallback);
        mWindow.setWindowControllerCallback(this);
        mWindow.setCallback(this);
}

Activity在這里實(shí)現(xiàn)了Window.Callback

public class Activity extends ContextThemeWrapper
        implements LayoutInflater.Factory2,
        Window.Callback,
}

   public interface Callback {   
        public boolean dispatchTouchEvent(MotionEvent event);
}
簡單暴力查看
image.png
總結(jié)下!

1.當(dāng)我們觸摸屏幕時(shí)净宵,通過INotify機(jī)制&Epoll機(jī)制改變和發(fā)送給WindowInputEventReceiver敲才。
2.WindowInputEventReceiver是ViewRootImpl的內(nèi)部類,通過enqueueInputEvent方法塘娶,將輸入事件加入輸入事件隊(duì)列中归斤,并進(jìn)行處理和轉(zhuǎn)發(fā)。
3.ViewPostImeInputStage收到輸入事件刁岸,將事件傳遞給DecorView的dispatchPointerEvent()方法(是View的方法)
4.dispatchPointerEvent()方法通過DecorView中的dispatchTouchEvent()方法脏里,調(diào)用了Activity的dispatchTouchEvent()方法。

到了Activity后續(xù)如何呢虹曙?請點(diǎn)擊迫横!

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末番舆,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子矾踱,更是在濱河造成了極大的恐慌恨狈,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件呛讲,死亡現(xiàn)場離奇詭異禾怠,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)贝搁,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門吗氏,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人雷逆,你說我怎么就攤上這事弦讽。” “怎么了膀哲?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵往产,是天一觀的道長。 經(jīng)常有香客問我某宪,道長仿村,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任缩抡,我火速辦了婚禮奠宜,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘瞻想。我一直安慰自己压真,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布蘑险。 她就那樣靜靜地躺著滴肿,像睡著了一般。 火紅的嫁衣襯著肌膚如雪佃迄。 梳的紋絲不亂的頭發(fā)上泼差,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機(jī)與錄音呵俏,去河邊找鬼堆缘。 笑死,一個(gè)胖子當(dāng)著我的面吹牛普碎,可吹牛的內(nèi)容都是我干的吼肥。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼缀皱!你這毒婦竟也來了斗这?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤啤斗,失蹤者是張志新(化名)和其女友劉穎表箭,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钮莲,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡免钻,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了臂痕。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伯襟。...
    茶點(diǎn)故事閱讀 38,064評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖握童,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情叛赚,我是刑警寧澤澡绩,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站俺附,受9級特大地震影響肥卡,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜事镣,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一步鉴、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧璃哟,春花似錦氛琢、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至铐伴,卻和暖如春撮奏,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背当宴。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工畜吊, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人户矢。 一個(gè)月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓玲献,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子青自,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評論 2 345