前语

今日听到隔壁搭档在面试校招生,两个人在那里对着 Window 侃侃而谈。说实话,作业中除了设置一些 Window 的属性调整界面显现之外,简直没怎么用到 Window,已然如此就挖一下 Window 这儿,加深一下对这儿的了解吧~~~

1. Window 是啥东西?

作为 Android 程序猿,多多少少都了解 Window 是什么,就算没深入了解过,必定也听过这个概念。

顾名思义,Window 便是窗口,能够了解为一个容器,承载着各式各样的 View。Window 其实只是一个抽象概念,并不是实在存在的。就像一个班集体,因为有了班级里的同学,集体这个概念才存在。Window 也是相同的,它的实体体现其实便是 View。

Window 是一个抽象类,它的仅有完结类是 PhoneWindowPhoneWindow 里边呢,有一个非常重要的逻辑便是创立 DecorView 目标。DecorView 也并不奥秘,便是 FrameLayout 的子类,换言之便是 View 的子类。PhoneWindow 之所以要创立 DecorView 目标,意图便是要把它作为视图树的根 View。当创立 Activity 的时分内部就会经过 PhoneWindow 来创立 DecorView

总而言之,Window 已然是 View 的容器,必定是要加载各式各样的 View 进来的,而 DecorView 便是这些 View 的根 View。
假如你还想多了解 DecorView,我之前有写过一篇文章 View 系列 —— 小白也能看懂的 DecorView 没事的话能够去瞅瞅。

2. 已然 Window 并不是实在存在的,实践上是以 View 的方式存在,那么为什么还需求 Window 这个概念呢?

幻想一下,假如没有 Window 这个容器,多个 View 在显现时谁在前面,谁在后边?假如我想在顶层显现一个 View,比方弹出一个 dialog,又该怎么操控呢?Window 便是起到操控 View 的效果。总而言之,Window 是 View 的办理者,同时也是 View 的载体,而 View 是 Window 的体现方式

3. 了解了 Window 是什么,那它是啥时分被创立的呢?

至于 Window 被创立的时机,当 Activity 被创立的时分就会去创立一个 Window。能够看看下面的 Activity 创立时的源码:

// ActivityThread.java
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...
    Activity activity = null;
    activity = mInstrumentation.newActivity(
        cl, component.getClassName(), r.intent);
    ...
    try {
        ...
        Window window = null;
        ...
        // 1.Activity的attach办法中会创立window
        activity.attach(appContext, this, getInstrumentation(), r.token,
                r.ident, app, r.intent, r.activityInfo, title, r.parent,
                r.embeddedID, r.lastNonConfigurationInstances, config,
                r.referrer, r.voiceInteractor, window, r.configCallback,
                r.assistToken, r.shareableActivityToken);
    }
    ..
    return activity;
}
// Activity.java
final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, IVoiceInteractor voiceInteractor,
        Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken,
        IBinder shareableActivityToken) {
    ...
    // 2.创立Window
    mWindow = new PhoneWindow(this, window, activityConfigCallback);
    ...

注释 1 处,在创立 Activity 的时分会经过 attach() 办法来创立 Window。注释 2 处,能够看到,attach()初始化PhoneWindow,前面说过 Window 的仅有完结类便是 PhoneWindow,所以很显然,attach() 中便是创立 Window 的当地。

4. 已然 Window 是 View 的容器,那么 Window 是怎么加载 View 的?

以 DecorView 为例,已然 DecorView 在 Activity 创立的时分去初始化,那它又是什么时分被加载到 Window 中来显现的呢?

揭晓答案,当处理 Activity 的 Resume 时,会将 DecorView 加载到 Window 中。看下源码:

// ActivityThread.java
@Override
public void handleResumeActivity(ActivityClientRecord r, boolean finalStateRequest,
        boolean isForward, String reason) {
    ...
    // 1.performResumeActivity 回调用 Activity 的 onResume
    if (!performResumeActivity(r, finalStateRequest, reason)) {
        return;
    }
    ...
    final Activity a = r.activity;
    ...
    if (r.window == null && !a.mFinished && willBeVisible) {
        r.window = r.activity.getWindow();
        // 2.获取 decorview
        View decor = r.window.getDecorView();
        // 3.decor 现在还不可见
        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;
                // 4.decor 增加到 WindowManger中
                wm.addView(decor, l);
            } else {
                a.onWindowAttributesChanged(l);
            }
        }
    }
    ...
}

上面的源码很清晰,当获取到当前 Window 的根 View DecorView 后,创立了 WindowManager,用 WindowManager 的 addView() 办法把 DecorView 给加载进去。

