Android 窗口实现原理

news2024/11/16 1:18:38

一、基本概念

1、窗口显示架构图

多窗口的核心原理其实就是分栈和设置栈边界

在这里插入图片描述

2、Android的窗口分类

Android应用程序窗口,这个是最常见的(拥有自己的WindowToken)譬如:Activity与Dialog
Android应用程序子窗口(必须依附到其他非子窗口才能存在,通常这个被依附的窗口类型Activity窗口) 例如:PopupWindow
Android系统窗口,其中我们最最常见的就是Toast窗口了

3、StackId

【Id:0】Home Stack,这个是Launcher所在的Stack。 其实还有一些系统界面也运行在这个Stack上,例如近期任务
【Id:1】FullScren Stack,全屏的Activity所在的Stack。 但其实在分屏模式下,Id为1的Stack只占了半个屏幕。
【Id:2】Freeform模式的Activity所在Stack
【Id:3】Docked Stack 下文中我们将看到,在分屏模式下,屏幕有一半运行了一个固定的应用,这个就是这里的Docked Stack
【Id:4】Pinned Stack 这个是画中画Activity所在的Stack

4、窗口管理中涉及的几个重要概念

IWindow: APP端窗口暴露给WMS的抽象实例,同时也是WMS向APP端发送消息的Binder通道,它在APP端的实现为W
IWindowSession:WMS服务用于提供给ViewRootImpl来和其进行跨Binder通信的接口
WindowState:WMS端窗口的令牌,与IWindow窗口一一对应,是WMS管理窗口的重要依据
WindowToken:是窗口的令牌,也是窗口分组的依据,在WMS端,和分组对应的数据结构是WindowToken
Token:是在AMS构建Activity对应的ActivityRecord时里面的IApplicationToken的实例,会在Activity创建过程中传递到AMS中,并且Token会在Activity从创建到显示的过程中会在App进程和AMS,WMS之间进行传递
ActivityManagerService
Activity管理机制的Binder服务端,属于一个系统服务。用于管理Activity的各种行为,控制Activity的生命周期,派发消息事件,低内存管理等等。实现了IBinder接口,可以用于进程间通信
ActivityStarter
用来负责处理Activity的Intent和Flags, 还有关联相关的Stack和TaskRecord
ActivityManagerProxy
AMS服务代理端,第三方应用借助该类实现对AMS的远程RPC请求
ActivityRecord
顾名思义,该数据结构和我们的Activiyt相对应,存储者Activiyt的相关信息,并且每个ActivityRecord会对应到一个TaskRecord,ActivityRecord中类型为TaskRecord的成员task,记录所属的Task,这里有一点需要注意的是Activity和ActivityRecord并不是一对一的,而是一对多,因为一个Actitiy可能存在多个启动方式进而导致存在多个ActivityRecord
TaskRecord
一个TaskRecord由一个或者多个ActivityRecord组成,这就是我们常说的任务栈,具有后进先出的特点
ActivityStack
用来管理TaskRecord,它有一个ArrayList类型的成员mTaskHistory,用于存储TaskRecord,系统总是显示位于栈顶的Activity
ActivityDisplay
ActivityDisplay表示一个屏幕,Android支持三种屏幕:主屏幕,外接屏幕(HDMI等),虚拟屏幕(投屏)。一般情况下,即只有主屏幕时,ActivityStackSupervisor与ActivityDisplay都是系统唯一;
ActivityDisplay是ActivityStackSupervisor的内部类,它相当于一个工具类,封装了移除和添加ActivityStack的方法
ActivityStackSupervisor
负责所有Activity栈的管理。内部管理了mHomeStack、mFocusedStack和mLastFocusedStack三个Activity栈。
    mHomeStack管理的是Launcher相关的Activity栈
    mFocusedStack管理的是当前显示在前台Activity的Activity栈
    mLastFocusedStack管理的是上一次显示在前台Activity的Activity栈

5、AMS和WMS交互图

在这里插入图片描述

6、AMS与WMS任务栈对应关系

AMS和WMS在应用窗口这块是有对应关系:
ActivityDisplay----------DisplayContent
ActivityStack------------TaskStack
TaskRecord-------------Task
ActivityRecord----------AppWindowToken

AMS这边依次为:
ActivityDisplay->ActivityStack->TaskRecord->ActivityRecord
WMS依次分为:
DisplayContent(TaskStackContainer)->TaskStack->Task->AppWindowToken

在这里插入图片描述

二、窗口添加并计算

1、Activity窗口尺寸

ViewRootImpl.setView()开始窗口视图的添加
    /**
         这里我们分析的是Activity的DecorView窗口视图添加的逻辑,所以此时不存在父视图的概念,
         不会走到这里,此时的panelParentView为null
     */
    public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                //顶层DecorView
                mView = view;
                      .......
                       //添加窗口到WMS,mWindow(Binder类型W,传给WMS以便WMS可以调用应用进程方法)
                      res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mTmpFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel,
                            mTempInsets);
                    //mTmpFrame为WMS计算得到的窗口尺寸
                    setFrame(mTmpFrame);
                      ......
                }
           }
    }


通过Seesion.java最终调到WindowManagerService.addWindow

