Android 重學(xué)系列 WMS在Activity啟動中的職責(zé) 計(jì)算窗體的大小(四)

前言

通過啟動窗口為例子埠通,大致上明白了WMS是如何添加渠啤,更新跛锌,移除窗口的工作原理。本文將會重點(diǎn)聊一聊窗口的大小計(jì)算邏輯丧诺。

下面的源碼都是來自Android 9.0

正文

窗口大小計(jì)算

計(jì)算窗口的大小和Android 4.4相比變化很大入桂。花了一點(diǎn)心思去重新學(xué)習(xí)了驳阎。在Android 4.4中,窗體的計(jì)算在onResume中調(diào)用了ViewRootImpl調(diào)用relayoutWindow對整個Window重新測量窗口大小以及邊距抗愁。

relayoutWindow這個方法是做什么的呢?當(dāng)我們在Activity的生命周期到達(dá)了onResume的階段呵晚,此時ViewRootImpl的setView蜘腌,開始走渲染的View的流程,并且調(diào)用requestLayout開始測量渲染饵隙。其中有一個核心的邏輯就是調(diào)用WMS的relayoutWindow撮珠,重新測量Window。

在Android 9.0中把這個流程和DisplayContent綁定起來金矛。讓我們稍微解剖一下這個方法芯急。

relayout

  public int relayoutWindow(Session session, IWindow client, int seq, LayoutParams attrs,
            int requestedWidth, int requestedHeight, int viewVisibility, int flags,
            long frameNumber, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
            Rect outVisibleInsets, Rect outStableInsets, Rect outOutsets, Rect outBackdropFrame,
            DisplayCutout.ParcelableWrapper outCutout, MergedConfiguration mergedConfiguration,
            Surface outSurface) {
        int result = 0;
        boolean configChanged;
        final boolean hasStatusBarPermission =
                mContext.checkCallingOrSelfPermission(permission.STATUS_BAR)
                        == PackageManager.PERMISSION_GRANTED;
        final boolean hasStatusBarServicePermission =
                mContext.checkCallingOrSelfPermission(permission.STATUS_BAR_SERVICE)
                        == PackageManager.PERMISSION_GRANTED;

        long origId = Binder.clearCallingIdentity();
        final int displayId;
        synchronized(mWindowMap) {
            WindowState win = windowForClientLocked(session, client, false);
            if (win == null) {
                return 0;
            }
            displayId = win.getDisplayId();

      ....
       
            mWindowPlacerLocked.performSurfacePlacement(true /* force */);

            if (shouldRelayout) {
                result = win.relayoutVisibleWindow(result, attrChanges, oldVisibility);

                try {
                    result = createSurfaceControl(outSurface, result, win, winAnimator);
                } catch (Exception e) {
                    mInputMonitor.updateInputWindowsLw(true /*force*/);
                    Binder.restoreCallingIdentity(origId);
                    return 0;
                }
                if ((result & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
                    focusMayChange = isDefaultDisplay;
                }
                if (win.mAttrs.type == TYPE_INPUT_METHOD && mInputMethodWindow == null) {
                    setInputMethodWindowLocked(win);
                    imMayMove = true;
                }
                win.adjustStartingWindowFlags();
                Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
            } else {
...
            }

            if (focusMayChange) {
                if (updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
                        false /*updateInputWindows*/)) {
                    imMayMove = false;
                }
            }

            boolean toBeDisplayed = (result & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0;
            final DisplayContent dc = win.getDisplayContent();
            if (imMayMove) {
                dc.computeImeTarget(true /* updateImeTarget */);
                if (toBeDisplayed) {
                    dc.assignWindowLayers(false /* setLayoutNeeded */);
                }
            }

...

            outFrame.set(win.mCompatFrame);
            outOverscanInsets.set(win.mOverscanInsets);
            outContentInsets.set(win.mContentInsets);
            win.mLastRelayoutContentInsets.set(win.mContentInsets);
            outVisibleInsets.set(win.mVisibleInsets);
            outStableInsets.set(win.mStableInsets);
            outCutout.set(win.mDisplayCutout.getDisplayCutout());
            outOutsets.set(win.mOutsets);
            outBackdropFrame.set(win.getBackdropFrame(win.mFrame));

            result |= mInTouchMode ? WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE : 0;

            mInputMonitor.updateInputWindowsLw(true /*force*/);

            win.mInRelayout = false;
        }
...
        Binder.restoreCallingIdentity(origId);
        return result;
    }

relayout大致上要做了以下的事情:

  • 1.通過IWindow找到對應(yīng)的WindowState,并且獲取各種參數(shù)驶俊。
  • 2.WindowPlacerLocked.performSurfacePlacement為Surface交互做準(zhǔn)備娶耍。
  • 3.創(chuàng)建Surface對象,開始和SurfaceFlinger做交互饼酿。
  • 4.updateFocusedWindowLocked Window發(fā)生變化則會嘗試著重新計(jì)算窗體大小的區(qū)域
  • 5.計(jì)算層級榕酒,設(shè)置窗體的各種邊距。

relayout的方法有點(diǎn)長故俐,本次我們將關(guān)注這一部分核心的邏輯想鹰。分別是兩個方法:

  • 1.mWindowPlacerLocked.performSurfacePlacement 當(dāng)窗體出現(xiàn)了變更,需要重新設(shè)置DisplayContent的各種參數(shù),銷毀不用的Surface药版,重新計(jì)算層級杖挣,計(jì)算觸點(diǎn)區(qū)域等等

  • 2.updateFocusedWindowLocked 當(dāng)發(fā)現(xiàn)Window可能發(fā)生變化,則重新計(jì)算窗口大小刚陡。

WindowPlacerLocked.performSurfacePlacement

    final void performSurfacePlacement(boolean force) {
        if (mDeferDepth > 0 && !force) {
            return;
        }
        int loopCount = 6;
        do {
            mTraversalScheduled = false;
            performSurfacePlacementLoop();
            mService.mAnimationHandler.removeCallbacks(mPerformSurfacePlacement);
            loopCount--;
        } while (mTraversalScheduled && loopCount > 0);
        mService.mRoot.mWallpaperActionPending = false;
    }

能看到在這里面對performSurfacePlacementLoop做最多為6次的循環(huán),這六次循環(huán)做什么呢惩妇?

private void performSurfacePlacementLoop() {
        ...
        mInLayout = true;

        boolean recoveringMemory = false;
        if (!mService.mForceRemoves.isEmpty()) {
            recoveringMemory = true;
            while (!mService.mForceRemoves.isEmpty()) {
                final WindowState ws = mService.mForceRemoves.remove(0);
                ws.removeImmediately();
            }
            Object tmp = new Object();
            synchronized (tmp) {
                try {
                    tmp.wait(250);
                } catch (InterruptedException e) {
                }
            }
        }

        try {
            mService.mRoot.performSurfacePlacement(recoveringMemory);

            mInLayout = false;

            if (mService.mRoot.isLayoutNeeded()) {
                if (++mLayoutRepeatCount < 6) {
                    requestTraversal();
                } else {
                    Slog.e(TAG, "Performed 6 layouts in a row. Skipping");
                    mLayoutRepeatCount = 0;
                }
            } else {
                mLayoutRepeatCount = 0;
            }

            if (mService.mWindowsChanged && !mService.mWindowChangeListeners.isEmpty()) {
                mService.mH.removeMessages(REPORT_WINDOWS_CHANGE);
                mService.mH.sendEmptyMessage(REPORT_WINDOWS_CHANGE);
            }
        } catch (RuntimeException e) {
            mInLayout = false;
        }
    }

能看到這里面的核心邏輯,首先會檢查WMS下mForceRemoves集合中是否還有對象筐乳。有則調(diào)用removeImmediately清空WindowState的中SurfaceControl和WindowContainer之間的綁定和Surface對象歌殃,以及銷毀WindowAnimator中的Surface。

做這個得到目的很簡單蝙云,因?yàn)橄乱粋€步驟將會申請一個Surface對象氓皱,而此時如果Android系統(tǒng)內(nèi)存過大了(OOM),mForceRemoves就存在對象,就可以銷毀不需要的Surface波材。這一點(diǎn)的設(shè)計(jì)和Davlik虛擬機(jī)申請對象時候的思路倒是一致的股淡。

銷毀需要一點(diǎn)時間,因此就需要做一個250毫秒的的等待廷区。接著會調(diào)用RootWindowContainer的performSurfacePlacement做真正的執(zhí)行唯灵。最后會通過handler通過ViewServer通知事件給DebugBridge調(diào)試類中。

每一次loop的最后隙轻,如果發(fā)現(xiàn)RootWindowContainer需要重新測量埠帕,就會把當(dāng)前這個方法,放入Handler中玖绿,等待下次的調(diào)用敛瓷,也是調(diào)用6次。這樣就能最大限度的保證在這段時間內(nèi)Window能夠測量每一次的窗體參數(shù)斑匪。

RootWindowContainer.performSurfacePlacement

