Android 源碼分析四 ViewRootImpl 相關

之前文章提到 View 的根是 ViewRootImpl 這個類。那么他們又是由誰關聯起來的呢?

要說這些關系之前,先了解一些接口:

public interface ViewManager
{
    public void addView(View view, ViewGroup.LayoutParams params);
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    public void removeView(View view);
}

實現 ViewManager 這個接口之后,你具有對View的基本操作(增刪改),另外還有之前常用到的 ViewParent 接口,每一個 ViewGroup ,都實現了這兩個接口。

接下來提出4 個問題:

  1. ViewRootImpl 被誰創建和管理
  2. ViewRootImplWindow 對應關系
  3. View 什么時候可以拿到具體寬高
  4. View 的事件分發源頭在哪兒

ViewRootImpl

先看看 ViewRootImpl 的定義:

/**
* The top of a view hierarchy, implementing the needed protocol between View
* and the WindowManager.  This is for the most part an internal implementation
* detail of {@link WindowManagerGlobal}.
*
* {@hide}
*/
@SuppressWarnings({"EmptyCatchBlock", "PointlessBooleanExpression"})
public final class ViewRootImpl implements ViewParent,
        View.AttachInfo.Callbacks, ThreadedRenderer.DrawCallbacks {

先看注釋,ViewRootImplView 樹的頂層。強調它有實現 ViewWindowManager 之間必要的協議,是WindowManagerGlobal 內部實現中重要的組成部分。其實信息點挺多,直接就涉及到開頭要說的那個問題,ViewWindow 之間的關系。另外還提到它和 WindowManagerGlobal 的關系,看這架勢,它的很多方法可能都是被 WindowManagerGlobal 調用。

在看接口實現和調用,它實現了 ViewParent 接口,但是,它并沒有實現 ViewManager 接口。對比 ViewGroup ,它少了 ViewManagerView 的增刪改能力。

接著看看它的構造方法:

public ViewRootImpl(Context context, Display display) {
    mContext = context;
    mWindowSession = WindowManagerGlobal.getWindowSession();
    mDisplay = display;
    mBasePackageName = context.getBasePackageName();
    mThread = Thread.currentThread();
    mLocation = new WindowLeaked(null);
    mLocation.fillInStackTrace();
    mWidth = -1;
    mHeight = -1;
    mDirty = new Rect();
    mTempRect = new Rect();
    mVisRect = new Rect();
    mWinFrame = new Rect();
    mWindow = new W(this);
    mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
    mViewVisibility = View.GONE;
    mTransparentRegion = new Region();
    mPreviousTransparentRegion = new Region();
    mFirst = true; // true for the first time the view is added
    mAdded = false;
    // attachInfo 很重要 
    mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
            context);
    mAccessibilityManager = AccessibilityManager.getInstance(context);
    mAccessibilityManager.addAccessibilityStateChangeListener(
            mAccessibilityInteractionConnectionManager, mHandler);
    mHighContrastTextManager = new HighContrastTextManager();
    mAccessibilityManager.addHighTextContrastStateChangeListener(
            mHighContrastTextManager, mHandler);
    mViewConfiguration = ViewConfiguration.get(context);
    mDensity = context.getResources().getDisplayMetrics().densityDpi;
    mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
    mFallbackEventHandler = new PhoneFallbackEventHandler(context);
    mChoreographer = Choreographer.getInstance();
    mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);

    if (!sCompatibilityDone) {
        sAlwaysAssignFocus = true;

        sCompatibilityDone = true;
    }

    loadSystemProperties();
}

ViewRootImpl 創建,初始化準備了很多東西,著重強調 AttachInfo 創建,這個類很重要,之前說的 軟解時 Canvas 的保存和復用,還有 View.post() 方法執行等等。

之前文章中,已經或多或少提到 ViewRootImpl 的職責。比如說測量繪制最后都是由它發起。

ViewRootImpl 中,TraversalRunnable 是一個重要的角色。

final class TraversalRunnable implements Runnable {
    @Override
    public void run() {
        doTraversal();
    }
}

它內部調用 doTraversal() 方法,最終觸發 performTraversals() ,在這個方法中,就開始了對整個 View 樹的測量繪制等等一系列操作。再細分就是根據情況調用 performMeasure() performLayout() performDraw() 方法,最后就回調到具體 ViewonMeasure() onLayout()onDraw() 方法,這個具體流程,在前面文章中有相關分析。

ViewRootImpl 根據注釋,是 WindowManagerGlobal 的重要組成部分,那就先瞅瞅 WindowManagerGlobal 是個啥呢?

WindowManagerGlobal

要說 WindowManagerGlobal ,那就要先看 WindowManager 接口啦,這個接口實現了 ViewManager, 說明它擁有對 View 的控制能力。根據注釋,這個接口就是我們用來和遠程服務 WindowManager service 溝通的。它的實現類就是 WindowManagerImpl 。WindowManagerImpl 中,有一個 mGlobal 字段:

 private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

到這里,看到 WindowManagerGlobal 的一點蹤影了。接著深入其內部一探究竟。

private final ArrayList<View> mViews = new ArrayList<View>();
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
private final ArrayList<WindowManager.LayoutParams> mParams =
        new ArrayList<WindowManager.LayoutParams>();
private final ArraySet<View> mDyingViews = new ArraySet<View>();

從定義的這些字段就可以看出,在 WindowManagerGlobal 內部是管理著 ViewRootImpl 和 其對應的 RootView 還有對應的 LayoutParams 等等。

