Activity、Window和View的關係源碼分析

Activity、Window和View的關係

要說它們之間的關係,就要從Activity的創建說起

Activity的創建

在這裏我們暫時不分析具體的startActivity啓動過程,直接看創建Activity的部分,調用startActivity新啓動了一個Activity會走到ActivityThread裏的performLaunchActivity()方法

//ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

    ···

    //創建ContextImpl,然後創建Activity
    ContextImpl appContext = createBaseContextForActivity(r);
    Activity activity = null;
    ···
    java.lang.ClassLoader cl = appContext.getClassLoader();
    activity = mInstrumentation.newActivity(
            cl, component.getClassName(), r.intent);
    ···

    //首先如果沒有創建Application,會先創建Application
    Application app = r.packageInfo.makeApplication(falsemInstrumentation);
    if (activity != null) {
        //調用Activity的attach
        appContext.setOuterContext(activity);
        activity.attach(appContext, this, getInstrumentation(), r.token,
                        r.ident, app, r.intent, r.activityInfo, title, r.parent,
                        r.embeddedID, r.lastNonConfigurationInstances, config,
                        r.referrer, r.voiceInteractor, window, r.configCallback,
                        r.assistToken);
    }
    ···
}
    
···

上面主要是創建出了Activity並調用了Activity的attach方法,而在attach()方法中會創建Window

Window的創建

//Activity.java
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, IBinder assistToken) {
    //將ContextImpl賦值給mBase對象
    attachBaseContext(context);
    //創建出PhoneWindow,並設置Activity實現的Callback接口,Callback接口主要回調輸入事件、窗口改變等
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    mWindow.setCallback(this);
    //將WindowManagerImpl設置進PhoneWindow中
    mWindow.setWindowManager(
                (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
                mToken, mComponent.flattenToString(),
                (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);

}

PhoneWindow只是負責處理一些應用窗口通用的邏輯(如標題欄,導航欄等)。
上面流程:

  • 將ContextImpl賦值給mBase對象
  • 創建了PhoneWindow並設置了回調
  • 將WindowManagerImpl設置進PhoneWindow中

View的創建

在開發時設置layout界面,使用Activity的setContentView方法設置一個View進去,系統自動幫我們處理好。仔細分析一下這裏應該就是創建View的地方,下面從Activity的setContentView來分析View的創建

//Activity.java
public void setContentView(@LayoutRes int layoutResID) {
    //這裏的getWindow對應的就是PhoneWindow
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
}

Activity的setContentView實際上是交給PhoneWindow來執行的


//PhoneWindow.java
@Override
public void setContentView(int layoutResID) {

    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }

    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent,layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        //將layout插入到mContentParent中,即我們設置的View都是mContentParent的子View
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
}

private void installDecor() {
    //創建DecorView
    if (mDecor == null) {
        mDecor = generateDecor(-1);
        ···
    }
    //創建mContentParent
    if (mContentParent == null) {
        mContentParent = generateLayout(mDecor);
    }
}

protected DecorView generateDecor(int featureId) {
    //這個this就是PhoneWindow
    return new DecorView(context, featureId, this, getAttributes());
}


protected ViewGroup generateLayout(DecorView decor) {
    ···
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    ···
}

@Nullable
public <T extends View> T findViewById(@IdRes int id) {
    return getDecorView().findViewById(id);
}

在setContentView後,先判斷是否創建了mContenParent和DecorView,如果沒有先創建。
接下來將要設置的View加入到mContentParent,作爲子View。
上面主要流程:

  • Activity的setContentView 調用 PhoneWindow的setContentView
  • 如果沒有創建mContentParent,則創建出DecorView和mContentParent,
  • 創建DecorView時傳入PhoneWindow將Window和DecorView關聯上
  • 創建mContentParent時,從DecorView中查找到id爲R.id.content的View

由此可以推斷出目前的一個關係

在這裏插入圖片描述

到目前,DecorView和Activity並沒有建立聯繫,Activity間接通過PhoneWindow調用mDecor。也不知道DecorView是何時被繪製到屏幕上的。

WindowManager的addView

在我們最早接觸Android時,分析Activity生命週期,在OnResume()後,界面對於用戶可見。造成這樣的原因是,在創建Activity後,會執行attach()->onCreate() 都是在準備需要顯示的數據,而在onResume階段纔會將PhoneWindow中的DecorView繪製到界面上。

在ActivityThread的handleResumeActivity中會調用WindowManager的addView方法將DecorView添加到WindowManagerService中,

//ActivityThread.java
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest,boolean isForward,String reason) {
    ···
    //其中回到用Activity的OnResume方法
    final ActivityClientRecord r = performResumeActivity(tokefinalStateRequest, reason);

    ···

    ViewManager wm = a.getWindowManager();
    ···
    //Activity對於用戶可見且window沒有添加過,則調用wm.addView(decor, l);
    //否則調用Activity的onWindowAttributesChanged
    if (a.mVisibleFromClient) {
        if (!a.mWindowAdded) {
            a.mWindowAdded = true;
            wm.addView(decor, l);
        } else {
            a.onWindowAttributesChanged(l);
        }
    }
}

