Android觸摸事件的傳遞(六)-ViewRootImpl-->Activity

了解更多,移步Android觸摸事件傳遞機制系列詳解
Android觸摸事件的傳遞(五)--輸入系統(tǒng)-InputChannel中事件傳遞給ViewRootImpl并調(diào)用deliverInputEvent處理事件。

1 ViewRootImpl的創(chuàng)建

  • 我們可以猜測,如果外界想要傳遞點擊事件給Activity,那么它就必須持有Activity的引用,這沒錯,在Activityattach方法中:
    mWindow持有了Activity的引用,它通過setCallback方法來持有Activity.(callback后面說)
    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) {
        attachBaseContext(context);

        mFragments.attachHost(null );

        mWindow = new PhoneWindow(this);
        mWindow.setCallback(this);
        mWindow.setOnWindowDismissedCallback(this);
        mWindow.getLayoutInflater().setPrivateFactory(this);
    }
  • Activity啟動以后,在它的onResume以后,DecorView才開始attachWindowManager從而顯示出來。(這句話好厲害)
  • 請看Activity的makeVisible方法,代碼如下:
    void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager()
            wm.addView(mDecor, getWindow().getAttributes())
            mWindowAdded = true
        }
        mDecor.setVisibility(View.VISIBLE)
    }
  • 系統(tǒng)就會完成添加Window的過程,看WindowManagerGlobaladdView方法
    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        ViewRootImpl root;
        View panelParentView = null;
        ...這里省略了一堆代碼
        root = new ViewRootImpl(view.getContext(), display);
        view.setLayoutParams(wparams);
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);

        
        try {
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            
            synchronized (mLock) {
                final int index = findViewLocked(view, false);
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
            }
            throw e;
        }
    }
  • 可以看到,ViewRootImpl創(chuàng)建了,在ViewRootImplsetView方法(此方法運行在UI線程)中,會通過跨進程的方式向WMS(WindowManagerService)發(fā)起一個調(diào)用,從而將DecorView最終添加到Window上。
  • 在這個過程中,ViewRootImpl、DecorViewWMS會彼此向關(guān)聯(lián),同時會創(chuàng)建InputChannel、InputQueueWindowInputEventReceiver來接受點擊事件的消息。
    如何調(diào)用到deliverInputEvent 移步Android觸摸事件的傳遞(五)--輸入系統(tǒng)-InputChannel

2 DecorView

ViewRootImpldispatchInputEvent方法調(diào)用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 (stage != null) {
            stage.deliver(q);
        } else {
            finishInputEvent(q);
        }
    }
  • ViewRootImpl中,有一系列類似于InputStage(輸入事件舞臺)的概念,每種InputStage可以處理一定的事件類型,比如AsyncInputStage、ViewPreImeInputStage、ViewPostImeInputStage等。
  • 對于點擊事件來說,ViewPostImeInputStage可以處理它,ViewPostImeInputStage中,有一個processPointerEvent方法,如下,它會調(diào)用mViewdispatchPointerEvent方法,注意,這里的mView其實就是DecorView。
  • mView變量是在setview方法中賦值的,對于應(yīng)用窗口來說,
  • mView變量指向PhoneWindow的內(nèi)部類DecorView對象,但是調(diào)用的是父類ViewdispatchPointerEvent方法,
    注:繼承關(guān)系如下
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker{...}

public class FrameLayout extends ViewGroup {...}

public abstract class ViewGroup extends View implements ViewParent, ViewManager{..}
        private int processPointerEvent(QueuedInputEvent q) {
            final MotionEvent event = (MotionEvent)q.mEvent;

            mAttachInfo.mUnbufferedDispatchRequested = false;
            boolean handled = mView.dispatchPointerEvent(event);
            if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) {
                mUnbufferedInputDispatch = true;
                if (mConsumeBatchedInputScheduled) {
                    scheduleConsumeBatchedInputImmediately();
                }
            }
            return handled ? FINISH_HANDLED : FORWARD;
        }

3 activity

View的實現(xiàn)中,dispatchPointerEvent的邏輯如下,這樣一來,點擊事件就傳遞給了DecorView的父View的dispatchTouchEvent方法。

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

DecorViewdispatchTouchEvent的實現(xiàn)如下,需要強調(diào)的是,DecorViewPhoneWindow的內(nèi)部類

        public boolean dispatchTouchEvent(MotionEvent ev) {
            final Callback cb = getCallback();
            return cb != null && !isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev)
                    : super.dispatchTouchEvent(ev);
        }
  • 這里這個cb對象其實就是Activity,就這樣點擊事件就傳遞給了Activity或dialog。
  • activityDialog都是Callback接口的具體實現(xiàn),主要看activitydispatchTouchEvent方法,
  • Activity實現(xiàn)了一個特殊的接口:Window.Callback。
    致此觸摸事件傳遞給了activity。

4 Window.Callback

Window.Callback

    /**
     * API from a Window back to its caller.  This allows the client to
     * intercept key dispatching, panels and menus, etc.
     */
    public interface Callback {
        /**
         * Called to process key events.  At the very least your
         * implementation must call
         * {@link android.view.Window#superDispatchKeyEvent} to do the
         * standard key processing.
         *
         * @param event The key event.
         *
         * @return boolean Return true if this event was consumed.
         */
        public boolean dispatchKeyEvent(KeyEvent event);

        /**
         * Called to process touch screen events.  At the very least your
         * implementation must call
         * {@link android.view.Window#superDispatchTouchEvent} to do the
         * standard touch screen processing.
         *
         * @param event The touch screen event.
         *
         * @return boolean Return true if this event was consumed.
         */
        public boolean dispatchTouchEvent(MotionEvent event);

        /**
         * Called to process trackball events.  At the very least your
         * implementation must call
         * {@link android.view.Window#superDispatchTrackballEvent} to do the
         * standard trackball processing.
         *
         * @param event The trackball event.
         *
         * @return boolean Return true if this event was consumed.
         */
        public boolean dispatchTrackballEvent(MotionEvent event);
        ...(省略若干代碼,下同)

Activity實現(xiàn)了一個特殊的接口:Window.Callback。

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

InputEvent有2個子類:KeyEventMotionEvent,其中KeyEvent表示鍵盤事件,而MotionEvent表示點擊事件。

參考

Android 中 MotionEvent 的來源和 ViewRootImpl

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容