5. 看来首要的加载逻辑都在 WindowManager 中,那就说说 WindowManager 吧

看名字 WindowManager 便是一个 Window 办理器,继承自 ViewManager。因为 Window 是一个抽象概念,运用中咱们并不会直接操作 Window,而是经过其办理类 WindowManager 来完结增加删去等操作。

public interface ViewManager
{
    // 增加 View
    public void addView(View view, ViewGroup.LayoutParams params);
    // 更新 View
    public void updateViewLayout(View view, ViewGroup.LayoutParams params);
    // 删去 View
    public void removeView(View view);
}

ViewManager 的首要办法就这三个,别离用来增加、更新和删去 View。从这儿的办法中传入的参数都是 View,也能看出 Window 的是以 View 的方式存在的。

WindowManager 这儿的逻辑就有点绕了,它有一个自己的完结类 WindowMangerImplWindowMangerImpl 又把增加、更新、删去 View 的操作托付给 WindowMangerGlobal 来完结。

// WindowMangerImpl.java
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyTokens(params);
    // 实践上是经过 WindowManagerGlobal 完结 addView 的操作的,删去和更新 View 也是相同的
    mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow,
            mContext.getUserId());
}

6. 先从 addView 开始,看看 WindowManagerGlobal 中是怎么处理的?

addView() 的代码挺长,源码嘛,咱们看重要部分就行啦。我拣选了一下首要的代码:

// WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow, int userId) {
    ...
    final WindowManager.LayoutParams wparams  = (WindowManager.LayoutParams) params;
    ...
    ViewRootImpl root;
    View panelParentView = null;
    // 1.同步办法
    synchronized (mLock) {
        ...
        // 2. 创立ViewrootImpl的目标
        root = new ViewRootImpl(view.getContext(), display);
        view.setLayoutParams(wparams);
        // View列表
        mViews.add(view);
        // ViewRootImpl列表
        mRoots.add(root);
        // 布局参数
        mParams.add(wparams);
        try {
            // 3.将View与ViewRootImpl目标进行相关
            root.setView(view, wparams, panelParentView, userId);
        } catch (RuntimeException e) {
            // BadTokenException or InvalidDisplayException, clean up.
            if (index >= 0) {
                removeViewLocked(index, true);
            }
            throw e;
        }
    }
}

首要从 addView 办法的第一个参数是 View 也说明晰 Window 是以 View 的方式存在的。该办法中保护了 3 个列表,别离是 View 列表、ViewRootImpl 列表和布局参数列表。在后边 removeView 办法中会相应的从列表中移除对应目标。

注释 1:WindowManagerGlobaladdView() 办法是一个同步办法,运用了 synchronized 关键字来保证线程安全。这是因为在多线程环境下,或许会有多个线程同时向 WindowManager 中增加 View,假如不进行同步,或许会导致 View 的显现出现问题。

注释 2:在 addView 办法中,首要会创立一个 ViewRootImpl 目标,然后将该目标存放到 mRoots 这个列表中。

注释 3:用 ViewRootImplsetView 办法,将 View 与该 ViewRootImpl 目标进行相关。setView 这个办法也很老生常谈了。了解 View 的作业流程的都知道 setView 里边最重要的办法便是 requestLayout() 了,里边会调用到 performTraversals() 办法开启 View 的三大作业流程。可是除此之外,setView 中还涉及到增加 Window 的操作。

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView, int userId) {
    synchronized (this) {
        if (mView == null) {
            mView = view;
            ...
            requestLayout();
            ...
            try {
                ...
                // 增加 Window
                res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(), userId,
                        mInsetsController.getRequestedVisibility(), inputChannel, mTempInsets,
                        mTempControls);
                ...
                }
            } catch (RemoteException e) {
                ...
            } 
        }
    }
}

增加 Window 的过程便是经过 mWindowSession 来完结的,该变量是 IWindowSession 类型,是一个 AIDL 文件,很显然 Window 的增加过程其实是一次 IPC 进程通讯的过程。调用的实践是 Session 类的 addToDisplayAsUser。在 addToDisplayAsUser 中终究是经过 WindowManagerServiceaddWindow 办法完结了 Window 的增加。

public int addToDisplayAsUser(IWindow window, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, int userId, InsetsVisibilities requestedVisibilities,
            InputChannel outInputChannel, InsetsState outInsetsState,
            InsetsSourceControl[] outActiveControls, Rect outAttachedFrame,
            float[] outSizeCompatScale) {
        // 终究运用WindowManagerService来增加View
        return mService.addWindow(this, window, attrs, viewVisibility, displayId, userId,
                requestedVisibilities, outInputChannel, outInsetsState, outActiveControls,
                outAttachedFrame, outSizeCompatScale);
    }