上面講過,ViewRootImpl 只實現了 ViewParent 接口,并沒有實現 ViewManager 接口,喪失了部分 parent 的能力。其實這部分能力就交由 WindowManager ,對于 ViewRootImpl 來說,再向上的 View 增傷改功能是和 Window 交互,需要和系統服務打交道。

WindowManagerImpl 中,我們看看 ViewManager 接口相關方法具體實現:

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

@Override
public void updateViewLayout(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    mGlobal.updateViewLayout(view, params);
}

@Override
public void removeView(View view) {
    mGlobal.removeView(view, false);
}

可以看到,全都是交由 WindowManagerGlobal 做具體實現。 WindowManagerImpl 代理了遠程系統服務, WindowManagerGlobal 代理了 WindowManagerImpl 的具體實現。

//WindowManagerGlobal
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    ...
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    //設置Window一些基本參數
    if (parentWindow != null) {
        parentWindow.adjustLayoutParamsForSubWindow(wparams);
    } else {
        // If there's no parent, then hardware acceleration for this view is
        // set from the application's hardware acceleration setting.
        final Context context = view.getContext();
        if (context != null
                && (context.getApplicationInfo().flags
                        & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
            wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
        }
    }

    ViewRootImpl root;
    View panelParentView = null;

    synchronized (mLock) {
        // Start watching for system property changes.
        if (mSystemPropertyUpdater == null) {
            mSystemPropertyUpdater = new Runnable() {
                @Override public void run() {
                    synchronized (mLock) {
                        for (int i = mRoots.size() - 1; i >= 0; --i) {
                            mRoots.get(i).loadSystemProperties();
                        }
                    }
                }
            };
            //監聽系統屬性設置變化:邊界布局 硬件加速支持等設置變化
            SystemProperties.addChangeCallback(mSystemPropertyUpdater);
        }
        // 判斷有沒有已經添加過 
        int index = findViewLocked(view, false);
        if (index >= 0) {
            if (mDyingViews.contains(view)) {
                // Don't wait for MSG_DIE to make it's way through root's queue.
                mRoots.get(index).doDie();
            } else {
                類似 ViewGroup 中child 已經有 parent 就會拋出異常
                throw new IllegalStateException("View " + view
                        + " has already been added to the window manager.");
            }
            // The previous removeView() had not completed executing. Now it has.
        }

        // If this is a panel window, then find the window it is being
        // attached to for future reference.
        // 這里涉及到 Window 類型處理 
        if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
            final int count = mViews.size();
            for (int i = 0; i < count; i++) {
                if (mRoots.get(i).mWindow.asBinder() == wparams.token) {
                    panelParentView = mViews.get(i);
                }
            }
        }
        // 創建 ViewRootImpl
        root = new ViewRootImpl(view.getContext(), display);

        view.setLayoutParams(wparams);
        // 保存到對應集合
        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);

        // do this last because it fires off messages to start doing things
        try {
            // 調用 ViewRootImpl 的 set 方法
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            if (index >= 0) {
                removeViewLocked(index, true);
            }
            throw e;
        }
    }
}

可以看到,在 WindowManageraddView() 方法中,最后會創建出一個新的 ViewRootImpl ,并調用 ViewRootImplsetView() 方法。

前面提到的前兩個問題已經有了答案, ViewRootImplWindowManagerGlobal 創建, ViewRootImplWindow 的對應關系是多對一,一個 Window 可以有多個 ViewRootImpl

接著看看 ViewRootImpl 中的 setView() 方法。

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

            mAttachInfo.mDisplayState = mDisplay.getState();
            mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);
            ...

            mSoftInputMode = attrs.softInputMode;
            mWindowAttributesChanged = true;
            mWindowAttributesChangesFlag = WindowManager.LayoutParams.EVERYTHING_CHANGED;
            mAttachInfo.mRootView = view;
            mAttachInfo.mScalingRequired = mTranslator != null;
            mAttachInfo.mApplicationScale =
                    mTranslator == null ? 1.0f : mTranslator.applicationScale;
            if (panelParentView != null) {
                mAttachInfo.mPanelParentWindowToken
                        = panelParentView.getApplicationWindowToken();
            }
            mAdded = true;
            int res; /* = WindowManagerImpl.ADD_OKAY; */

            // Schedule the first layout -before- adding to the window
            // manager, to make sure we do the relayout before receiving
            // any other events from the system.
            // 調用這個方法之后會直接出發 requestLayout() 
            requestLayout();
            if ((mWindowAttributes.inputFeatures
                    & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {
                mInputChannel = new InputChannel();
            }
            mForceDecorViewVisibility = (mWindowAttributes.privateFlags
                    & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
            try {
                mOrigWindowType = mWindowAttributes.type;
                mAttachInfo.mRecomputeGlobalAttributes = true;
                collectViewAttributes();
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(),
                        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                        mAttachInfo.mOutsets, mInputChannel);
            } catch (RemoteException e) {
                mAdded = false;
                mView = null;
                mAttachInfo.mRootView = null;
                mInputChannel = null;
                mFallbackEventHandler.setView(null);
                unscheduleTraversals();
                setAccessibilityFocus(null, null);
                throw new RuntimeException("Adding window failed", e);
            } finally {
                if (restore) {
                    attrs.restore();
                }
            }
            ...
            if (res < WindowManagerGlobal.ADD_OKAY) {
                mAttachInfo.mRootView = null;
                mAdded = false;
                mFallbackEventHandler.setView(null);
                unscheduleTraversals();
                setAccessibilityFocus(null, null);
                switch (res) {
                    // 異常處理
                    case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
                    case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
                        throw new WindowManager.BadTokenException(
                                "Unable to add window -- token " + attrs.token
                                + " is not valid; is your activity running?");
                    ...
                }
                throw new RuntimeException(
                        "Unable to add window -- unknown error code " + res);
            }

            if (view instanceof RootViewSurfaceTaker) {
                mInputQueueCallback =
                    ((RootViewSurfaceTaker)view).willYouTakeTheInputQueue();
            }
            // 輸入事件相關
            if (mInputChannel != null) {
                if (mInputQueueCallback != null) {
                    mInputQueue = new InputQueue();
                    mInputQueueCallback.onInputQueueCreated(mInputQueue);
                }
                // Window 事件 receiver 
                mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                        Looper.myLooper());
            }

            view.assignParent(this);
            mAddedTouchMode = (res & WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE) != 0;
            mAppVisible = (res & WindowManagerGlobal.ADD_FLAG_APP_VISIBLE) != 0;

            if (mAccessibilityManager.isEnabled()) {
                mAccessibilityInteractionConnectionManager.ensureConnection();
            }

            if (view.getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
                view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
            }

            // Set up the input pipeline.
            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;
            mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
        }
    }
}

