Android 重學系列 WMS在Activity啟動中的職責(一)

前言

好久沒有繼續(xù)寫Android重學系列了昼捍。這次我們繼續(xù)聊聊當Activity創(chuàng)建之后烟勋。Android接下來就會嘗試的顯示界面ui。此時就會牽扯到一個核心的服務WindowManagerService(當然Activity的啟動也牽扯到了WMS,兩者是互相糾纏但是職能不同),窗口管理服務偏化。我原本是想和大家聊聊SurfaceFlinger,但是可惜的是匪凡,還沒到時候严衬,而且屬于學習起來需要一點OpenGL的知識,因此換一個方向抚笔,從上至下一點點剖析整個Android的顯示系統(tǒng)扶认。

因為Android的窗口也是顯示系統(tǒng)中的一員,同時也在Activity啟動中也嵌入了不少邏輯殊橙。因此本文將會從WMS的啟動以及Activity的啟動WMS在其中的角色這兩個角度辐宾,來聊聊WMS在Android中的職能狱从。

如果遇到問題請在http://www.reibang.com/p/1fd180ea5d0e
聯系本人,歡迎討論。

注意以下源碼全部來自Android9.0.

正文

概論

在重學系列的Android啟動中叠纹,我介紹核心類ActivityRecord在整個Android系統(tǒng)中的變換與流轉季研。但是并沒有涉及這個Activity怎么顯示到屏幕上的。也就說并沒有提及WindowManagerService(以后稱為WMS)誉察,系統(tǒng)窗口服務与涡。更加沒有提及SurfaceFlinger這個系統(tǒng)渲染類。

Android的顯示系統(tǒng)是一個十分大的體系持偏。本文作為Android顯示系統(tǒng)的第一篇驼卖,作為一個Android開發(fā),時刻接觸UI鸿秆,我們有必要聊聊這個體系中酌畜,各個核心類的職責。

WMS是做什么的谬莹?WMS顧名思義就是系統(tǒng)的窗口管理者檩奠。

窗口是什么?從Activity用戶交互界面角度看來附帽,就是指應用的頁面窗口埠戳。而從底層的SurfaceFlinger來看,WMS管理的每一個窗口都是一個可以從中獲取到像素數據通過GPU/CPU渲染到屏幕的Layer蕉扮。從WMS角度來看整胃,每一個窗口都是一個WindowState,用于管理窗口狀態(tài)喳钟。從每一個View來看屁使,View的根布局存在一個ViewRootImpl,所有的view的渲染像素都保存在ViewRootImpl的Surface對象中奔则。從I/O系統(tǒng)來看蛮寂,WMS還必須響應觸屏,鍵盤等事件的派發(fā)易茬。

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(事件分發(fā)服務)傳遞到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系統(tǒng)的顯示體系。
這里只是一個參考圖洲鸠,實際上在下面這幅圖還有些不夠準確堂淡。但是足以讓人大致上了解到Android顯示體系中,大體的研究方向扒腕,以及核心思想绢淀。

從WMS看Android的顯示體系.png

WindowContainer與WindowContainerController

為了之后的邏輯能夠更加輕易的整理。這邊需要先理清楚WMS這兩個核心類瘾腰,WindowContainer,WindowContainerController皆的。比起過去版本的Android系統(tǒng)(如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綁定在一起。一般的院塞,我們應用程序是基于它之上創(chuàng)建的遮晚。
我們能夠看到其泛型擴展的是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兴垦。

在系統(tǒng)第一次生成Window的時候,Android系統(tǒng)并不希望裁減掉最初當前的顯示范圍字柠。surfaceInsets代表著渲染的范圍探越,此時其實充滿屏幕的窗體能夠移動。因此會選擇最大尺寸*2窑业,讓窗體足夠的控件顯示钦幔。

包含了mWindowingLayer 和mOverlayLayer。OverLayer這些命名應該比較熟悉常柄,實際上有點像View之上的Overlayer一樣鲤氢,為Window動畫做鋪墊。

DisplayContent控制的WindowContainer

接著會把如下幾個WindowConatiner的添加到DisplayContent中:

  • 1.mBelowAppWindowsContainers(類型NonAppWindowContainers)
    一切的應該在Activity之下的Window容器西潘,比如wrapper壁紙卷玉。

  • 2.mTaskStackContainers(類型TaskStackContainers)
    這里面一般是指應用的Activity。實際上也就是我們常說的Activity對應著整個系統(tǒng)棧的管理者

  • 3.mAboveAppWindowsContainers(類型AboveAppWindowContainers)
    這里是指一切在Activity之上的窗體容器喷市,比如說StatusBar狀態(tài)欄相种。

  • 5.mImeWindowsContainers(類型NonMagnifiableWindowContainers)
    這里是指如Dialog,輸入鍵盤的窗體容器品姓。

因此我能夠依據順序能夠了解到寝并,實際上在整個Android系統(tǒng)中箫措,一旦開始繪制將會依照如下順序進行繪制一個界面:


系統(tǒng)窗體.png

最后統(tǒng)統(tǒng)添加到RootWindowContainer中。

當然既然有這些WindowContainer不可能不添加衬潦。這里替代掉addChild方法斤蔓,一般使用addWindowToken來添加到對應的WindowConatiner中。關于addWindowToken詳細會在之后聊到镀岛。

RootWindowContainer

RootWindowContainer 顧名思義弦牡,根部WindowContainer,一切WindowContainer的總管理者哎媚。其泛型確定了是DisplayContent喇伯。因此確切的說,這是專門用來管理邏輯顯示屏幕對應區(qū)域的窗體容器拨与。

當需要大范圍的尋找子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);
    }