public int addWindow(Session session, IWindow client, int seq,
        WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
        Rect outContentInsets, Rect outStableInsets, InputChannel outInputChannel) {
    int res = mPolicy.checkAddPermission(attrs, appOp);
    //...
    synchronized(mWindowMap) {
        //...
        final DisplayContent displayContent = getDisplayContentLocked(displayId);
        if (displayContent == null) {
            Slog.w(TAG, "Attempted to add window to a display that does not exist: "
                    + displayId + ".  Aborting.");
            return WindowManagerGlobal.ADD_INVALID_DISPLAY;
        }
        //...
        WindowToken token = mTokenMap.get(attrs.token);
        //...
        // 新的WindowState对象在其构造函数中根据窗口类型初始化了其主序mBaseLayer和mSubLayer
        win = new WindowState(this, session, client, token,
                attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
     ......
    return res;
}

WindowState.WindowState构造初始化

 WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
           WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a,
           int viewVisibility, final DisplayContent displayContent) {
        ...
        //如果是子窗口,使用它依附的窗口类型来计算
        if ((mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW)) {
            mBaseLayer = mPolicy.windowTypeToLayerLw(
                    attachedWindow.mAttrs.type) * WindowManagerService.TYPE_LAYER_MULTIPLIER
                    + WindowManagerService.TYPE_LAYER_OFFSET;//计算mBaseLayer,关键点1
            //表示子窗口和父窗口的相对位置
            mSubLayer = mPolicy.subWindowTypeToLayerLw(a.type);//计算mSubLayer,关键点2
            ...
        } else {//非子窗口,直接使用窗口类型来计算
            //注:TYPE_LAYER_MULTIPLIER的值是10000,TYPE_LAYER_OFFSET的值是1000
            mBaseLayer = mPolicy.windowTypeToLayerLw(a.type)
                    * WindowManagerService.TYPE_LAYER_MULTIPLIER
                    + WindowManagerService.TYPE_LAYER_OFFSET;
            mSubLayer = 0;//无效,仅在子窗口中有用
            ...
        }
        ...
    }

WindowManagerPolicy.windowTypeToLayerLw

计算主窗口位置
 //根据类型返回窗口的种类,从1-31,
    public int windowTypeToLayerLw(int type) {
        if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) {
            return 2;
        }
        switch (type) {
        case TYPE_UNIVERSE_BACKGROUND:
            return 1;
        case TYPE_PRIVATE_PRESENTATION:
            return 2;
        case TYPE_WALLPAPER:
            // wallpaper is at the bottom, though the window manager may move it.
            return 2;
        case TYPE_PHONE:
            return 3;
        case TYPE_SEARCH_BAR:
            return 4;
        ...
        case TYPE_HIDDEN_NAV_CONSUMER:
            return 30;
        /// M:JB migration
        case TYPE_TOP_MOST:
            return 31;
        }
        Log.e(TAG, "Unknown window type: " + type);
        return 2;
    }

WindowManagerPolicy.subWindowTypeToLayerLw

计算子窗口位置

public int subWindowTypeToLayerLw(int type) {
        switch (type) {
        case TYPE_APPLICATION_PANEL:
        case TYPE_APPLICATION_ATTACHED_DIALOG:
            return APPLICATION_PANEL_SUBLAYER;//等于1
        case TYPE_APPLICATION_MEDIA:
            return APPLICATION_MEDIA_SUBLAYER;//等于-2
        case TYPE_APPLICATION_MEDIA_OVERLAY:
            return APPLICATION_MEDIA_OVERLAY_SUBLAYER;//等于-1
        case TYPE_APPLICATION_SUB_PANEL:
            return APPLICATION_SUB_PANEL_SUBLAYER;//等于2
        }
        Log.e(TAG, "Unknown sub-window type: " + type);
        return 0;
    }

WindowManagerService.getLayoutHintLw: 计算窗口的大小

public int addWindow(Session session, IWindow client, int seq,
        WindowManager.LayoutParams attrs, int viewVisibility, int displayId,
        Rect outContentInsets, Rect outStableInsets, InputChannel outInputChannel) {
      ......
            final Rect taskBounds;
            final boolean floatingStack;
            if (atoken != null && atoken.getTask() != null) {
                taskBounds = mTmpRect;
                //重点:这里getBounds得到的尺寸是在Activity启动阶段调用setBounds设置的
                atoken.getTask().getBounds(mTmpRect);
                floatingStack = atoken.getTask().isFloating();
            } else {
                taskBounds = null;
                floatingStack = false;
            }
             //重点方法getLayoutHintLw
            if (displayPolicy.getLayoutHintLw(win.mAttrs, taskBounds, displayFrames, floatingStack,
                    outFrame, outContentInsets, outStableInsets, outOutsets, outDisplayCutout)) {
                res |= WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS;
            }
    return res;
}

6、DisplayPolicy.getLayoutHintLw