setView() 方法里面邏輯挺多的,首先會直接調用一次 requestLayout() ,然后會處理 addView() 的異常情況,類似于 Badtoken 這些異常。最后還有添加 Window 事件相關的監聽。

在調用 requestLayout() 之后, View 會進行相關測量繪制。在這之后,肯定能拿到 View 對應寬高。那么第三個問題, View 什么時候能拿到對應寬高,似乎說是在 ViewRootImpl 調用 setView() 方法之后也沒什么毛病。

那么對于 Activity 而言,什么時候會調用到 WindowManager.addView() 呢?通常我們會在 onCreate() 回調方法中調用 setContentView() 添加對應布局。那么這個方法的 View 是什么時候被真正添加到 Window 上的呢,或者說,這個 View 到底被添加到哪里了呢 ?

Activity 的啟動是在 ActivityThread 類中進行的。在 ActivityThreadperformLaunchActivity() 中,會完成 Activity 的創建(反射),并且會調用 Activity.attach() 方法,這個方法在第一篇文章 Activity 何時添加 LayoutInflater Factory 時有提及。

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) {
    attachBaseContext(context);

    mFragments.attachHost(null /*parent*/);
    // 創建出 PhoneWindow 
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    // 設置相關 callback 
    mWindow.setWindowControllerCallback(this);
    mWindow.setCallback(this);
    mWindow.setOnWindowDismissedCallback(this);
    mWindow.getLayoutInflater().setPrivateFactory(this);
    if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
        mWindow.setSoftInputMode(info.softInputMode);
    }
    if (info.uiOptions != 0) {
        mWindow.setUiOptions(info.uiOptions);
    }
    mUiThread = Thread.currentThread();

    mMainThread = aThread;
    mInstrumentation = instr;
    mToken = token;
    mIdent = ident;
    mApplication = application;
    mIntent = intent;
    mReferrer = referrer;
    mComponent = intent.getComponent();
    mActivityInfo = info;
    mTitle = title;
    mParent = parent;
    mEmbeddedID = id;
    mLastNonConfigurationInstances = lastNonConfigurationInstances;
    if (voiceInteractor != null) {
        if (lastNonConfigurationInstances != null) {
            mVoiceInteractor = lastNonConfigurationInstances.voiceInteractor;
        } else {
            mVoiceInteractor = new VoiceInteractor(voiceInteractor, this, this,
                    Looper.myLooper());
        }
    }

    mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
    if (mParent != null) {
        mWindow.setContainer(mParent.getWindow());
    }
    mWindowManager = mWindow.getWindowManager();
    mCurrentConfig = config;

    mWindow.setColorMode(info.colorMode);
}

對于本文最重要的就是在 attach() 中給 Activity 創建了對應的 PhoneWindow , 有了 PhoneWindow 才能有后文。對于 Activity ,我們設置的 ContentView 并不是頂層 View ,最頂層應該是 DecorViewDecorView 是定義在 PhoneWindow 中:

// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;

所以,現在的問題就是 DecorView 什么時候被添加到 PhoneWindow 上。在 ActivityThread 中有 handleResumeActivity() 方法,在這個方法中:

final void handleResumeActivity(IBinder token,
        boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
    ActivityClientRecord r = mActivities.get(token);
    ...
    // 在 performResumeActivity() 中會回調 onResume()
    r = performResumeActivity(token, clearHide, reason);

    if (r != null) {
        final Activity a = r.activity;

        ...
        if (r.window == null && !a.mFinished && willBeVisible) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            decor.setVisibility(View.INVISIBLE);
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            ...
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    // 調用 windowManager.addView()
                    wm.addView(decor, l);
                } else {
                    a.onWindowAttributesChanged(l);
                }
            }

        ...
}

代碼只保留 addView() 相關部分,在調用這個之前,會先調用 performResumeActivity() ,在這個方法中,就會回調 ActivityonResume() 方法。也就是說,在 Activity 中,一個 View 的寬高,是在 Activity 第一次回調 onResume() 方法之后,第一次的 onResume() 方法時并不能拿到寬高。 在這之后, DecorView 才添加到 PhoneWindow 中,接著觸發 windowManagerGlobal.addView() 方法,接著調用 ViewRootImlp.setView() 方法,然后開始 requestLayout() ,最后觸發 performTraversals() 方法,在這個方法中將會調用 Viewmeasure() layout()draw() 方法。

performTraversals()

