Android 重学系列 WMS在Activity启动中的职责(一)

前言

好久没有继续写Android重学系列了。这次我们继续聊聊当Activity创建之后。Android接下来就会尝试的显示界面ui。此时就会牵扯到一个核心的服务WindowManagerService(当然Activity的启动也牵扯到了WMS,两者是互相纠缠但是职能不同),窗口管理服务。我原本是想和大家聊聊SurfaceFlinger,但是可惜的是,还没到时候,而且属于学习起来需要一点OpenGL的知识,因此换一个方向,从上至下一点点剖析整个Android的显示系统。

因为Android的窗口也是显示系统中的一员,同时也在Activity启动中也嵌入了不少逻辑。因此本文将会从WMS的启动以及Activity的启动WMS在其中的角色这两个角度,来聊聊WMS在Android中的职能。

如果遇到问题请在https://www.jianshu.com/p/1fd180ea5d0e
联系本人,欢迎讨论。

注意以下源码全部来自Android9.0.

正文

概论

在重学系列的Android启动中,我介绍核心类ActivityRecord在整个Android系统中的变换与流转。但是并没有涉及这个Activity怎么显示到屏幕上的。也就说并没有提及WindowManagerService(以后称为WMS),系统窗口服务。更加没有提及SurfaceFlinger这个系统渲染类。

Android的显示系统是一个十分大的体系。本文作为Android显示系统的第一篇,作为一个Android开发,时刻接触UI,我们有必要聊聊这个体系中,各个核心类的职责。

WMS是做什么的?WMS顾名思义就是系统的窗口管理者。

窗口是什么?从Activity用户交互界面角度看来,就是指应用的页面窗口。而从底层的SurfaceFlinger来看,WMS管理的每一个窗口都是一个可以从中获取到像素数据通过GPU/CPU渲染到屏幕的Layer。从WMS角度来看,每一个窗口都是一个WindowState,用于管理窗口状态。从每一个View来看,View的根布局存在一个ViewRootImpl,所有的view的渲染像素都保存在ViewRootImpl的Surface对象中。从I/O系统来看,WMS还必须响应触屏,键盘等事件的派发。

WMS的启动

废话不多说,让我们先粗略的看看WMS的启动,我们能够从启动中窥探到WMS大致上控制了什么。还记的SystemServer启动的时候,我们初始化了很多服务吗,其中就有初始化WMS:

 wm = WindowManagerService.main(context, inputManager,
                    mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL,
                    !mFirstBoot, mOnlyCore, new PhoneWindowManager());
            ServiceManager.addService(Context.WINDOW_SERVICE, wm, /* allowIsolated= */ false,
                    DUMP_FLAG_PRIORITY_CRITICAL | DUMP_FLAG_PROTO);
            ServiceManager.addService(Context.INPUT_SERVICE, inputManager,
                    /* allowIsolated= */ false, DUMP_FLAG_PRIORITY_CRITICAL);

能看到此时WMS将会调用main方法,把InputManager(事件分发服务)传递到WMS中,并且生成了Window的窗口策略PhoneWindowManager。这个PhoneWindowManager实际上包含了计算窗口大小等策略,是一个核心类。

    public static WindowManagerService main(final Context context, final InputManagerService im,
            final boolean haveInputMethods, final boolean showBootMsgs, final boolean onlyCore,
            WindowManagerPolicy policy) {
        DisplayThread.getHandler().runWithScissors(() ->
                sInstance = new WindowManagerService(context, im, haveInputMethods, showBootMsgs,
                        onlyCore, policy), 0);
        return sInstance;
    }
private WindowManagerService(Context context, InputManagerService inputManager,
            boolean haveInputMethods, boolean showBootMsgs, boolean onlyCore,
            WindowManagerPolicy policy) {
        installLock(this, INDEX_WINDOW);
        mContext = context;
...
        LocalServices.getService(DisplayManagerInternal.class);
        mDisplaySettings = new DisplaySettings();
        mDisplaySettings.readSettingsLocked();

        mPolicy = policy;
        mAnimator = new WindowAnimator(this);
        mRoot = new RootWindowContainer(this);

        mWindowPlacerLocked = new WindowSurfacePlacer(this);
        mTaskSnapshotController = new TaskSnapshotController(this);

        mWindowTracing = WindowTracing.createDefaultAndStartLooper(context);

        LocalServices.addService(WindowManagerPolicy.class, mPolicy);

        if(mInputManager != null) {
            final InputChannel inputChannel = mInputManager.monitorInput(TAG_WM);
            mPointerEventDispatcher = inputChannel != null
                    ? new PointerEventDispatcher(inputChannel) : null;
        } else {
            mPointerEventDispatcher = null;
        }

        mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);

        mKeyguardDisableHandler = new KeyguardDisableHandler(mContext, mPolicy);

        mPowerManager = (PowerManager)context.getSystemService(Context.POWER_SERVICE);
        mPowerManagerInternal = LocalServices.getService(PowerManagerInternal.class);

      ...
        mScreenFrozenLock = mPowerManager.newWakeLock(
                PowerManager.PARTIAL_WAKE_LOCK, "SCREEN_FROZEN");
        mScreenFrozenLock.setReferenceCounted(false);

        mAppTransition = new AppTransition(context, this);
        mAppTransition.registerListenerLocked(mActivityManagerAppTransitionNotifier);

        final AnimationHandler animationHandler = new AnimationHandler();
        animationHandler.setProvider(new SfVsyncFrameCallbackProvider());
        mBoundsAnimationController = new BoundsAnimationController(context, mAppTransition,
                AnimationThread.getHandler(), animationHandler);

        mActivityManager = ActivityManager.getService();
        mAmInternal = LocalServices.getService(ActivityManagerInternal.class);
        mAppOps = (AppOpsManager)context.getSystemService(Context.APP_OPS_SERVICE);