下面這個方法十分長呐籽,我們只看核心;

   void performSurfacePlacement(boolean recoveringMemory) {


        int i;
        boolean updateInputWindowsNeeded = false;
//核心事件1
        if (mService.mFocusMayChange) {
            mService.mFocusMayChange = false;
            updateInputWindowsNeeded = mService.updateFocusedWindowLocked(
                    UPDATE_FOCUS_WILL_PLACE_SURFACES, false /*updateInputWindows*/);
        }

        // 核心事件2
        final int numDisplays = mChildren.size();
        for (int displayNdx = 0; displayNdx < numDisplays; ++displayNdx) {
            final DisplayContent displayContent = mChildren.get(displayNdx);
            displayContent.setExitingTokensHasVisible(false);
        }

        mHoldScreen = null;
...
        final DisplayInfo defaultInfo = defaultDisplay.getDisplayInfo();
        final int defaultDw = defaultInfo.logicalWidth;
        final int defaultDh = defaultInfo.logicalHeight;

...

        final WindowSurfacePlacer surfacePlacer = mService.mWindowPlacerLocked;

        // 核心事件3
        if (mService.mAppTransition.isReady()) {
            final int layoutChanges = surfacePlacer.handleAppTransitionReadyLocked();
            defaultDisplay.pendingLayoutChanges |= layoutChanges;
            if (DEBUG_LAYOUT_REPEATS)
                surfacePlacer.debugLayoutRepeats("after handleAppTransitionReadyLocked",
                        defaultDisplay.pendingLayoutChanges);
        }

        if (!isAppAnimating() && mService.mAppTransition.isRunning()) {
            defaultDisplay.pendingLayoutChanges |=
                    mService.handleAnimatingStoppedAndTransitionLocked();
        }

        // 核心事件4
        final RecentsAnimationController recentsAnimationController =
            mService.getRecentsAnimationController();
        if (recentsAnimationController != null) {
            recentsAnimationController.checkAnimationReady(mWallpaperController);
        }

        if (mWallpaperForceHidingChanged && defaultDisplay.pendingLayoutChanges == 0
                && !mService.mAppTransition.isReady()) {
            defaultDisplay.pendingLayoutChanges |= FINISH_LAYOUT_REDO_LAYOUT;
        }
        mWallpaperForceHidingChanged = false;

...
        if (mService.mFocusMayChange) {
            mService.mFocusMayChange = false;
            if (mService.updateFocusedWindowLocked(UPDATE_FOCUS_PLACING_SURFACES,
                    false /*updateInputWindows*/)) {
                updateInputWindowsNeeded = true;
                defaultDisplay.pendingLayoutChanges |= FINISH_LAYOUT_REDO_ANIM;
            }
        }

...
        final ArraySet<DisplayContent> touchExcludeRegionUpdateDisplays = handleResizingWindows();

....
        // 核心事件5
        boolean wallpaperDestroyed = false;
        i = mService.mDestroySurface.size();
        if (i > 0) {
            do {
                i--;
                WindowState win = mService.mDestroySurface.get(i);
                win.mDestroying = false;
                if (mService.mInputMethodWindow == win) {
                    mService.setInputMethodWindowLocked(null);
                }
                if (win.getDisplayContent().mWallpaperController.isWallpaperTarget(win)) {
                    wallpaperDestroyed = true;
                }
                win.destroySurfaceUnchecked();
                win.mWinAnimator.destroyPreservedSurfaceLocked();
            } while (i > 0);
            mService.mDestroySurface.clear();
        }

...

        // 核心事件6
        mService.mInputMonitor.updateInputWindowsLw(true /*force*/);

        mService.setHoldScreenLocked(mHoldScreen);
...

        if (mSustainedPerformanceModeCurrent != mSustainedPerformanceModeEnabled) {
            mSustainedPerformanceModeEnabled = mSustainedPerformanceModeCurrent;
            mService.mPowerManagerInternal.powerHint(
                    PowerHint.SUSTAINED_PERFORMANCE,
                    (mSustainedPerformanceModeEnabled ? 1 : 0));
        }

....

//事件7
        final int N = mService.mPendingRemove.size();
        if (N > 0) {
            if (mService.mPendingRemoveTmp.length < N) {
                mService.mPendingRemoveTmp = new WindowState[N+10];
            }
            mService.mPendingRemove.toArray(mService.mPendingRemoveTmp);
            mService.mPendingRemove.clear();
            ArrayList<DisplayContent> displayList = new ArrayList();
            for (i = 0; i < N; i++) {
                final WindowState w = mService.mPendingRemoveTmp[i];
                w.removeImmediately();
                final DisplayContent displayContent = w.getDisplayContent();
                if (displayContent != null && !displayList.contains(displayContent)) {
                    displayList.add(displayContent);
                }
            }

            for (int j = displayList.size() - 1; j >= 0; --j) {
                final DisplayContent dc = displayList.get(j);
                dc.assignWindowLayers(true /*setLayoutNeeded*/);
            }
        }

        // 事件8
        for (int displayNdx = mChildren.size() - 1; displayNdx >= 0; --displayNdx) {
            mChildren.get(displayNdx).checkCompleteDeferredRemoval();
        }

        if (updateInputWindowsNeeded) {
            mService.mInputMonitor.updateInputWindowsLw(false /*force*/);
        }
        mService.setFocusTaskRegionLocked(null);
        if (touchExcludeRegionUpdateDisplays != null) {
            final DisplayContent focusedDc = mService.mFocusedApp != null
                    ? mService.mFocusedApp.getDisplayContent() : null;
            for (DisplayContent dc : touchExcludeRegionUpdateDisplays) {
                if (focusedDc != dc) {
                    dc.setTouchExcludeRegion(null /* focusedTask */);
                }
            }
        }

        // 核心事件9
        mService.enableScreenIfNeededLocked();

        mService.scheduleAnimationLocked();

    }

我在上面劃分了9個部分:

  • 1.如果WMS發(fā)現(xiàn)當(dāng)前的焦點(diǎn)Window發(fā)生了改變,則會調(diào)用updateFocusedWindowLocked重新測量窗口大小蚀瘸。
  • 2.設(shè)置所有即將推出的WindowToken為不可見的標(biāo)志位狡蝶。
  • 3.執(zhí)行App transition(窗體動畫)的時候,檢測所有的窗體都可見苍姜。接著我們需要自然的完成我們的窗體動畫,此時還沒有真正的把視圖繪制到屏幕上悬包,因此為了實(shí)現(xiàn)這個事情衙猪,就需要推遲很多操作。如顯示隱藏程序布近,按照z軸擺列窗體等等垫释,因此需要重新建立Window的層級。
  • 4.推遲壁紙的繪制撑瞧。
  • 5.清除需要銷毀的Surface
  • 6.更新觸點(diǎn)事件的范圍
  • 7.清除掉那些當(dāng)動畫結(jié)束之后棵譬,需要推遲清除的Surface,接著重新對層級進(jìn)行排序
  • 8.更新觸點(diǎn)事件策略预伺,并且更新觸點(diǎn)事件范圍(這個會有專門的輸入系統(tǒng)專欄和大家聊聊)
  • 9.執(zhí)行窗體動畫

這里只給總覽订咸,之后有機(jī)會再進(jìn)去里面抓細(xì)節(jié)。

WMS.updateFocusedWindowLocked

我們能夠看到無論是在哪里酬诀,如果窗口發(fā)生了變化脏嚷,都會調(diào)用updateFocusedWindowLocked方法。實(shí)際上這個方法才是真正的核心測量窗口大小邏輯瞒御。

boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
        WindowState newFocus = mRoot.computeFocusedWindow();
        if (mCurrentFocus != newFocus) {
...
            final DisplayContent displayContent = getDefaultDisplayContentLocked();
            boolean imWindowChanged = false;
            if (mInputMethodWindow != null) {
                final WindowState prevTarget = mInputMethodTarget;
                final WindowState newTarget =
                        displayContent.computeImeTarget(true /* updateImeTarget*/);

                imWindowChanged = prevTarget != newTarget;

                if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS
                        && mode != UPDATE_FOCUS_WILL_PLACE_SURFACES) {
                    final int prevImeAnimLayer = mInputMethodWindow.mWinAnimator.mAnimLayer;
                    displayContent.assignWindowLayers(false /* setLayoutNeeded */);
                    imWindowChanged |=
                            prevImeAnimLayer != mInputMethodWindow.mWinAnimator.mAnimLayer;
                }
            }

            if (imWindowChanged) {
                mWindowsChanged = true;
                displayContent.setLayoutNeeded();
                newFocus = mRoot.computeFocusedWindow();
            }

...
            int focusChanged = mPolicy.focusChangedLw(oldFocus, newFocus);

            if (imWindowChanged && oldFocus != mInputMethodWindow) {

                if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
                    displayContent.performLayout(true /*initial*/,  updateInputWindows);
                    focusChanged &= ~FINISH_LAYOUT_REDO_LAYOUT;
                } else if (mode == UPDATE_FOCUS_WILL_PLACE_SURFACES) {
                    displayContent.assignWindowLayers(false /* setLayoutNeeded */);
                }
            }

            if ((focusChanged & FINISH_LAYOUT_REDO_LAYOUT) != 0) {
                displayContent.setLayoutNeeded();
                if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
                    displayContent.performLayout(true /*initial*/, updateInputWindows);
                }
            }

            if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) {
                mInputMonitor.setInputFocusLw(mCurrentFocus, updateInputWindows);
            }
...
            return true;
        }
        return false;
    }

這里注意一下isWindowChange是判斷輸入法焦點(diǎn)是否一致父叙,而窗體焦點(diǎn)則是通過不同的WindowState來判斷。

  • 1.首先如果發(fā)現(xiàn)有輸入法彈窗則重新計(jì)算層級,或者說如果輸入法焦點(diǎn)窗口發(fā)生變化趾唱,也要從RootWindowContainer找到當(dāng)前的焦點(diǎn)窗口涌乳。
  • 2.如果輸入法焦點(diǎn)出現(xiàn)了變化,且當(dāng)前的模式是UPDATE_FOCUS_PLACING_SURFACES(需要強(qiáng)制重繪)則要重新計(jì)算窗體大小甜癞,否則則直接做一次層級變化即可夕晓。
    1. 一般的情況下,如果UPDATE_FOCUS_PLACING_SURFACES這個模式带欢,則需要performLayout重新測量窗體各個邊距大小
  • 4.不是UPDATE_FOCUS_WILL_ASSIGN_LAYERS模式运授,則則需要處理觸點(diǎn)焦點(diǎn)邊距。

實(shí)際上核心測量的真正動作是DisplayContent.performLayout乔煞。我們仔細(xì)一想也就知道吁朦,在Android 9.0的時候,DisplayContent象征著邏輯屏幕渡贾,我們討論無分屏的情況逗宜,實(shí)際上就是指我們當(dāng)前窗體鋪滿邏輯顯示屏各個邊距的大小。

窗體邊距的類型

在正式開始聊窗體大小的測量之前空骚,實(shí)際上纺讲,在Android系統(tǒng)中,為了把Window各個邊界標(biāo)記出來囤屹,實(shí)際上隨著時代和審美潮流的演進(jìn)熬甚,誕生越來越多的邊距類型,我們往往可以通過這些邊距來測定窗體的大小肋坚。

在DisplayFrame中有了大致的分區(qū)乡括,如下:

type 描述
mOverScan 帶有這個前后綴的邊距名字代表著過掃描。過掃描是什么東西智厌?實(shí)際上在我們在自己看自己的手機(jī)屏幕诲泌,會發(fā)現(xiàn)手機(jī)屏幕顯示范圍并未鋪滿全屏,而是留有一點(diǎn)黑色的邊框铣鹏。而這個黑色的部分就是就是過掃描區(qū)域敷扫。原因是如果把顯示范圍鋪面全屏,如電視機(jī)之類的屏幕會導(dǎo)致失真诚卸。
過掃描區(qū)域.png
mOverScanScreen 代表著顯示屏真實(shí)寬高葵第,是帶上了過掃描的邊距范圍
image.png
mRestrictedOverscan 類似OverScanScreen,適當(dāng)?shù)臅r候允許移動到OverScanScreen中
mUnrestricted 真實(shí)屏幕大小合溺,但是不包含OverScan區(qū)域
mRestricted 當(dāng)前屏幕大小;如果狀態(tài)欄無法隱藏羹幸,則這些值可能不同于(0,0)-(dw,dh);在這種情況下,它有效地將顯示區(qū)域從所有其他窗口分割出來辫愉。
mSystem 在布局期間栅受,所有可見的SysytemUI元素區(qū)域
mStable 穩(wěn)定不變的應(yīng)用內(nèi)容區(qū)域
mStableFullscreen 對于穩(wěn)定不變的應(yīng)用區(qū)域,但是這個Window是添加了FullScreen標(biāo)志,這是除了StatusBar以外的區(qū)域
mCurrent 在布局期間屏镊,當(dāng)前屏幕且?guī)湘I盤依疼,狀態(tài)欄的區(qū)域(雖然不好理解,但是如果是分屏和自由窗口模式就好理解了)
mContent 布局期間而芥,向用戶展示內(nèi)容的所有區(qū)域律罢,包括所有的外部裝飾如狀態(tài)來和鍵盤,一般和mCurrent一樣棍丐,除非使用了嵌套模式误辑,則會比mCurrent更大。
mVoiceContent 布局期間歌逢,我們聲量變化時候的系統(tǒng)區(qū)域
mDock 輸入法窗體區(qū)域
mDisplayCutout 劉海屏上面那一塊的區(qū)域
mDisplayCutoutSafe 劉海屏不允許使用交叉部分
image.png

可以看到巾钉,這些窗體的邊距實(shí)際上是跟著這些年潮流走的。如Android 7.0的自由窗體模式秘案,嵌套窗體模式砰苍,劉海屏等等,這些邊距的共同作用阱高,才會誕生一個真正的Window大小赚导。有了這些基礎(chǔ)知識之后,我們?nèi)タ纯礈y量大小的邏輯赤惊。

DisplayContent.performLayout

    void performLayout(boolean initial, boolean updateInputWindows) {
        if (!isLayoutNeeded()) {
            return;
        }
        clearLayoutNeeded();

        final int dw = mDisplayInfo.logicalWidth;
        final int dh = mDisplayInfo.logicalHeight;

        mDisplayFrames.onDisplayInfoUpdated(mDisplayInfo,
                calculateDisplayCutoutForRotation(mDisplayInfo.rotation));
        mDisplayFrames.mRotation = mRotation;
        mService.mPolicy.beginLayoutLw(mDisplayFrames, getConfiguration().uiMode);
        if (isDefaultDisplay) {
            mService.mSystemDecorLayer = mService.mPolicy.getSystemDecorLayerLw();
            mService.mScreenRect.set(0, 0, dw, dh);
        }

        int seq = mLayoutSeq + 1;
        if (seq < 0) seq = 0;
        mLayoutSeq = seq;

        mTmpWindow = null;
        mTmpInitial = initial;

        // 首先測量那些沒有綁定父窗口的窗口
        forAllWindows(mPerformLayout, true /* traverseTopToBottom */);
...
       //測量那些綁定了父窗口的窗口
        forAllWindows(mPerformLayoutAttached, true /* traverseTopToBottom */);
...
    }

我們這里把這個方法拆成如下幾個部分:

  • 設(shè)置DisplayFrame
  • beginLayoutLw開始測量
  • 測量那些沒有綁定父窗口的窗口
  • 測量那些綁定了父窗口的窗口

設(shè)置DisplayFrame

    public void onDisplayInfoUpdated(DisplayInfo info, WmDisplayCutout displayCutout) {
        mDisplayWidth = info.logicalWidth;
        mDisplayHeight = info.logicalHeight;
        mRotation = info.rotation;
        mDisplayInfoOverscan.set(
                info.overscanLeft, info.overscanTop, info.overscanRight, info.overscanBottom);
        mDisplayInfoCutout = displayCutout != null ? displayCutout : WmDisplayCutout.NO_CUTOUT;
    }

能看到吼旧,此時會設(shè)置當(dāng)前顯示屏幕的大小耸三,以及獲取過掃描區(qū)域吏口,還會判斷當(dāng)前手機(jī)屏幕是否支持劉海屏。這一切實(shí)際上都是由硬件回饋到DisplayService航徙,我們再從中獲取的信息处面。

PhoneWindowManager.beginLayoutLw開始測量大小與邊距

實(shí)際上如果有讀者注意到我寫的WMS第一篇就會看到實(shí)際上WMS初始化的時候厂置,我們能夠看到WMS會初始化一個WindowManagerPolicy的策略菩掏,而這個策略就是PhoneWindowManager魂角。實(shí)際上這也支持了系統(tǒng)開發(fā)自定義策略,從而辦到自己想要的窗體計(jì)算結(jié)果智绸。

public void beginLayoutLw(DisplayFrames displayFrames, int uiMode) {
        displayFrames.onBeginLayout();
        mSystemGestures.screenWidth = displayFrames.mUnrestricted.width();
        mSystemGestures.screenHeight = displayFrames.mUnrestricted.height();
        mDockLayer = 0x10000000;
        mStatusBarLayer = -1;

        // start with the current dock rect, which will be (0,0,displayWidth,displayHeight)
        final Rect pf = mTmpParentFrame; //父窗口大小
        final Rect df = mTmpDisplayFrame; //顯示屏大小
        final Rect of = mTmpOverscanFrame;//過掃描大小
        final Rect vf = mTmpVisibleFrame;//可見區(qū)域大小
        final Rect dcf = mTmpDecorFrame;//輸入法大小
        vf.set(displayFrames.mDock);
        of.set(displayFrames.mDock);
        df.set(displayFrames.mDock);
        pf.set(displayFrames.mDock);
        dcf.setEmpty();  // Decor frame N/A for system bars.

        if (displayFrames.mDisplayId == DEFAULT_DISPLAY) {
            final int sysui = mLastSystemUiFlags;
            boolean navVisible = (sysui & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0;
            boolean navTranslucent = (sysui
                    & (View.NAVIGATION_BAR_TRANSLUCENT | View.NAVIGATION_BAR_TRANSPARENT)) != 0;
            boolean immersive = (sysui & View.SYSTEM_UI_FLAG_IMMERSIVE) != 0;
            boolean immersiveSticky = (sysui & View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY) != 0;
            boolean navAllowedHidden = immersive || immersiveSticky;
            navTranslucent &= !immersiveSticky;  // transient trumps translucent
            boolean isKeyguardShowing = isStatusBarKeyguard() && !mKeyguardOccluded;
            if (!isKeyguardShowing) {
                navTranslucent &= areTranslucentBarsAllowed();
            }
            boolean statusBarExpandedNotKeyguard = !isKeyguardShowing && mStatusBar != null
                    && mStatusBar.getAttrs().height == MATCH_PARENT
                    && mStatusBar.getAttrs().width == MATCH_PARENT;
...
            navVisible |= !canHideNavigationBar();

            boolean updateSysUiVisibility = layoutNavigationBar(displayFrames, uiMode, dcf,
                    navVisible, navTranslucent, navAllowedHidden, statusBarExpandedNotKeyguard);
            updateSysUiVisibility |= layoutStatusBar(
                    displayFrames, pf, df, of, vf, dcf, sysui, isKeyguardShowing);
            if (updateSysUiVisibility) {
                updateSystemUiVisibilityLw();
            }
        }
        layoutScreenDecorWindows(displayFrames, pf, df, dcf);

        if (displayFrames.mDisplayCutoutSafe.top > displayFrames.mUnrestricted.top) {
            displayFrames.mDisplayCutoutSafe.top = Math.max(displayFrames.mDisplayCutoutSafe.top,
                    displayFrames.mStable.top);
        }
    }

首先初始化幾個參數(shù)野揪,父窗體,屏幕瞧栗,過掃描斯稳,可見區(qū)域,輸入法區(qū)域?yàn)楫?dāng)前邏輯顯示屏的大小迹恐,等到后面做裁剪挣惰。

能看到所有的事情實(shí)際上是關(guān)注的是系統(tǒng)UI上的判斷,檢測NavBar,StatusBar大小憎茂。最后再判斷當(dāng)前劉海屏的不允許交叉的區(qū)域頂部和顯示屏頂部哪個大珍语。如果mDisplayCutoutSafe的top大于mUnrestricted的top,說明mDisplayCutoutSafe在mUnrestricted下面竖幔,也就是我上面那個包含一段黑色的區(qū)域板乙。此時會拿穩(wěn)定的應(yīng)用區(qū)域和劉海區(qū)域頂部的最大值,作為劉海屏幕的區(qū)域拳氢。這樣就能保證劉海屏的頂部就是狀態(tài)欄募逞。

提一句如果NavigationBar隱藏,則會創(chuàng)建一個虛假的區(qū)域把輸入事件都捕捉起來馋评。

里面有四個關(guān)鍵函數(shù):

  • 函數(shù)onBeginLayout放接,初始化了所有邊距值
  • layoutNavigationBar ,測量NavigationBar
  • layoutStatusBar 測量layoutStatusBar
  • layoutScreenDecorWindows 測量所有裝飾窗口

DisplayFrame.onBeginLayout

    public void onBeginLayout() {
        switch (mRotation) {
            case ROTATION_90:
...
                break;
            case ROTATION_180:
...
                break;
            case ROTATION_270:
...
                break;
            default:
                mRotatedDisplayInfoOverscan.set(mDisplayInfoOverscan);
                break;
        }

        mRestrictedOverscan.set(0, 0, mDisplayWidth, mDisplayHeight);
        mOverscan.set(mRestrictedOverscan);
        mSystem.set(mRestrictedOverscan);
        mUnrestricted.set(mRotatedDisplayInfoOverscan);
        mUnrestricted.right = mDisplayWidth - mUnrestricted.right;
        mUnrestricted.bottom = mDisplayHeight - mUnrestricted.bottom;
        mRestricted.set(mUnrestricted);
        mDock.set(mUnrestricted);
        mContent.set(mUnrestricted);
        mVoiceContent.set(mUnrestricted);
        mStable.set(mUnrestricted);
        mStableFullscreen.set(mUnrestricted);
        mCurrent.set(mUnrestricted);

        mDisplayCutout = mDisplayInfoCutout;
        mDisplayCutoutSafe.set(Integer.MIN_VALUE, Integer.MIN_VALUE,
                Integer.MAX_VALUE, Integer.MAX_VALUE);
        if (!mDisplayCutout.getDisplayCutout().isEmpty()) {
            final DisplayCutout c = mDisplayCutout.getDisplayCutout();
            if (c.getSafeInsetLeft() > 0) {
                mDisplayCutoutSafe.left = mRestrictedOverscan.left + c.getSafeInsetLeft();
            }
            if (c.getSafeInsetTop() > 0) {
                mDisplayCutoutSafe.top = mRestrictedOverscan.top + c.getSafeInsetTop();
            }
            if (c.getSafeInsetRight() > 0) {
                mDisplayCutoutSafe.right = mRestrictedOverscan.right - c.getSafeInsetRight();
            }
            if (c.getSafeInsetBottom() > 0) {
                mDisplayCutoutSafe.bottom = mRestrictedOverscan.bottom - c.getSafeInsetBottom();
            }
        }
    }