// ViewRootImpl
private void performTraversals() {
    // cache mView since it is used so much below...
    final View host = mView;
    if (host == null || !mAdded)
        return;

    mIsInTraversal = true;
    mWillDrawSoon = true;
    boolean windowSizeMayChange = false;
    boolean newSurface = false;
    boolean surfaceChanged = false;
    WindowManager.LayoutParams lp = mWindowAttributes;

    int desiredWindowWidth;
    int desiredWindowHeight;

    ...

    Rect frame = mWinFrame;
    // 第一次 執行
    if (mFirst) {
        mFullRedrawNeeded = true;
        mLayoutRequested = true;

        ...
        // 設置一些基本參數
        mAttachInfo.mUse32BitDrawingCache = true;
        mAttachInfo.mHasWindowFocus = false;
        mAttachInfo.mWindowVisibility = viewVisibility;
        mAttachInfo.mRecomputeGlobalAttributes = false;
        mLastConfigurationFromResources.setTo(config);
        mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
        // Set the layout direction if it has not been set before (inherit is the default)
        if (mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {
            host.setLayoutDirection(config.getLayoutDirection());
        }
        // mFirst 時調用  dispatchAttachedToWindow()
        host.dispatchAttachedToWindow(mAttachInfo, 0);
        mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
        dispatchApplyInsets(host);
        //Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn);

    } else {
        desiredWindowWidth = frame.width();
        desiredWindowHeight = frame.height();
        if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
            if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame);
            mFullRedrawNeeded = true;
            mLayoutRequested = true;
            windowSizeMayChange = true;
        }
    }

    ...

    // Non-visible windows can't hold accessibility focus.
    if (mAttachInfo.mWindowVisibility != View.VISIBLE) {
        host.clearAccessibilityFocus();
    }

    // Execute enqueued actions on every traversal in case a detached view enqueued an action
    getRunQueue().executeActions(mAttachInfo.mHandler);

    boolean insetsChanged = false;
    // 是否需要 layout 的標志 第一次為 true 
    boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
    // 第一次測量
    if (layoutRequested) {

        final Resources res = mView.getContext().getResources();

        if (mFirst) {
            // make sure touch mode code executes by setting cached value
            // to opposite of the added touch mode.
            mAttachInfo.mInTouchMode = !mAddedTouchMode;
            ensureTouchModeLocally(mAddedTouchMode);
        } else {
            ...
            if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
                    || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
                windowSizeMayChange = true;

                if (shouldUseDisplaySize(lp)) {
                    // NOTE -- system code, won't try to do compat mode.
                    Point size = new Point();
                    mDisplay.getRealSize(size);
                    desiredWindowWidth = size.x;
                    desiredWindowHeight = size.y;
                } else {
                    Configuration config = res.getConfiguration();
                    desiredWindowWidth = dipToPx(config.screenWidthDp);
                    desiredWindowHeight = dipToPx(config.screenHeightDp);
                }
            }
        }

        // measureHierarchy() 可能會修改 Windowsize 內部會調用 performMeasure()
        windowSizeMayChange |= measureHierarchy(host, lp, res,
                desiredWindowWidth, desiredWindowHeight);
    }

    if (collectViewAttributes()) {
        params = lp;
    }
    if (mAttachInfo.mForceReportNewAttributes) {
        mAttachInfo.mForceReportNewAttributes = false;
        params = lp;
    }

    ...

    if (layoutRequested) {
        // 這里已經重置 mLayoutRequested 
        mLayoutRequested = false;
    }
    <---- 判斷 Window是否改變 --->
    boolean windowShouldResize = layoutRequested && windowSizeMayChange
        && ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight())
            || (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
                    frame.width() < desiredWindowWidth && frame.width() != mWidth)
            || (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
                    frame.height() < desiredWindowHeight && frame.height() != mHeight));
    windowShouldResize |= mDragResizing && mResizeMode == RESIZE_MODE_FREEFORM;

    // If the activity was just relaunched, it might have unfrozen the task bounds (while
    // relaunching), so we need to force a call into window manager to pick up the latest
    // bounds.
    windowShouldResize |= mActivityRelaunched;
    <----------- windowShouldResize over --------------->
    ...
    if (mFirst || windowShouldResize || insetsChanged ||
            viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
        mForceNextWindowRelayout = false;
        ...
        boolean hwInitialized = false;
        boolean contentInsetsChanged = false;
        boolean hadSurface = mSurface.isValid();
        ...
        if (mWidth != frame.width() || mHeight != frame.height()) {
            // 更新對應 寬高
            mWidth = frame.width();
            mHeight = frame.height();
        }

        ...

        if (!mStopped || mReportNextDraw) {
            boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
                    (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
            if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
                    || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
                    updatedConfiguration) {
                int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
                int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);

                if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed!  mWidth="
                        + mWidth + " measuredWidth=" + host.getMeasuredWidth()
                        + " mHeight=" + mHeight
                        + " measuredHeight=" + host.getMeasuredHeight()
                        + " coveredInsetsChanged=" + contentInsetsChanged);

                 // Ask host how big it wants to be
                 // 第二次測量
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);

                // Implementation of weights from WindowManager.LayoutParams
                // We just grow the dimensions as needed and re-measure if
                // needs be
                int width = host.getMeasuredWidth();
                int height = host.getMeasuredHeight();
                boolean measureAgain = false;

                if (lp.horizontalWeight > 0.0f) {
                    width += (int) ((mWidth - width) * lp.horizontalWeight);
                    childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
                            MeasureSpec.EXACTLY);
                    measureAgain = true;
                }
                if (lp.verticalWeight > 0.0f) {
                    height += (int) ((mHeight - height) * lp.verticalWeight);
                    childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
                            MeasureSpec.EXACTLY);
                    measureAgain = true;
                }

                if (measureAgain) {
                    if (DEBUG_LAYOUT) Log.v(mTag,
                            "And hey let's measure once more: width=" + width
                            + " height=" + height);
                    // 再次測量 Again
                    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                }

                layoutRequested = true;
            }
        }
    } else {
        // Not the first pass and no window/insets/visibility change but the window
        // may have moved and we need check that and if so to update the left and right
        // in the attach info. We translate only the window frame since on window move
        // the window manager tells us only for the new frame but the insets are the
        // same and we do not want to translate them more than once.
        maybeHandleWindowMove(frame);
    }

    final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
    boolean triggerGlobalLayoutListener = didLayout
            || mAttachInfo.mRecomputeGlobalAttributes;
    if (didLayout) {
        // 開始 layout 
        performLayout(lp, mWidth, mHeight);
        ...
    }

    if (triggerGlobalLayoutListener) {
        mAttachInfo.mRecomputeGlobalAttributes = false;
        mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
    }
    ...
    final boolean changedVisibility = (viewVisibilityChanged || mFirst) && isViewVisible;
    final boolean hasWindowFocus = mAttachInfo.mHasWindowFocus && isViewVisible;
    final boolean regainedFocus = hasWindowFocus && mLostWindowFocus;
    if (regainedFocus) {
        mLostWindowFocus = false;
    } else if (!hasWindowFocus && mHadWindowFocus) {
        mLostWindowFocus = true;
    }

    ...
    // 更新一些 flag 
    mFirst = false;
    mWillDrawSoon = false;
    mNewSurfaceNeeded = false;
    mActivityRelaunched = false;
    mViewVisibility = viewVisibility;
    mHadWindowFocus = hasWindowFocus;
    ...
    boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;

    if (!cancelDraw && !newSurface) {
        ...
        // 開始繪制
        performDraw();
    } else {
        // cancelDraw 之后,如果 View 是可見的 那么會重走 scheduleTraversals() 方法
        if (isViewVisible) {
            // Try again
            scheduleTraversals();
        } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
            ...
        }
    }
    // 執行完畢 mIsInTraversal 重置 false 
    mIsInTraversal = false;
}