总结
Window 调用 addView 办法时,实践上托付给了 WindowManagerGlobal 来处理,为了保证线程安全,addView 是同步办法。在办法中会创立一个 ViewRootImpl 目标,并调用其 setView 办法用来把 View 增加到 Window 中。setView 的内部涉及到了进程通讯,终究会经过 WindowManagerServiceaddWindow 办法完结了 View 的增加。

7. removeView()

话不多说,先看看 removeView() 的源码:

// WindowManagerGlobal.java
public void removeView(View view, boolean immediate) {
    if (view == null) {
        throw new IllegalArgumentException("view must not be null");
    }
    synchronized (mLock) {
        // 1.找到要删去的View的index
        int index = findViewLocked(view, true);
        View curView = mRoots.get(index).getView();
        // 2.删去操作
        removeViewLocked(index, immediate);
        if (curView == view) {
            return;
        }
        throw new IllegalStateException("Calling with view " + view
                + " but the ViewAncestor is attached to " + curView);
    }
}

删去办法看起来很直观,先经过 findViewLocked 办法找到需求删去的 View 的索引,然后调用 removeViewLocked 进行删去,这儿的入参 immediate 表明当即删去,也便是同步删去。若为 false,表明能够异步删去。

// WindowManagerGlobal.java
private void removeViewLocked(int index, boolean immediate) {
    // 在addView时会把ViewRootImpl目标存储在列表中,依据索引将其取出
    ViewRootImpl root = mRoots.get(index);
    View view = root.getView();
    if (root != null) {
        root.getImeFocusController().onWindowDismissed();
    }
    // ViewRootImpl履行die()
    boolean deferred = root.die(immediate);
    if (view != null) {
        view.assignParent(null);
        if (deferred) {
            mDyingViews.add(view);
        }
    }
}

removeViewLocked 办法中,首要获取要删去的 index 所对应的 ViewRootImpl 目标。然后履行其 die 办法:

// ViewRootImpl.java
boolean die(boolean immediate) {
    // 当即删去
    if (immediate && !mIsInTraversal) {
        doDie();
        return false;
    }
    ...
    // 不用当即删去的话,就往音讯行列发送个音讯
    mHandler.sendEmptyMessage(MSG_DIE);
    return true;
}

die 办法中会对 immediate 进行判断,假如为 true,需求当即履行删去操作 doDie(),不然会发送一个 MSG_DIE 的音讯,等 ViewRootImplHandler 处理该音讯并调用 doDie()

void doDie() {
    // ViewRootImpl.java
    checkThread();
    synchronized (this) {
        // 1. 是否已经履行过doDie办法了,履行过就不再履行了
        if (mRemoved) {
            return;
        }
        mRemoved = true;
        // 2. 假如该View已经增加到Window中了,履行dispatchDetachedFromWindow进行删去
        if (mAdded) {
            dispatchDetachedFromWindow();
        }
        ...
        mInsetsController.onControlsChanged(null);
        // 3. 整理与View相关的状况
        mAdded = false;
    }
    // 4. 履行一些WindowManagerGlobal中与要删去的View相关的参数。
    WindowManagerGlobal.getInstance().doRemoveView(this);
}

依据代码中的剖析,能够看到 doDie() 首要会对与该 View 相关的一些参数进行判断和设置,删去的逻辑在 dispatchDetachedFromWindow 中,终究履行 doRemoveView 删去的View相关的参数。下面咱们先看一下 dispatchDetachedFromWindow 办法:

void dispatchDetachedFromWindow() {
    ...
    if (mView != null && mView.mAttachInfo != null) {
        // 1. 调用View的dispatchDetachedFromWindow
        mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(false);
        mView.dispatchDetachedFromWindow();
    }
    // 2. 各种资源收回、移除监听的操作。
    mAccessibilityInteractionConnectionManager.ensureNoConnection();
    mAccessibilityManager.removeAccessibilityStateChangeListener(
            mAccessibilityInteractionConnectionManager);
    mAccessibilityManager.removeHighTextContrastStateChangeListener(
            mHighContrastTextManager);
    removeSendWindowContentChangedCallback();
    destroyHardwareRenderer();
    setAccessibilityFocus(null, null);
    mInsetsController.cancelExistingAnimations();
    mView.assignParent(null);
    mView = null;
    mAttachInfo.mRootView = null;
    destroySurface();
    if (mInputQueueCallback != null && mInputQueue != null) {
        mInputQueueCallback.onInputQueueDestroyed(mInputQueue);
        mInputQueue.dispose();
        mInputQueueCallback = null;
        mInputQueue = null;
    }
    // 3. 删去Window的当地
    try {
        mWindowSession.remove(mWindow);
    } catch (RemoteException e) {
    }
    ...
}