可以看到所有的所有的間距將會設(shè)置為mUnrestricted的初始寬高,也就是不包含OverScan區(qū)域栗恩。如果是遇到劉海屏透乾,則會根據(jù)設(shè)置的SafeInset區(qū)域來設(shè)置mDisplayCutoutSafe的安全區(qū)域。也就是我上面那種情況磕秤。比如設(shè)置了LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT這種情況乳乌,顯示區(qū)域?qū)⒉粫^劉海屏的底部。

layoutNavigationBar測量NavigationBar

    private boolean layoutNavigationBar(DisplayFrames displayFrames, int uiMode, Rect dcf,
            boolean navVisible, boolean navTranslucent, boolean navAllowedHidden,
            boolean statusBarExpandedNotKeyguard) {
        if (mNavigationBar == null) {
            return false;
        }
        boolean transientNavBarShowing = mNavigationBarController.isTransientShowing();

        final int rotation = displayFrames.mRotation;
        final int displayHeight = displayFrames.mDisplayHeight;
        final int displayWidth = displayFrames.mDisplayWidth;
        final Rect dockFrame = displayFrames.mDock;
        mNavigationBarPosition = navigationBarPosition(displayWidth, displayHeight, rotation);

        final Rect cutoutSafeUnrestricted = mTmpRect;
        cutoutSafeUnrestricted.set(displayFrames.mUnrestricted);
        cutoutSafeUnrestricted.intersectUnchecked(displayFrames.mDisplayCutoutSafe);

        if (mNavigationBarPosition == NAV_BAR_BOTTOM) {
            final int top = cutoutSafeUnrestricted.bottom
                    - getNavigationBarHeight(rotation, uiMode);
            mTmpNavigationFrame.set(0, top, displayWidth, displayFrames.mUnrestricted.bottom);
            displayFrames.mStable.bottom = displayFrames.mStableFullscreen.bottom = top;
....
            if (navVisible && !navTranslucent && !navAllowedHidden
                    && !mNavigationBar.isAnimatingLw()
                    && !mNavigationBarController.wasRecentlyTranslucent()) {

                displayFrames.mSystem.bottom = top;
            }
        }
//不關(guān)注旋轉(zhuǎn)后的測量
....

        displayFrames.mCurrent.set(dockFrame);
        displayFrames.mVoiceContent.set(dockFrame);
        displayFrames.mContent.set(dockFrame);
        mStatusBarLayer = mNavigationBar.getSurfaceLayer();

        mNavigationBar.computeFrameLw(mTmpNavigationFrame, mTmpNavigationFrame,
                mTmpNavigationFrame, displayFrames.mDisplayCutoutSafe, mTmpNavigationFrame, dcf,
                mTmpNavigationFrame, displayFrames.mDisplayCutoutSafe,
                displayFrames.mDisplayCutout, false /* parentFrameWasClippedByDisplayCutout */);
        mNavigationBarController.setContentFrame(mNavigationBar.getContentFrameLw());

        return mNavigationBarController.checkHiddenLw();
    }

我們關(guān)注到mTmpNavigationFrame這個對象的賦值市咆,在正常的情況下的范圍是如下:

left: 0
top:劉海屏的安全區(qū) - Navigation高度(也就是劉海屏幕的安全區(qū)向上移動Navigation高度)
right:displayWidth(屏幕寬度)
bottom: mUnrestricted(不包含過掃描區(qū)域的底部)

此時mStable和mStableFullscreen區(qū)域的底部都是對應(yīng)著top汉操,也就是對應(yīng)著Navigation頂部。System系統(tǒng)元素的底部也是Navigation頂部蒙兰。

最后經(jīng)過computeFrameLw重新計(jì)算這個區(qū)域的值磷瘤。這個方法稍后會聊到,但是在正常手機(jī)開發(fā)中搜变,其實(shí)是沒有變化的采缚。也就說,實(shí)際上對于mNavigationBar來說:

可見區(qū)域挠他,過掃描區(qū)域扳抽,內(nèi)容區(qū)域,Dock區(qū)域:mTmpNavigationFrame
過掃描區(qū)域殖侵,穩(wěn)定區(qū)域贸呢,劉海裁剪安全區(qū):displayFrames.mDisplayCutoutSafe

layoutStatusBar 測量layoutStatusBar

    private boolean layoutStatusBar(DisplayFrames displayFrames, Rect pf, Rect df, Rect of, Rect vf,
            Rect dcf, int sysui, boolean isKeyguardShowing) {
        // decide where the status bar goes ahead of time
        if (mStatusBar == null) {
            return false;
        }
        of.set(displayFrames.mUnrestricted);
        df.set(displayFrames.mUnrestricted);
        pf.set(displayFrames.mUnrestricted);
        vf.set(displayFrames.mStable);

        mStatusBarLayer = mStatusBar.getSurfaceLayer();
size.
        mStatusBar.computeFrameLw(pf /* parentFrame */, df /* displayFrame */,
                vf /* overlayFrame */, vf /* contentFrame */, vf /* visibleFrame */,
                dcf /* decorFrame */, vf /* stableFrame */, vf /* outsetFrame */,
                displayFrames.mDisplayCutout, false /* parentFrameWasClippedByDisplayCutout */);

        displayFrames.mStable.top = displayFrames.mUnrestricted.top
                + mStatusBarHeightForRotation[displayFrames.mRotation];
        displayFrames.mStable.top = Math.max(displayFrames.mStable.top,
                displayFrames.mDisplayCutoutSafe.top);

...
        mStatusBarController.setContentFrame(mTmpRect);

...
        if (mStatusBar.isVisibleLw() && !statusBarTransient) {
            final Rect dockFrame = displayFrames.mDock;
            dockFrame.top = displayFrames.mStable.top;
            displayFrames.mContent.set(dockFrame);
            displayFrames.mVoiceContent.set(dockFrame);
            displayFrames.mCurrent.set(dockFrame);


            if (!mStatusBar.isAnimatingLw() && !statusBarTranslucent
                    && !mStatusBarController.wasRecentlyTranslucent()) {
                displayFrames.mSystem.top = displayFrames.mStable.top;
            }
        }
        return mStatusBarController.checkHiddenLw();
    }

同理對于statusBar來說:

父親區(qū)域,顯示屏區(qū)域拢军,過掃描區(qū)域楞陷,內(nèi)容區(qū)域,可見區(qū)域茉唉,穩(wěn)定區(qū)域固蛾,外部區(qū)域全部都是mUnrestricted
Dock區(qū)域?yàn)?

注意结执,此時如果statusBar可見,則做如下計(jì)算:

displayFrames.mStable的頂部向下移動StatusBar的高度位置艾凯,接著判斷安全裁剪去和當(dāng)前哪個更接近底部一點(diǎn)昌犹,則獲取哪個。這樣就能保證我們的應(yīng)用一定能在StatusBar之下览芳,且能夠被劉海安全裁剪斜姥。

如果Statusbar可見,當(dāng)然dockFrame沧竟,整體也要從屏幕去除過掃描區(qū)域的頂部向下移動StatusBar高度的位置铸敏。mSystem代表的系統(tǒng)元素也是同理。這樣就能保證沒人擋住系統(tǒng)狀態(tài)欄悟泵。

這種情況挺常見的杈笔,我們從一個隱藏狀態(tài)欄的頁面跳轉(zhuǎn)到有狀態(tài)欄的頁面,國有有個PopupWindow糕非,你能看到這個popwindow會明顯向下移動蒙具。

layoutScreenDecorWindows

private void layoutScreenDecorWindows(DisplayFrames displayFrames, Rect pf, Rect df, Rect dcf) {
        if (mScreenDecorWindows.isEmpty()) {
            return;
        }

        final int displayId = displayFrames.mDisplayId;
        final Rect dockFrame = displayFrames.mDock;
        final int displayHeight = displayFrames.mDisplayHeight;
        final int displayWidth = displayFrames.mDisplayWidth;

        for (int i = mScreenDecorWindows.size() - 1; i >= 0; --i) {
            final WindowState w = mScreenDecorWindows.valueAt(i);
            if (w.getDisplayId() != displayId || !w.isVisibleLw()) {
                // Skip if not on the same display or not visible.
                continue;
            }

            w.computeFrameLw(pf /* parentFrame */, df /* displayFrame */, df /* overlayFrame */,
                    df /* contentFrame */, df /* visibleFrame */, dcf /* decorFrame */,
                    df /* stableFrame */, df /* outsetFrame */, displayFrames.mDisplayCutout,
                    false /* parentFrameWasClippedByDisplayCutout */);
            final Rect frame = w.getFrameLw();

            if (frame.left <= 0 && frame.top <= 0) {
                // Docked at left or top.
                if (frame.bottom >= displayHeight) {
                    // Docked left.
                    dockFrame.left = Math.max(frame.right, dockFrame.left);
                } else if (frame.right >= displayWidth ) {
                    // Docked top.
                    dockFrame.top = Math.max(frame.bottom, dockFrame.top);
                } else {

                }
            } else if (frame.right >= displayWidth && frame.bottom >= displayHeight) {
                // Docked at right or bottom.
                if (frame.top <= 0) {
                    // Docked right.
                    dockFrame.right = Math.min(frame.left, dockFrame.right);
                } else if (frame.left <= 0) {
                    // Docked bottom.
                    dockFrame.bottom = Math.min(frame.top, dockFrame.bottom);
                } else {

                }
            } else {

            }
        }

        displayFrames.mRestricted.set(dockFrame);
        displayFrames.mCurrent.set(dockFrame);
        displayFrames.mVoiceContent.set(dockFrame);
        displayFrames.mSystem.set(dockFrame);
        displayFrames.mContent.set(dockFrame);
        displayFrames.mRestrictedOverscan.set(dockFrame);
    }

在這個方法中mScreenDecorWindows這個集合實(shí)際上是在adjustWindowParamsLw以及prepareAddWindowLw這兩個方法中加入。加入的條件是朽肥,每當(dāng)有新的Window加入(WMS的addView)或者Window需要重新調(diào)整(WMS的relayoutWindow)禁筏,當(dāng)前新增得到Window或者需要重新relayout的Window有StatusBar有權(quán)限,且顯示則會添加到mScreenDecorWindows集合衡招。