performTraversals() 方法太長邏輯太多,這里只保留回調 View measure() layout() draw() 方法的核心部分。在該方法中,首先有在 mFirst 中調用 dispatchAttachedToWindow()

這也是 View 或者 ActivityonAttachedToWindow() 觸發的地方。回調 ViewonAttachedToWindow() 很好理解,但是,怎么又和 Activity 關聯起來的呢? 這又要說到 DecorView

// DecorView
@Override
protected void onAttachedToWindow() {
    super.onAttachedToWindow();

    final Window.Callback cb = mWindow.getCallback();
    if (cb != null && !mWindow.isDestroyed() && mFeatureId < 0) {
        cb.onAttachedToWindow();
    }
    ...
}

DecorView 中又會去調用 Window.CallbackonAttachedToWindow(), 而這個 callback 就在上面 Activityattach() 方法中有設置,其實就是 Activity 自己。

接著再看看在第一次測量時調用的 measureHierarchy()方法。

private boolean measureHierarchy(final View host, final WindowManager.LayoutParams lp,
        final Resources res, final int desiredWindowWidth, final int desiredWindowHeight) {
    int childWidthMeasureSpec;
    int childHeightMeasureSpec;
    boolean windowSizeMayChange = false;

    boolean goodMeasure = false;
    if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT) {
        // On large screens, we don't want to allow dialogs to just
        // stretch to fill the entire width of the screen to display
        // one line of text.  First try doing the layout at a smaller
        // size to see if it will fit.
        final DisplayMetrics packageMetrics = res.getDisplayMetrics();
        res.getValue(com.android.internal.R.dimen.config_prefDialogWidth, mTmpValue, true);
        int baseSize = 0;
        if (mTmpValue.type == TypedValue.TYPE_DIMENSION) {
            baseSize = (int)mTmpValue.getDimension(packageMetrics);
        }
        if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": baseSize=" + baseSize
                + ", desiredWindowWidth=" + desiredWindowWidth);
        if (baseSize != 0 && desiredWindowWidth > baseSize) {
            childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
            childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
            performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
            if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
                    + host.getMeasuredWidth() + "," + host.getMeasuredHeight()
                    + ") from width spec: " + MeasureSpec.toString(childWidthMeasureSpec)
                    + " and height spec: " + MeasureSpec.toString(childHeightMeasureSpec));
            // 之前文章中有提到的 MEASURED_STATE_TOO_SMALL 異常狀態
            if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                goodMeasure = true;
            } else {
                // Didn't fit in that size... try expanding a bit.
                baseSize = (baseSize+desiredWindowWidth)/2;
                if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": next baseSize="
                        + baseSize);
                childWidthMeasureSpec = getRootMeasureSpec(baseSize, lp.width);
                performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
                if (DEBUG_DIALOG) Log.v(mTag, "Window " + mView + ": measured ("
                        + host.getMeasuredWidth() + "," + host.getMeasuredHeight() + ")");
                if ((host.getMeasuredWidthAndState()&View.MEASURED_STATE_TOO_SMALL) == 0) {
                    if (DEBUG_DIALOG) Log.v(mTag, "Good!");
                    goodMeasure = true;
                }
            }
        }
    }

    if (!goodMeasure) {
        childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
        childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
        if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()) {
            windowSizeMayChange = true;
        }
    }
    return windowSizeMayChange;
}

