Android P WMS(5) -- relayoutWindow

Android P WMS(1) -- wms簡介

Android P WMS(2) -- wms初始化

Android O WMS(3) -- addwindow

Android P WMS(4) -- removewindow

Android P WMS(5) -- relayoutWindow

Android P WMS(6) -- windowanimator

Android P WMS(7) --wms 問題種類和debug技巧

Android P WMS(8) --View SYstem 簡介

Android P WMS(9) --Surface

1.1 參數意思(Link深入理解android 卷3):


session:調用者所在進程的Session實例。
client::要進行 relayout 的窗口。
seq: —個和狀態欄/導航欄可見性相關的序列號
attrs:窗口的新佈局屬性。relayoutWindow()的主要目的就是根據attrs所提供的佈局 
參數重新佈局一個窗口。客戶端可以通過relayoutWindow()函數改變attrs中所定義的 
幾乎所有佈局屬性。但是唯獨無法改變窗口類型。
requestedWidth與requestedHeight:客戶端所要求的窗口尺寸。在重新佈局的過程中, 
WMS會盡貴將窗口的尺寸佈局爲客戶端所要求的大小。
viewVisiblility:窗口的可見性。
flags:定義一些佈局行爲。
□outFrame :由relayoutWindow()函數返回給調用者的一個Rect類型的實例.它保存了 
窗U被重新佈局後的位置與大小
UoutContentlnsets與outVisiblelnsets:這兩個參數表示了窗口可以繪製內容的矩形邊界 
與可視矩形邊界在四個方向上到mFrame的像素差。
outConfiguration:重新佈局後,WMS爲此窗口計算出的Configuration。
outSurface:用來接收WMS爲此窗口分配的Surface。窗口的第一次relayout完成後就 
可以通過它在窗口中進行繪圖了。

1.2 relayoutWindow code flow