public boolean getLayoutHintLw(LayoutParams attrs, Rect taskBounds,
            DisplayFrames displayFrames, boolean floatingStack, Rect outFrame,
            Rect outContentInsets, Rect outStableInsets,
            Rect outOutsets, DisplayCutout.ParcelableWrapper outDisplayCutout) {
        //这里获取和窗口尺寸计算相关的flag,
        //如WindowManager.LayoutParams.FLAG_FULLSCREEN,全屏
        //WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS等,半透明状态栏
        final int fl = PolicyControl.getWindowFlags(null, attrs);
        final int pfl = attrs.privateFlags;
       //获取和SystemUI相关 flag,
       //这些flag定义在View中,大多和是否全屏显示,是否隐藏状态栏,是否隐藏导航栏相关
        final int requestedSysUiVis = PolicyControl.getSystemUiVisibility(null, attrs);
       
        final int sysUiVis = requestedSysUiVis | getImpliedSysUiFlagsForLayout(attrs);
       //屏幕旋转角度
        final int displayRotation = displayFrames.mRotation;
    
       //是否使用超出真实屏幕的底部像素值
        final boolean useOutsets = outOutsets != null && shouldUseOutsets(attrs, fl);
        if (useOutsets) {
            //这个值定义在configs.xml中(config_windowOutsetBottom),默认为0,
            int outset = mWindowOutsetBottom;
            if (outset > 0) {
                if (displayRotation == Surface.ROTATION_0) {
                    outOutsets.bottom += outset;
                } else if (displayRotation == Surface.ROTATION_90) {
                    outOutsets.right += outset;
                } else if (displayRotation == Surface.ROTATION_180) {
                    outOutsets.top += outset;
                } else if (displayRotation == Surface.ROTATION_270) {
                    outOutsets.left += outset;
                }
            }
        }

        final boolean layoutInScreen = (fl & FLAG_LAYOUT_IN_SCREEN) != 0;
        final boolean layoutInScreenAndInsetDecor = layoutInScreen
                && (fl & FLAG_LAYOUT_INSET_DECOR) != 0;
        final boolean screenDecor = (pfl & PRIVATE_FLAG_IS_SCREEN_DECOR) != 0;

        if (layoutInScreenAndInsetDecor && !screenDecor) {
            if ((sysUiVis & SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0) {
                //包含状态栏导航栏的屏幕可见区域  Rect(0, 0 - 800, 480)
                outFrame.set(displayFrames.mUnrestricted);
            } else {
                //除开导航栏的内容区域  Rect(0, 0 - 800, 396)
                outFrame.set(displayFrames.mRestricted);
            }

            final Rect sf;
            
            //悬浮栈,窗口模式为自由窗口或者画中画的栈
            if (floatingStack) {
                sf = null;
            } else {
               //除开状态栏,导航栏的内容区域  Rect(0, 57 - 800, 396)
                sf = displayFrames.mStable;
            }

            final Rect cf;
            if (floatingStack) {
                cf = null;
            } else if ((sysUiVis & View.SYSTEM_UI_FLAG_LAYOUT_STABLE) != 0) {
                if ((fl & FLAG_FULLSCREEN) != 0) {
                    //除开导航栏的内容区域  Rect(0, 0 - 800, 396)
                    cf = displayFrames.mStableFullscreen;
                } else {
                    //除开状态栏,导航栏的内容区域  Rect(0, 57 - 800, 396)
                    cf = displayFrames.mStable;
                }
            } else if ((fl & FLAG_FULLSCREEN) != 0 || (fl & FLAG_LAYOUT_IN_OVERSCAN) != 0) {
                //真实屏幕的尺寸  Rect(0, 0 - 800, 480)
                cf = displayFrames.mOverscan;
            } else {
                //除开状态栏,导航栏,输入法的内容区域  Rect(0, 57 - 800, 396)
                cf = displayFrames.mCurrent;
            }
     
            if (taskBounds != null) {
                //taskBounds为Activity自己设置的大小,Rect(400, 57 - 800, 396)
                outFrame.intersect(taskBounds);
            }
        
            InsetUtils.insetsBetweenFrames(outFrame, cf, outContentInsets);
            InsetUtils.insetsBetweenFrames(outFrame, sf, outStableInsets);
            outDisplayCutout.set(displayFrames.mDisplayCutout.calculateRelativeTo(outFrame)
                    .getDisplayCutout());
            return mForceShowSystemBars;
        } else {
            if (layoutInScreen) {
                //包含状态栏导航栏的屏幕可见区域  Rect(0, 0 - 800, 480)
                outFrame.set(displayFrames.mUnrestricted);
            } else {
                  //除开状态栏,导航栏的内容区域  Rect(0, 57 - 800, 396)
                outFrame.set(displayFrames.mStable);
            }
            if (taskBounds != null) {
                //taskBounds为Activity自己设置的大小,Rect(400, 57 - 800, 396)
                outFrame.intersect(taskBounds);
            }

            outContentInsets.setEmpty();
            outStableInsets.setEmpty();
            outDisplayCutout.set(DisplayCutout.NO_CUTOUT);
            return mForceShowSystemBars;
        }
    }

DisplayFrames一些关键介绍

//DisplayFrames.java
//以模拟器尺寸Rect(0, 0 - 800, 480)为例
 /**
     * 真实屏幕的尺寸  Rect(0, 0 - 800, 480)
     */
        public final Rect mOverscan = new Rect();
   /**
     * 除开状态栏,导航栏,输入法的内容区域  Rect(0, 57 - 800, 396)
     */
    public final Rect mCurrent = new Rect();
/**
     * 包含状态栏导航栏的屏幕可见区域  Rect(0, 0 - 800, 480)
     */
    public final Rect mUnrestricted = new Rect();
 /**
     * 除开导航栏的内容区域  Rect(0, 0 - 800, 396)
     */
    public final Rect mRestricted = new Rect();
/** 除开状态栏,导航栏的内容区域  Rect(0, 57 - 800, 396) */
    public final Rect mStable = new Rect();
  /**
     * 除开导航栏的内容区域  Rect(0, 0 - 800, 396)
     */
    public final Rect mStableFullscreen = new Rect();

2、窗口尺寸计算

ViewRootImpl.performTraversals
private void performTraversals() {
        //DecorView
        final View host = mView;
       ......

        WindowManager.LayoutParams lp = mWindowAttributes;
        //这两个变量用来记录Activity窗口的宽高尺寸
        int desiredWindowWidth;
        int desiredWindowHeight;
      
      ......
      //mWinFrame用来记录Activity窗口的尺寸,这个值是WMS计算的到的 Rect(400, 57 - 800, 396)
      Rect frame = mWinFrame;
    //首次进入
        if (mFirst) {
            mFullRedrawNeeded = true;
            mLayoutRequested = true;

            final Configuration config = mContext.getResources().getConfiguration();
            //是否使用屏幕的尺寸,TYPE_STATUS_BAR_PANEL,TYPE_INPUT_METHOD,TYPE_VOLUME_OVERLAY这三种类型窗口返回true
            if (shouldUseDisplaySize(lp)) {
                Point size = new Point();
                mDisplay.getRealSize(size);
                desiredWindowWidth = size.x;
                desiredWindowHeight = size.y;
            } else {
                //将Activity宽高保存在这两个变量中,w = 400,h = 339
                desiredWindowWidth = mWinFrame.width();
                desiredWindowHeight = mWinFrame.height();
            }
            ......
                //此方法用来处理可能出现的系统窗口,状态栏,导航栏,输入法,罗升阳老师文章中叫做边衬区域,暂时略过
            dispatchApplyInsets(host);
        } else {
            desiredWindowWidth = frame.width();
            desiredWindowHeight = frame.height();
            //mWidth和mHeight记录上次WMS为Activity计算得到的窗口宽高
            if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
                //不等说明窗口尺寸发生了变化
                mFullRedrawNeeded = true;
                mLayoutRequested = true;
                windowSizeMayChange = true;
            }
        }
          ...
          //对DecorView进行测量,宽高使用WMS.addWindow计算出来的尺寸
            windowSizeMayChange |= measureHierarchy(host, lp, res,
                    desiredWindowWidth, desiredWindowHeight);
       ......
           //这里有六个条件,满足其中之一就会对窗口进行再次计算
           if (mFirst || windowShouldResize || insetsChanged ||
                viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
               ......
             //针对窗口发生变化的情况进行再次计算
             relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
               ......
           }

    .....
           
}


    private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
            boolean insetsPending) throws RemoteException {
       
        //缩放系数 等于1
        float appScale = mAttachInfo.mApplicationScale;
         ......
        //调用WMS的relayout方法
        int relayoutResult = mWindowSession.relayout(mWindow, mSeq, params,
                (int) (mView.getMeasuredWidth() * appScale + 0.5f),
                (int) (mView.getMeasuredHeight() * appScale + 0.5f), viewVisibility,
                insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, frameNumber,
                mTmpFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
                mPendingStableInsets, mPendingOutsets, mPendingBackDropFrame, mPendingDisplayCutout,
                mPendingMergedConfiguration, mSurfaceControl, mTempInsets);
        if (mSurfaceControl.isValid()) {
            mSurface.copyFrom(mSurfaceControl);
        } else {
            destroySurface();
        }
       ......
        //设置WMS再次计算得到窗口尺寸
        setFrame(mTmpFrame);
        mInsetsController.onStateChanged(mTempInsets);
        return relayoutResult;
    }