在這個方法中,如果傳入的 layoutparameterwrap_content 這種類型。那么就會去計算是否存在 MEASURED_STATE_TOO_SMALL 的異常狀態,如果是的話,那就把對應 size 再調大一些,可以看到,這里存在多次調用 performMeasure() 的情況。之前 Android 源碼分析二 View 測量

resolveSizeAndState() 方法時看到的 MEASURED_STATE_TOO_SMALL 狀態也有相關使用的地方啦。

第一次測量之后,如果 contentInsetsChanged 或者 updatedConfiguration 為 true ,將再次觸發 performMeasure()

接著會根據 layoutRequested 等字段決定是否 調用 performLayout() 方法。

最后是執行調用 performDraw() 方法相關,如果 mAttachInfo.mTreeObserver.dispatchOnPreDraw()返回了 true ,那么它將要跳過這次 performDraw() 的執行,但是,它居然會重新調用 scheduleTraversals() 方法,這是我之前不清楚,那么如果我的 viewTreeObserver.addOnPreDrawListener 一直返回 false ,它會不會就死循環然后掛了呢?當然不會,因為 mLayoutRequested 在第一次測量之后就被重置為 false ,此時你再調用 performTravels() 方法,
layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw) 直接就是 false 。

到這里, ViewRootImpl 創建(在 WindowManagerGlobal 中),管理 View 樹的測量繪制分析完畢。最后還有觸摸事件的分發。

事件分發

既然有了 Window 概念,觸摸事件肯定是從物理層面的觸摸屏,最后分發到每一個抽象的 View 上。一開始毫無思緒,不知從何看起。這時候就想到一招,拋個異常看看咯。

java.lang.RuntimeException: xxxx
    at com.lovejjfg.demo.FloatViewHelper$addFloatView$1.onTouch(FloatViewHelper.kt:94)
    <---------View --------->       
    at android.view.View.dispatchTouchEvent(View.java:10719)
    at android.view.ViewGroup.dispatchTransformedTouchEvent(ViewGroup.java:2865)
    at android.view.ViewGroup.dispatchTouchEvent(ViewGroup.java:2492)
    at android.view.View.dispatchPointerEvent(View.java:10952)
    <---------ViewRootImpl --------->
    at android.view.ViewRootImpl$ViewPostImeInputStage.processPointerEvent(ViewRootImpl.java:5121)
    at android.view.ViewRootImpl$ViewPostImeInputStage.onProcess(ViewRootImpl.java:4973)
    at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4504)

    at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4557)
    at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4523)
    at android.view.ViewRootImpl$AsyncInputStage.forward(ViewRootImpl.java:4656)
    at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4531)
    at android.view.ViewRootImpl$AsyncInputStage.apply(ViewRootImpl.java:4713)
    at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4504)

    at android.view.ViewRootImpl$InputStage.onDeliverToNext(ViewRootImpl.java:4557)
    at android.view.ViewRootImpl$InputStage.forward(ViewRootImpl.java:4523)
    at android.view.ViewRootImpl$InputStage.apply(ViewRootImpl.java:4531)
    at android.view.ViewRootImpl$InputStage.deliver(ViewRootImpl.java:4504)
    at android.view.ViewRootImpl.deliverInputEvent(ViewRootImpl.java:7011)
    at android.view.ViewRootImpl.doProcessInputEvents(ViewRootImpl.java:6940)
    at android.view.ViewRootImpl.enqueueInputEvent(ViewRootImpl.java:6901)
    at android.view.ViewRootImpl$WindowInputEventReceiver.onInputEvent(ViewRootImpl.java:7121)
    <---------InputEventReceiver --------->
    at android.view.InputEventReceiver.dispatchInputEvent(InputEventReceiver.java:185)
    <----和遠程服務通過 handler 發的 message ---->
    at android.os.MessageQueue.nativePollOnce(Native Method)
    at android.os.MessageQueue.next(MessageQueue.java:323)
    at android.os.Looper.loop(Looper.java:136)
    at android.app.ActivityThread.main(ActivityThread.java:6682)
    at java.lang.reflect.Method.invoke(Native Method)
    at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1520)
    at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1410)

從這個堆棧信息中闊以看到在應用層相關堆棧,但是幕后黑手是誰還是一個謎。 InputEventReceiver 是一個抽象類,ViewRootImpl$WindowInputEventReceiver 是它的一個實現類。

// Called from native code.
@SuppressWarnings("unused")
private void dispatchInputEvent(int seq, InputEvent event, int displayId) {
    mSeqMap.put(event.getSequenceNumber(), seq);
    onInputEvent(event, displayId);
}

ViewRootImpl 類的 setView() 方法中有 WindowInputEventReceiver 的創建。

        ...
        if (mInputChannel != null) {
            if (mInputQueueCallback != null) {
                mInputQueue = new InputQueue();
                mInputQueueCallback.onInputQueueCreated(mInputQueue);
            }
            mInputEventReceiver = new WindowInputEventReceiver(mInputChannel,
                    Looper.myLooper());
        }
        ...

這里有涉及到 InputChannel 這個類。

/**
* An input channel specifies the file descriptors used to send input events to
* a window in another process.  It is Parcelable so that it can be sent
* to the process that is to receive events.  Only one thread should be reading
* from an InputChannel at a time.
* @hide
*/
public final class InputChannel implements Parcelable 

那結合起來,屏幕上的觸摸事件,通過 WindowInputEventReceiverdispatchInputEvent() 方法調用到當前進程,接著 在 onInputEvent() 方法中開始一次分發傳遞。

ViewRootImpl 中定義了三個 inputStage,