此時一旦發(fā)現,如果這個WindowToken已經綁過了并且還呆在這個這個目標DisplayContent就沒有必要繼續(xù)添加皂吮。否則將會判斷當前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中。這個數據結構很重要艺挪,后文會繼續(xù)聊慌盯。

如果判斷到這個WindowToken不是Activity對應的WindowToken根據WindowToken傳進來的windowType來判斷孵睬,如果是壁紙則添加到mBelowAppWindowsContainers支示,如果是輸入法彈窗則添加到mImeWindowsContainers,剩下的如StatusBar添加到mAboveAppWindowsContainers移盆。

此時的操作悼院,是把所有游離的彈窗都收集到DisplayContent。畢竟無論是StatusBar咒循,輸入法還是壁紙都能夠脫離Activity存在的据途。

而Activity對應的Window窗體又是什么時候添加的呢?這里先留給懸念叙甸。

AppWindowToken

從上面的WindowToken颖医,我們能發(fā)現這么一個子類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區(qū)分對待俺榆,ActivityRecord是Acivity對應在AMS中的實例感昼。而AppWindowToken是Activity窗體對應在WMS中的實例。

到了后面我們就能看到實際上ActivityRecord和AppWindowToken都通過一個IApplicationToken的Binder對象維系起來罐脊。

此時AppWindowToken會對應一個AppWindowContainerController 定嗓。

WindowState

WindowState實際上是WMS用來控制每一個Window的狀態(tài)。里面包含了復雜的邏輯如計算當前窗體的大小萍桌,如控制Session的綁定等宵溅。詳細的不展開,將會在后文聊到上炎。

Task

Task這個名詞讓我們聯想到Activity對應的棧恃逻。那么這個和TaskRecord又有什么區(qū)別呢?實際上我們我們仔細看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,只會監(jiān)聽當前WindowConatainer回調的onOverrideConfigurationChanged匆浙。

AppWindowContainerController

從類的繼承關系看來:

public class AppWindowContainerController
        extends WindowContainerController<AppWindowToken, AppWindowContainerListener>

能從確定的泛型看到這個控制者安寺,從父類角度來看,監(jiān)聽的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在創(chuàng)建查排,需要調用createAppWindow 方法創(chuàng)建一個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創(chuàng)建一個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);
            }
        }

能夠很輕易的發(fā)現,此時在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閱讀 206,378評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件旗们,死亡現場離奇詭異蚓哩,居然都是意外死亡,警方通過查閱死者的電腦和手機上渴,發(fā)現死者居然都...
    沈念sama閱讀 88,356評論 2 382
  • 文/潘曉璐 我一進店門岸梨,熙熙樓的掌柜王于貴愁眉苦臉地迎上來喜颁,“玉大人,你說我怎么就攤上這事盛嘿÷宄玻” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評論 0 342
  • 文/不壞的土叔 我叫張陵次兆,是天一觀的道長稿茉。 經常有香客問我,道長芥炭,這世上最難降的妖魔是什么漓库? 我笑而不...
    開封第一講書人閱讀 55,259評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮园蝠,結果婚禮上渺蒿,老公的妹妹穿的比我還像新娘。我一直安慰自己彪薛,他們只是感情好茂装,可當我...
    茶點故事閱讀 64,263評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著善延,像睡著了一般少态。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上易遣,一...
    開封第一講書人閱讀 49,036評論 1 285
  • 那天彼妻,我揣著相機與錄音,去河邊找鬼豆茫。 笑死侨歉,一個胖子當著我的面吹牛,可吹牛的內容都是我干的揩魂。 我是一名探鬼主播幽邓,決...
    沈念sama閱讀 38,349評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼火脉!你這毒婦竟也來了颊艳?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 36,979評論 0 259
  • 序言:老撾萬榮一對情侶失蹤忘分,失蹤者是張志新(化名)和其女友劉穎棋枕,沒想到半個月后,有當地人在樹林里發(fā)現了一具尸體妒峦,經...
    沈念sama閱讀 43,469評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡重斑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,938評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現自己被綠了肯骇。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片窥浪。...
    茶點故事閱讀 38,059評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡祖很,死狀恐怖,靈堂內的尸體忽然破棺而出漾脂,到底是詐尸還是另有隱情假颇,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評論 4 323
  • 正文 年R本政府宣布骨稿,位于F島的核電站笨鸡,受9級特大地震影響,放射性物質發(fā)生泄漏坦冠。R本人自食惡果不足惜形耗,卻給世界環(huán)境...
    茶點故事閱讀 39,257評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望辙浑。 院中可真熱鬧激涤,春花似錦、人聲如沸判呕。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春遵倦,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背般贼。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工愧哟, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留奥吩,地道東北人。 一個月前我還...
    沈念sama閱讀 45,501評論 2 354
  • 正文 我出身青樓蕊梧,卻偏偏與公主長得像霞赫,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子肥矢,可洞房花燭夜當晚...
    茶點故事閱讀 42,792評論 2 345

推薦閱讀更多精彩內容