...

        mPmInternal = LocalServices.getService(PackageManagerInternal.class);
        final IntentFilter suspendPackagesFilter = new IntentFilter();
        ...

        // Get persisted window scale setting
      ...

        mSettingsObserver = new SettingsObserver();

        mHoldingScreenWakeLock = mPowerManager.newWakeLock(
                PowerManager.SCREEN_BRIGHT_WAKE_LOCK | PowerManager.ON_AFTER_RELEASE, TAG_WM);
        mHoldingScreenWakeLock.setReferenceCounted(false);

        mSurfaceAnimationRunner = new SurfaceAnimationRunner();

...
        mTaskPositioningController = new TaskPositioningController(
                this, mInputManager, mInputMonitor, mActivityManager, mH.getLooper());
        mDragDropController = new DragDropController(this, mH.getLooper());

        LocalServices.addService(WindowManagerInternal.class, new LocalService());
    }

能看到启动如下几个核心类。

  • 1.WindowAnimator 窗口动画对象
  • 2.RootWindowContainer 根部窗口容器,管理窗口上所有的子窗口
  • 3.WindowSurfacePlacer 窗口大小大小测量者
  • 4.AnimationHandler Window处理动画事件的Handler
  • 5.PackageManagerInternal 包核心服务
  • 6.DisplayManager 显示器管理者
  • 7.PowerManager 电源管理者
  • 8.ActivityManager Activity操作相关的管理者

本文也将围绕RootWindowContainer 的父类WindowContainer 这个类进行讲解。

从WMS角度看Android显示体系的总览图

结合上面的构造函数,我这里弄出了一幅从WMS角度看Android系统的显示体系。
这里只是一个参考图,实际上在下面这幅图还有些不够准确。但是足以让人大致上了解到Android显示体系中,大体的研究方向,以及核心思想。

从WMS看Android的显示体系.png

WindowContainer与WindowContainerController

为了之后的逻辑能够更加轻易的整理。这边需要先理清楚WMS这两个核心类,WindowContainer,WindowContainerController。比起过去版本的Android系统(如4.4,7.0),Android9.0在窗口管理体系上做了很大的努力的抽象。

WindowContainer 在整个WMS中承担了所有可以看做Window容器的角色,其本身能够控制所有绑定进来的子WindowContainer。注意了,这里是指Window容器,而不是window本身。

WindowContainerController 在整个WMS中承担着控制WindowContainer的角色。

下面是一个UML图


WindowContainer大家族.png

能看到整个WMS几乎所有的核心操作需要核心类都在这里面了。

从名字我们就能窥探到在Activity启动流程中缺失的Window处理部分。也能从其泛型了解到每一个WindowContainer的关联。首先我们大致上来聊聊每一个WindowContainer的作用。

WindowContainer

WindowContainer 作为每一个可以看做是Window的容器的抽象类。其核心作用就是提供共有的处理子WindowConatiner操作。其核心添加子WindowConatiner如下:

protected final WindowList<E> mChildren = new WindowList<E>();


protected void addChild(E child, Comparator<E> comparator) {
        if (child.getParent() != null) {
            throw new IllegalArgumentException("addChild: container=" + child.getName()
                    + " is already a child of container=" + child.getParent().getName()
                    + " can't add to container=" + getName());
        }

        int positionToAdd = -1;
        if (comparator != null) {
            final int count = mChildren.size();
            for (int i = 0; i < count; i++) {
                if (comparator.compare(child, mChildren.get(i)) < 0) {
                    positionToAdd = i;
                    break;
                }
            }
        }

        if (positionToAdd == -1) {
            mChildren.add(child);
        } else {
            mChildren.add(positionToAdd, child);
        }
        onChildAdded(child);

        // Set the parent after we've actually added a child in case a subclass depends on this.
        child.setParent(this);
    }