InputStage mFirstInputStage;
InputStage mFirstPostImeInputStage;
InputStage mSyntheticInputStage;

接著在 setView() 方法末尾:

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    ...
    // Set up the input pipeline.
    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;
    mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
}

結合堆棧信息可以看到,第一個是 EarlyPostImeInputStage 第二個是 NativePostImeInputStage 第三個是 ViewPostImeInputStage

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

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

    mAttachInfo.mUnbufferedDispatchRequested = false;
    mAttachInfo.mHandlingPointerEvent = true;
    // 回調 View dispatchPointerEvent
    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;
}

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

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

DecorView 中有復寫 dispatchTouchEvent() 方法,上面講過,這個 callback 就是 Activity ,所以說事件分發首先傳入到 Activity 中。

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

Activity 中,又會優先調用 Window.superDispatchTouchEvent(ev) ,如果它返回 false ,然后才是自己消費。

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

PhoneWindow 中,接著又會調用 DecorView.superDispatchTouchEvent() ,這個方法最終會走自己的 dispatchTouchEvent() 方法,轉了一圈,互相客氣了一下。

removeView

說完 View 的創建和添加到 Window 整個過程,接著再看下一個 View 是如何移除的呢?先看看 ViewGroup 中。

// ViewGroup
private void removeViewInternal(int index, View view) {
    if (mTransition != null) {
        mTransition.removeChild(this, view);
    }

    boolean clearChildFocus = false;
    if (view == mFocused) {
        // 清除焦點
        view.unFocus(null);
        clearChildFocus = true;
    }
    if (view == mFocusedInCluster) {
        clearFocusedInCluster(view);
    }

    view.clearAccessibilityFocus();
    // 觸摸 target 相關清除
    cancelTouchTarget(view);
    cancelHoverTarget(view);

    if (view.getAnimation() != null ||
            (mTransitioningViews != null && mTransitioningViews.contains(view))) {
        // 加入 mDisappearingChildren 集合
        addDisappearingView(view);
    } else if (view.mAttachInfo != null) {
       // 回調 onDetachFromWindow()
       view.dispatchDetachedFromWindow();
    }

    if (view.hasTransientState()) {
        childHasTransientStateChanged(view, false);
    }

    needGlobalAttributesUpdate(false);
    // 將 parent 置空 并將自己移除
    removeFromArray(index);

    if (view == mDefaultFocus) {
        clearDefaultFocus(view);
    }
    if (clearChildFocus) {
        clearChildFocus(view);
        if (!rootViewRequestFocus()) {
            notifyGlobalFocusCleared(this);
        }
    }

    dispatchViewRemoved(view);

    if (view.getVisibility() != View.GONE) {
        notifySubtreeAccessibilityStateChangedIfNeeded();
    }

    int transientCount = mTransientIndices == null ? 0 : mTransientIndices.size();
    for (int i = 0; i < transientCount; ++i) {
        final int oldIndex = mTransientIndices.get(i);
        if (index < oldIndex) {
            mTransientIndices.set(i, oldIndex - 1);
        }
    }

    if (mCurrentDragStartEvent != null) {
        mChildrenInterestedInDrag.remove(view);
    }
}

需要注意的是 addDisappearingView() ,調用這個方法之后,就回到上篇文章 Android 源碼分析三 View 繪制 分析的 draw() 方法中。它會繼續執行動畫,在動畫結束后調用 finishAnimatingView() , 在這個方法中將其 detachFromWindow 。

那這個 ViewTree 被移除呢?

// WindowManagerGlobal
private void removeViewLocked(int index, boolean immediate) {
    ViewRootImpl root = mRoots.get(index);
    View view = root.getView();

    if (view != null) {
        InputMethodManager imm = InputMethodManager.getInstance();
        if (imm != null) {
            imm.windowDismissed(mViews.get(index).getWindowToken());
        }
    }
    // 調用 ViewRootImpl 的 die() 方法
    boolean deferred = root.die(immediate);
    if (view != null) {
        view.assignParent(null);
        if (deferred) {
            mDyingViews.add(view);
        }
    }
}

核心方法就是 ViewRootImpl.die() ,這個方法接受一個參數,是否立即執行,但是其實這個也有一個前提條件,就是當前必須沒有正在執行
performTraversals() 方法。直接執行連隊列都不會放,直接干,其他時候是走 Handler 。另外這個方法還有返回值,如果返回 true ,添加到 Handler 隊列沒有馬上移除,在 WindowManagerGlobal 中就會將它放入 mDyingViews 集合暫存。

// ViewRootImpl
boolean die(boolean immediate) {
    // Make sure we do execute immediately if we are in the middle of a traversal or the damage
    // done by dispatchDetachedFromWindow will cause havoc on return.
    if (immediate && !mIsInTraversal) {
        doDie();
        return false;
    }

    if (!mIsDrawing) {
        destroyHardwareRenderer();
    } else {
        Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
                "  window=" + this + ", title=" + mWindowAttributes.getTitle());
    }
    mHandler.sendEmptyMessage(MSG_DIE);
    return true;
}

die() 方法的核心又在 doDie() 方法中。

// ViewRootImpl
void doDie() {
    checkThread();
    if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
    synchronized (this) {
        if (mRemoved) {
            return;
        }
        mRemoved = true;
        if (mAdded) {
            dispatchDetachedFromWindow();
        }

        if (mAdded && !mFirst) {
            destroyHardwareRenderer();

            if (mView != null) {
                int viewVisibility = mView.getVisibility();
                boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
                if (mWindowAttributesChanged || viewVisibilityChanged) {
                    // If layout params have been changed, first give them
                    // to the window manager to make sure it has the correct
                    // animation info.
                    try {
                        if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
                                & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
                            mWindowSession.finishDrawing(mWindow);
                        }
                    } catch (RemoteException e) {
                    }
                }

                mSurface.release();
            }
        }

        mAdded = false;
    }
    WindowManagerGlobal.getInstance().doRemoveView(this);
}