通过Session.java调用到WMS.relayoutWindow

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,
            SurfaceControl outSurfaceControl, InsetsState outInsetsState) {
            
                ......
                if (viewVisibility != View.GONE) {
                    //requestedWidth和requestedHeight是Activity窗口经过测量后得到的自己的想要的宽高
                win.setRequestedSize(requestedWidth, requestedHeight);
            }
                ......
                    //WMS核心功能,刷新系统UI,这里面会去计算窗口尺寸,
                    //遍历系统所有窗口调用其WindowState的computeFrameLw方法
                    mWindowPlacerLocked.performSurfacePlacement(true /* force */);
                ......
                    win.getCompatFrame(outFrame);
                 ......
            }


DisplayPolicy.layoutWindowLw

public void layoutWindowLw(WindowState win, WindowState attached, DisplayFrames displayFrames) {
    ......
     final WindowFrames windowFrames = win.getWindowFrames();
    
        final Rect pf = windowFrames.mParentFrame;
        final Rect df = windowFrames.mDisplayFrame;
        final Rect of = windowFrames.mOverscanFrame;
        final Rect cf = windowFrames.mContentFrame;
        final Rect vf = windowFrames.mVisibleFrame;
        final Rect dcf = windowFrames.mDecorFrame;
        final Rect sf = windowFrames.mStableFrame;
    ......
           //经过各种条件判断,最后会对上述Rect赋值,赋的值全部来自DisplayFrames中
           
        .....
        //有了上述窗口的基础边界之后便开始窗口自己尺寸的计算了
       win.computeFrameLw();
    .....
}