mScreenDecorWindows從上面的描述篱昔,能得知實(shí)際上這個步驟還沒有根據(jù)層級作區(qū)分。但是沒關(guān)系始腾,此時僅僅只是初步的測量州刽。

明白了mScreenDecorWindows之后,我們閱讀上面這個方法就很簡單了浪箭。

layoutScreenDecorWindows做的事情就如名字一樣穗椅,要測量Window上裝飾部分,如StatusBar奶栖,如輸入法匹表。此時經(jīng)過循環(huán),自尾部往頭部調(diào)用所有的WindowState的computeFrameLw計(jì)算每一個WindowState的對應(yīng)Window的窗體大小驼抹。

當(dāng)計(jì)算出每一個窗體大小之后桑孩,將會把事件分成兩個情況拜鹤,當(dāng)計(jì)算出來的當(dāng)前的Window的left和top都小于等于0框冀,也就是說,當(dāng)前的Window的頂部邊緣并且左邊緣超過了當(dāng)前的屏幕敏簿。

說明了有什么東西在右下側(cè)把整個Window定上去了明也。因此dockFrame的計(jì)算就很簡單了:

  • 當(dāng)當(dāng)前的Frame的底部大于等于屏幕高度宣虾,說明底部可能沒東西,dockFrame在右邊温数。計(jì)算最左側(cè)的的位置(dockFrame.left)就是當(dāng)前窗體和之前WindowState相比誰在右側(cè)就取誰绣硝。
  • 當(dāng)當(dāng)前的Frame的右側(cè)大于等于屏幕寬度,說明右邊可能沒東西撑刺,dockFrame在底部鹉胖。只需要計(jì)算dockFrame的頂部(dockFrame.top)和frame的底部誰大(誰更靠近底部)就獲誰。

如果計(jì)算出來的bottom大于等于屏幕高度且right大于等于屏幕寬度够傍。說明有什么東西在左上方把整個Window頂下去了甫菠。

  • 當(dāng)當(dāng)前的Frame的頂部小于等于0,說明沒有東西頂住上方冕屯,dock在左邊寂诱。只要需要計(jì)算dockFrame右側(cè)即可,計(jì)算原來的dockFrame的右側(cè)(dockFrame.right)和當(dāng)前的Frame的左側(cè)更加靠左獲取誰安聘。
  • 當(dāng)當(dāng)前的Frame左側(cè)小于等于0痰洒,則說明沒有東西在左側(cè),dock在頂部浴韭。只需要計(jì)算dockFrame的底部(dockFrame.bottom)和當(dāng)前的frame的頂部誰更加小(靠近頂部)則獲取誰丘喻。

最后再設(shè)置這個把displayFrames的可見等區(qū)域都設(shè)置為dockFrame。聯(lián)合上下文念颈,實(shí)際上這里就是把整個區(qū)域的頂部移動到了statusBar之下仓犬。

WindowState.computeFrameLw

    public void computeFrameLw(Rect parentFrame, Rect displayFrame, Rect overscanFrame,
            Rect contentFrame, Rect visibleFrame, Rect decorFrame, Rect stableFrame,
            Rect outsetFrame, WmDisplayCutout displayCutout,
            boolean parentFrameWasClippedByDisplayCutout) {
...

        final Rect layoutContainingFrame;
        final Rect layoutDisplayFrame;

        final int layoutXDiff;
        final int layoutYDiff;
//核心事件1
        if (inFullscreenContainer || layoutInParentFrame()) {
            // 當(dāng)全屏的時候,設(shè)置內(nèi)容區(qū)域就是父親區(qū)域舍肠,顯示屏區(qū)域就是傳進(jìn)來的顯示屏區(qū)域搀继,并且窗體沒有位移
            mContainingFrame.set(parentFrame);
            mDisplayFrame.set(displayFrame);
            layoutDisplayFrame = displayFrame;
            layoutContainingFrame = parentFrame;
            layoutXDiff = 0;
            layoutYDiff = 0;
        } else {
//當(dāng)不是的全屏或者在父親窗體內(nèi)部的模式
            getBounds(mContainingFrame);
            if (mAppToken != null && !mAppToken.mFrozenBounds.isEmpty()) {

                Rect frozen = mAppToken.mFrozenBounds.peek();
                mContainingFrame.right = mContainingFrame.left + frozen.width();
                mContainingFrame.bottom = mContainingFrame.top + frozen.height();
            }
            if (imeWin != null && imeWin.isVisibleNow() && isInputMethodTarget()) {
                if (inFreeformWindowingMode()
                        && mContainingFrame.bottom > contentFrame.bottom) {
                    mContainingFrame.top -= mContainingFrame.bottom - contentFrame.bottom;
                } else if (!inPinnedWindowingMode()
                        && mContainingFrame.bottom > parentFrame.bottom) {
                    mContainingFrame.bottom = parentFrame.bottom;
                }
            }

...
//顯示屏區(qū)域就是內(nèi)容區(qū)域
            mDisplayFrame.set(mContainingFrame);
//計(jì)算偏移量,這個偏移量的計(jì)算是根據(jù)動畫的臨時區(qū)域來變化
            layoutXDiff = !mInsetFrame.isEmpty() ? mInsetFrame.left - mContainingFrame.left : 0;
            layoutYDiff = !mInsetFrame.isEmpty() ? mInsetFrame.top - mContainingFrame.top : 0;
            layoutContainingFrame = !mInsetFrame.isEmpty() ? mInsetFrame : mContainingFrame;
            mTmpRect.set(0, 0, dc.getDisplayInfo().logicalWidth, dc.getDisplayInfo().logicalHeight);
//合并所有的區(qū)域到顯示屏區(qū)域中
            subtractInsets(mDisplayFrame, layoutContainingFrame, displayFrame, mTmpRect);
            if (!layoutInParentFrame()) {
                subtractInsets(mContainingFrame, layoutContainingFrame, parentFrame, mTmpRect);
                subtractInsets(mInsetFrame, layoutContainingFrame, parentFrame, mTmpRect);
            }
            layoutDisplayFrame = displayFrame;
            layoutDisplayFrame.intersect(layoutContainingFrame);
        }

        final int pw = mContainingFrame.width();
        final int ph = mContainingFrame.height();

        if (!mParentFrame.equals(parentFrame)) {
            mParentFrame.set(parentFrame);
            mContentChanged = true;
        }
        if (mRequestedWidth != mLastRequestedWidth || mRequestedHeight != mLastRequestedHeight) {
            mLastRequestedWidth = mRequestedWidth;
            mLastRequestedHeight = mRequestedHeight;
            mContentChanged = true;
        }
//核心事件2
//設(shè)置各個區(qū)域的參數(shù)
        mOverscanFrame.set(overscanFrame);
        mContentFrame.set(contentFrame);
        mVisibleFrame.set(visibleFrame);
        mDecorFrame.set(decorFrame);
        mStableFrame.set(stableFrame);
        final boolean hasOutsets = outsetFrame != null;
        if (hasOutsets) {
            mOutsetFrame.set(outsetFrame);
        }

        final int fw = mFrame.width();
        final int fh = mFrame.height();
//設(shè)置Window的Gravity
        applyGravityAndUpdateFrame(layoutContainingFrame, layoutDisplayFrame);
//設(shè)置Window外部填充區(qū)域如果存在則是mOutsetFrame - mContentFrame
        if (hasOutsets) {
            mOutsets.set(Math.max(mContentFrame.left - mOutsetFrame.left, 0),
                    Math.max(mContentFrame.top - mOutsetFrame.top, 0),
                    Math.max(mOutsetFrame.right - mContentFrame.right, 0),
                    Math.max(mOutsetFrame.bottom - mContentFrame.bottom, 0));
        } else {
            mOutsets.set(0, 0, 0, 0);
        }
//核心事件3
        if (windowsAreFloating && !mFrame.isEmpty()) {
...
        } else if (mAttrs.type == TYPE_DOCK_DIVIDER) {
....
        } else {
//計(jì)算顯示區(qū)域mContentFrame  mFrame 兩者之間更大的區(qū)域
            mContentFrame.set(Math.max(mContentFrame.left, mFrame.left),
                    Math.max(mContentFrame.top, mFrame.top),
                    Math.min(mContentFrame.right, mFrame.right),
                    Math.min(mContentFrame.bottom, mFrame.bottom));

            mVisibleFrame.set(Math.max(mVisibleFrame.left, mFrame.left),
                    Math.max(mVisibleFrame.top, mFrame.top),
                    Math.min(mVisibleFrame.right, mFrame.right),
                    Math.min(mVisibleFrame.bottom, mFrame.bottom));

            mStableFrame.set(Math.max(mStableFrame.left, mFrame.left),
                    Math.max(mStableFrame.top, mFrame.top),
                    Math.min(mStableFrame.right, mFrame.right),
                    Math.min(mStableFrame.bottom, mFrame.bottom));
        }
//設(shè)置過掃描區(qū)間(邊距)
        if (inFullscreenContainer && !windowsAreFloating) {
            mOverscanInsets.set(Math.max(mOverscanFrame.left - layoutContainingFrame.left, 0),
                    Math.max(mOverscanFrame.top - layoutContainingFrame.top, 0),
                    Math.max(layoutContainingFrame.right - mOverscanFrame.right, 0),
                    Math.max(layoutContainingFrame.bottom - mOverscanFrame.bottom, 0));
        }

        if (mAttrs.type == TYPE_DOCK_DIVIDER) {
...
        } else {
            getDisplayContent().getBounds(mTmpRect);
            boolean overrideRightInset = !windowsAreFloating && !inFullscreenContainer
                    && mFrame.right > mTmpRect.right;
            boolean overrideBottomInset = !windowsAreFloating && !inFullscreenContainer
                    && mFrame.bottom > mTmpRect.bottom;
            mContentInsets.set(mContentFrame.left - mFrame.left,
                    mContentFrame.top - mFrame.top,
                    overrideRightInset ? mTmpRect.right - mContentFrame.right
                            : mFrame.right - mContentFrame.right,
                    overrideBottomInset ? mTmpRect.bottom - mContentFrame.bottom
                            : mFrame.bottom - mContentFrame.bottom);

            mVisibleInsets.set(mVisibleFrame.left - mFrame.left,
                    mVisibleFrame.top - mFrame.top,
                    overrideRightInset ? mTmpRect.right - mVisibleFrame.right
                            : mFrame.right - mVisibleFrame.right,
                    overrideBottomInset ? mTmpRect.bottom - mVisibleFrame.bottom
                            : mFrame.bottom - mVisibleFrame.bottom);

            mStableInsets.set(Math.max(mStableFrame.left - mFrame.left, 0),
                    Math.max(mStableFrame.top - mFrame.top, 0),
                    overrideRightInset ? Math.max(mTmpRect.right - mStableFrame.right, 0)
                            : Math.max(mFrame.right - mStableFrame.right, 0),
                    overrideBottomInset ? Math.max(mTmpRect.bottom - mStableFrame.bottom, 0)
                            :  Math.max(mFrame.bottom - mStableFrame.bottom, 0));
        }

        mDisplayCutout = displayCutout.calculateRelativeTo(mFrame);

        // 設(shè)置位移區(qū)域
        mFrame.offset(-layoutXDiff, -layoutYDiff);
        mCompatFrame.offset(-layoutXDiff, -layoutYDiff);
        mContentFrame.offset(-layoutXDiff, -layoutYDiff);
        mVisibleFrame.offset(-layoutXDiff, -layoutYDiff);
        mStableFrame.offset(-layoutXDiff, -layoutYDiff);

        mCompatFrame.set(mFrame);
//設(shè)置屏幕內(nèi)容的縮放
        if (mEnforceSizeCompat) {
            mOverscanInsets.scale(mInvGlobalScale);
            mContentInsets.scale(mInvGlobalScale);
            mVisibleInsets.scale(mInvGlobalScale);
            mStableInsets.scale(mInvGlobalScale);
            mOutsets.scale(mInvGlobalScale);

            mCompatFrame.scale(mInvGlobalScale);
        }
//更新壁紙的位移
        if (mIsWallpaper && (fw != mFrame.width() || fh != mFrame.height())) {
            final DisplayContent displayContent = getDisplayContent();
            if (displayContent != null) {
                final DisplayInfo displayInfo = displayContent.getDisplayInfo();
                getDisplayContent().mWallpaperController.updateWallpaperOffset(
                        this, displayInfo.logicalWidth, displayInfo.logicalHeight, false);
            }
        }
    }