performTraversals會調用relayoutWindow;其請求WMS來計算相關的窗口大小,創建Surface等等;本章試着分析這個函數,就目前的理解performTraversals主要側重於客戶端對DecorView及其子View的measure,layout,draw等三大流程的處理;而binder call到system_server的WMS,是着重於系統側對窗口的處理

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);  //獲取重新佈局的window對象
        if (win == null) {
            return 0;
        }
        displayId = win.getDisplayId();

        WindowStateAnimator winAnimator = win.mWinAnimator;
        if (viewVisibility != View.GONE) {
            win.setRequestedSize(requestedWidth, requestedHeight);  //把請求window的width和height保存到window
        }

        win.setFrameNumber(frameNumber);
        int attrChanges = 0;
        int flagChanges = 0;
        if (attrs != null) {
            mPolicy.adjustWindowParamsLw(win, attrs, hasStatusBarServicePermission);    //調整窗口參數
            // if they don't have the permission, mask out the status bar bits
            ...
            if (win.mAttrs.type != attrs.type) {
                throw new IllegalArgumentException(
                        "Window type can not be changed after the window is added.");
            }

            // Odd choice but less odd than embedding in copyFrom()
            if ((attrs.privateFlags & WindowManager.LayoutParams.PRIVATE_FLAG_PRESERVE_GEOMETRY)
                    != 0) {
                attrs.x = win.mAttrs.x;
                attrs.y = win.mAttrs.y;
                attrs.width = win.mAttrs.width;
                attrs.height = win.mAttrs.height;
            }

            flagChanges = win.mAttrs.flags ^= attrs.flags;
            attrChanges = win.mAttrs.copyFrom(attrs);    //copyfrom獲取變化的屬性
            if ((attrChanges & (WindowManager.LayoutParams.LAYOUT_CHANGED
                    | WindowManager.LayoutParams.SYSTEM_UI_VISIBILITY_CHANGED)) != 0) {
                win.mLayoutNeeded = true;
            }
            ...
        }

        if (DEBUG_LAYOUT) Slog.v(TAG_WM, "Relayout " + win + ": viewVisibility=" + viewVisibility
                + " req=" + requestedWidth + "x" + requestedHeight + " " + win.mAttrs);
        ...

        final int oldVisibility = win.mViewVisibility;

        // If the window is becoming visible, visibleOrAdding may change which may in turn
        // change the IME target.
        
        final boolean becameVisible =
                (oldVisibility == View.INVISIBLE || oldVisibility == View.GONE)
                        && viewVisibility == View.VISIBLE;
        //判斷是否需要輸入法到window
        boolean imMayMove = (flagChanges & (FLAG_ALT_FOCUSABLE_IM | FLAG_NOT_FOCUSABLE)) != 0
                || becameVisible;
        final boolean isDefaultDisplay = win.isDefaultDisplay();
        boolean focusMayChange = isDefaultDisplay && (win.mViewVisibility != viewVisibility     //判斷焦點是否改變
                || ((flagChanges & FLAG_NOT_FOCUSABLE) != 0)
                || (!win.mRelayoutCalled));

        boolean wallpaperMayMove = win.mViewVisibility != viewVisibility    //判斷是否需要移動壁紙
                && (win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0;
        wallpaperMayMove |= (flagChanges & FLAG_SHOW_WALLPAPER) != 0;
        if ((flagChanges & FLAG_SECURE) != 0 && winAnimator.mSurfaceController != null) {
            winAnimator.mSurfaceController.setSecure(isSecureLocked(win));
        }
        ...
        // This must be called before the call to performSurfacePlacement.
        if (!shouldRelayout && winAnimator.hasSurface() && !win.mAnimatingExit) {
            if (DEBUG_VISIBILITY) {
                Slog.i(TAG_WM,
                        "Relayout invis " + win + ": mAnimatingExit=" + win.mAnimatingExit);
            }
            result |= RELAYOUT_RES_SURFACE_CHANGED;
            if (!win.mWillReplaceWindow) {
                focusMayChange = tryStartExitingAnimation(win, winAnimator, isDefaultDisplay,
                        focusMayChange);
            }
        }

        // We may be deferring layout passes at the moment, but since the client is interested
        // in the new out values right now we need to force a layout.
        //核心方法  WMS的窗口計算、通知繪製
        mWindowPlacerLocked.performSurfacePlacement(true /* force */);    //api  23以前的核心方法——performLayoutAndPlaceSurfacesLockedInner

        if (shouldRelayout) {
            Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: viewVisibility_1");

            result = win.relayoutVisibleWindow(result, attrChanges, oldVisibility);   //銷燬窗口surface

            try {
                result = createSurfaceControl(outSurface, result, win, winAnimator);
            } catch (Exception e) {
                mInputMonitor.updateInputWindowsLw(true /*force*/);

                Slog.w(TAG_WM, "Exception thrown when creating surface for client "
                         + client + " (" + win.mAttrs.getTitle() + ")",
                         e);
                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) {
            //System.out.println("Focus may change: " + win.mAttrs.getTitle());
            if (updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,   //focus改變更新window
                    false /*updateInputWindows*/)) {
                imMayMove = false;
            }
            //System.out.println("Relayout " + win + ": focus=" + mCurrentFocus);
        }

        // updateFocusedWindowLocked() already assigned layers so we only need to
        // reassign them at this point if the IM window state gets shuffled
        boolean toBeDisplayed = (result & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0;
        final DisplayContent dc = win.getDisplayContent();
        if (imMayMove) {
            dc.computeImeTarget(true /* updateImeTarget */);            //輸入法移動的話,重新計算ime位置
            if (toBeDisplayed) {
                // Little hack here -- we -should- be able to rely on the function to return
                // true if the IME has moved and needs its layer recomputed. However, if the IME
                // was hidden and isn't actually moved in the list, its layer may be out of data
                // so we make sure to recompute it.
                dc.assignWindowLayers(false /* setLayoutNeeded */);    //重新計算layer
            }
        }

        if (wallpaperMayMove) {
            win.getDisplayContent().pendingLayoutChanges |=
                    WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
        }

        if (win.mAppToken != null) {
            mUnknownAppVisibilityController.notifyRelayouted(win.mAppToken);
        }

        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER,
                "relayoutWindow: updateOrientationFromAppTokens");
        configChanged = updateOrientationFromAppTokensLocked(displayId);
        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);

        if (toBeDisplayed && win.mIsWallpaper) {
            DisplayInfo displayInfo = win.getDisplayContent().getDisplayInfo();
            dc.mWallpaperController.updateWallpaperOffset(
                    win, displayInfo.logicalWidth, displayInfo.logicalHeight, false);   //壁紙移動,更新壁紙
        }
        ...
        
        //把佈局結果給調用者
        outFrame.set(win.mCompatFrame);   //Outframe  最終屏幕的位置和大小
        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));
      ...
    }

    if (configChanged) {
        Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: sendNewConfiguration");
        sendNewConfiguration(displayId);     //通知ams更新配置,  打印evwntlog configuration_changed
        Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
    }
    Binder.restoreCallingIdentity(origId);
    return result;
}

 