WindowState.computeFrameLw : 窗口自己尺寸的计算

computeFrameLw方法的核心是计算出mWindowFrames.mFrame的值,这个值就是窗口的实际尺寸

@Override
    public void computeFrameLw() {
        if (mWillReplaceWindow && (mAnimatingExit || !mReplacingRemoveRequested)) {
            return;
        }
        mHaveFrame = true;
        
        //获取窗口对应的task,非Activity窗口为空
        final Task task = getTask();
        //是否为全屏,非多窗口模式并且getBounds等于Display的getBounds,我们分析的是分屏窗口,所以这里为false
        final boolean isFullscreenAndFillsDisplay = !inMultiWindowMode() && matchesDisplayBounds();
        //分屏不是悬浮窗口,此处为false
        final boolean windowsAreFloating = task != null && task.isFloating();
        
        final DisplayContent dc = getDisplayContent();
        //这里getBounds返回分屏窗口启动是所设置的边界值,即为Rect(400, 57 - 800, 396)
        mInsetFrame.set(getBounds());


        final Rect layoutContainingFrame;
        final Rect layoutDisplayFrame;

        final int layoutXDiff;
        final int layoutYDiff;
        //是否有输入法窗口
        final WindowState imeWin = mWmService.mRoot.getCurrentInputMethodWindow();
        //当前窗口是否为输入法窗口的目标窗口
        final boolean isImeTarget =
                imeWin != null && imeWin.isVisibleNow() && isInputMethodTarget();
        
        if (isFullscreenAndFillsDisplay || layoutInParentFrame()) {
            //分屏窗口不走这里,省略
            ......
        } else {
            //这里的getDisplayedBounds就等于task.getBounds,即为Rect(400, 57 - 800, 396)
            mWindowFrames.mContainingFrame.set(getDisplayedBounds());
            if (mAppToken != null && !mAppToken.mFrozenBounds.isEmpty()) {
                  //冻屏窗口的情况,省略
                  ......
            }
            // 当前窗口是否为输入法窗口的目标窗口
            if (isImeTarget) {
                 //需要计算输入法的情况,省略
                ......
            }
           
            if (windowsAreFloating) {
                //悬浮窗口的情况,省略
                ......
            }
            //获取窗口所在的栈,
            final TaskStack stack = getStack();
            
            if (inPinnedWindowingMode() && stack != null
                    && stack.lastAnimatingBoundsWasToFullscreen()) {
                //画中画模式的窗口,省略
                ......
            }
            //mWindowFrames.mDisplayFrame值为 Rect(0, 0 - 800, 480)
            layoutDisplayFrame = new Rect(mWindowFrames.mDisplayFrame);
            //mWindowFrames.mContainingFrame值为 Rect(400, 57 - 800, 396)
            mWindowFrames.mDisplayFrame.set(mWindowFrames.mContainingFrame);
            //layout得到的尺寸和实际尺寸的偏移量,大多数情况为0
            layoutXDiff = mInsetFrame.left - mWindowFrames.mContainingFrame.left;
            layoutYDiff = mInsetFrame.top - mWindowFrames.mContainingFrame.top;
            layoutContainingFrame = mInsetFrame;
            //mTmpRect保存屏幕尺寸,为 Rect(0, 0 - 800, 480)
            mTmpRect.set(0, 0, dc.getDisplayInfo().logicalWidth, dc.getDisplayInfo().logicalHeight);
            
            subtractInsets(mWindowFrames.mDisplayFrame, layoutContainingFrame, layoutDisplayFrame,
                    mTmpRect);
            //layoutInParentFrame代表当前计算尺寸的是否为子窗口
            if (!layoutInParentFrame()) {
                subtractInsets(mWindowFrames.mContainingFrame, layoutContainingFrame,
                        mWindowFrames.mParentFrame, mTmpRect);
                subtractInsets(mInsetFrame, layoutContainingFrame, mWindowFrames.mParentFrame,
                        mTmpRect);
            }
            layoutDisplayFrame.intersect(layoutContainingFrame);
        }
       //对于当前窗口为子窗口或者全屏的情况mWindowFrames.mContainingFrame保存的是父窗口的尺寸,
        //否则mWindowFrames.mContainingFrame保存的就是自己的尺寸
        final int pw = mWindowFrames.mContainingFrame.width();
        final int ph = mWindowFrames.mContainingFrame.height();
        //mRequestedWidth和mRequestedHeight是Activity自己测量出来的自己的DecorView的宽高
        //WMS要结合这个应用自己请求的宽高来计算窗口的尺寸
        if (mRequestedWidth != mLastRequestedWidth || mRequestedHeight != mLastRequestedHeight) {
            mLastRequestedWidth = mRequestedWidth;
            mLastRequestedHeight = mRequestedHeight;
            mWindowFrames.setContentChanged(true);
        }
        
        //mWindowFrames.mFrame保存的是最终窗口计算出来的实际尺寸,computeFrameLw方法最终要计算的就是它的值
        //目前这里还是0
        final int fw = mWindowFrames.mFrame.width();
        final int fh = mWindowFrames.mFrame.height();
        
        //计算mFrame的核心方法,layoutContainingFrame代表的是父窗口尺寸区域,layoutDisplayFrame代表当前窗口栈所在区域
        //大多数情况下这两个值都相等
        applyGravityAndUpdateFrame(layoutContainingFrame, layoutDisplayFrame);

        // 计算超出屏幕区域的部分,省略
          .....

        
        if (windowsAreFloating && !mWindowFrames.mFrame.isEmpty()) {
              //悬浮窗口的情况,省略
            ......
        } else if (mAttrs.type == TYPE_DOCK_DIVIDER) {
             //窗口类型为TYPE_DOCK_DIVIDER,省略
            ......
        } else {
            //mContentFrame的值来自DisplayFrames,为除开状态栏,导航栏的区域 Rect(0, 57 - 800, 396)
            //这里mContentFrame取值为mContentFrame和mFrame中较小的区域
            mWindowFrames.mContentFrame.set(
                    Math.max(mWindowFrames.mContentFrame.left, mWindowFrames.mFrame.left),
                    Math.max(mWindowFrames.mContentFrame.top, mWindowFrames.mFrame.top),
                    Math.min(mWindowFrames.mContentFrame.right, mWindowFrames.mFrame.right),
                    Math.min(mWindowFrames.mContentFrame.bottom, mWindowFrames.mFrame.bottom));
            
            //mVisibleFrame的值来自DisplayFrames,为除开状态栏,导航栏的区域 Rect(0, 57 - 800, 396)
            //这里mVisibleFrame取值为mVisibleFrame和mFrame中较小的区域
            mWindowFrames.mVisibleFrame.set(
                    Math.max(mWindowFrames.mVisibleFrame.left, mWindowFrames.mFrame.left),
                    Math.max(mWindowFrames.mVisibleFrame.top, mWindowFrames.mFrame.top),
                    Math.min(mWindowFrames.mVisibleFrame.right, mWindowFrames.mFrame.right),
                    Math.min(mWindowFrames.mVisibleFrame.bottom, mWindowFrames.mFrame.bottom));
            
            //mStableFrame的值来自DisplayFrames,为除开状态栏,导航栏的区域 Rect(0, 57 - 800, 396)
            //这里mStableFrame取值为mStableFrame和mFrame中较小的区域
            mWindowFrames.mStableFrame.set(
                    Math.max(mWindowFrames.mStableFrame.left, mWindowFrames.mFrame.left),
                    Math.max(mWindowFrames.mStableFrame.top, mWindowFrames.mFrame.top),
                    Math.min(mWindowFrames.mStableFrame.right, mWindowFrames.mFrame.right),
                    Math.min(mWindowFrames.mStableFrame.bottom, mWindowFrames.mFrame.bottom));
            
            //上述三个区域mContentFrame,mVisibleFrame,mStableFrame最后得到的值相等
            //mFrame为计算出来的窗口实际尺寸
        }

        if (isFullscreenAndFillsDisplay && !windowsAreFloating) {
            //全屏并且非悬浮窗口,省略
            ......
        }

        if (mAttrs.type == TYPE_DOCK_DIVIDER) {
          //类型为TYPE_DOCK_DIVIDER的窗口,省略
            ......
        } else {
            //mTmpRect保存了屏幕的尺寸,Rect(0, 0 - 800, 480),将这个尺寸保存到DisplayContent中去
            getDisplayContent().getBounds(mTmpRect);
            mWindowFrames.calculateInsets(
                    windowsAreFloating, isFullscreenAndFillsDisplay, mTmpRect);
        }

         ......
       //将mFrame保存到mCompatFrame
        mWindowFrames.mCompatFrame.set(mWindowFrames.mFrame);
        ......
            
        if (mIsWallpaper && (fw != mWindowFrames.mFrame.width()
                || fh != mWindowFrames.mFrame.height())) {
                //壁纸窗口
            ......
        }

        ......
    }