方法很長翠语,我這里只截取了需要注意的地方叽躯,并且添加了注釋。這里稍微總結(jié)一下:

mFrame只有在自由窗體模式和固定模式才有值肌括。否則都是(0,0,0,0)
這個mFrame讓我困惑一下子点骑,就明白了。實(shí)際上mFrame的誕生就是為了保證在自由窗體模式下有最小的內(nèi)容值谍夭,因?yàn)樽杂纱绑w模式類似PC上的窗體一樣黑滴,可以變化。如果是一般情況下紧索,實(shí)際上是沒有必要設(shè)置這個值袁辈,因?yàn)橛衜ContentFrame等區(qū)域就能確定窗體大小。

接下來就有如下的計(jì)算窗口Frame公式

mOverscanFrame = OverscanFrame
mContentFrame = Min(mContentFrame, mFrame)
mVisibleFrame = Min(mVisibleFrame,mFrame)
mDecorFrame = mTmpDecorFrame
mStableFrame = Min(mStableFrame,mFrame)

當(dāng)是全屏?xí)r候:

layoutContainingFrame = parentFrame

不是全屏:

layoutContainingFrame = !mInsetFrame.isEmpty() ? mInsetFrame : mContainingFrame;

mInsetFrame這個是臨時的Frame珠漂,為做動畫準(zhǔn)備的Frame晚缩。不看動畫實(shí)際上就是mContainingFrame

計(jì)算窗體的Insets

mOverscanInsets = Max(mOverscanFrame-layoutContainingFrame,0)
mContentInsets = mContentFrame - mFrame
mVisibleInsets = mVisibleFrame - mFrame
mStableInsets = mStableFrame - mFrame

我們關(guān)注到這個之后就能明白在Window中的Frame實(shí)際上是連同內(nèi)部的總區(qū)域尾膊,Inset是指真正的區(qū)域。但是這里只是簡單的處理了荞彼。都是根據(jù)顯示屏寬高來做簡單的差值計(jì)算冈敛。

測量那些沒有綁定父窗口的窗口(所有的根部窗口)

核心邏輯:

 forAllWindows(mPerformLayout, true /* traverseTopToBottom */);