能看到,在这个方法中,只要每一种Window容器都有自己的策略,通过这种策略调整插入的位置,最后才会进行插入子Window容器。并且获得父亲Window容器是什么。

实际上这种设计就是一个双向链表,只是更加的复杂。因此当WindowConatiner1实现的子类确定了什么泛型,说明这种WindowContainer控制的子WindowConatiner的类型也就确定了。

WindowContainer当然有其他重要的操作,这里先不聊。

DisplayContent

屏幕显示内容的WindowConatiner。其和逻辑显示屏幕id绑定在一起。一般的,我们应用程序是基于它之上创建的。
我们能够看到其泛型扩展的是DisplayChildWindowContainer:

static class DisplayChildWindowContainer<E extends WindowContainer> extends WindowContainer<E> {

        DisplayChildWindowContainer(WindowManagerService service) {
            super(service);
        }

        @Override
        boolean fillsParent() {
            return true;
        }

        @Override
        boolean isVisible() {
            return true;
        }
    }

实现十分简单,DisplayContent必定是充满全父容器,且可见的。注意了,既然是代表的是逻辑上的显示屏那必定不可能存在子的显示屏,因此addChlid是禁止的:

    @Override
    protected void addChild(DisplayChildWindowContainer child,
            Comparator<DisplayChildWindowContainer> comparator) {
        throw new UnsupportedOperationException("See DisplayChildWindowContainer");
    }

    @Override
    protected void addChild(DisplayChildWindowContainer child, int index) {
        throw new UnsupportedOperationException("See DisplayChildWindowContainer");
    }

了解这些不足够让我们对DisplayContent有一个粗略的了解,让我们看看构造函数:

 DisplayContent(Display display, WindowManagerService service,
            WallpaperController wallpaperController, DisplayWindowController controller) {
        super(service);
        setController(controller);
        if (service.mRoot.getDisplayContent(display.getDisplayId()) != null) {
            throw new IllegalArgumentException("Display with ID=" + display.getDisplayId()
                    + " already exists=" + service.mRoot.getDisplayContent(display.getDisplayId())
                    + " new=" + display);
        }

        mDisplay = display;
        mDisplayId = display.getDisplayId();
        mWallpaperController = wallpaperController;
        display.getDisplayInfo(mDisplayInfo);
        display.getMetrics(mDisplayMetrics);
        isDefaultDisplay = mDisplayId == DEFAULT_DISPLAY;
        mDisplayFrames = new DisplayFrames(mDisplayId, mDisplayInfo,
                calculateDisplayCutoutForRotation(mDisplayInfo.rotation));
        initializeDisplayBaseInfo();
        mDividerControllerLocked = new DockedStackDividerController(service, this);
        mPinnedStackControllerLocked = new PinnedStackController(service, this);

        // We use this as our arbitrary surface size for buffer-less parents
        // that don't impose cropping on their children. It may need to be larger
        // than the display size because fullscreen windows can be shifted offscreen
        // due to surfaceInsets. 2 times the largest display dimension feels like an
        // appropriately arbitrary number. Eventually we would like to give SurfaceFlinger
        // layers the ability to match their parent sizes and be able to skip
        // such arbitrary size settings.
        mSurfaceSize = Math.max(mBaseDisplayHeight, mBaseDisplayWidth) * 2;

        final SurfaceControl.Builder b = mService.makeSurfaceBuilder(mSession)
                .setSize(mSurfaceSize, mSurfaceSize)
                .setOpaque(true);
        mWindowingLayer = b.setName("Display Root").build();
        mOverlayLayer = b.setName("Display Overlays").build();

        getPendingTransaction().setLayer(mWindowingLayer, 0)
                .setLayerStack(mWindowingLayer, mDisplayId)
                .show(mWindowingLayer)
                .setLayer(mOverlayLayer, 1)
                .setLayerStack(mOverlayLayer, mDisplayId)
                .show(mOverlayLayer);
        getPendingTransaction().apply();

        // These are the only direct children we should ever have and they are permanent.
        super.addChild(mBelowAppWindowsContainers, null);
        super.addChild(mTaskStackContainers, null);
        super.addChild(mAboveAppWindowsContainers, null);
        super.addChild(mImeWindowsContainers, null);

        // Add itself as a child to the root container.
        mService.mRoot.addChild(this, null);

        // TODO(b/62541591): evaluate whether this is the best spot to declare the
        // {@link DisplayContent} ready for use.
        mDisplayReady = true;
    }

    boolean isReady() {
        // The display is ready when the system and the individual display are both ready.
        return mService.mDisplayReady && mDisplayReady;
    }

此时能看到DisplayContent在构造函数中,首先会把相关的屏幕大小,密度,id等信息绑定。

