Android 源碼分析四 ViewRootImpl 相關(guān)

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

要說這些關(guān)系之前,先了解一些接口:

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

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

接下來提出4 個問題:

  1. ViewRootImpl 被誰創(chuàng)建和管理
  2. ViewRootImplWindow 對應(yīng)關(guān)系
  3. View 什么時候可以拿到具體寬高
  4. View 的事件分發(fā)源頭在哪兒

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 樹的頂層。強調(diào)它有實現(xiàn) ViewWindowManager 之間必要的協(xié)議,是WindowManagerGlobal 內(nèi)部實現(xiàn)中重要的組成部分。其實信息點挺多,直接就涉及到開頭要說的那個問題,ViewWindow 之間的關(guān)系。另外還提到它和 WindowManagerGlobal 的關(guān)系,看這架勢,它的很多方法可能都是被 WindowManagerGlobal 調(diào)用。

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

接著看看它的構(gòu)造方法:

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 創(chuàng)建,初始化準(zhǔn)備了很多東西,著重強調(diào) AttachInfo 創(chuàng)建,這個類很重要,之前說的 軟解時 Canvas 的保存和復(fù)用,還有 View.post() 方法執(zhí)行等等。

之前文章中,已經(jīng)或多或少提到 ViewRootImpl 的職責(zé)。比如說測量繪制最后都是由它發(fā)起。

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

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

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

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

WindowManagerGlobal

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

 private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

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

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 內(nèi)部是管理著 ViewRootImpl 和 其對應(yīng)的 RootView 還有對應(yīng)的 LayoutParams 等等。

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

WindowManagerImpl 中,我們看看 ViewManager 接口相關(guān)方法具體實現(xiàn):

//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 做具體實現(xiàn)。 WindowManagerImpl 代理了遠(yuǎn)程系統(tǒng)服務(wù), WindowManagerGlobal 代理了 WindowManagerImpl 的具體實現(xiàn)。

//WindowManagerGlobal
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
    ...
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    //設(shè)置Window一些基本參數(shù)
    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();
                        }
                    }
                }
            };
            //監(jiān)聽系統(tǒng)屬性設(shè)置變化:邊界布局 硬件加速支持等設(shè)置變化
            SystemProperties.addChangeCallback(mSystemPropertyUpdater);
        }
        // 判斷有沒有已經(jīng)添加過 
        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 已經(jīng)有 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);
                }
            }
        }
        // 創(chuàng)建 ViewRootImpl
        root = new ViewRootImpl(view.getContext(), display);

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

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

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

前面提到的前兩個問題已經(jīng)有了答案, ViewRootImplWindowManagerGlobal 創(chuàng)建, ViewRootImplWindow 的對應(yīng)關(guān)系是多對一,一個 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.
            // 調(diào)用這個方法之后會直接出發(fā) 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();
            }
            // 輸入事件相關(guān)
            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() 方法里面邏輯挺多的,首先會直接調(diào)用一次 requestLayout() ,然后會處理 addView() 的異常情況,類似于 Badtoken 這些異常。最后還有添加 Window 事件相關(guān)的監(jiān)聽。

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

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

Activity 的啟動是在 ActivityThread 類中進(jìn)行的。在 ActivityThreadperformLaunchActivity() 中,會完成 Activity 的創(chuàng)建(反射),并且會調(diào)用 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*/);
    // 創(chuàng)建出 PhoneWindow 
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    // 設(shè)置相關(guān) 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 創(chuàng)建了對應(yīng)的 PhoneWindow , 有了 PhoneWindow 才能有后文。對于 Activity ,我們設(shè)置的 ContentView 并不是頂層 View ,最頂層應(yīng)該是 DecorViewDecorView 是定義在 PhoneWindow 中:

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

所以,現(xiàn)在的問題就是 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() 中會回調(diào) 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;
                    // 調(diào)用 windowManager.addView()
                    wm.addView(decor, l);
                } else {
                    a.onWindowAttributesChanged(l);
                }
            }

        ...
}

代碼只保留 addView() 相關(guān)部分,在調(diào)用這個之前,會先調(diào)用 performResumeActivity() ,在這個方法中,就會回調(diào) ActivityonResume() 方法。也就是說,在 Activity 中,一個 View 的寬高,是在 Activity 第一次回調(diào) onResume() 方法之后,第一次的 onResume() 方法時并不能拿到寬高。 在這之后, DecorView 才添加到 PhoneWindow 中,接著觸發(fā) windowManagerGlobal.addView() 方法,接著調(diào)用 ViewRootImlp.setView() 方法,然后開始 requestLayout() ,最后觸發(fā) performTraversals() 方法,在這個方法中將會調(diào)用 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;
    // 第一次 執(zhí)行
    if (mFirst) {
        mFullRedrawNeeded = true;
        mLayoutRequested = true;

        ...
        // 設(shè)置一些基本參數(shù)
        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 時調(diào)用  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 的標(biāo)志 第一次為 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 內(nèi)部會調(diào)用 performMeasure()
        windowSizeMayChange |= measureHierarchy(host, lp, res,
                desiredWindowWidth, desiredWindowHeight);
    }

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

    ...

    if (layoutRequested) {
        // 這里已經(jīng)重置 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()) {
            // 更新對應(yīng) 寬高
            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) {
            ...
        }
    }
    // 執(zhí)行完畢 mIsInTraversal 重置 false 
    mIsInTraversal = false;
}

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

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

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

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

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