實(shí)際上forAllWindow就是循環(huán)DisplayContent中所有的窗體,我們只需要看看這個實(shí)現(xiàn)的接口:

 private final Consumer<WindowState> mPerformLayout = w -> {
        final boolean gone = (mTmpWindow != null && mService.mPolicy.canBeHiddenByKeyguardLw(w))
                || w.isGoneForLayoutLw();
...
        if (!gone || !w.mHaveFrame || w.mLayoutNeeded
                || ((w.isConfigChanged() || w.setReportResizeHints())
                && !w.isGoneForLayoutLw() &&
                ((w.mAttrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0 ||
                        (w.mHasSurface && w.mAppToken != null &&
                                w.mAppToken.layoutConfigChanges)))) {
            if (!w.mLayoutAttached) {
                w.mLayoutNeeded = false;
                w.prelayout();
                final boolean firstLayout = !w.isLaidOut();
                mService.mPolicy.layoutWindowLw(w, null, mDisplayFrames);
...
            }
        }
    };

一旦確定其可見且鸣皂,則會調(diào)用PhoneWindowManager的layoutWindowLw抓谴。

PhoneWindowManager.layoutWindowLw

這個方法更加的冗長,下面是拆成兩個部分聊聊:

  • 1.根據(jù)type設(shè)置區(qū)域的上下左右的邊緣
  • 2.根據(jù)其他標(biāo)志寞缝,最后設(shè)置區(qū)域
根據(jù)type設(shè)置區(qū)域的上下左右的邊緣

這里不關(guān)注旋轉(zhuǎn)之后以及狀態(tài)欄下拉窗口等其他窗口齐邦,指關(guān)注我們常用的Activity窗口。

    public void layoutWindowLw(WindowState win, WindowState attached, DisplayFrames displayFrames) {
//不處理狀態(tài)欄第租,NavigationBar措拇,以及mScreenDecorWindows包含的裝飾WindowState,因?yàn)樵赽egin中已經(jīng)處理了
        if ((win == mStatusBar && !canReceiveInput(win)) || win == mNavigationBar
                || mScreenDecorWindows.contains(win)) {
            return;
        }
...
//獲取type
        final int type = attrs.type;
        final int fl = PolicyControl.getWindowFlags(win, attrs);
        final int pfl = attrs.privateFlags;
        final int sim = attrs.softInputMode;
        final int requestedSysUiFl = PolicyControl.getSystemUiVisibility(null, attrs);
        final int sysUiFl = requestedSysUiFl | getImpliedSysUiFlagsForLayout(attrs);
//初始化當(dāng)前的區(qū)域
        final Rect pf = mTmpParentFrame;
        final Rect df = mTmpDisplayFrame;
        final Rect of = mTmpOverscanFrame;
        final Rect cf = mTmpContentFrame;
        final Rect vf = mTmpVisibleFrame;
        final Rect dcf = mTmpDecorFrame;
        final Rect sf = mTmpStableFrame;
        Rect osf = null;
        dcf.setEmpty();

        final boolean hasNavBar = (isDefaultDisplay && mHasNavigationBar
                && mNavigationBar != null && mNavigationBar.isVisibleLw());

        final int adjust = sim & SOFT_INPUT_MASK_ADJUST;

        final boolean requestedFullscreen = (fl & FLAG_FULLSCREEN) != 0
                || (requestedSysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) != 0;

        final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) == FLAG_LAYOUT_IN_SCREEN;
        final boolean layoutInsetDecor = (fl & FLAG_LAYOUT_INSET_DECOR) == FLAG_LAYOUT_INSET_DECOR;

        sf.set(displayFrames.mStable);

        if (type == TYPE_INPUT_METHOD) {
//如果當(dāng)前是輸入法區(qū)域慎宾,所有區(qū)域先設(shè)置為輸入對應(yīng)的mDock區(qū)域
            vf.set(displayFrames.mDock);
            cf.set(displayFrames.mDock);
            of.set(displayFrames.mDock);
            df.set(displayFrames.mDock);
            pf.set(displayFrames.mDock);

            pf.bottom = df.bottom = of.bottom = displayFrames.mUnrestricted.bottom;

            cf.bottom = vf.bottom = displayFrames.mStable.bottom;
...
            attrs.gravity = Gravity.BOTTOM;
            mDockLayer = win.getSurfaceLayer();
        } else if (type == TYPE_VOICE_INTERACTION) {
//測量聲音窗口
...
        } else if (type == TYPE_WALLPAPER) {
//測量壁紙
...
        } else if (win == mStatusBar) {
//測量狀態(tài)欄
            of.set(displayFrames.mUnrestricted);
            df.set(displayFrames.mUnrestricted);
            pf.set(displayFrames.mUnrestricted);
            cf.set(displayFrames.mStable);
            vf.set(displayFrames.mStable);

            if (adjust == SOFT_INPUT_ADJUST_RESIZE) {
                cf.bottom = displayFrames.mContent.bottom;
            } else {
                cf.bottom = displayFrames.mDock.bottom;
                vf.bottom = displayFrames.mContent.bottom;
            }
        } else {
//測量內(nèi)容型彈窗
            dcf.set(displayFrames.mSystem);
            final boolean inheritTranslucentDecor =
                    (attrs.privateFlags & PRIVATE_FLAG_INHERIT_TRANSLUCENT_DECOR) != 0;
            final boolean isAppWindow =
                    type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW;
            final boolean topAtRest =
                    win == mTopFullscreenOpaqueWindowState && !win.isAnimatingLw();
            if (isAppWindow && !inheritTranslucentDecor && !topAtRest) {
                if ((sysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0
                        && (fl & FLAG_FULLSCREEN) == 0
                        && (fl & FLAG_TRANSLUCENT_STATUS) == 0
                        && (fl & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0
                        && (pfl & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND) == 0) {
                    dcf.top = displayFrames.mStable.top;
                }
                if ((fl & FLAG_TRANSLUCENT_NAVIGATION) == 0
                        && (sysUiFl & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0
                        && (fl & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0) {
                    dcf.bottom = displayFrames.mStable.bottom;
                    dcf.right = displayFrames.mStable.right;
                }
            }

            if (layoutInScreen && layoutInsetDecor) {
                if (attached != null) {
                    setAttachedWindowFrames(win, fl, adjust, attached, true, pf, df, of, cf, vf,
                            displayFrames);
                } else {
                    if (type == TYPE_STATUS_BAR_PANEL || type == TYPE_STATUS_BAR_SUB_PANEL) {
...
                    } else if ((fl & FLAG_LAYOUT_IN_OVERSCAN) != 0
                            && type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW) {
                        of.set(displayFrames.mOverscan);
                        df.set(displayFrames.mOverscan);
                        pf.set(displayFrames.mOverscan);
                    } else if (canHideNavigationBar()
                            && (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
                            && (type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW
                            || type == TYPE_VOLUME_OVERLAY)) {
                        df.set(displayFrames.mOverscan);
                        pf.set(displayFrames.mOverscan);
                        of.set(displayFrames.mUnrestricted);
                    } else {
                        df.set(displayFrames.mRestrictedOverscan);
                        pf.set(displayFrames.mRestrictedOverscan);
                        of.set(displayFrames.mUnrestricted);
                    }

                    if ((fl & FLAG_FULLSCREEN) == 0) {
                        if (win.isVoiceInteraction()) {
                            cf.set(displayFrames.mVoiceContent);
                        } else {
                            if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
                                cf.set(displayFrames.mDock);
                            } else {
                                cf.set(displayFrames.mContent);
                            }
                        }
                    } else {
                        cf.set(displayFrames.mRestricted);
                    }
                    applyStableConstraints(sysUiFl, fl, cf, displayFrames);
                    if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
                        vf.set(displayFrames.mCurrent);
                    } else {
                        vf.set(cf);
                    }
                }
            } else if (layoutInScreen || (sysUiFl
                    & (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN
                            | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)) != 0) {
                if (type == TYPE_STATUS_BAR_PANEL || type == TYPE_STATUS_BAR_SUB_PANEL) {
...
                    }
                } else if (type == TYPE_NAVIGATION_BAR || type == TYPE_NAVIGATION_BAR_PANEL) {
                    // The navigation bar has Real Ultimate Power.
                    of.set(displayFrames.mUnrestricted);
                    df.set(displayFrames.mUnrestricted);
                    pf.set(displayFrames.mUnrestricted);
                } else if ((type == TYPE_SECURE_SYSTEM_OVERLAY || type == TYPE_SCREENSHOT)
                        && ((fl & FLAG_FULLSCREEN) != 0)) {
                    cf.set(displayFrames.mOverscan);
                    of.set(displayFrames.mOverscan);
                    df.set(displayFrames.mOverscan);
                    pf.set(displayFrames.mOverscan);
                } else if (type == TYPE_BOOT_PROGRESS) {
...
                } else if ((fl & FLAG_LAYOUT_IN_OVERSCAN) != 0
                        && type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW) {

                    cf.set(displayFrames.mOverscan);
                    of.set(displayFrames.mOverscan);
                    df.set(displayFrames.mOverscan);
                    pf.set(displayFrames.mOverscan);
                } else if (canHideNavigationBar()
                        && (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0
                        && (type == TYPE_STATUS_BAR
                            || type == TYPE_TOAST
                            || type == TYPE_DOCK_DIVIDER
                            || type == TYPE_VOICE_INTERACTION_STARTING
                            || (type >= FIRST_APPLICATION_WINDOW && type <= LAST_SUB_WINDOW))) {
                    cf.set(displayFrames.mUnrestricted);
                    of.set(displayFrames.mUnrestricted);
                    df.set(displayFrames.mUnrestricted);
                    pf.set(displayFrames.mUnrestricted);
                } else if ((sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) != 0) {
                    of.set(displayFrames.mRestricted);
                    df.set(displayFrames.mRestricted);
                    pf.set(displayFrames.mRestricted);
                    if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
                        cf.set(displayFrames.mDock);
                    } else {
                        cf.set(displayFrames.mContent);
                    }
                } else {
                    cf.set(displayFrames.mRestricted);
                    of.set(displayFrames.mRestricted);
                    df.set(displayFrames.mRestricted);
                    pf.set(displayFrames.mRestricted);
                }

                applyStableConstraints(sysUiFl, fl, cf,displayFrames);

                if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
                    vf.set(displayFrames.mCurrent);
                } else {
                    vf.set(cf);
                }
            } else if (attached != null) {

                setAttachedWindowFrames(win, fl, adjust, attached, false, pf, df, of, cf, vf,
                        displayFrames);
            } else {

                if (type == TYPE_STATUS_BAR_PANEL) {
...
                } else if (type == TYPE_TOAST || type == TYPE_SYSTEM_ALERT) {
                    // These dialogs are stable to interim decor changes.
                    cf.set(displayFrames.mStable);
                    of.set(displayFrames.mStable);
                    df.set(displayFrames.mStable);
                    pf.set(displayFrames.mStable);
                } else {
                    pf.set(displayFrames.mContent);
                    if (win.isVoiceInteraction()) {
...
                    } else if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
                        cf.set(displayFrames.mDock);
                        of.set(displayFrames.mDock);
                        df.set(displayFrames.mDock);
                    } else {
                        cf.set(displayFrames.mContent);
                        of.set(displayFrames.mContent);
                        df.set(displayFrames.mContent);
                    }
                    if (adjust != SOFT_INPUT_ADJUST_NOTHING) {
                        vf.set(displayFrames.mCurrent);
                    } else {
                        vf.set(cf);
                    }
                }
            }
        }


我們大致上可以分以下幾種情況進(jìn)行測量:

  • 輸入法
  • 狀態(tài)欄
  • 壁紙
  • 聲音窗口
  • 狀態(tài)欄下拉窗口
  • 其他(如Activity,Dialog對應(yīng)的窗口(PhoneWindow),或者啟動窗口等等丐吓。內(nèi)容型窗口。

本文只關(guān)注趟据,輸入法券犁,狀態(tài)欄,內(nèi)容型窗口汹碱。

輸入法

輸入法區(qū)域 = mDock
底部需要調(diào)整:
輸入法的過掃描區(qū)域底部粘衬,顯示屏區(qū)域底部,父區(qū)域底部 = mUnrestricted.bottom
內(nèi)容區(qū)域咳促,可見區(qū)域 = mStable.bottom
整個輸入法的中心都在整個窗體的底部

狀態(tài)欄

狀態(tài)欄父區(qū)域稚新,顯示屏區(qū)域,過掃描區(qū)域 = mUnrestricted
可見區(qū)域跪腹,內(nèi)容區(qū)域 = mStable

需要判斷Window的標(biāo)志位是否是SOFT_INPUT_ADJUST_RESIZE:
打開SOFT_INPUT_ADJUST_RESIZE:

內(nèi)容區(qū)域的底部 = mContent.bottom

關(guān)閉SOFT_INPUT_ADJUST_RESIZE:

內(nèi)容區(qū)域的底部 = mDock.bottom
可見區(qū)域的底部 = mContent.bottom

關(guān)閉了SOFT_INPUT_ADJUST_RESIZE褂删,比如說打開了adjustPan,整個屏幕向上移動了冲茸。此時的內(nèi)容區(qū)域就是鍵盤的底部屯阀,可見區(qū)域當(dāng)然是就是原來的內(nèi)容區(qū)域的底部。

雖然狀態(tài)欄一致位于頂部轴术,實(shí)際上顯示出來的樣子只是冰山一角难衰,只是其余部分被內(nèi)容型彈窗遮住了。為了嚴(yán)謹(jǐn)逗栽,狀態(tài)欄的底部也需要一起改變盖袭,才是適應(yīng)window的高度的變化。

打開了SOFT_INPUT_ADJUST_RESIZE,就會通過調(diào)整Activity內(nèi)容來騰出鍵盤的空間苍凛,所以底部還是內(nèi)容區(qū)域的底部。

內(nèi)容型窗口
  • 首先調(diào)整DecorFrame兵志,裝飾區(qū)域(外部window掛載區(qū)域如statusBar)

DecorFrame = mSystem

如果此時是App應(yīng)用的窗口:
當(dāng)打開了fitSystemWindows醇蝴,沒有打開如下標(biāo)志位:

  • SYSTEM_UI_FLAG_FULLSCREEN
  • FLAG_FULLSCREEN
  • FLAG_TRANSLUCENT_STATUS
  • FLAG_DRAWS_SYSTEM_BAR_BACKGROUND
  • PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND
    實(shí)際上就是我們常見的Activity,Dialog想罕。

DecorFrame.top = mStable.top

如果隱藏了Nav Bar:

DecorFrame.left = mStable.bottom;
DecorFrame.right = mStable.right;

接下來調(diào)整過掃描區(qū)域(OverScanFrame),顯示屏區(qū)域(displayFrame),父區(qū)域(parentFrame),最后調(diào)整內(nèi)容區(qū)域(ContentFrame)和可視區(qū)域(VisiblityFrame)

如果同時打開標(biāo)志位layoutInScreen 和 layoutInsetDecor且沒有綁定窗口
如果打開了FLAG_LAYOUT_IN_OVERSCAN:

過掃描悠栓,顯示屏區(qū)域,父區(qū)域 = mOverScan

如果隱藏NavBar:

顯示屏按价,父區(qū)域 = mOverscan惭适;過掃描區(qū)域=mUnrestricted
確保沒有任何窗口超過導(dǎo)航欄,確保沒有內(nèi)容插入到過掃描區(qū)

其他情況:

顯示屏區(qū)域楼镐,父區(qū)域 = mRestrictedOverscan癞志;過掃描區(qū)域 = mUnrestricted
確保沒有內(nèi)容插入到過掃描區(qū)

如果關(guān)閉全屏:

沒有打開SOFT_INPUT_ADJUST_RESIZE:
內(nèi)容區(qū)域 = mDock
打開SOFT_INPUT_ADJUST_RESIZE:
內(nèi)容區(qū)域 = mContent

如果打開全屏:

內(nèi)容區(qū)域 = mRestricted

和mStable和mStableFullScreen,獲取大的一方框产。

adjustNothing關(guān)閉:

可見區(qū)域 = mCurrent

adjustNothing打開:

可見區(qū)域 = 剛剛測量好的內(nèi)容區(qū)域

因此這個狀態(tài)能夠處理PopWindow因?yàn)闋顟B(tài)欄等原因出現(xiàn)了移動的問題凄杯。

如果只打開標(biāo)志位layoutInScreen,隱藏Nav Bar且沒有綁定窗口
其實(shí)和上面十分相似秉宿,實(shí)際上就是每一次根據(jù)標(biāo)志為多同步設(shè)置了內(nèi)容區(qū)域戒突。但是可見區(qū)域還是由adjustNothing做處理。

也就是說這個狀態(tài)將不會對全屏做更多的處理描睦。

普通情況
這里我們不管Toast等情況:
當(dāng)關(guān)閉SOFT_INPUT_ADJUST_RESIZE:

顯示屏區(qū)域膊存,父區(qū)域,內(nèi)容區(qū)域 = mDock

當(dāng)打開SOFT_INPUT_ADJUST_RESIZE:

顯示屏區(qū)域忱叭,父區(qū)域隔崎,內(nèi)容區(qū)域 = mContent

關(guān)閉SOFT_INPUT_ADJUST_NOTHING:

可見區(qū)域 = mCurrent

adjustNothing打開:

可見區(qū)域 = 剛剛測量好的內(nèi)容區(qū)域

根據(jù)其他標(biāo)志,最后設(shè)置區(qū)域

        boolean parentFrameWasClippedByDisplayCutout = false;
        final int cutoutMode = attrs.layoutInDisplayCutoutMode;
        final boolean attachedInParent = attached != null && !layoutInScreen;
        final boolean requestedHideNavigation =
                (requestedSysUiFl & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) != 0;

        final boolean floatingInScreenWindow = !attrs.isFullscreen() && layoutInScreen
                && type != TYPE_BASE_APPLICATION;

        if (cutoutMode != LAYOUT_IN_DISPLAY_CUTOUT_MODE_ALWAYS) {
            final Rect displayCutoutSafeExceptMaybeBars = mTmpDisplayCutoutSafeExceptMaybeBarsRect;
            displayCutoutSafeExceptMaybeBars.set(displayFrames.mDisplayCutoutSafe);
            if (layoutInScreen && layoutInsetDecor && !requestedFullscreen
                    && cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT) {
                displayCutoutSafeExceptMaybeBars.top = Integer.MIN_VALUE;
            }
            if (layoutInScreen && layoutInsetDecor && !requestedHideNavigation
                    && cutoutMode == LAYOUT_IN_DISPLAY_CUTOUT_MODE_DEFAULT) {
                switch (mNavigationBarPosition) {
                    case NAV_BAR_BOTTOM:
                        displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
                        break;
                    case NAV_BAR_RIGHT:
...
                        break;
                    case NAV_BAR_LEFT:
...
                        break;
                }
            }
            if (type == TYPE_INPUT_METHOD && mNavigationBarPosition == NAV_BAR_BOTTOM) {
                displayCutoutSafeExceptMaybeBars.bottom = Integer.MAX_VALUE;
            }

            if (!attachedInParent && !floatingInScreenWindow) {
                mTmpRect.set(pf);
                pf.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
                parentFrameWasClippedByDisplayCutout |= !mTmpRect.equals(pf);
            }

            df.intersectUnchecked(displayCutoutSafeExceptMaybeBars);
        }

        cf.intersectUnchecked(displayFrames.mDisplayCutoutSafe);

....
        win.computeFrameLw(pf, df, of, cf, vf, dcf, sf, osf, displayFrames.mDisplayCutout,
                parentFrameWasClippedByDisplayCutout);
        if (type == TYPE_INPUT_METHOD && win.isVisibleLw()
                && !win.getGivenInsetsPendingLw()) {
            setLastInputMethodWindowLw(null, null);
            offsetInputMethodWindowLw(win, displayFrames);
        }
        if (type == TYPE_VOICE_INTERACTION && win.isVisibleLw()
                && !win.getGivenInsetsPendingLw()) {
            offsetVoiceInputWindowLw(win, displayFrames);
        }
    }