WindowManager的addView的作用是:

  • DecorView渲染繪製到屏幕上顯示
  • DecorView可以接受屏幕觸摸事件

Activity的getWindowManager實際調用的是WindowManagerImpl對象。
調用路徑是Activity#getWindowManager() -> Window#getWindowManager()
拿到的就是Activity中attach方法設置WindowManagerImpl對象

接下來看WindowManagerImpl的addView方法

//WindowManagerImpl.java
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParamsparams) {
    applyDefaultToken(params);
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

//WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {
    ViewRootImpl root;
    //創建了ViewRootImpl
    root = new ViewRootImpl(view.getContext(), display);

    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);

    //觸發啓動操作
    try {
        root.setView(view, wparams, panelParentView);
    } catch (RuntimeException e) {
        // BadTokenException or InvalidDisplayException, clean up.
        if (index >= 0) {
            removeViewLocked(index, true);
        }
        throw e;
    }
}

WindowManagerGlobal是一個進程單例對象。上面這段的核心就是創建ViewRootImpl,然後調用它的setView方法。

//ViewRootImpl.java
//只有1個子View
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    //在添加到窗口管理器之前安排第一個佈局
    //確保從我們的系統接收任何其他事件之前進行重新佈局measure -> layout -> draw。
    requestLayout();
    //mWindowSession是和WMS交互的代理對象,將View將調到WMS中
    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                            mTempInsets);
}


上面流程:

  • 在接收到WMS消息之前,先完成View的測量、佈局和繪製操作
  • 通過mWindowSession的addToDisplay方法將View添加到WMS中

來看mWindowSession是一個什麼類型的對象

//ViewRootImpl.java
public ViewRootImpl(Context context, Display display) {
    mWindowSession = WindowManagerGlobal.getWindowSession();
}


//WindowManagerGlobal.java
@UnsupportedAppUsage
public static IWindowSession getWindowSession() {
    synchronized (WindowManagerGlobal.class) {
        if (sWindowSession == null) {
            try {
                //模擬傳統行爲。 
                //在此處實例化了InputMethodManager的全局實例
                InputMethodManagerensureDefaultInstanceForDefaultDisplayIfNecessary();
                //獲取WMS的Proxy代理對象
                IWindowManager windowManager = getWindowManagerService();
                //sWindowSession是一個IWindowSession的Binder代理對象,調用WMS的openSession獲取IWindowSession,真正實現的是系統服務的Session對象
                sWindowSession = windowManager.openSession(
                        new IWindowSessionCallback.Stub() {
                            @Override
                            public void onAnimatorScaleChanged(float scale) {
                                ValueAnimator.setDurationScale(scale);
                            }
                        });
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        return sWindowSession;
    }
}


//WindowManagerService.java
@Override
public IWindowSession openSession(IWindowSessionCallback callback,IInputMethodClient client,
        IInputContext inputContext) {
    //實際操作的Session對象,創建Session對象,傳入了WMS和回調等
    Session session = new Session(this, callback, client, inputContext);
    return session;
}

上面獲取IWindowSession的流程

  • ViewRootImpl構造函數中爲mWindowSession賦值
  • 然後這個值又是WindowManagerGlobal的getWindowSession()
  • sWindowSession是一個單例,創建時先獲取WMS代理對象windowManager,然後通過windowManager的openSession遠程調用WMS獲取到sWindowSession
  • 而sWindowSession在WMS中返回的是創建的Session

mWindowSession.addToDisplay

@Override
public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
            Rect outStableInsets, Rect outOutsets,
            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) {
    return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
                outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel);
    }