接著再看看在第一次測量時調(diào)用的 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 異常狀態(tài)
            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 的異常狀態(tài),如果是的話,那就把對應(yīng) size 再調(diào)大一些,可以看到,這里存在多次調(diào)用 performMeasure() 的情況。之前 Android 源碼分析二 View 測量

resolveSizeAndState() 方法時看到的 MEASURED_STATE_TOO_SMALL 狀態(tài)也有相關(guān)使用的地方啦。

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

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

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

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

事件分發(fā)

既然有了 Window 概念,觸摸事件肯定是從物理層面的觸摸屏,最后分發(fā)到每一個抽象的 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)
    <----和遠(yuǎn)程服務(wù)通過 handler 發(fā)的 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)

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

// 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 的創(chuàng)建。

        ...
        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 

那結(jié)合起來,屏幕上的觸摸事件,通過 WindowInputEventReceiverdispatchInputEvent() 方法調(diào)用到當(dāng)前進(jìn)程,接著 在 onInputEvent() 方法中開始一次分發(fā)傳遞。

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

結(jié)合堆棧信息可以看到,第一個是 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;
    // 回調(diào) 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 中有復(fù)寫 dispatchTouchEvent() 方法,上面講過,這個 callback 就是 Activity ,所以說事件分發(fā)首先傳入到 Activity 中。

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

Activity 中,又會優(yōu)先調(diào)用 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 中,接著又會調(diào)用 DecorView.superDispatchTouchEvent() ,這個方法最終會走自己的 dispatchTouchEvent() 方法,轉(zhuǎn)了一圈,互相客氣了一下。

removeView

說完 View 的創(chuàng)建和添加到 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 相關(guān)清除
    cancelTouchTarget(view);
    cancelHoverTarget(view);

    if (view.getAnimation() != null ||
            (mTransitioningViews != null && mTransitioningViews.contains(view))) {
        // 加入 mDisappearingChildren 集合
        addDisappearingView(view);
    } else if (view.mAttachInfo != null) {
       // 回調(diào) 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() ,調(diào)用這個方法之后,就回到上篇文章 Android 源碼分析三 View 繪制 分析的 draw() 方法中。它會繼續(xù)執(zhí)行動畫,在動畫結(jié)束后調(diào)用 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());
        }
    }
    // 調(diào)用 ViewRootImpl 的 die() 方法
    boolean deferred = root.die(immediate);
    if (view != null) {
        view.assignParent(null);
        if (deferred) {
            mDyingViews.add(view);
        }
    }
}

核心方法就是 ViewRootImpl.die() ,這個方法接受一個參數(shù),是否立即執(zhí)行,但是其實這個也有一個前提條件,就是當(dāng)前必須沒有正在執(zhí)行
performTraversals() 方法。直接執(zhí)行連隊列都不會放,直接干,其他時候是走 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()方法中,有幾個關(guān)鍵點,首先是重置 mRemoved 字段,接著,如果已經(jīng) add 過,將會調(diào)用 dispatchDetachedFromWindow() 方法開始分發(fā)。

// 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();
    // 觸摸事件處理相關(guān)解除
    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);
    }
}

可以看到,首先調(diào)用 view.dispatchDetachedFromWindow() ,這里的 View 有可能是 DecorView 嘛,我特意看了下,它并沒有復(fù)寫該方法,那直接先看到 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 分發(fā)一個 cancel 結(jié)束觸摸事件
    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 回調(diào) 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();
    }
    // 調(diào)用 View.dispatchDetachedFromWindow()
    super.dispatchDetachedFromWindow();
}
// View.dispatchDetachedFromWindow
void dispatchDetachedFromWindow() {
    ...
    onDetachedFromWindow();
    onDetachedFromWindowInternal();
    ...
}

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

protected void onDetachedFromWindowInternal() {
    //  清除標(biāo)志位
    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 的創(chuàng)建以及銷毀分析完畢,期間將之前分析的一些方法和細(xì)節(jié)串聯(lián)起來了,比如說測量時 resolveSizeAndState() 方法中的 state 的作用。繪制時, mDisappearingChildren 中的 child 什么時候有添加,最后再釋放等等。 ViewRootImpl + WindowManagerGlobal 可以理解為最終的 ViewGroup ,這個 ViewGroup 是頂級的,和 Window 直接交互的。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 230,048評論 6 542
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 99,414評論 3 429
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,169評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經(jīng)常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,722評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 72,465評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 55,823評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,813評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 43,000評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 49,554評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 41,295評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,513評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,035評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 44,722評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,125評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,430評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,237評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 48,482評論 2 379

推薦閱讀更多精彩內(nèi)容