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

相关文章
相关标签/搜索