1.3  performSurfacePlacement

計算window的大小、執行動畫、更新surface等。而這些關鍵工作在android api版本23以前(包括api 23)是由performLayoutAndPlaceSurfacesLockedInner這個方法實現的。WindowSurfacePlacer的出現原因可能是因爲WMS的performLayoutAndPlaceSurfacesLockedInner方法過於臃腫影響可讀性(其實WindowSurfacePlacer裏也沒好到哪兒去),更重要的原因爲了實現7.0中的多窗口功能。

@/frameworks/base/services/core/java/com/android/server/wm/WindowSurfacePlacer.java
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;
}

private void performSurfacePlacementLoop() {
    ...
    Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "wmLayout");
    mInLayout = true;

    boolean recoveringMemory = false;
    if (!mService.mForceRemoves.isEmpty()) {
        recoveringMemory = true;
        // Wait a little bit for things to settle down, and off we go.
        while (!mService.mForceRemoves.isEmpty()) {
            final WindowState ws = mService.mForceRemoves.remove(0);
            Slog.i(TAG, "Force removing: " + ws);
            ws.removeImmediately();
        }
        Slog.w(TAG, "Due to memory failure, waiting a bit for next layout");
        Object tmp = new Object();
        synchronized (tmp) {
            try {
                tmp.wait(250);
            } catch (InterruptedException e) {
            }
        }
    }

    try {
        mService.mRoot.performSurfacePlacement(recoveringMemory);  //here

        mInLayout = false;

        ...
    } ...
}
    
@/frameworks/base/services/core/java/com/android/server/wm/RootWindowContainer.java
void performSurfacePlacement(boolean recoveringMemory) {
    if (DEBUG_WINDOW_TRACE) Slog.v(TAG, "performSurfacePlacementInner: entry. Called by "
            + Debug.getCallers(3));

    int i;
    ...
    if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
            ">>> OPEN TRANSACTION performLayoutAndPlaceSurfaces");
    mService.openSurfaceTransaction();
    try {
        applySurfaceChangesTransaction(recoveringMemory, defaultDw, defaultDh);  //here
    } catch (RuntimeException e) {
        Slog.wtf(TAG, "Unhandled exception in Window Manager", e);
    } finally {
        mService.closeSurfaceTransaction("performLayoutAndPlaceSurfaces");
        if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG,
                "<<< CLOSE TRANSACTION performLayoutAndPlaceSurfaces");
    }
...

    mService.scheduleAnimationLocked();  //2 開始動畫
}

通過在phonewindowManager layoutWindowLw 添加callback看,流程如下

Session#relayout --> Wms#relayoutWindow --> WindowSurfacePlacer.performSurfacePlacement --> PerformSurfacePlacementLoop --> RootWindowContainer.performSurfacePlacement
 --> ApplySurfacechangesTransaction --> DisplayContent.applySurfaceChangesTransaction --> DisplayContent.performlayout -->  ForAllWindow --> apply -->  PhoneWindowManager.layoutWindowLw
     
Session.remove   -->    Wms.removeWindow  --> WindowState.removeifpossible  -->    SetupWindowForRemoveOnExit   --> WindowSurfacePlacer.performSurfacePlacement --> PerformSurfacePlacementLoop --> RootWindowContainer.performSurfacePlacement
 --> ApplySurfacechangesTransaction --> DisplayContent.applySurfaceChangesTransaction --> DisplayContent.performlayout -->  ForAllWindow --> apply -->  PhoneWindowManager.layoutWindowLw

1.4  DisplayContent#performLayout(執行佈局)

DisplayContent#performLayout
準備階段:調用PhoneWindowManager類的成員函數beginLayoutLw來設置屏幕的大小;包括狀態欄,導航欄
計算階段:調用forAllWindows分別計算父窗口和子窗口的大小。(如上文callback)
結束階段:調用PhoneWindowManager類的成員函數finishLayoutLw來執行一些清理工作

#performLayout