接着实例化SurfaceControl这个对象。记住这个对象是核心,联通Window和Android渲染核心的类。

所有渲染像素都会保存在Surface中,因此这个实例化告诉Surface是,此时需要透明,并且此时渲染屏幕的大小公式为:Max(屏幕宽度,屏幕高度)*2。

在系统第一次生成Window的时候,Android系统并不希望裁减掉最初当前的显示范围。surfaceInsets代表着渲染的范围,此时其实充满屏幕的窗体能够移动。因此会选择最大尺寸*2,让窗体足够的控件显示。

包含了mWindowingLayer 和mOverlayLayer。OverLayer这些命名应该比较熟悉,实际上有点像View之上的Overlayer一样,为Window动画做铺垫。

DisplayContent控制的WindowContainer

接着会把如下几个WindowConatiner的添加到DisplayContent中:

  • 1.mBelowAppWindowsContainers(类型NonAppWindowContainers)
    一切的应该在Activity之下的Window容器,比如wrapper壁纸。

  • 2.mTaskStackContainers(类型TaskStackContainers)
    这里面一般是指应用的Activity。实际上也就是我们常说的Activity对应着整个系统栈的管理者

  • 3.mAboveAppWindowsContainers(类型AboveAppWindowContainers)
    这里是指一切在Activity之上的窗体容器,比如说StatusBar状态栏。

  • 5.mImeWindowsContainers(类型NonMagnifiableWindowContainers)
    这里是指如Dialog,输入键盘的窗体容器。

因此我能够依据顺序能够了解到,实际上在整个Android系统中,一旦开始绘制将会依照如下顺序进行绘制一个界面:


系统窗体.png

最后统统添加到RootWindowContainer中。

当然既然有这些WindowContainer不可能不添加。这里替代掉addChild方法,一般使用addWindowToken来添加到对应的WindowConatiner中。关于addWindowToken详细会在之后聊到。

RootWindowContainer

RootWindowContainer 顾名思义,根部WindowContainer,一切WindowContainer的总管理者。其泛型确定了是DisplayContent。因此确切的说,这是专门用来管理逻辑显示屏幕对应区域的窗体容器。

当需要大范围的寻找子Window容器,可以通过RootWindowContainer 进行轮询查找。

WindowToken

从名字上来看就知道这是一个句柄。这是一个关于Window的句柄。甚至可以推测WindowToken中肯定包含一个IBinder对象,来对应Android端的Window。具体这个IBinder是指哪一个,稍后就揭晓。

为了能够粗略的了解WindowToken,让我们先看看它的构造函数:

    WindowToken(WindowManagerService service, IBinder _token, int type, boolean persistOnEmpty,
            DisplayContent dc, boolean ownerCanManageAppTokens, boolean roundedCornerOverlay) {
        super(service);
        token = _token;
        windowType = type;
        mPersistOnEmpty = persistOnEmpty;
        mOwnerCanManageAppTokens = ownerCanManageAppTokens;
        mRoundedCornerOverlay = roundedCornerOverlay;
        onDisplayChanged(dc);
    }

在这里面有一个核心的函数onDisplayChanged,当WindowToken生成了,说明应用端有一个新的Window诞生了,需要做addWindow到WindowManager的操作,此时需要告诉DisplayContent,窗体列表需要更新了。

void onDisplayChanged(DisplayContent dc) {
        dc.reParentWindowToken(this);
        mDisplayContent = dc;

        // The rounded corner overlay should not be rotated. We ensure that by moving it outside
        // the windowing layer.
        if (mRoundedCornerOverlay) {
            mDisplayContent.reparentToOverlay(mPendingTransaction, mSurfaceControl);
        }

        // TODO(b/36740756): One day this should perhaps be hooked
        // up with goodToGo, so we don't move a window
        // to another display before the window behind
        // it is ready.

        super.onDisplayChanged(dc);
    }
WindowToken添加到DisplayContent中

此时调用了DisplayContent的reParentWindowToken方法,把当前的WindowToken绑定到DisplayContent中。

/** Changes the display the input window token is housed on to this one. */
    void reParentWindowToken(WindowToken token) {
        final DisplayContent prevDc = token.getDisplayContent();
        if (prevDc == this) {
            return;
        }
        if (prevDc != null && prevDc.mTokenMap.remove(token.token) != null
                && token.asAppWindowToken() == null) {
            // Removed the token from the map, but made sure it's not an app token before removing
            // from parent.
            token.getParent().removeChild(token);
        }

        addWindowToken(token.token, token);
    }

此时一旦发现,如果这个WindowToken已经绑过了并且还呆在这个这个目标DisplayContent就没有必要继续添加。否则将会判断当前WindowToken是否已经绑定了父WindowContainer,把它从父WindowContainer移除出来。

接下来就是addWindowToken的逻辑。