WindowState.applyGravityAndUpdateFrame : mFrame计算

窗口的尺寸计算到此就完成了,最终的结果是保存在mFrame中,最后这个值会返回给APP进程,APP进程ViewRootImpl中调用的relayoutWindow方法主要目的就是请求WMS对窗口进行计算得到mFrame的值,最后APP将此值保存在了ViewRootImpl的成员变量mWinFrame中。

private void applyGravityAndUpdateFrame(Rect containingFrame, Rect displayFrame) {  // Rect(400, 57 - 800, 396)
        
        final int pw = containingFrame.width();
        final int ph = containingFrame.height();
        final Task task = getTask();
       //当前窗口是否占满父容器,对分屏窗口来说inNonFullscreenContainer为true,即不会占满父容器
        final boolean inNonFullscreenContainer = !inAppWindowThatMatchesParentBounds();
       //是否允许当前窗口的大小无限制,对分屏窗口来说noLimits为false
        final boolean noLimits = (mAttrs.flags & FLAG_LAYOUT_NO_LIMITS) != 0;

        //是否占满整个屏幕,对分屏窗口来说fitToDisplay为false
        final boolean fitToDisplay = (task == null || !inNonFullscreenContainer)
                || ((mAttrs.type != TYPE_BASE_APPLICATION) && !noLimits);
        float x, y;
        int w,h;
       //是否运行在兼容模式,这里为false
        final boolean inSizeCompatMode = inSizeCompatMode();
       //是否指定窗口缩放系数
        if ((mAttrs.flags & FLAG_SCALED) != 0) {
               //没有指定,省略
               .......
        } else {
            
            if (mAttrs.width == MATCH_PARENT) {
                //如果分屏窗口宽指定为MATCH_PARENT,则w等于栈宽度
                w = pw;
            } else if (inSizeCompatMode) {//兼容模式
                w = (int)(mRequestedWidth * mGlobalScale + .5f);
            } else {
                //否则w等于分屏应用请求的宽度
                w = mRequestedWidth;
            }
            if (mAttrs.height == MATCH_PARENT) {
                //如果分屏窗口高指定为MATCH_PARENT,则h等于栈高度
                h = ph;
            } else if (inSizeCompatMode) {//兼容模式
                h = (int)(mRequestedHeight * mGlobalScale + .5f);
            } else {
                //否则h等于分屏应用请求的高度
                h = mRequestedHeight;
            }
          
        }
        if (inSizeCompatMode) {
            //兼容模式  
            x = mAttrs.x * mGlobalScale;
            y = mAttrs.y * mGlobalScale;
        } else {
            x = mAttrs.x;
            y = mAttrs.y;
        }
        //非占满父容器,并且非子窗口
        if (inNonFullscreenContainer && !layoutInParentFrame()) {
            //这里是确保窗口的宽高是合理的,对于Activity类型窗口,其最大宽高只能等于所在栈的宽高
            w = Math.min(w, pw);
            h = Math.min(h, ph);
        }

        // 给mFrame赋值,这里会考虑当前窗口的gravity,x,y的位置,Margin来最终计算mFrame
        Gravity.apply(mAttrs.gravity, w, h, containingFrame,
                (int) (x + mAttrs.horizontalMargin * pw),
                (int) (y + mAttrs.verticalMargin * ph), mWindowFrames.mFrame);

        if (fitToDisplay) {
            //这里是确定计算出来的窗口尺寸在屏幕区域之内
            Gravity.applyDisplay(mAttrs.gravity, displayFrame, mWindowFrames.mFrame);
        }

      //给mCompatFrame设置mFrame同样的值
        mWindowFrames.mCompatFrame.set(mWindowFrames.mFrame);
        if (inSizeCompatMode) {
           
            mWindowFrames.mCompatFrame.scale(mInvGlobalScale);
        }
    }


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/677172.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