實(shí)際上這一段就是根據(jù)劉海屏幕的處理區(qū)間韵丑,最后調(diào)用computeFrameLw設(shè)置區(qū)域仍稀。接下來的邏輯在上面已經(jīng)聊過了。

測量那些綁定了父窗口的窗口

實(shí)際上這里的邏輯和上面很相似埂息,不過走的是attach的邏輯:

    private void setAttachedWindowFrames(WindowState win, int fl, int adjust, WindowState attached,
            boolean insetDecors, Rect pf, Rect df, Rect of, Rect cf, Rect vf,
            DisplayFrames displayFrames) {
        if (!win.isInputMethodTarget() && attached.isInputMethodTarget()) {
            vf.set(displayFrames.mDock);
            cf.set(displayFrames.mDock);
            of.set(displayFrames.mDock);
            df.set(displayFrames.mDock);
        } else {
            if (adjust != SOFT_INPUT_ADJUST_RESIZE) {
                cf.set((fl & FLAG_LAYOUT_ATTACHED_IN_DECOR) != 0
                        ? attached.getContentFrameLw() : attached.getOverscanFrameLw());
            } else {
                cf.set(attached.getContentFrameLw());
                if (attached.isVoiceInteraction()) {
                    cf.intersectUnchecked(displayFrames.mVoiceContent);
                } else if (win.isInputMethodTarget() || attached.isInputMethodTarget()) {
                    cf.intersectUnchecked(displayFrames.mContent);
                }
            }
            df.set(insetDecors ? attached.getDisplayFrameLw() : cf);
            of.set(insetDecors ? attached.getOverscanFrameLw() : cf);
            vf.set(attached.getVisibleFrameLw());
        }
        pf.set((fl & FLAG_LAYOUT_IN_SCREEN) == 0 ? attached.getFrameLw() : df);
    }

如果附著的窗體是輸入法技潘,一切都被輸入法限制住。
關(guān)閉SOFT_INPUT_ADJUST_RESIZE:

FLAG_LAYOUT_ATTACHED_IN_DECOR 是否打開千康?
關(guān)閉則內(nèi)容區(qū)域 = 父窗體內(nèi)容區(qū)域
打開則內(nèi)容區(qū)域 = 父窗體的掃描區(qū)域

打開SOFT_INPUT_ADJUST_RESIZE:

則獲取比較子內(nèi)容區(qū)域和mContent更大取哪個

顯示享幽,過掃描,可見區(qū)域 = 父(被父窗體限制)

總結(jié)

beginLayoutLw拾弃,layoutWindowLw通過對整個Window的邊距的確定值桩,從而確定Window的大小。
beginLayoutLw豪椿,做了如下的事情:

  • 測量Nav Bar
  • 測量status Bar
  • 測量layoutDecorScreen

對整個屏幕做了初步的測量奔坟,把剩下的Window都限定到了statusBar 之下携栋。不允許任何窗體遮擋它。

layoutWindowLw咳秉,做了如下的事情:
測量所有窗體的顯示屏區(qū)域婉支,過掃描區(qū)域,父區(qū)域澜建,內(nèi)容區(qū)域向挖,可見區(qū)域。
能夠注意到的是炕舵,有兩個標(biāo)志為layoutInScreen何之,layoutInDecor。
這兩個標(biāo)志位確定了窗口能夠移動的最大范圍咽筋。

內(nèi)容區(qū)域溶推,是被adjustResize標(biāo)志位確定。如果是打開則是內(nèi)容區(qū)域的范圍奸攻。因?yàn)檫@個標(biāo)志是處理了Activity調(diào)整空間給鍵盤騰出空間悼潭。如果是關(guān)閉,打開layoutInScreen則內(nèi)容區(qū)域?yàn)閙Dock舞箍,否則內(nèi)容區(qū)域?yàn)閙Content舰褪,或者根據(jù)標(biāo)志為走。

可視區(qū)域疏橄,是由adjustNothing確定占拍,如果打開了可視區(qū)域就等于內(nèi)容區(qū)域,關(guān)閉了則內(nèi)容區(qū)域?yàn)閙Current(內(nèi)容帶上鍵盤)捎迫,任由系統(tǒng)自己默認(rèn)適配晃酒。

后話

這就是WMS的最后一篇,實(shí)際上還有窗體動畫以及Surface如何管理沒有講解窄绒。但是還沒有涉及到SurfaceFlinger是如何工作的贝次,View的繪制流程又是如何。接下來彰导,將會以這個為突破口蛔翅,和大家聊聊SurfaceFlinger的核心原理。不過位谋,還需要點(diǎn)其他知識山析,除了OpenGL之外,還需要Skia相關(guān)的知識掏父。

跟著我看OpenGL的朋友應(yīng)該沒有多少問題笋轨,如果對OpenGL感興趣的可以看看我的OpenGL的學(xué)習(xí)日記。之后我還會放出幾篇關(guān)于Skia的學(xué)習(xí)筆記。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末爵政,一起剝皮案震驚了整個濱河市仅讽,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌钾挟,老刑警劉巖洁灵,帶你破解...
    沈念sama閱讀 218,858評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異等龙,居然都是意外死亡处渣,警方通過查閱死者的電腦和手機(jī)伶贰,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,372評論 3 395
  • 文/潘曉璐 我一進(jìn)店門蛛砰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人黍衙,你說我怎么就攤上這事泥畅。” “怎么了琅翻?”我有些...
    開封第一講書人閱讀 165,282評論 0 356
  • 文/不壞的土叔 我叫張陵位仁,是天一觀的道長。 經(jīng)常有香客問我方椎,道長聂抢,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,842評論 1 295
  • 正文 為了忘掉前任棠众,我火速辦了婚禮琳疏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘闸拿。我一直安慰自己空盼,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,857評論 6 392
  • 文/花漫 我一把揭開白布新荤。 她就那樣靜靜地躺著揽趾,像睡著了一般。 火紅的嫁衣襯著肌膚如雪苛骨。 梳的紋絲不亂的頭發(fā)上篱瞎,一...
    開封第一講書人閱讀 51,679評論 1 305
  • 那天,我揣著相機(jī)與錄音痒芝,去河邊找鬼奔缠。 笑死,一個胖子當(dāng)著我的面吹牛吼野,可吹牛的內(nèi)容都是我干的校哎。 我是一名探鬼主播,決...
    沈念sama閱讀 40,406評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼闷哆!你這毒婦竟也來了腰奋?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,311評論 0 276
  • 序言:老撾萬榮一對情侶失蹤抱怔,失蹤者是張志新(化名)和其女友劉穎劣坊,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體屈留,經(jīng)...
    沈念sama閱讀 45,767評論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡局冰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,945評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了灌危。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片康二。...
    茶點(diǎn)故事閱讀 40,090評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖勇蝙,靈堂內(nèi)的尸體忽然破棺而出沫勿,到底是詐尸還是另有隱情,我是刑警寧澤味混,帶...
    沈念sama閱讀 35,785評論 5 346
  • 正文 年R本政府宣布产雹,位于F島的核電站,受9級特大地震影響翁锡,放射性物質(zhì)發(fā)生泄漏蔓挖。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,420評論 3 331
  • 文/蒙蒙 一馆衔、第九天 我趴在偏房一處隱蔽的房頂上張望瘟判。 院中可真熱鬧,春花似錦哈踱、人聲如沸荒适。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,988評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽刀诬。三九已至,卻和暖如春邪财,著一層夾襖步出監(jiān)牢的瞬間陕壹,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,101評論 1 271
  • 我被黑心中介騙來泰國打工树埠, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留糠馆,地道東北人。 一個月前我還...
    沈念sama閱讀 48,298評論 3 372
  • 正文 我出身青樓怎憋,卻偏偏與公主長得像又碌,于是被迫代替她去往敵國和親九昧。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,033評論 2 355

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