private void addWindowToken(IBinder binder, WindowToken token) {
        final DisplayContent dc = mService.mRoot.getWindowTokenDisplay(token);
        if (dc != null) {
            // We currently don't support adding a window token to the display if the display
            // already has the binder mapped to another token. If there is a use case for supporting
            // this moving forward we will either need to merge the WindowTokens some how or have
            // the binder map to a list of window tokens.
            throw new IllegalArgumentException("Can't map token=" + token + " to display="
                    + getName() + " already mapped to display=" + dc + " tokens=" + dc.mTokenMap);
        }
        if (binder == null) {
            throw new IllegalArgumentException("Can't map token=" + token + " to display="
                    + getName() + " binder is null");
        }
        if (token == null) {
            throw new IllegalArgumentException("Can't map null token to display="
                    + getName() + " binder=" + binder);
        }

        mTokenMap.put(binder, token);

        if (token.asAppWindowToken() == null) {
            // Add non-app token to container hierarchy on the display. App tokens are added through
            // the parent container managing them (e.g. Tasks).
            switch (token.windowType) {
                case TYPE_WALLPAPER:
                    mBelowAppWindowsContainers.addChild(token);
                    break;
                case TYPE_INPUT_METHOD:
                case TYPE_INPUT_METHOD_DIALOG:
                    mImeWindowsContainers.addChild(token);
                    break;
                default:
                    mAboveAppWindowsContainers.addChild(token);
                    break;
            }
        }
    }

首先会把所有的WindowToken添加到TokenMap中。这个数据结构很重要,后文会继续聊。

如果判断到这个WindowToken不是Activity对应的WindowToken根据WindowToken传进来的windowType来判断,如果是壁纸则添加到mBelowAppWindowsContainers,如果是输入法弹窗则添加到mImeWindowsContainers,剩下的如StatusBar添加到mAboveAppWindowsContainers。

此时的操作,是把所有游离的弹窗都收集到DisplayContent。毕竟无论是StatusBar,输入法还是壁纸都能够脱离Activity存在的。

而Activity对应的Window窗体又是什么时候添加的呢?这里先留给悬念。

AppWindowToken

从上面的WindowToken,我们能发现这么一个子类AppWindowToken。它实际上是专门指代Activity的Window。从源码的角度看来,就是指PhoneWindow对应到WMS的句柄对象。

我们还是粗略看看其构造函数

AppWindowToken(WindowManagerService service, IApplicationToken token, boolean voiceInteraction,
            DisplayContent dc, long inputDispatchingTimeoutNanos, boolean fullscreen,
            boolean showForAllUsers, int targetSdk, int orientation, int rotationAnimationHint,
            int configChanges, boolean launchTaskBehind, boolean alwaysFocusable,
            AppWindowContainerController controller) {
        this(service, token, voiceInteraction, dc, fullscreen);
        setController(controller);
        mInputDispatchingTimeoutNanos = inputDispatchingTimeoutNanos;
        mShowForAllUsers = showForAllUsers;
        mTargetSdk = targetSdk;
        mOrientation = orientation;
        layoutConfigChanges = (configChanges & (CONFIG_SCREEN_SIZE | CONFIG_ORIENTATION)) != 0;
        mLaunchTaskBehind = launchTaskBehind;
        mAlwaysFocusable = alwaysFocusable;
        mRotationAnimationHint = rotationAnimationHint;

        // Application tokens start out hidden.
        setHidden(true);
        hiddenRequested = true;
    }

持有了一个IApplicationToken,判断该窗口隶属于哪个应用程序。此时传入了AppWindowContainerController 一个WindowConatinerController。这个将会控制着这个AppWindowToken。

当然这里需要和ActivityRecord区分对待,ActivityRecord是Acivity对应在AMS中的实例。而AppWindowToken是Activity窗体对应在WMS中的实例。

到了后面我们就能看到实际上ActivityRecord和AppWindowToken都通过一个IApplicationToken的Binder对象维系起来。

此时AppWindowToken会对应一个AppWindowContainerController 。

WindowState

WindowState实际上是WMS用来控制每一个Window的状态。里面包含了复杂的逻辑如计算当前窗体的大小,如控制Session的绑定等。详细的不展开,将会在后文聊到。

Task

Task这个名词让我们联想到Activity对应的栈。那么这个和TaskRecord又有什么区别呢?实际上我们我们仔细看Task的继承关系,Task是继承于WindowConatainer,确定了内部的泛型为AppWindowToken。

class Task extends WindowContainer<AppWindowToken>

可以说,其本质上就是为了控制应用端Activity窗口对应的AppWindowToken的List集合。除此之外还包含了计算当前WindowContainer的边缘,大小,位置等。