深度学习-第T11周——优化器对比实验

深度学习-第T11周——优化器对比实验 深度学习-第T11周——优化器对比实验一、前言二、我的环境三、前期工作1、导入数据集2、查看图片数目3、查看数据 四、数据预处理1、 加载数据1、设置图片格式2、划分训练集3、划分验证集4、查看标签 2、数据可视化3、检查数据4、配置数据集…

6月份读书学习好文记录

看看CHATGPT在最近几个月的发展趋势 https://blog.csdn.net/csdnnews/article/details/130878125?spm1000.2115.3001.5927 这是属于 AI 开发者的好时代&#xff0c;有什么理由不多去做一些尝试呢。 北大教授陈钟谈 AI 未来&#xff1a;逼近 AGI、融进元宇宙&#xff0c;开源…

06-浏览器渲染原理

什么是渲染&#xff1f; render&#xff0c;HTML字符串 --渲染--> 像素信息 URL地址是一个字符串&#xff0c;HTML、css、js都在里面 可以把渲染想象成一个函数&#xff0c;上代码&#xff1a; function render (html) {/* 第一行第二行*/return pixels; } 渲染时间点 …

【深入浅出 Spring Security(十二)】使用第三方(Github)授权登录

使用第三方&#xff08;Github&#xff09;授权登录 一、OAuth2 简单概述二、OAuth2 四种授权模式之授权码模式三、Github 授权登录准备工作创建 Spring Boot 项目Vue 测试代码测试效果 &#xff08;Github授权登录的具体操作在目录第三“章”&#xff09; 一、OAuth2 简单概述…

Spring Boot 优雅集成 Spring Security 5.7(安全框架)与 JWT(双令牌机制)

Spring Boot 集成 Spring Security &#xff08;安全框架&#xff09; 本章节将介绍 Spring Boot 集成 Spring Security 5.7&#xff08;安全框架&#xff09;。 &#x1f916; Spring Boot 2.x 实践案例&#xff08;代码仓库&#xff09; 介绍 Spring Security 是一个能够为基…

【CSDN创作纪念日】——博客小梦的“256”鸭~

博客小梦的创作纪念日&#x1f60e; 前言&#x1f64c;与CSDN的相遇浑水摸鱼的日常CSDN上的小小收获收获了 一群热爱编程&#xff0c;热爱创作的CSDN挚友创作上的小荣誉 憧憬未来 总结撒花&#x1f49e; &#x1f60e;博客昵称&#xff1a;博客小梦 &#x1f60a;最喜欢的座右铭…

【Spring】— Spring MVC简单数据绑定(一)

目录 Spring MVC数据绑定1.数据绑定概述2.简单数据绑定2.1 绑定默认数据类型2.2 绑定简单数据类型 Spring MVC数据绑定 1.数据绑定概述 在执行程序时&#xff0c;Spring MVC根据客户端请求参数的不同将请求消息中的信息以一定的方式转换并绑定到控制器类的方法参数中。这种将…

基于VMD-LSTM-IOWA-RBF的碳排放混合预测研究(Matlab代码实现)