在這一步就將Window成功傳遞到了WMS

觸摸事件是怎麼傳遞到Activity的

在上面的ViewRootImpl的setView方法中還有接受觸摸事件的操作

//ViewRootImpl.java
//只有1個子View
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    //在添加到窗口管理器之前安排第一個佈局
    //確保從我們的系統接收任何其他事件之前進行重新佈局measure -> layout -> draw。
    requestLayout();
    //mWindowSession是和WMS交互的代理對象,將View將調到WMS中
    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                            mTempInsets);

    // 設置輸入管道,使用的責任鏈模式,依次接受事件
    CharSequence counterSuffix = attrs.getTitle();
    mSyntheticInputStage = new SyntheticInputStage();
    InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
    InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
                        "aq:native-post-ime:" + counterSuffix);
    InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
    InputStage imeStage = new ImeInputStage(earlyPostImeStage,
                        "aq:ime:" + counterSuffix);
    InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
    InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
                        "aq:native-pre-ime:" + counterSuffix);

    mFirstInputStage = nativePreImeStage;
    mFirstPostImeInputStage = earlyPostImeStage;
}

final class ViewPostImeInputStage extends InputStage {
    public ViewPostImeInputStage(InputStage next) {
        super(next);
    }

    @Override
    protected int onProcess(QueuedInputEvent q) {
        if (q.mEvent instanceof KeyEvent) {
            return processKeyEvent(q);
        } else {
            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);
            }
        }
    }

    private int processPointerEvent(QueuedInputEvent q) {
        final MotionEvent event = (MotionEvent)q.mEvent;
        ···
        //mView是DecorView,而dispatchPointerEvent是在View中,DecorView是View的子類
        boolean handled = mView.dispatchPointerEvent(event);
        ···
        mAttachInfo.mHandlingPointerEvent = false;
        ···
        return handled ? FINISH_HANDLED : FORWARD;
    }
}

//View.java
@UnsupportedAppUsage
public final boolean dispatchPointerEvent(MotionEvent event) {
    if (event.isTouchEvent()) {
        return dispatchTouchEvent(event);
    } else {
        return dispatchGenericMotionEvent(event);
    }
}

//DecorView.java
@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);
}

cb是在Activity#attach時,創建PhoneWindow後setCallback(this)的,而傳入的是當前Activity,所以實際調用的就是Activity的dispatchTouchEvent,因此觸摸事件就傳遞到了Activity中

//Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

//PhoneWindow.java
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

//DecorView.java
public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}


上面流程:

  • 在ViewRootImpl的setView時調用添加接受觸摸事件的管道攔截器
  • 當觸摸事件傳入,會到達ViewPostImeInputStage#onProcess方法中處理,觸摸事件會調用processPointerEvent
  • 之後會傳入DecorView的processPointerEvent,然後通過window的callback回調傳入Activity
  • 傳遞到Activity後,會調用getWindow().superDispatchTouchEvent(ev)傳遞到PhoneWindow中
  • 然後PhoneWindow中調用mDecor.superDispatchTouchEvent(event)傳遞到了DecorView
  • 然後依照ViewGroup的事件傳遞機制層層傳遞

總結

通過Activity創建、setContentView的流程,來分析了Activity、Window、View之間關係,並分析了他們之間事件傳遞的路徑

最後在簡單的總結幾點

  • 一個Activity有一個PhoneWindow,在PhoneWindow中有一個DecorView,我們添加的view是放在DecorView的ContentParent中
  • 一個進程僅有一個WindowManagerGlobal
  • 一個PhoneWindow對應一個ViewRootImpl對象
  • WindowManagerGlobal通過ViewRootImpl的setView方法完成WMS的添加過程。以及輸入事件管道的過程
  • ViewRootImpl的setView方法主要完成:View的渲染事件和接受觸摸事件
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章