在这个办法中,会调用 View 的 dispatchDetachedFromWindow 办法,给一个回调,代表 View 被移除了。然后履行资源收回和移除监听的操作。再经过 mWindowSession 进行 IPC 操作调用 WindowManagerServiceremoveWindow() 移除 View。

总结
WindowManagerGlobalremoveView 中首要会去定位到要删去 View 的 index,依据这个 index 咱们就能在列表中获取到对应的 ViewRootImpl 目标,并履行 die()doDie() 能够选择是否要当即删去,当即删去就会直接履行 doDie() 办法,不然的话就运用 Handler 往音讯行列发送音讯等待履行。不管是否当即履行,都是会调用 doDie()。详细的删去逻辑是在 dispatchDetachedFromWindow 办法中,同样是经过进程通讯,运用 WindowManagerServiceremoveWindow() 移除 View 的。

8. updateViewLayout()

终究看看 Window 是怎么更新 View 的:

// WindowManagerGlobal.java
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
    if (view == null) {
        throw new IllegalArgumentException("view must not be null");
    }
    if (!(params instanceof WindowManager.LayoutParams)) {
        throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
    }
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;
    view.setLayoutParams(wparams);
    synchronized (mLock) {
        // 获取View的index
        int index = findViewLocked(view, true);
        // 依据index得到ViewRootImpl目标
        ViewRootImpl root = mRoots.get(index);
        // 移除旧布局参数
        mParams.remove(index);
        // 增加新布局参数
        mParams.add(index, wparams);
        // 把新布局设置给View
        root.setLayoutParams(wparams, false);
    }
}

更新操作比较简单,和 remove 办法相同,都是要经过 View 的 index 来得到 ViewRootImpl 目标。然后会更新 View 的 LayoutParams 并替换掉老的。接着调用 setLayoutParams 办法把新的布局参数设置给 View。

// ViewRootImpl.java
public void setLayoutParams(WindowManager.LayoutParams attrs, boolean newView) {
    synchronized (this) {
        ...
        // Window更新View的时分,newView是false
        if (newView) {
            mSoftInputMode = attrs.softInputMode;
            requestLayout();
        }
        ...
        // 终究会履行scheduleTraversals
        scheduleTraversals();
    }
}

setLayoutParams 办法终究会履行到咱们熟悉的 scheduleTraversals() 办法中,终究经过 performTraversals() 开启 View 的三大作业流程。不了解 scheduleTraversals()performTraversals() 的能够看看这篇文章 总算搞了解了什么是同步屏障。

更新 View 也同样会进行进程通讯,在 performTraversals() 中会履行 relayoutWindow()

private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
        boolean insetsPending) throws RemoteException {
    ...
    int relayoutResult = mWindowSession.relayout(mWindow, params,
            (int) (mView.getMeasuredWidth() * appScale + 0.5f),
            (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
            insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
            mTmpFrames, mPendingMergedConfiguration, mSurfaceControl, mTempInsets,
            mTempControls, mSurfaceSize);
    ...

同样是经过 mWindowSession 进行 IPC,终究经过 WindowManagerServicerelayoutWindow() 来完结。

总结

OK,讲了这么多,咱们终究就来捋一捋与 Window 相关的类之间的联系:

  • Window:Window 作为 View 的载体是一个抽象概念,其实体是 View。

  • WindowManager:接口,供给了创立、更新和删去 Window 等办法。实践上,应用程序经过 WindowManager 调用的接口终究都会传递给 WMS 来履行。

  • ViewRootImpl:在调用 WindowManager 的 addView() 时,会给这个 View 树分配一个ViewRootImpl,ViewRootImpl 负责与其他服务(WMS)联络并辅导 View 树的绘制作业。

  • WindowManagerService(WMS):体系级服务,负责办理 Window 的创立、显现、更新和毁掉。它是Android体系中窗口办理的中心,终究都是经过 IPC 来调用 WMS 中的办法来办理 Window 的。

参阅

直面底层:这一次,彻底搞懂Android 中的Window

捋一捋,到底怎么样去了解Window机制?