&#x1f4a5;&#x1f4a5;&#x1f49e;&#x1f49e;欢迎来到本博客❤️❤️&#x1f4a5;&#x1f4a5; &#x1f3c6;博主优势&#xff1a;&#x1f31e;&#x1f31e;&#x1f31e;博客内容尽量做到思维缜密&#xff0c;逻辑清晰&#xff0c;为了方便读者。 ⛳️座右铭&a…

第J3-1周:DenseNet算法 实现乳腺癌识别

目录 一、理论基础1.前言2.设计理念3.网络结构4.与其他算法效果对比 二、 前期准备1. 设置GPU2. 导入数据3. 划分数据集 三、搭建网络模型1. DenseLayer模块2. DenseBlock模块3. Transition模块4. 构建DenseNet5. 构建densenet121 四、 训练模型1. 编写训练函数2. 编写测试函数…

I/O多路复用+高性能网络模式

前言&#xff1a; 本篇文章将介绍客户端-服务端之间从最简单的Socket模型到I/O多路复用的模式演变过程&#xff0c;并介绍Reactor和Proactor两种高性能网络模式 文章内容摘自&#xff1a;小林Coding I/O多路复用高性能网络模式 . 传统Socket模型传统Socket模型的性能瓶颈多进程…

【Java基础学习打卡12】Java入门程序

目录 前言一、Java程序开发运行流程二、Java程序源代码编写三、Java程序源代码编译四、Java程序运行五、Java入门程序问题总结 前言 本文首先介绍Java程序开发运行基础流程&#xff0c;然后先进行程序源代码编写&#xff0c;然后对Java程序代码进行编译&#xff0c;最后要运行…

Python学习笔记(1)--环境搭建,开发工具PyCharm 安装及初步使用

传送门>B站黑马python入门教程 目录 1.Python环境安装搭建安装python基础包验证安装文件 2.hello world3.开发工具PyCharm 安装及初步使用安装基础设置 1.Python环境安装搭建 安装python基础包 首先,打开python 官网 https://www.python.org/ 下载windows版 下载后进行安装 …

Triton教程 --- 解耦后端和模型

Triton教程 — 解耦后端和模型 Triton系列教程: 快速开始利用Triton部署你自己的模型Triton架构模型仓库存储代理模型设置优化动态批处理速率限制器模型管理自定义算子 解耦后端和模型 Triton 可以支持为一个请求发送多个响应或为一个请求发送零个响应的后端和模型。 解耦的…

论文笔记--Prompt Consistency for Zero-Shot Task Generalization

论文笔记--Prompt Consistency for Zero-Shot Task Generalization 1. 文章简介2. 文章概括3 文章重点技术3.1 Prompt-based zero-shot task generalization3.2 Prompt Consistency Training3.3 如何防止遗忘和退化&#xff1f; 4. 文章亮点5. 原文传送门 1. 文章简介 标题&am…

【numpy模块上}——数据分析01

目录索引 介绍&#xff1a;用处与特点&#xff1a;构成&#xff1a;导包&#xff1a;创建数组&#xff1a; numpy常用方法&#xff1a;常用属性查看&#xff1a;*获取秩的大小&#xff1a;**获取数组形状&#xff1a;**获取元素个数&#xff1a;**获取元素类型&#xff1a;**获…

行为型设计模式10-解释器模式

&#x1f9d1;‍&#x1f4bb;作者&#xff1a;猫十二懿 ❤️‍&#x1f525;账号&#xff1a;CSDN 、掘金 、个人博客 、Github &#x1f389;公众号&#xff1a;猫十二懿 解释器模式 1、解释器模式介绍 解释器模式&#xff08;Interpreter Pattern&#xff09;是一种行为设…

Kafka系列之:对源连接器的的Exactly-Once支持

Kafka系列之&#xff1a;对源连接器的的Exactly-Once支持 一、背景二、目标三、公共接口四、连接器 API 扩展五、REST API验证六、新指标七、计划变更八、任务计数记录九、重新平衡的准备十、源任务启动十一、领导者访问配置主题十二、用于隔离事务生产者的管理 API十三、解决了…

论文阅读 - SegFormer

文章目录 1 概述2 模型说明2.1 总体结构2.2 Hierarchical Transformer Encoder2.3 Lightweight All-MLP Decoder 3 SegFormer和SETR的比较参考资料 1 概述 图像分割任务和图像分类任务是非常相关的&#xff0c;前者是像素级别的分类&#xff0c;后者是图像级别的分类。基于分类…

不到3000块,搭建IT人的实验平台!性能可媲美服务器!

作为IT从业者&#xff0c;特别是运维这个岗位&#xff0c;没有自己的实验平台真的特别难受&#xff0c;那么如何搭建自己的实验平台呢&#xff1f;这是我最近思考并付诸实践的一个事情&#xff0c;最终找到了自己觉得比较可以的方案。 01 我的需求是什么&#xff1f; 大内存容量…

TypeScript——类(class)

ES6 中类的用法 下面我们先回顾一下 ES6 中类的用法&#xff0c;更详细的介绍可以参考 ECMAScript 6 入门 - Class 属性和方法 使用 class 定义类&#xff0c;使用 constructor 定义构造函数。 通过new生成新实例的时候&#xff0c;会自动调用构造函数。 class Person{con…