让我们看看其构造函数:

    Task(int taskId, TaskStack stack, int userId, WindowManagerService service, int resizeMode,
            boolean supportsPictureInPicture, TaskDescription taskDescription,
            TaskWindowContainerController controller) {
        super(service);
        mTaskId = taskId;
        mStack = stack;
        mUserId = userId;
        mResizeMode = resizeMode;
        mSupportsPictureInPicture = supportsPictureInPicture;
        setController(controller);
        setBounds(getOverrideBounds());
        mTaskDescription = taskDescription;

        // Tasks have no set orientation value (including SCREEN_ORIENTATION_UNSPECIFIED).
        setOrientation(SCREEN_ORIENTATION_UNSET);
    }

里面包含了当前Task的id。当前Task处于哪一个Task栈中,ActivityInfo对应的resizeMode,Task描述等等。

这里的Task和TaskRecord的关系其实就和AppWindowToken和ActivityRecord的关系一样。可以抽象的看成同一种对象在两种不同的服务的表现形式。

而TaskRecord和Task又是通过什么维系起来的呢?我们能够从构造函数中了解到,此时的Task和TaskRecord都有一个TaskId把两者联系起来。

TaskStack

TaskStack,从名字上看来,是一个Task的管理栈。从实现的角度来看其确定了泛型是Task。说明这是WMS管理Task的一个数据结构。当然每一个Stack都有自己的Id作为唯一的标识。

看看构造函数:

   TaskStack(WindowManagerService service, int stackId, StackWindowController controller) {
        super(service);
        mStackId = stackId;
        setController(controller);
        mDockedStackMinimizeThickness = service.mContext.getResources().getDimensionPixelSize(
                com.android.internal.R.dimen.docked_stack_minimize_thickness);
        EventLog.writeEvent(EventLogTags.WM_STACK_CREATED, stackId);
    }

能够看到此时TaskStack又会对应一个StackId,对应一个Window容器控制者,StackWindowController 。

WindowContainerController

作为WindowContainer的控制者。实际上其应用场景在Activity启动中十分广泛。

从上面的WindowConatiner,我们能够知道每一个WindowContainerController和WindowContainer都有如下的控制关系。

WindowContainer和WindowContainerController关系.png

为了更加深刻的理解这几个数据结构,让我们也稍微过一下这些控制者们。

首先来看看所有窗体容器控制者的父类,WindowContainerController.

WindowContainerController

这个类很简单,直接放出整个类出来:

class WindowContainerController<E extends WindowContainer, I extends WindowContainerListener>
        implements ConfigurationContainerListener {

    final WindowManagerService mService;
    final RootWindowContainer mRoot;
    final WindowHashMap mWindowMap;

    // The window container this controller owns.
    E mContainer;
    // Interface for communicating changes back to the owner.
    final I mListener;

    WindowContainerController(I listener, WindowManagerService service) {
        mListener = listener;
        mService = service;
        mRoot = mService != null ? mService.mRoot : null;
        mWindowMap = mService != null ? mService.mWindowMap : null;
    }

    void setContainer(E container) {
        if (mContainer != null && container != null) {
            throw new IllegalArgumentException("Can't set container=" + container
                    + " for controller=" + this + " Already set to=" + mContainer);
        }
        mContainer = container;
        if (mContainer != null && mListener != null) {
            mListener.registerConfigurationChangeListener(this);
        }
    }

    void removeContainer() {
        // TODO: See if most uses cases should support removeIfPossible here.
        //mContainer.removeIfPossible();
        if (mContainer == null) {
            return;
        }

        mContainer.setController(null);
        mContainer = null;
        if (mListener != null) {
            mListener.unregisterConfigurationChangeListener(this);
        }
    }

    @Override
    public void onOverrideConfigurationChanged(Configuration overrideConfiguration) {
        synchronized (mWindowMap) {
            if (mContainer == null) {
                return;
            }
            mContainer.onOverrideConfigurationChanged(overrideConfiguration);
        }
    }
}

从这个Controller,我们能够了解到,他的父类在构造函数的时候,会把RootWindowContainer传进来。并且传入mWindowMap这个Map数据结构。记住这个mWindowMap数据结构,它的重要性和上面的mTokenMap一样。

接着就能知道Controller每一次只会控制一个WindowContainer,只会监听当前WindowConatainer回调的onOverrideConfigurationChanged。

AppWindowContainerController

从类的继承关系看来:

public class AppWindowContainerController
        extends WindowContainerController<AppWindowToken, AppWindowContainerListener>