doDie()方法中,有幾個關鍵點,首先是重置 mRemoved 字段,接著,如果已經 add 過,將會調用 dispatchDetachedFromWindow() 方法開始分發。

// ViewRootImpl
void dispatchDetachedFromWindow() {
    if (mView != null && mView.mAttachInfo != null) {
        mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
        mView.dispatchDetachedFromWindow();
    }

    ...
    // 釋放 Render 
    destroyHardwareRenderer();

    setAccessibilityFocus(null, null);

    mView.assignParent(null);
    mView = null;
    mAttachInfo.mRootView = null;

    mSurface.release();
    // 觸摸事件處理相關解除
    if (mInputQueueCallback != null && mInputQueue != null) {
        mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
        mInputQueue.dispose();
        mInputQueueCallback = null;
        mInputQueue = null;
    }
    if (mInputEventReceiver != null) {
        mInputEventReceiver.dispose();
        mInputEventReceiver = null;
    }
    try {
        // 移除 該 window
        mWindowSession.remove(mWindow);
    } catch (RemoteException e) {
    }

    // Dispose the input channel after removing the window so the Window Manager
    // doesn't interpret the input channel being closed as an abnormal termination.
    if (mInputChannel != null) {
        mInputChannel.dispose();
        mInputChannel = null;
    }

    mDisplayManager.unregisterDisplayListener(mDisplayListener);

    unscheduleTraversals();
}
// ViewRootImpl
void unscheduleTraversals() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
        mChoreographer.removeCallbacks(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
    }
}

可以看到,首先調用 view.dispatchDetachedFromWindow() ,這里的 View 有可能是 DecorView 嘛,我特意看了下,它并沒有復寫該方法,那直接先看到 ViewGroupdispatchDetachedFromWindow() 方法。

// ViewGroup.dispatchDetachedFromWindow
@Override
void dispatchDetachedFromWindow() {
    // If we still have a touch target, we are still in the process of
    // dispatching motion events to a child; we need to get rid of that
    // child to avoid dispatching events to it after the window is torn
    // down. To make sure we keep the child in a consistent state, we
    // first send it an ACTION_CANCEL motion event.
    // 給之前的 View 分發一個 cancel 結束觸摸事件
    cancelAndClearTouchTargets(null);

    // Similarly, set ACTION_EXIT to all hover targets and clear them.
    exitHoverTargets();
    exitTooltipHoverTargets();

    // In case view is detached while transition is running
    mLayoutCalledWhileSuppressed = false;

    // Tear down our drag tracking
    mChildrenInterestedInDrag = null;
    mIsInterestedInDrag = false;
    if (mCurrentDragStartEvent != null) {
        mCurrentDragStartEvent.recycle();
        mCurrentDragStartEvent = null;
    }

    final int count = mChildrenCount;
    final View[] children = mChildren;
    for (int i = 0; i < count; i++) {
        // child 回調 dispatchDetachedFromWindow
        children[i].dispatchDetachedFromWindow();
    }
    // 清楚 Disappearing child 
    clearDisappearingChildren();
    final int transientCount = mTransientViews == null ? 0 : mTransientIndices.size();
    for (int i = 0; i < transientCount; ++i) {
        View view = mTransientViews.get(i);
        view.dispatchDetachedFromWindow();
    }
    // 調用 View.dispatchDetachedFromWindow()
    super.dispatchDetachedFromWindow();
}
// View.dispatchDetachedFromWindow
void dispatchDetachedFromWindow() {
    ...
    onDetachedFromWindow();
    onDetachedFromWindowInternal();
    ...
}

最后在 View.dispatchDetachedFromWindow() 方法中,回調 onDetachedFromWindow() 方法,在 DecorView 的該方法中,會調用到 Activity.onDetachedFromWindow() ,并做其他一些資源釋放。接著看看 onDetachedFromWindowInternal() 方法,看看內部一個 View 最后的釋放操作。

protected void onDetachedFromWindowInternal() {
    //  清除標志位
    mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
    mPrivateFlags3 &= ~PFLAG3_IS_LAID_OUT;
    mPrivateFlags3 &= ~PFLAG3_TEMPORARY_DETACH;

    // 移除callback 
    removeUnsetPressCallback();
    removeLongPressCallback();
    removePerformClickCallback();
    removeSendViewScrolledAccessibilityEventCallback();
    stopNestedScroll();

    // Anything that started animating right before detach should already
    // be in its final state when re-attached.
    jumpDrawablesToCurrentState();

    // 清除 DrawingCache
    destroyDrawingCache();

    // 清除 RenderNode
    cleanupDraw();
    // 講 Animation 重置
    mCurrentAnimation = null;

    if ((mViewFlags & TOOLTIP) == TOOLTIP) {
        hideTooltip();
    }
}

到這里, ViewRootImpl 的創建以及銷毀分析完畢,期間將之前分析的一些方法和細節串聯起來了,比如說測量時 resolveSizeAndState() 方法中的 state 的作用。繪制時, mDisappearingChildren 中的 child 什么時候有添加,最后再釋放等等。 ViewRootImpl + WindowManagerGlobal 可以理解為最終的 ViewGroup ,這個 ViewGroup 是頂級的,和 Window 直接交互的。

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容