Android 源碼分析四 ViewRootImpl 相關

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

要說這些關係以前,先了解一些接口:android

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 ,都實現了這兩個接口。windows

接下來提出4 個問題:markdown

  1. ViewRootImpl 被誰建立和管理
  2. ViewRootImplWindow 對應關係
  3. View 何時能夠拿到具體寬高
  4. View 的事件分發源頭在哪兒

ViewRootImpl

先看看 ViewRootImpl 的定義:app

/**
* 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 調用。less

在看接口實現和調用,它實現了 ViewParent 接口,可是,它並無實現 ViewManager 接口。對比 ViewGroup ,它少了 ViewManagerView 的增刪改能力。ide

接着看看它的構造方法:oop

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 直接交互的。

相關文章
相關標籤/搜索