能从确定的泛型看到这个控制者,从父类角度来看,监听的AppWindowToken中回调AppWindowContainerListener。同时控制着AppWindowToken。究竟怎么控制,看看构造函数就清楚了。

    public AppWindowContainerController(TaskWindowContainerController taskController,
            IApplicationToken token, AppWindowContainerListener listener, int index,
            int requestedOrientation, boolean fullscreen, boolean showForAllUsers, int configChanges,
            boolean voiceInteraction, boolean launchTaskBehind, boolean alwaysFocusable,
            int targetSdkVersion, int rotationAnimationHint, long inputDispatchingTimeoutNanos,
            WindowManagerService service) {
        super(listener, service);
        mHandler = new H(service.mH.getLooper());
        mToken = token;
        synchronized(mWindowMap) {
            AppWindowToken atoken = mRoot.getAppWindowToken(mToken.asBinder());
            if (atoken != null) {
                // TODO: Should this throw an exception instead?
                Slog.w(TAG_WM, "Attempted to add existing app token: " + mToken);
                return;
            }

            final Task task = taskController.mContainer;
            if (task == null) {
                throw new IllegalArgumentException("AppWindowContainerController: invalid "
                        + " controller=" + taskController);
            }

            atoken = createAppWindow(mService, token, voiceInteraction, task.getDisplayContent(),
                    inputDispatchingTimeoutNanos, fullscreen, showForAllUsers, targetSdkVersion,
                    requestedOrientation, rotationAnimationHint, configChanges, launchTaskBehind,
                    alwaysFocusable, this);
            if (DEBUG_TOKEN_MOVEMENT || DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "addAppToken: " + atoken
                    + " controller=" + taskController + " at " + index);
            task.addChild(atoken, index);
        }
    }

从构造函数中就能看到如下逻辑:

  • 1.首先会根据ActivityRecord中的IApplicationToken去RootWindContainer中查找有没有对应的AppWindowToken.如果这个窗口本身存在,那么必定会存在,不存在说明Activity在创建,需要调用createAppWindow 方法创建一个AppWindowToken对应上来。

但是AppWindowToken和ActivityRecord一样,数据结构上必须相似,也就说会跟着Task。如果找不到Task,说明此时是非法。找到则会通过Task的addChlid的方法添加到从构造函数传下来的位置。

TaskWindowContainerController

相似的,看看Task的窗体管理者的构造函数:

public TaskWindowContainerController(int taskId, TaskWindowContainerListener listener,
            StackWindowController stackController, int userId, Rect bounds, int resizeMode,
            boolean supportsPictureInPicture, boolean toTop, boolean showForAllUsers,
            TaskDescription taskDescription, WindowManagerService service) {
        super(listener, service);
        mTaskId = taskId;
        mHandler = new H(new WeakReference<>(this), service.mH.getLooper());

        synchronized(mWindowMap) {
            if (DEBUG_STACK) Slog.i(TAG_WM, "TaskWindowContainerController: taskId=" + taskId
                    + " stack=" + stackController + " bounds=" + bounds);

            final TaskStack stack = stackController.mContainer;
            if (stack == null) {
                throw new IllegalArgumentException("TaskWindowContainerController: invalid stack="
                        + stackController);
            }
            EventLog.writeEvent(WM_TASK_CREATED, taskId, stack.mStackId);
            final Task task = createTask(taskId, stack, userId, resizeMode,
                    supportsPictureInPicture, taskDescription);
            final int position = toTop ? POSITION_TOP : POSITION_BOTTOM;
            // We only want to move the parents to the parents if we are creating this task at the
            // top of its stack.
            stack.addTask(task, position, showForAllUsers, toTop /* moveParents */);
        }
    }

其逻辑和AppWindowContainerController很相似,每一个Task中都会添加到一个TaskStack中。Task的生成将会由TaskWindowContainerController的createTask控制。

StackWindowController

同样的,看看StackWindowController 作为最顶层的数据结构,窗体又是怎么控制的。

    public StackWindowController(int stackId, StackWindowListener listener,
            int displayId, boolean onTop, Rect outBounds, WindowManagerService service) {
        super(listener, service);
        mStackId = stackId;
        mHandler = new H(new WeakReference<>(this), service.mH.getLooper());

        synchronized (mWindowMap) {
            final DisplayContent dc = mRoot.getDisplayContent(displayId);
            if (dc == null) {
                throw new IllegalArgumentException("Trying to add stackId=" + stackId
                        + " to unknown displayId=" + displayId);
            }

            dc.createStack(stackId, onTop, this);
            getRawBounds(outBounds);
        }
    }

能看到的是,只要调用了StackWindowController构造函数,就必定根据当前的传入的stackId,尝试着通过DisplayContent创建一个Stack出来。

因此我们看看DisplayContent中的方法。

DisplayContent.createStack

文件:http://androidxref.com/9.0.0_r3/xref/frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java

    TaskStack createStack(int stackId, boolean onTop, StackWindowController controller) {
        if (DEBUG_STACK) Slog.d(TAG_WM, "Create new stackId=" + stackId + " on displayId="
                + mDisplayId);

        final TaskStack stack = new TaskStack(mService, stackId, controller);
        mTaskStackContainers.addStackToDisplay(stack, onTop);
        return stack;
    }