@/frameworks/base/services/core/java/com/android/server/wm/DisplayContent.java
    void performLayout(boolean initial, boolean updateInputWindows) {
        if (!isLayoutNeeded()) {  //第一次performLayout  initial = true
            return;
        }
        clearLayoutNeeded();

        final int dw = mDisplayInfo.logicalWidth;
        final int dh = mDisplayInfo.logicalHeight;
        if (DEBUG_LAYOUT) {
            Slog.v(TAG, "-------------------------------------");
            Slog.v(TAG, "performLayout: needed=" + isLayoutNeeded() + " dw=" + dw + " dh=" + dh);
        }

        mDisplayFrames.onDisplayInfoUpdated(mDisplayInfo,
                calculateDisplayCutoutForRotation(mDisplayInfo.rotation));
        // TODO: Not sure if we really need to set the rotation here since we are updating from the
        // display info above...
        mDisplayFrames.mRotation = mRotation;
        mService.mPolicy.beginLayoutLw(mDisplayFrames, getConfiguration().uiMode);    //1 設置屏幕的大小;包括狀態欄,導航欄
        if (isDefaultDisplay) { //mDisplayFrames維護整個屏幕的大小屬性
            // Not needed on non-default displays.
            mService.mSystemDecorLayer = mService.mPolicy.getSystemDecorLayerLw();
            mService.mScreenRect.set(0, 0, dw, dh);
        }

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

        // Used to indicate that we have processed the dream window and all additional windows are
        // behind it.
        mTmpWindow = null;
        mTmpInitial = initial;

        // First perform layout of any root windows (not attached to another window).
        forAllWindows(mPerformLayout, true /* traverseTopToBottom */);  //2 計算所有父窗口的大小

        // Used to indicate that we have processed the dream window and all additional attached
        // windows are behind it.
        mTmpWindow2 = mTmpWindow;
        mTmpWindow = null;

        // Now perform layout of attached windows, which usually depend on the position of the
        // window they are attached to. XXX does not deal with windows that are attached to windows
        // that are themselves attached.
        forAllWindows(mPerformLayoutAttached, true /* traverseTopToBottom */);

        // Window frames may have changed. Tell the input dispatcher about it.
        mService.mInputMonitor.layoutInputConsumers(dw, dh);
        mService.mInputMonitor.setUpdateInputWindowsNeededLw();
        if (updateInputWindows) {
            mService.mInputMonitor.updateInputWindowsLw(false /*force*/);
        }

        mService.mH.sendEmptyMessage(UPDATE_DOCKED_STACK_DIVIDER);
    }

 

1.4.1 PhoneWindowManager#beginLayoutLw

    @Override
    public void beginLayoutLw(DisplayFrames displayFrames, int uiMode) {
        displayFrames.onBeginLayout();    //初始化DisplayFrames的屬性值
        // TODO(multi-display): This doesn't seem right...Maybe only apply to default display?
        mSystemGestures.screenWidth = displayFrames.mUnrestricted.width();
        mSystemGestures.screenHeight = displayFrames.mUnrestricted.height();
        mDockLayer = 0x10000000;
        mStatusBarLayer = -1;
        dcf.setEmpty();  // Decor frame N/A for system bars.

        if (displayFrames.mDisplayId == DEFAULT_DISPLAY) {
          ...

            boolean updateSysUiVisibility = layoutNavigationBar(displayFrames, uiMode, dcf,
                    navVisible, navTranslucent, navAllowedHidden, statusBarExpandedNotKeyguard);
            if (DEBUG_LAYOUT) Slog.i(TAG, "mDock rect:" + displayFrames.mDock);
            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) {
            // Make sure that the zone we're avoiding for the cutout is at least as tall as the
            // status bar; otherwise fullscreen apps will end up cutting halfway into the status
            // bar.
            displayFrames.mDisplayCutoutSafe.top = Math.max(displayFrames.mDisplayCutoutSafe.top,
                    displayFrames.mStable.top);
        }
    }

1.4.2 PhoneWindowManager#beginLayoutLw

計算窗口大小是在layoutWindowLw中完成的,該方法不會直接計算出窗口大小,而是先計算出窗口能夠擴展的最大空間,然後再調用WindowState的computeFrameLw來計算出窗口的最終大小(很長,針對各種類型窗口的處理)

參考: WMS相關學習-relayoutWindow(6)

    /** {@inheritDoc} */
    @Override
    public void layoutWindowLw(WindowState win, WindowState attached, DisplayFrames displayFrames) {
        // We've already done the navigation bar, status bar, and all screen decor windows. If the
        // status bar can receive input, we need to layout it again to accommodate for the IME
        // window.

 

參考:

WMS相關學習-relayoutWindow(6)

Android7.0窗口動畫設置流程

WMS相關學習-relayoutWindow(5)

WMS:窗口大小的計算

深入理解Android 卷3

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章