此时就能看到了,这个时候将会生成一个TaskStack的对象。不过令我吃惊的是,居然没有对同一个StackId的情况做处理。这个地方虽然不太可能出现相同id的TaskStack,不过感觉还是不够严谨。

还记得我上面在addWindowToken中说过的话吗?addWindowToken只是处理游离在Activity外面的Window,如壁纸和输入法弹窗。而Activity相关的Window实际上是借由Stack生成的时候,把TaskStack这个WindowContainer添加到DisplayContent的内部类TaskStackContainers这个WindowContainer中。

实际上TaskStackContainers源码上的注释并不是很准确,实际上管理不光光只是Activity,而是管理着TaskStack这个总管理者。

我们稍微看看TaskStackContainers这个类。

TaskStackContainers

private final class TaskStackContainers extends DisplayChildWindowContainer<TaskStack>

我们能够看到DisplayContent内部类TaskStackContainers 也是继承了DisplayChildWindowContainer。换句话说,实际上包含了上面的WindowContainer控制子WindowContainer的操作。

void addStackToDisplay(TaskStack stack, boolean onTop) {
            addStackReferenceIfNeeded(stack);
            addChild(stack, onTop);
            stack.onDisplayChanged(DisplayContent.this);
        }

在添加的时候,我们看看addStackReferenceIfNeeded方法:

private void addStackReferenceIfNeeded(TaskStack stack) {
            if (stack.isActivityTypeHome()) {
                if (mHomeStack != null) {
                    throw new IllegalArgumentException("addStackReferenceIfNeeded: home stack="
                            + mHomeStack + " already exist on display=" + this + " stack=" + stack);
                }
                mHomeStack = stack;
            }
            final int windowingMode = stack.getWindowingMode();
            if (windowingMode == WINDOWING_MODE_PINNED) {
                if (mPinnedStack != null) {
                    throw new IllegalArgumentException("addStackReferenceIfNeeded: pinned stack="
                            + mPinnedStack + " already exist on display=" + this
                            + " stack=" + stack);
                }
                mPinnedStack = stack;
            } else if (windowingMode == WINDOWING_MODE_SPLIT_SCREEN_PRIMARY) {
                if (mSplitScreenPrimaryStack != null) {
                    throw new IllegalArgumentException("addStackReferenceIfNeeded:"
                            + " split-screen-primary" + " stack=" + mSplitScreenPrimaryStack
                            + " already exist on display=" + this + " stack=" + stack);
                }
                mSplitScreenPrimaryStack = stack;
                mDividerControllerLocked.notifyDockedStackExistsChanged(true);
            }
        }

能够很轻易的发现,此时在TaskStackContainers 中会控制着当前逻辑屏幕主要的Stack,如Home,分屏等。这样就佐证了,实际上DisplayContent确实代表着逻辑显示屏幕。如果不是分屏的情况下,则会只有一个DisplayContent,也只有一个TaskStackContainers 。这个TaskContainer会控制着应用各个窗口的栈。

总结

根据上面WindowContainer之间的关系,WindowContainerController和WindowContainer之间的关系,我们能够构造出下面这个关系。

先来看看WindowContainer之间的关系:


WindowContainer之间的联系.png

因为WMS的窗体管理体系在Android9.0比起Android4.4,7.0来说抽象出了不少的对象,如果事先没有先对这些对象有一定了解,直接冲到源码里面阅读一定会晕头转向。

如果仅仅只是阅读我的Activity的启动流程一文,一定会感觉到意犹未尽,甚至越来越糊涂,因为上一个系列并没有涉及到窗口相关的内容。

相信就算是看到这些WindowContainer的工作原理,大体上已经对WMS的工作有一点了解。只剩下把这些线索串起来,下一篇文章将会走一遍核心流程,看看WMS究竟是怎么在Activity启动流程中增加Window,把这些线索串起来,把Activity的启动流程串起来,相信会有不一样的理解。

后话

实际上,我阅读Android的显示体系已经花了挺久时间的,阅读到了底层更是需要对OpenGL es有一定的了解。一直没有多少把握写好Android显示体系的文章。因为涉及面实在太多了,有时候一个点看不太懂一看就是一个星期。WMS相对底层来说,就显得十分的可爱,没有c/c++那样的艰涩难懂。

当然,如果你对OpenGL es没有多少了解也没关系,可以跟着我写的OpenGL学习笔记一起学习一些基本的OpenGL,相信你会有不少收获。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,340评论 5 467
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,762评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,329评论 0 329
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,678评论 1 270
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,583评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,995评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,493评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,145评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,293评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,250评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,267评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,973评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,556评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,648评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,873评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,257评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,809评论 2 339

推荐阅读更多精彩内容