Android系统服务-WMS计算窗口位置,大小

wms服务总结没有梳理好,因为关联的模块实在太多,最后只能先把整体框架梳理清楚,根据需要准备把activity窗口位置,大小计算流程详细分析下。本文基于Android O代码分析。
activity只是界面框架,真正控制视图的是view, 先总结下view整体绘制流程,计算窗口的位置,大小只是绘制流程中的一小部分流程,总分总的思路分析流程。

一、 View 的测量、布局、绘制流程

下图是ViewRootImpl里面整体绘制流程

View绘制流程

第1步,这里简单理解系统从ViewRootImpl#performTraversals来开始绘制,Choreographer封装VSYNC信号处理,并回调doTraversal->performTraversals,Choreographer特性参考
performTraversals整体主要做4件事情:

    1. performMeasure:用来测量View大小
    2. relayoutWindow:用来测量Window大小,这里面会创建surface, 计算window位置,大小。
    3. performLayout:用来对View布局
    4. performDraw:处理View的绘制
//ViewRootImpl.java, 
1578     private void performTraversals() {
1579         // cache mView since it is used so much below...
1580         final View host = mView;
1581 
1582         if (host == null || !mAdded)
1583             return;
1584 
1585         mIsInTraversal = true;
1586         mWillDrawSoon = true;
1587         boolean windowSizeMayChange = false;
1588         boolean newSurface = false;
1589         boolean surfaceChanged = false;
1590         WindowManager.LayoutParams lp = mWindowAttributes;
1592         int desiredWindowWidth;  //view期望的宽,高
1593         int desiredWindowHeight;
1594 
1595         //...
1596 
1597         mWindowAttributesChangesFlag = 0;
1598 
1599         Rect frame = mWinFrame;  // 保存wms返回计算的window的位置,大小
1600         if (mFirst) {  //  当前view第一次执行performTraversals
1601             mFullRedrawNeeded = true;
1602             mLayoutRequested = true;
1603 
1604             final Configuration config = mContext.getResources().getConfiguration();
1605             if (shouldUseDisplaySize(lp)) {
1606                 // NOTE -- system code, won't try to do compat mode.
1607                 Point size = new Point();
1608                 mDisplay.getRealSize(size);
1609                 desiredWindowWidth = size.x;
1610                 desiredWindowHeight = size.y;
1611             } else {
1612                 desiredWindowWidth = dipToPx(config.screenWidthDp);
1613                 desiredWindowHeight = dipToPx(config.screenHeightDp);
1614             }
1615             //...
1616             host.dispatchAttachedToWindow(mAttachInfo, 0); //向子类分发attachWindow 事件,只有第一次的时候才会。这里其实是很重要的把mAttachInfo 传进去了
1617             mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
1618             dispatchApplyInsets(host);
1619             //Log.i(mTag, "Screen on initialized: " + attachInfo.mKeepScreenOn);
1620 
1621         } else {  //判断大小是否改变
1622             desiredWindowWidth = frame.width();
1623             desiredWindowHeight = frame.height();
1624             if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
1625                 if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame);
1626                 mFullRedrawNeeded = true;
1627                 mLayoutRequested = true;
1628                 windowSizeMayChange = true;
1629             }
1630         }
1631 
1632         if (viewVisibilityChanged) {  //判断可见性是否改变
1633             mAttachInfo.mWindowVisibility = viewVisibility;
1634             host.dispatchWindowVisibilityChanged(viewVisibility);
1635             if (viewUserVisibilityChanged) {
1636                 host.dispatchVisibilityAggregated(viewVisibility == View.VISIBLE);
1637             }
1638             if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) {
1639                 endDragResizing();
1640                 destroyHardwareResources();
1641             }
1642             if (viewVisibility == View.GONE) {
1643                 // After making a window gone, we will count it as being
1644                 // shown for the first time the next time it gets focus.
1645                 mHasHadWindowFocus = false;
1646             }
1647         }
1648 
1649         // Non-visible windows can't hold accessibility focus.
1650         if (mAttachInfo.mWindowVisibility != View.VISIBLE) {
1651             host.clearAccessibilityFocus();
1652         }
1653 
1654         // Execute enqueued actions on every traversal in case a detached view enqueued an action
1655         getRunQueue().executeActions(mAttachInfo.mHandler);
1656 
1657         boolean insetsChanged = false;  // 边衬区是否改变
1658 
1659         boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
1660         if (layoutRequested) {
1661 
1662             final Resources res = mView.getContext().getResources();
1663 
1664             if (mFirst) {
1665                 // make sure touch mode code executes by setting cached value
1666                 // to opposite of the added touch mode.
1667                 mAttachInfo.mInTouchMode = !mAddedTouchMode;
1668                 ensureTouchModeLocally(mAddedTouchMode);
1669             } else {  //判断边衬去是否改变
1670                 if (!mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets)) {
1671                     insetsChanged = true;
1672                 }
1673                 if (!mPendingContentInsets.equals(mAttachInfo.mContentInsets)) {
1674                     insetsChanged = true;
1675                 }
1676                 if (!mPendingStableInsets.equals(mAttachInfo.mStableInsets)) {
1677                     insetsChanged = true;
1678                 }
1679                 if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) {
1680                     mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
1681                     if (DEBUG_LAYOUT) Log.v(mTag, "Visible insets changing to: "
1682                             + mAttachInfo.mVisibleInsets);
1683                 }
1684                 //...
1685             }
1686 
1687             // Ask host how big it wants to be
1688             // step1, 初步对view测量,计算预期大小
1689             windowSizeMayChange |= measureHierarchy(host, lp, res,
1690                     desiredWindowWidth, desiredWindowHeight);
1691         }
1692 
1693         if (collectViewAttributes()) {
1694             params = lp;
1695         }
1696         if (mAttachInfo.mForceReportNewAttributes) {
1697             mAttachInfo.mForceReportNewAttributes = false;
1698             params = lp;
1699         }
1700 
1701         if (mFirst || mAttachInfo.mViewVisibilityChanged) {  //第一次或者可见性变化
1702             mAttachInfo.mViewVisibilityChanged = false;
1703             int resizeMode = mSoftInputMode &
1704                     WindowManager.LayoutParams.SOFT_INPUT_MASK_ADJUST;
1705             // If we are in auto resize mode, then we need to determine
1706             // what mode to use now.
1707             if (resizeMode == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
1708                 final int N = mAttachInfo.mScrollContainers.size();
1709                 //...
1710             }
1711         }
1712         if (layoutRequested) {
1713             // Clear this now, so that if anything requests a layout in the
1714             // rest of this function we will catch it and re-run a full
1715             // layout pass.
1716             mLayoutRequested = false;
1717         }
1718 
1719         boolean windowShouldResize = layoutRequested && windowSizeMayChange
1720             && ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight())
1721                 || (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
1722                         frame.width() < desiredWindowWidth && frame.width() != mWidth)
1723                 || (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
1724                         frame.height() < desiredWindowHeight && frame.height() != mHeight));
1725         windowShouldResize |= mDragResizing && mResizeMode == RESIZE_MODE_FREEFORM;
1726 
1727         // If the activity was just relaunched, it might have unfrozen the task bounds (while
1728         // relaunching), so we need to force a call into window manager to pick up the latest
1729         // bounds.
1730         windowShouldResize |= mActivityRelaunched;
1731 
1732         // Determine whether to compute insets.
1733         // If there are no inset listeners remaining then we may still need to compute
1734         // insets in case the old insets were non-empty and must be reset.
1735         final boolean computesInternalInsets =
1736                 mAttachInfo.mTreeObserver.hasComputeInternalInsetsListeners()
1737                 || mAttachInfo.mHasNonEmptyGivenInternalInsets;
1738 
1739         boolean insetsPending = false;
1740         int relayoutResult = 0;
1741         boolean updatedConfiguration = false;
1742 
1743         final int surfaceGenerationId = mSurface.getGenerationId();
1744 
1745         final boolean isViewVisible = viewVisibility == View.VISIBLE;
1746         final boolean windowRelayoutWasForced = mForceNextWindowRelayout;
1747         //第一次进来、window 需要重新测量、边衬区改变、view可见性改变、params 不为null 体现的是Window 的属性变化等、强制刷新
1748         if (mFirst || windowShouldResize || insetsChanged ||
1749                 viewVisibilityChanged || params != null || mForceNextWindowRelayout) {
1750             mForceNextWindowRelayout = false;
1751 
1752             //...
1753             try {
1754                 if (DEBUG_LAYOUT) {
1755                     Log.i(mTag, "host=w:" + host.getMeasuredWidth() + ", h:" +
1756                             host.getMeasuredHeight() + ", params=" + params);
1757                 }
1758                 //TODO: step2, 向wms申请surface, 并计算window位置,大小
1759                 relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
1760 
1761                 if (DEBUG_LAYOUT) Log.v(mTag, "relayout: frame=" + frame.toShortString()
1762                         + " overscan=" + mPendingOverscanInsets.toShortString()
1763                         + " content=" + mPendingContentInsets.toShortString()
1764                         + " visible=" + mPendingVisibleInsets.toShortString()
1765                         + " visible=" + mPendingStableInsets.toShortString()
1766                         + " outsets=" + mPendingOutsets.toShortString()
1767                         + " surface=" + mSurface);
1768 
1769                 final Configuration pendingMergedConfig =
1770                         mPendingMergedConfiguration.getMergedConfiguration();
1771                 if (pendingMergedConfig.seq != 0) {
1772                     if (DEBUG_CONFIGURATION) Log.v(mTag, "Visible with new config: "
1773                             + pendingMergedConfig);
1774                     performConfigurationChange(mPendingMergedConfiguration, !mFirst,
1775                             INVALID_DISPLAY /* same display */);
1776                     pendingMergedConfig.seq = 0;
1777                     updatedConfiguration = true;
1778                 }
1779 
1780                 // 处理衬边区域的改变
1781                 final boolean overscanInsetsChanged = !mPendingOverscanInsets.equals(
1782                         mAttachInfo.mOverscanInsets);
1783                 contentInsetsChanged = !mPendingContentInsets.equals(
1784                         mAttachInfo.mContentInsets);
1785                 final boolean visibleInsetsChanged = !mPendingVisibleInsets.equals(
1786                         mAttachInfo.mVisibleInsets);
1787                 final boolean stableInsetsChanged = !mPendingStableInsets.equals(
1788                         mAttachInfo.mStableInsets);
1789                 final boolean outsetsChanged = !mPendingOutsets.equals(mAttachInfo.mOutsets);
1790                 final boolean surfaceSizeChanged = (relayoutResult
1791                         & WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED) != 0;
1792                 final boolean alwaysConsumeNavBarChanged =
1793                         mPendingAlwaysConsumeNavBar != mAttachInfo.mAlwaysConsumeNavBar;
1794                 //...
1795 
1796                 if (!hadSurface) {  //处理surface,申请buffer
1797                     if (mSurface.isValid()) {
1798                         }
1799                     }
1800                 } else if (!mSurface.isValid()) {
1801                     // If the surface has been removed, then reset the scroll
1802                 }
1803 
1804             mAttachInfo.mWindowLeft = frame.left;
1805             mAttachInfo.mWindowTop = frame.top;
1806 
1807             if (mSurfaceHolder != null) {  //已经拥有了Surface,准备开始绘制
1808                 // The app owns the surface; tell it about what is going on.
1809                 if (mSurface.isValid()) {
1810                     // XXX .copyFrom() doesn't work!
1811                     //mSurfaceHolder.mSurface.copyFrom(mSurface);
1812                     mSurfaceHolder.mSurface = mSurface;
1813                 }
1814             }
1815 
1816             if (!mStopped || mReportNextDraw) {
1817                 boolean focusChangedDueToTouchMode = ensureTouchModeLocally(
1818                         (relayoutResult&WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE) != 0);
1819                 if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
1820                         || mHeight != host.getMeasuredHeight() || contentInsetsChanged ||
1821                         updatedConfiguration) { // focus 改变、大小发生变化、边衬区发生变化、更新配置
1822                     int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
1823                     int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
1824 
1825                     if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed!  mWidth="
1826                             + mWidth + " measuredWidth=" + host.getMeasuredWidth()
1827                             + " mHeight=" + mHeight
1828                             + " measuredHeight=" + host.getMeasuredHeight()
1829                             + " coveredInsetsChanged=" + contentInsetsChanged);
1830 
1831                      // Ask host how big it wants to be
1832                     performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
1833 
1834             }
1835         } else {
1836         }
1837 
1838         final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
1839         boolean triggerGlobalLayoutListener = didLayout
1840                 || mAttachInfo.mRecomputeGlobalAttributes;
1841         if (didLayout) {  //step3. 开始处理view布局
1842             performLayout(lp, mWidth, mHeight);
1843 
1844             // By this point all views have been sized and positioned
1845             // We can compute the transparent area
1846 
1847             if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
1848                 // start out transparent
1849                 // TODO: AVOID THAT CALL BY CACHING THE RESULT?
1850                 host.getLocationInWindow(mTmpLocation);
1851                 mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1],
1852                         mTmpLocation[0] + host.mRight - host.mLeft,
1853                         mTmpLocation[1] + host.mBottom - host.mTop);
1854 
1855                 host.gatherTransparentRegion(mTransparentRegion);
1856                 if (mTranslator != null) {
1857                     mTranslator.translateRegionInWindowToScreen(mTransparentRegion);
1858                 }
1859 
1860                 if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
1861                     mPreviousTransparentRegion.set(mTransparentRegion);
1862                     mFullRedrawNeeded = true;
1863                     // reconfigure window manager
1864                     try {
1865                         mWindowSession.setTransparentRegion(mWindow, mTransparentRegion);
1866                     } catch (RemoteException e) {
1867                     }
1868                 }
1869             }
1870 
1871         }
1872 
1873         if (triggerGlobalLayoutListener) {
1874             mAttachInfo.mRecomputeGlobalAttributes = false;
1875             mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
1876         }
1877 
1878         if (computesInternalInsets) {
1879             // Clear the original insets.
1880             final ViewTreeObserver.InternalInsetsInfo insets = mAttachInfo.mGivenInternalInsets;
1881             insets.reset();
1882         }
1883 
1884         if (mFirst && sAlwaysAssignFocus) {  // 第一次获取焦点
1885             // handle first focus request
1886             if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: mView.hasFocus()="
1887                     + mView.hasFocus());
1888             if (mView != null) {
1889                 if (!mView.hasFocus()) {
1890                     mView.restoreDefaultFocus();
1891                     if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: requested focused view="
1892                             + mView.findFocus());
1893                 } else {
1894                     if (DEBUG_INPUT_RESIZE) Log.v(mTag, "First: existing focused view="
1895                             + mView.findFocus());
1896                 }
1897             }
1898         }
1899 
1900         final boolean changedVisibility = (viewVisibilityChanged || mFirst) && isViewVisible;
1901         final boolean hasWindowFocus = mAttachInfo.mHasWindowFocus && isViewVisible;
1902         final boolean regainedFocus = hasWindowFocus && mLostWindowFocus;
1903         if (regainedFocus) {
1904             mLostWindowFocus = false;
1905         } else if (!hasWindowFocus && mHadWindowFocus) {
1906             mLostWindowFocus = true;
1907         }
1908 
1909         if (changedVisibility || regainedFocus) {
1910             // Toasts are presented as notifications - don't present them as windows as well
1911             boolean isToast = (mWindowAttributes == null) ? false
1912                     : (mWindowAttributes.type == WindowManager.LayoutParams.TYPE_TOAST);
1913             if (!isToast) {
1914                 host.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED);
1915             }
1916         }
1917 
1918         mFirst = false;
1919         mWillDrawSoon = false;
1920         mNewSurfaceNeeded = false;
1921         mActivityRelaunched = false;
1922         mViewVisibility = viewVisibility;
1923         mHadWindowFocus = hasWindowFocus;
1924 
1925         if (hasWindowFocus && !isInLocalFocusMode()) {
1926             final boolean imTarget = WindowManager.LayoutParams
1927                     .mayUseInputMethod(mWindowAttributes.flags);
1928             if (imTarget != mLastWasImTarget) {
1929                 mLastWasImTarget = imTarget;
1930                 InputMethodManager imm = InputMethodManager.peekInstance();
1931                 if (imm != null && imTarget) {
1932                     imm.onPreWindowFocus(mView, hasWindowFocus);
1933                     imm.onPostWindowFocus(mView, mView.findFocus(),
1934                             mWindowAttributes.softInputMode,
1935                             !mHasHadWindowFocus, mWindowAttributes.flags);
1936                 }
1937             }
1938         }
1939 
1940         // Remember if we must report the next draw.
1941         if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
1942             reportNextDraw();
1943         }
1944 
1945         boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || !isViewVisible;
1946 
1947         if (!cancelDraw && !newSurface) {
1948             if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
1949                 for (int i = 0; i < mPendingTransitions.size(); ++i) {
1950                     mPendingTransitions.get(i).startChangingAnimations();
1951                 }
1952                 mPendingTransitions.clear();
1953             }
1954 
1955             //step4: 开始绘制view
1956             performDraw();
1957         } else {
1958             if (isViewVisible) {
1959                 // Try again
1960                 scheduleTraversals();
1961             } else if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
1962                 for (int i = 0; i < mPendingTransitions.size(); ++i) {
1963                     mPendingTransitions.get(i).endChangingAnimations();
1964                 }
1965                 mPendingTransitions.clear();
1966             }
1967         }
1968 
1969         mIsInTraversal = false;
1970     }

第1689行开始测量View,第83行是测量完Window以后发现大小变化以后重新测量View。
第1759行测量Window。
第1842行开始对View布局。
第1956行开始绘制View。
这里先了解下View的测量(measure)、布局(layout)和绘制(draw)的整体流程,下面还是重点分析下relayoutWindow流程。

二、Android窗口位置,大小计算基础知识整理

参考老罗的博客,Activity窗口的大小等于整个屏幕的大小,但是它并不占据着整块屏幕。为了理解这一点,我们首先分析一下Activity窗口的区域是如何划分的。

content region和content insets区域

Activity窗口的上方一般会有一个状态栏,用来显示4G信号、电量使用等图标,如下图:


Activity窗口Content区域示意图

从Activity窗口剔除掉状态栏所占用的区域之后,所得到的区域就称为内容区域(Content Region)。顾名思义,内容区域就是用来显示Activity窗口的内容的。我们再抽象一下,假设Activity窗口的四周都有一块类似状态栏的区域,那么将这些区域剔除之后,得到中间的那一块区域就称为内容区域,而被剔除出来的区域所组成的区域就称为内容边衬区域(Content Insets)。Activity窗口的内容边衬区域可以用一个四元组(content-left, content-top, content-right, content-bottom)来描述,其中,content-left、content-right、content-top、content-bottom分别用来描述内容区域与窗口区域的左右上下边界距离。

visible region和visible insets区域

Activity窗口有时候需要显示输入法窗口, 如下图:


activity窗口Visible区域示意图

这时候Activity窗口的内容区域的大小有可能没有发生变化,这取决于它的Soft Input Mode。我们假设Activity窗口的内容区域没有发生变化,但是它在底部的一些区域被输入法窗口遮挡了,即它在底部的一些内容是不可见的。从Activity窗口剔除掉状态栏和输入法窗口所占用的区域之后,所得到的区域就称为可见区域(Visible Region)。同样,我们再抽象一下,假设Activity窗口的四周都有一块类似状态栏和输入法窗口的区域,那么将这些区域剔除之后,得到中间的那一块区域就称为可见区域,而被剔除出来的区域所组成的区域就称为可见边衬区域(Visible Insets)。Activity窗口的可见边衬区域可以用一个四元组(visible-left, visible-top, visible-right, visible-bottom)来描述,其中,visible-left、visible-right、visible-top、visible-bottom分别用来描述可见区域与窗口区域的左右上下边界距离。
在大多数情况下,Activity窗口的内容区域和可见区域的大小是一致的,而状态栏和输入法窗口所占用的区域又称为屏幕装饰区。理解了这些概念之后,我们就可以推断,WindowManagerService服务实际上就是需要根据屏幕以及可能出现的状态栏和输入法窗口的大小来计算出Activity窗口的整体大小及其内容区域边衬和可见区域边衬的大小。有了这三个数据之后,Activity窗口就可以对它里面的UI元素进行测量、布局以及绘制等操作了。

ViewRootImpl中与窗口大小相关的变量

ViewRootImpl中很多窗口相关的变量都是通过wms计算后返回,ViewRootImpl进一步给View布局使用。

Member Description
int mWidth;
int mHeight;
当前真实展示宽高
Rect mWinFrame; WSM提供,当Activity窗口大小改变时,WMS通过W.resized接口通知客户端mWinFrame就用于记录WMS提供的宽高
Rect mPendingVisibleInsets 假设一个Window 的四周都有被一块类似输入框的区域遮住(注意是遮住,那块区域还是存在),得到中间的那一块区域就称为可见区域,而被剔除出来的区域所组成的区域就称为可见边衬区域(Visible Insets)
Rect mPendingContentInsets 假设一个Window 的四周都有一块类似状态栏的区域,那么将这些区域剔除之后,得到中间的那一块区域就称为内容区域,而被剔除出来的区域所组成的区域就称为内容边衬区域(Content Insets)(来自老罗博客,最下面有地址)
Rect mPendingStableInsets 代表的是剔除System Bar 所占据的位置以后的区域(不管Status Bar 和Navigation Bar 是否可见都会计算在内)
WindowState中成员变量的含义

WindowState是WMS 用来标识一个Window的,wms中保存最终计算好的位置,大小信息。

Member Description
mRequestedWidth
mRequestedHeight
mLastRequestedWidth
mLastRequestedHeight
The window size that was requested by the application
应用请求Window 的大小
mVisibleInsets
mLastVisibleInsets
mVisibleInsetsChanged
Insets that determine the actually visible area
Window 的可见边衬区
mContentInsets
mLastContentInsets
mContentInsetsChanged
Insets that are covered by system windows (such as the status bar) and transient docking windows (such as the IME)
Window 的内容边衬区
mOverscanInsets
mLastOverscanInsets
mOverscanInsetsChanged
Insets that determine the area covered by the display overscan region.
Window 的过扫描边衬区
mStableInsets
mLastStableInsets
mStableInsetsChanged
Insets that determine the area covered by the stable system windows
Window 被系统Window 遮住的固定边衬区
mOutsets
mLastOutsets
mOutsetsChanged
Outsets determine the area outside of the surface where we want to pretend that it's possible to draw anyway
不在Window 的Surface 区域但可以绘制的区域
mFrame
mLastFrame
mFrameSizeChanged
"Real" frame that the application sees, in display coordinate space
最后展示的区域
PhoneWindowManager中成员变量的含义
Member Description
mOverscanScreenLeft
mOverscanScreenTop
mOverscanScreenWidth
mOverscanScreenHeight
The current size of the screen; really; extends into the overscan area of the screen and doesn't account for any system elements like the status bar
屏幕的真实坐标,包含了过扫描的区域。代表的是左上角坐标和屏幕宽高
mUnrestrictedScreenLeft
mUnrestrictedScreenTop
mUnrestrictedScreenWidth
mUnrestrictedScreenHeight
The current visible size of the screen; really; (ir)regardless of whether the status bar can be hidden but not extending into the overscan area
不包含过扫描区域,包含了状态栏的区域的大小
mRestrictedOverscanScreenLeft
mRestrictedOverscanScreenTop
mRestrictedOverscanScreenWidth
mRestrictedOverscanScreenHeight
Like mOverscanScreen, but allowed to move into the overscan region where appropriate
与mOverscanScreen
类似,但是可以移动到过扫描区域
mRestrictedScreenLeft
mRestrictedScreenTop
mRestrictedScreenWidth
mRestrictedScreenHeight
The current size of the screen; these may be different than (0,0)-(dw,dh) if the status bar can't be hidden; in that case it effectively carves out that area of the display from all other windows
屏幕当前的区域,但是不包含状态栏
mSystemLeft
mSystemTop
mSystemRight
mSystemBottom
During layout, the current screen borders accounting for any currently visible system UI elements.
所有可见的UI元素区域(包含了系统状态栏区域)
mStableLeft
mStableTop
mStableRight
mStableBottom
For applications requesting stable content insets, these are them.
不包含状态栏的区域(不管状态栏是否可见)
mStableFullscreenLeft
mStableFullscreenTop
mStableFullscreenRight
mStableFullscreenBottom
For applications requesting stable content insets but have also set the fullscreen window flag, these are the stable dimensions without the status bar.
与mStable*类似。不包含状态栏的区域
mCurLeft
mCurTop
mCurRight
mCurBottom
During layout, the current screen borders with all outer decoration (status bar, input method dock) accounted for.
包含状态栏、输入法的内容区域
mContentLeft
mContentTop
mContentRight
mContentBottom
During layout, the frame in which content should be displayed to the user, accounting for all screen decoration except for any space they deem as available for other content. This is usually the same as mCur*, but may be larger if the screen decor has supplied content insets
内容区域
mVoiceContentLeft
mVoiceContentTop
mVoiceContentRight
mVoiceContentBottom
During layout, the frame in which voice content should be displayed to the user, accounting for all screen decoration except for any space they deem as available for other content.
语音区域
mDockLeft
mDockTop
mDockRight
mDockBottom
During layout, the current screen borders along which input method windows are placed
输入法区域

PhoneWindowManager中还有一系列关于WindowState计算的值。下面的9个值都是Rect,代表的都是左上角和右下角的坐标。

Field 含义 备注
mTmpParentFrame 父窗口区域 一般就是mTmpDisplayFrame
mTmpDisplayFrame Window完整展示区域 StatusBar一般是全屏,因为StatusBar是下拉展示全屏
mTmpOverscanFrame 左上角(overscanLeft,overscanTop)
右下角(displayWidth-overscanRight,displayHeight-overscanBottom)
去掉overscan 区域以后的区域
mTmpContentFrame Window内容区域 --
mTmpVisibleFrame Window可见区域 --
mTmpDecorFrame
mTmpStableFrame 左上角(0,statusBarHeight)
右下角(displayWidth,displayHeight-navigationBarHeight)
没有计算overscan
mTmpNavigationFrame NavigationBar对应的坐标
mTmpOutsetFrame Surface区域外

WindowManagerService计算窗口位置,大小流程

WMS测量的流程图。

wms relayoutWindow流程

源码分析

WMS.relayoutWindow
//WindowManagerService.java
1907     public int relayoutWindow(Session session, IWindow client, int seq,
1908             WindowManager.LayoutParams attrs, int requestedWidth,
1909             int requestedHeight, int viewVisibility, int flags,
1910             Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
1911             Rect outVisibleInsets, Rect outStableInsets, Rect outOutsets, Rect outBackdropFrame,
1912             MergedConfiguration mergedConfiguration, Surface outSurface) {
1913         int result = 0;
1914         boolean configChanged; // 配置是否发生了变化
1915         boolean hasStatusBarPermission =
1916                 mContext.checkCallingOrSelfPermission(android.Manifest.permission.STATUS_BAR)
1917                         == PackageManager.PERMISSION_GRANTED;
1918 
1919         long origId = Binder.clearCallingIdentity();
1920         final int displayId;
1921         synchronized(mWindowMap) { //访问到共享资源,注意同步
1922             //TODO: step1, 根据IWindow找到对应的WindowState,
1923             WindowState win = windowForClientLocked(session, client, false);
1924             if (win == null) {
1925                 return 0;
1926             }
1927             displayId = win.getDisplayId();
1928 
1929             WindowStateAnimator winAnimator = win.mWinAnimator;
1930             if (viewVisibility != View.GONE) {
1931                 win.setRequestedSize(requestedWidth, requestedHeight);
1932             }
             //..... 省略很多
2014             final int oldVisibility = win.mViewVisibility;
2015             win.mViewVisibility = viewVisibility;
2016             if (DEBUG_SCREEN_ON) {
2017                 RuntimeException stack = new RuntimeException();
2018                 stack.fillInStackTrace();
2019                 Slog.i(TAG_WM, "Relayout " + win + ": oldVis=" + oldVisibility
2020                         + " newVis=" + viewVisibility, stack);
2021             }
2022             if (viewVisibility == View.VISIBLE &&
2023                     (win.mAppToken == null || win.mAttrs.type == TYPE_APPLICATION_STARTING
2024                             || !win.mAppToken.isClientHidden())) {
2025 
2026                 Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: viewVisibility_1");
2027 
2028                 // We are about to create a surface, but we didn't run a layout yet. So better run
2029                 // a layout now that we already know the right size, as a resize call will make the
2030                 // surface transaction blocking until next vsync and slow us down.
2031                 // TODO: Ideally we'd create the surface after running layout a bit further down,
2032                 // but moving this seems to be too risky at this point in the release.
2033                 //
2034                 // 我们即将创建一个Surface,但我们还没有运行布局,所以最好在我们已经知道正确大小
2035                 // 的情况下运行布局,因为调整大小调用会使表面失误受阻,直到下一次vsync并减慢我们
2036                 // 的速度。
2037                 // 理想情况下,我们会在将布局进一步向下运行后创建Surface,但是在版本发布点上
2038                 // 移动它似乎风险太大。
2039                 if (win.mLayoutSeq == -1) {
2040                     win.setDisplayLayoutNeeded();
2041                     //TODO: step2: 销毁surface, 重新计算图层,计算触电区域
2042                     mWindowPlacerLocked.performSurfacePlacement(true);
2043                 }
2044                 result = win.relayoutVisibleWindow(mergedConfiguration, result, attrChanges,
2045                         oldVisibility);
2046 
2047                 try {
2048                     //TODO: step3: 这里创建Surface,  使用SurfaceControl和 SurfaceFlinger通信 
2049                     result = createSurfaceControl(outSurface, result, win, winAnimator);
2050                 } catch (Exception e) {
2051                     mInputMonitor.updateInputWindowsLw(true /*force*/);
2052 
2053                     Slog.w(TAG_WM, "Exception thrown when creating surface for client "
2054                              + client + " (" + win.mAttrs.getTitle() + ")",
2055                              e);
2056                     Binder.restoreCallingIdentity(origId);
2057                     return 0;
2058                 }
2059                 if ((result & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
2060                     focusMayChange = isDefaultDisplay;
2061                 }
2062                 if (win.mAttrs.type == TYPE_INPUT_METHOD && mInputMethodWindow == null) {
2063                     setInputMethodWindowLocked(win);
2064                     imMayMove = true;
2065                 }
2066                 win.adjustStartingWindowFlags();
      //.... 省略很多
2101                 if (viewVisibility == View.VISIBLE && winAnimator.hasSurface()) {
2102                     // We already told the client to go invisible, but the message may not be
2103                     // handled yet, or it might want to draw a last frame. If we already have a
2104                     // surface, let the client use that, but don't create new surface at this point.
2105                     Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: getSurface");
2106                     winAnimator.mSurfaceController.getSurface(outSurface);
2107                     Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
2108                 } else {
2109                     if (DEBUG_VISIBILITY) Slog.i(TAG_WM, "Releasing surface in: " + win);
2110 
2111                     try {
2112                         Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmReleaseOutSurface_"
2113                                 + win.mAttrs.getTitle());
2114                         outSurface.release();
2115                     } finally {
2116                         Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
2117                     }
2118                 }
2119 
2120                 Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
2121             }
2122 
2123             if (focusMayChange) {
2124                 //System.out.println("Focus may change: " + win.mAttrs.getTitle());
2125                 //TODO: step4:  window焦点发生变化, 重新计算窗口大小
2126                 if (updateFocusedWindowLocked(UPDATE_FOCUS_WILL_PLACE_SURFACES,
2127                         false /*updateInputWindows*/)) {
2128                     imMayMove = false;
2129                 }
2130                 //System.out.println("Relayout " + win + ": focus=" + mCurrentFocus);
2131             }
2132 
2133             // updateFocusedWindowLocked() already assigned layers so we only need to
2134             // reassign them at this point if the IM window state gets shuffled
2135             boolean toBeDisplayed = (result & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0;
2136             final DisplayContent dc = win.getDisplayContent();
2137             if (imMayMove) {
2138                 dc.computeImeTarget(true /* updateImeTarget */);
2139                 if (toBeDisplayed) {
2140                     // Little hack here -- we -should- be able to rely on the function to return
2141                     // true if the IME has moved and needs its layer recomputed. However, if the IME
2142                     // was hidden and isn't actually moved in the list, its layer may be out of data
2143                     // so we make sure to recompute it.
2144                     // TODO: step5 计算z-order层级
2145                     dc.assignWindowLayers(false /* setLayoutNeeded */);
2146                 }
2147             }
          //.... 省略很多代码
2165             // We may be deferring layout passes at the moment, but since the client is interested
2166             // in the new out values right now we need to force a layout.
2167             mWindowPlacerLocked.performSurfacePlacement(true /* force */);
2168             if (toBeDisplayed && win.mIsWallpaper) {
2169                 DisplayInfo displayInfo = win.getDisplayContent().getDisplayInfo();
2170                 dc.mWallpaperController.updateWallpaperOffset(
2171                         win, displayInfo.logicalWidth, displayInfo.logicalHeight, false);
2172             }
2173             if (win.mAppToken != null) {
2174                 win.mAppToken.updateReportedVisibilityLocked();
2175             }
2176             if (winAnimator.mReportSurfaceResized) {
2177                 winAnimator.mReportSurfaceResized = false;
2178                 result |= WindowManagerGlobal.RELAYOUT_RES_SURFACE_RESIZED;
2179             }
2180             if (mPolicy.isNavBarForcedShownLw(win)) {
2181                 result |= WindowManagerGlobal.RELAYOUT_RES_CONSUME_ALWAYS_NAV_BAR;
2182             }
2183             if (!win.isGoneForLayoutLw()) {
2184                 win.mResizedWhileGone = false;
2185             }
2186             //TODO: step6, 设置已经计算好的边界,并返回给view.
2187             outFrame.set(win.mCompatFrame);
2188             outOverscanInsets.set(win.mOverscanInsets);
2189             outContentInsets.set(win.mContentInsets);
2190             win.mLastRelayoutContentInsets.set(win.mContentInsets);
2191             outVisibleInsets.set(win.mVisibleInsets);
2192             outStableInsets.set(win.mStableInsets);
2193             outOutsets.set(win.mOutsets);
2194             outBackdropFrame.set(win.getBackdropFrame(win.mFrame));
2195             if (localLOGV) Slog.v(
2196                 TAG_WM, "Relayout given client " + client.asBinder()
2197                 + ", requestedWidth=" + requestedWidth
2198                 + ", requestedHeight=" + requestedHeight
2199                 + ", viewVisibility=" + viewVisibility
2200                 + "\nRelayout returning frame=" + outFrame
2201                 + ", surface=" + outSurface);
2202 
2203             if (localLOGV || DEBUG_FOCUS) Slog.v(
2204                 TAG_WM, "Relayout of " + win + ": focusMayChange=" + focusMayChange);
2205 
2206             result |= mInTouchMode ? WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE : 0;
2207 
2208             mInputMonitor.updateInputWindowsLw(true /*force*/);
2209 
2210             if (DEBUG_LAYOUT) {
2211                 Slog.v(TAG_WM, "Relayout complete " + win + ": outFrame=" + outFrame.toShortString());
2212             }
2213             win.mInRelayout = false;
2214         }
2215 
2216         if (configChanged) {
2217             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "relayoutWindow: sendNewConfiguration");
2218             sendNewConfiguration(displayId);
2219             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
2220         }
2221         Binder.restoreCallingIdentity(origId);
2222         return result;
2223     }

relayout大致上要做了以下的事情:
step1#1923行, 通过IWindow找到对应的WindowState,并且获取各种参数。
step2#2042行, 销毁surface, 重新计算图层,输入焦点区域
step3#2049行,创建Surface对象,通关SurfaceControl和SurfaceFlinger通信
step4#2126行,更新焦点窗口区域,重新计算所有窗口的位置,大小,焦点区域。
step5#2145行,计算z-order层级
step6#2186行, 保持计算好的window位置,大小,和创建好的surface返回给viewRootImpl。

relayout的方法有点长,本次我们将关注这一部分核心的逻辑。分别是两个方法:

  • 1.mWindowPlacerLocked.performSurfacePlacement 当窗体出现了变更,需要重新设置DisplayContent的各种参数,销毁不用的Surface,重新计算层级,计算触点区域等等
  • 2.updateFocusedWindowLocked 当发现Window可能发生变化,则重新计算窗口大小。
WMS.updateFocusedWindowLocked
//WindowManagerService.java
5886     // TODO: Move to DisplayContent
5887     boolean updateFocusedWindowLocked(int mode, boolean updateInputWindows) {
5888         WindowState newFocus = mRoot.computeFocusedWindow();
5889         if (mCurrentFocus != newFocus) {
5890             Trace.traceBegin(TRACE_TAG_WINDOW_MANAGER, "wmUpdateFocus");
5891             // This check makes sure that we don't already have the focus
5892             // change message pending.
5893             mH.removeMessages(H.REPORT_FOCUS_CHANGE);
5894             mH.sendEmptyMessage(H.REPORT_FOCUS_CHANGE);
5895             // TODO(multidisplay): Focused windows on default display only.
5896             final DisplayContent displayContent = getDefaultDisplayContentLocked();
5897             boolean imWindowChanged = false;
5898             //step1, 输入窗口不等于空,或者输入法窗口焦点发生变化,重新找的焦点窗口
5899             if (mInputMethodWindow != null) {
5900                 final WindowState prevTarget = mInputMethodTarget;
5901                 final WindowState newTarget =
5902                         displayContent.computeImeTarget(true /* updateImeTarget*/);
5903 
5904                 imWindowChanged = prevTarget != newTarget;
5905 
5906                 if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS
5907                         && mode != UPDATE_FOCUS_WILL_PLACE_SURFACES) {
5908                     final int prevImeAnimLayer = mInputMethodWindow.mWinAnimator.mAnimLayer;
5909                     displayContent.assignWindowLayers(false /* setLayoutNeeded */);
5910                     imWindowChanged |=
5911                             prevImeAnimLayer != mInputMethodWindow.mWinAnimator.mAnimLayer;
5912                 }
5913             }
5914 
5915             if (imWindowChanged) {
5916                 mWindowsChanged = true;
5917                 displayContent.setLayoutNeeded();
5918                 newFocus = mRoot.computeFocusedWindow();
5919             }
5920 
5921             if (DEBUG_FOCUS_LIGHT || localLOGV) Slog.v(TAG_WM, "Changing focus from " +
5922                     mCurrentFocus + " to " + newFocus + " Callers=" + Debug.getCallers(4));
5923             final WindowState oldFocus = mCurrentFocus;
5924             mCurrentFocus = newFocus;
5925             mLosingFocus.remove(newFocus);
5926 
5927             if (mCurrentFocus != null) {
5928                 mWinAddedSinceNullFocus.clear();
5929                 mWinRemovedSinceNullFocus.clear();
5930             }
5931 
5932             int focusChanged = mPolicy.focusChangedLw(oldFocus, newFocus);
5933 
5934             //step2, 如果输入法焦点出现了变化,且当前模式是UPDATE_FOCUS_PLACING_SURFACES
5935             //  (需要强制绘制),则重新计算窗体的大小,否则直接做一次层级变化
5936             if (imWindowChanged && oldFocus != mInputMethodWindow) {
5937                 // Focus of the input method window changed. Perform layout if needed.
5938                 if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
5939                     displayContent.performLayout(true /*initial*/,  updateInputWindows);
5940                     focusChanged &= ~FINISH_LAYOUT_REDO_LAYOUT;
5941                 } else if (mode == UPDATE_FOCUS_WILL_PLACE_SURFACES) {
5942                     // Client will do the layout, but we need to assign layers
5943                     // for handleNewWindowLocked() below.
5944                     displayContent.assignWindowLayers(false /* setLayoutNeeded */);
5945                 }
5946             }
5947 
5948             //step3, 一般情况下,如果UPDATE_FOCUS_PLACING_SURFACES这个模式,则需要performLayout
5949             //  重新测量窗体的各个边距大小
5950             if ((focusChanged & FINISH_LAYOUT_REDO_LAYOUT) != 0) {
5951                 // The change in focus caused us to need to do a layout.  Okay.
5952                 displayContent.setLayoutNeeded();
5953                 if (mode == UPDATE_FOCUS_PLACING_SURFACES) {
5954                     displayContent.performLayout(true /*initial*/, updateInputWindows);
5955                 }
5956             }
5957 
5958             //step4, 如果UPDATE_FOCUS_WILL_ASSIGN_LAYERS模式,则需要处理输入焦点
5959             if (mode != UPDATE_FOCUS_WILL_ASSIGN_LAYERS) {
5960                 // If we defer assigning layers, then the caller is responsible for
5961                 // doing this part.
5962                 mInputMonitor.setInputFocusLw(mCurrentFocus, updateInputWindows);
5963             }
5964 
5965             displayContent.adjustForImeIfNeeded();
5966 
5967             // We may need to schedule some toast windows to be removed. The toasts for an app that
5968             // does not have input focus are removed within a timeout to prevent apps to redress
5969             // other apps' UI.
5970             displayContent.scheduleToastWindowsTimeoutIfNeededLocked(oldFocus, newFocus);
5971 
5972             Trace.traceEnd(TRACE_TAG_WINDOW_MANAGER);
5973             return true;
5974         }
5975         return false;
5976     }

step1#5899,首先如果发现有输入法弹窗则重新计算层级,或者说如果输入法焦点窗口发生变化,也要从RootWindowContainer找到当前的焦点窗口。
step2#5936, 如果输入法焦点出现了变化,且当前的模式是UPDATE_FOCUS_PLACING_SURFACES(需要强制重绘)则要重新计算窗体大小,否则则直接做一次层级变化即可。
step3#5952, 一般的情况下,如果UPDATE_FOCUS_PLACING_SURFACES这个模式,则需要performLayout重新测量窗体各个边距大小
step4#5962, 不是UPDATE_FOCUS_WILL_ASSIGN_LAYERS模式,则则需要处理触点焦点边距。

DisplayContent.performLayout
//DisplayContent.java
2779     void performLayout(boolean initial, boolean updateInputWindows) {
2780         if (!isLayoutNeeded()) {
2781             return;
2782         }
2783         clearLayoutNeeded();
2784             
2785         final int dw = mDisplayInfo.logicalWidth;
2786         final int dh = mDisplayInfo.logicalHeight;
2787          
2788         if (DEBUG_LAYOUT) {
2789             Slog.v(TAG, "-------------------------------------");
2790             Slog.v(TAG, "performLayout: needed=" + isLayoutNeeded() + " dw=" + dw + " dh=" + dh);
2791         }
2792             
2793         //step1, 开始布局,准备初始化变量
2794         mService.mPolicy.beginLayoutLw(isDefaultDisplay, dw, dh, mRotation,
2795                 getConfiguration().uiMode);
2796         if (isDefaultDisplay) {
2797             // Not needed on non-default displays.
2798             mService.mSystemDecorLayer = mService.mPolicy.getSystemDecorLayerLw();
2799             mService.mScreenRect.set(0, 0, dw, dh);
2800         }
2801         
2802         mService.mPolicy.getContentRectLw(mContentRect);
2803         
2804         int seq = mService.mLayoutSeq + 1;
2805         if (seq < 0) seq = 0;
2806         mService.mLayoutSeq = seq;
2807          
2808         // Used to indicate that we have processed the dream window and all additional windows are
2809         // behind it.
2810         mTmpWindow = null;
2811         mTmpInitial = initial;
2812         
2813         //step2, 首先测量那些没有绑定父窗口的窗口
2814         // First perform layout of any root windows (not attached to another window).
2815         forAllWindows(mPerformLayout, true /* traverseTopToBottom */);
2816          
2817         // Used to indicate that we have processed the dream window and all additional attached
2818         // windows are behind it.
2819         mTmpWindow2 = mTmpWindow;
2820         mTmpWindow = null;
2821         
2822         //step3. 测量子窗口
2823         // Now perform layout of attached windows, which usually depend on the position of the
2824         // window they are attached to. XXX does not deal with windows that are attached to windows
2825         // that are themselves attached.
2826         forAllWindows(mPerformLayoutAttached, true /* traverseTopToBottom */);
2827         
2828         //step4, 更新输入法焦点窗口
2829         // Window frames may have changed. Tell the input dispatcher about it.
2830         mService.mInputMonitor.layoutInputConsumers(dw, dh);
2831         mService.mInputMonitor.setUpdateInputWindowsNeededLw();
2832         if (updateInputWindows) {
2833             mService.mInputMonitor.updateInputWindowsLw(false /*force*/);
2834         }
2835 
2836         //step5, 完成布局测量
2837         mService.mPolicy.finishLayoutLw();
2838         mService.mH.sendEmptyMessage(UPDATE_DOCKED_STACK_DIVIDER);
2839     }

step1#2794, 开始layout,准备初始化变量
step2#2815, 首先测量那些没有绑定父窗口的窗口
step3#2826, 测量子窗口
step4#2830, 更新输入法焦点窗口
step5#2837,完成测量

PhoneWindowManager.beginLayoutLw开始测量大小与边距
//PhoneWindowManager.java
4485     public void beginLayoutLw(boolean isDefaultDisplay, int displayWidth, int displayHeight,
4486                               int displayRotation, int uiMode) {
4487         mDisplayRotation = displayRotation;
4488         final int overscanLeft, overscanTop, overscanRight, overscanBottom;
4489 
4490         if (isDefaultDisplay) {
4491             switch (displayRotation) {
4492                 case Surface.ROTATION_90:
4493                     overscanLeft = mOverscanTop;
4494                     overscanTop = mOverscanRight;
4495                     overscanRight = mOverscanBottom;
4496                     overscanBottom = mOverscanLeft;
4497                     break;
4498                 case Surface.ROTATION_180:
4499                     overscanLeft = mOverscanRight;
4500                     overscanTop = mOverscanBottom;
4501                     overscanRight = mOverscanLeft;
4502                     overscanBottom = mOverscanTop;
4503                     break;
//  省略一些代码
4523         mOverscanScreenLeft = mRestrictedOverscanScreenLeft = 0;
4524         mOverscanScreenTop = mRestrictedOverscanScreenTop = 0;
4525         
4526         mOverscanScreenWidth = mRestrictedOverscanScreenWidth = displayWidth;
4527         mOverscanScreenHeight = mRestrictedOverscanScreenHeight = displayHeight;
4528         mSystemLeft = 0;
4529         mSystemTop = 0;
4530         mSystemRight = displayWidth;
4531         mSystemBottom = displayHeight;
4532         mUnrestrictedScreenLeft = overscanLeft;
4533         mUnrestrictedScreenTop = overscanTop;
4534         mUnrestrictedScreenWidth = displayWidth - overscanLeft - overscanRight;
4535         mUnrestrictedScreenHeight = displayHeight - overscanTop - overscanBottom;
4536         mRestrictedScreenLeft = mUnrestrictedScreenLeft;
4537         mRestrictedScreenTop = mUnrestrictedScreenTop;
4538         mRestrictedScreenWidth = mSystemGestures.screenWidth = mUnrestrictedScreenWidth;
4539         mRestrictedScreenHeight = mSystemGestures.screenHeight = mUnrestrictedScreenHeight;
4540         mDockLeft = mContentLeft = mVoiceContentLeft = mStableLeft = mStableFullscreenLeft
4541                 = mCurLeft = mUnrestrictedScreenLeft;
4542         mDockTop = mContentTop = mVoiceContentTop = mStableTop = mStableFullscreenTop
4543                 = mCurTop = mUnrestrictedScreenTop;
4544         mDockRight = mContentRight = mVoiceContentRight = mStableRight = mStableFullscreenRight
4545                 = mCurRight = displayWidth - overscanRight;
4546         mDockBottom = mContentBottom = mVoiceContentBottom = mStableBottom = mStableFullscreenBottom
4547                 = mCurBottom = displayHeight - overscanBottom;
4548         mDockLayer = 0x10000000;
4549         mStatusBarLayer = -1;
4550 
4551         // start with the current dock rect, which will be (0,0,displayWidth,displayHeight)
4552         final Rect pf = mTmpParentFrame;
4553         final Rect df = mTmpDisplayFrame;
4554         final Rect of = mTmpOverscanFrame;
4555         final Rect vf = mTmpVisibleFrame;
4556         final Rect dcf = mTmpDecorFrame;
4557         //step1, 初始化pf,df,of, vf, dcf 窗口大小
4558         pf.left = df.left = of.left = vf.left = mDockLeft;
4559         pf.top = df.top = of.top = vf.top = mDockTop;
4560         pf.right = df.right = of.right = vf.right = mDockRight;
4561         pf.bottom = df.bottom = of.bottom = vf.bottom = mDockBottom;
4562         dcf.setEmpty();  // Decor frame N/A for system bars.
//省略一些代码。。。
4583             // When the navigation bar isn't visible, we put up a fake
4584             // input window to catch all touch events.  This way we can
4585             // detect when the user presses anywhere to bring back the nav
4586             // bar and ensure the application doesn't see the event.
4587             if (navVisible || navAllowedHidden) {
4588                 if (mInputConsumer != null) {
4589                     mHandler.sendMessage(
4590                             mHandler.obtainMessage(MSG_DISPOSE_INPUT_CONSUMER, mInputConsumer));
4591                     mInputConsumer = null;
4592                 }
4593             } else if (mInputConsumer == null) {
4594                 mInputConsumer = mWindowManagerFuncs.createInputConsumer(mHandler.getLooper(),
4595                         INPUT_CONSUMER_NAVIGATION,
4596                         (channel, looper) -> new HideNavInputEventReceiver(channel, looper));
4597                 // As long as mInputConsumer is active, hover events are not dispatched to the app
4598                 // and the pointer icon is likely to become stale. Hide it to avoid confusion.
4599                 InputManager.getInstance().setPointerIconType(PointerIcon.TYPE_NULL);
4600             }
4601 
4602             // For purposes of positioning and showing the nav bar, if we have
4603             // decided that it can't be hidden (because of the screen aspect ratio),
4604             // then take that into account.
4605             navVisible |= !canHideNavigationBar();
4606 
4607             //step2, 计算导航栏layout
4608             boolean updateSysUiVisibility = layoutNavigationBar(displayWidth, displayHeight,
4609                     displayRotation, uiMode, overscanLeft, overscanRight, overscanBottom, dcf, navVisible, navTranslucent,
4610                     navAllowedHidden, statusBarExpandedNotKeyguard);
4611             if (DEBUG_LAYOUT) Slog.i(TAG, String.format("mDock rect: (%d,%d - %d,%d)",
4612                     mDockLeft, mDockTop, mDockRight, mDockBottom));
4613             //step3, 计算状态栏layout
4614             updateSysUiVisibility |= layoutStatusBar(pf, df, of, vf, dcf, sysui, isKeyguardShowing);
4615             if (updateSysUiVisibility) {
4616                 updateSystemUiVisibilityLw();
4617             }
4618         }
4619     }
PhoneWindowManager.layoutWindowLw计算pf,df,of,cf,vf,
//PhoneWindowManager.java
4917     public void layoutWindowLw(WindowState win, WindowState attached) {
4918         // We've already done the navigation bar and status bar. If the status bar can receive
4919         // input, we need to layout it again to accomodate for the IME window.
4920         if ((win == mStatusBar && !canReceiveInput(win)) || win == mNavigationBar) {
4921             return;
4922         }
4923         final WindowManager.LayoutParams attrs = win.getAttrs();
4924         final boolean isDefaultDisplay = win.isDefaultDisplay();
4925         final boolean needsToOffsetInputMethodTarget = isDefaultDisplay &&
4926                 (win == mLastInputMethodTargetWindow && mLastInputMethodWindow != null);
4927         if (needsToOffsetInputMethodTarget) {
4928             if (DEBUG_LAYOUT) Slog.i(TAG, "Offset ime target window by the last ime window state");
4929             offsetInputMethodWindowLw(mLastInputMethodWindow);
4930         }
4931 
// 省略代码... 
5408          Slog.v(TAG, "Compute frame " + attrs.getTitle()
5409                 + ": sim=#" + Integer.toHexString(sim)
5410                 + " attach=" + attached + " type=" + attrs.type
5411                 + String.format(" flags=0x%08x", fl)
5412                 + " pf=" + pf.toShortString() + " df=" + df.toShortString()
5413                 + " of=" + of.toShortString()
5414                 + " cf=" + cf.toShortString() + " vf=" + vf.toShortString()
5415                 + " dcf=" + dcf.toShortString()
5416                 + " sf=" + sf.toShortString()
5417                 + " osf=" + (osf == null ? "null" : osf.toShortString()));
5418 
5419         win.computeFrameLw(pf, df, of, cf, vf, dcf, sf, osf);
5420 
5421         // Dock windows carve out the bottom of the screen, so normal windows
5422         // can't appear underneath them.
5423         if (attrs.type == TYPE_INPUT_METHOD && win.isVisibleLw()
5424                 && !win.getGivenInsetsPendingLw()) {
5425             setLastInputMethodWindowLw(null, null);
5426             offsetInputMethodWindowLw(win);
5427         }
5428         if (attrs.type == TYPE_VOICE_INTERACTION && win.isVisibleLw()
5429                 && !win.getGivenInsetsPendingLw()) {
5430             offsetVoiceInputWindowLw(win);
5431         }
5432     }

各种计算出... 临时变量pf,df,of,cf,vf后调用computeFrameLw方案

WindowState.computeFrameLw
//WindowState.java
 735     public void computeFrameLw(Rect parentFrame, Rect displayFrame, Rect overscanFrame,
 736             Rect contentFrame, Rect visibleFrame, Rect decorFrame, Rect stableFrame,
 737             Rect outsetFrame) {
 738         if (mWillReplaceWindow && (mAnimatingExit || !mReplacingRemoveRequested)) {
 739             // This window is being replaced and either already got information that it's being
 740             // removed or we are still waiting for some information. Because of this we don't
 741             // want to apply any more changes to it, so it remains in this state until new window
 742             // appears.
 743             return;
 744         }
 745         mHaveFrame = true;
 //...
 800         // Denotes the actual frame used to calculate the insets and to perform the layout. When
 801         // resizing in docked mode, we'd like to freeze the layout, so we also need to freeze the
 802         // insets temporarily. By the notion of a task having a different layout frame, we can
 803         // achieve that while still moving the task around.
 804         // 表示用于计算插图和执行布局的实际窗口大小,在可调整大小的dock模式下,我们希望冻结布局,
 805         // 所以我们希望冻结插图,通过具有不同布局框架的任务概率,我们可以在移动任务的同时实现这一点。
 806         // 我理解这两个变量是暂时把父窗口和显示窗口大小固定下来, 看后面怎么用的?
 807         final Rect layoutContainingFrame;
 808         final Rect layoutDisplayFrame;
 809 
 810         // The offset from the layout containing frame to the actual containing frame.
 811         final int layoutXDiff;
 812         final int layoutYDiff;
 813         //step1, 当全屏的时候,设置内容区域据说父亲区域,显示屏大小就是换地尽量的显示屏区域,
 814         //   并且窗体没有位移
 815         if (inFullscreenContainer || layoutInParentFrame()) {
 816             // We use the parent frame as the containing frame for fullscreen and child windows
 817             mContainingFrame.set(parentFrame);
 818             mDisplayFrame.set(displayFrame);
 819             layoutDisplayFrame = displayFrame;
 820             layoutContainingFrame = parentFrame;
 821             layoutXDiff = 0;
 822             layoutYDiff = 0;
 823         } else {
 824             // 当不上全屏,或在父窗口内部的模式
 825             getContainerBounds(mContainingFrame);
 826             if (mAppToken != null && !mAppToken.mFrozenBounds.isEmpty()) {
 827 
 828                 // If the bounds are frozen, we still want to translate the window freely and only
 829                 // freeze the size.
 830                 Rect frozen = mAppToken.mFrozenBounds.peek();
 831                 mContainingFrame.right = mContainingFrame.left + frozen.width();
 832                 mContainingFrame.bottom = mContainingFrame.top + frozen.height();
 833             }
//...
 860             // 显示屏区域被设置成内容区域
 861             mDisplayFrame.set(mContainingFrame);
 862             // 计算偏移量,这个偏移量是根据临时内容区域变化来
 863             layoutXDiff = !mInsetFrame.isEmpty() ? mInsetFrame.left - mContainingFrame.left : 0;
 864             layoutYDiff = !mInsetFrame.isEmpty() ? mInsetFrame.top - mContainingFrame.top : 0;
 865             layoutContainingFrame = !mInsetFrame.isEmpty() ? mInsetFrame : mContainingFrame;
 866             mTmpRect.set(0, 0, dc.getDisplayInfo().logicalWidth, dc.getDisplayInfo().logicalHeight);
 867             // 合并所有的区域到显示屏区域中
 868             subtractInsets(mDisplayFrame, layoutContainingFrame, displayFrame, mTmpRect);
 869             if (!layoutInParentFrame()) {
 870                 subtractInsets(mContainingFrame, layoutContainingFrame, parentFrame, mTmpRect);
 871                 subtractInsets(mInsetFrame, layoutContainingFrame, parentFrame, mTmpRect);
 872             }
 873             layoutDisplayFrame = displayFrame;
 874             layoutDisplayFrame.intersect(layoutContainingFrame);
 875         }
 876 
 877         final int pw = mContainingFrame.width();
 878         final int ph = mContainingFrame.height();
 879 
 880         if (!mParentFrame.equals(parentFrame)) {
 881             //Slog.i(TAG_WM, "Window " + this + " content frame from " + mParentFrame
 882             //        + " to " + parentFrame);
 883             mParentFrame.set(parentFrame);
 884             mContentChanged = true;
 885         }
 886         if (mRequestedWidth != mLastRequestedWidth || mRequestedHeight != mLastRequestedHeight) {
 887             mLastRequestedWidth = mRequestedWidth;
 888             mLastRequestedHeight = mRequestedHeight;
 889             mContentChanged = true;
 890         }
 891 
 892         //step2, 保存ov, cf, vf, df, sf到winstate变量里面
 893         mOverscanFrame.set(overscanFrame);
 894         mContentFrame.set(contentFrame);
 895         mVisibleFrame.set(visibleFrame);
 896         mDecorFrame.set(decorFrame);
 897         mStableFrame.set(stableFrame);
 898         final boolean hasOutsets = outsetFrame != null;
 899         if (hasOutsets) {
 900             mOutsetFrame.set(outsetFrame);
 901         }
//....
 903         final int fw = mFrame.width();
 904         final int fh = mFrame.height();
 905 
 906         // 根据window Gravity计算边距
 907         applyGravityAndUpdateFrame(layoutContainingFrame, layoutDisplayFrame);
 908 
 909         // Calculate the outsets before the content frame gets shrinked to the window frame.
 910         // 计算window外部添加区域,
 911         if (hasOutsets) {
 912             mOutsets.set(Math.max(mContentFrame.left - mOutsetFrame.left, 0),
 913                     Math.max(mContentFrame.top - mOutsetFrame.top, 0),
 914                     Math.max(mOutsetFrame.right - mContentFrame.right, 0),
 915                     Math.max(mOutsetFrame.bottom - mContentFrame.bottom, 0));
 916         } else {
 917             mOutsets.set(0, 0, 0, 0);
 918         }
 919 
 920         // Make sure the content and visible frames are inside of the
 921         // final window frame.
 922         // step3, 确保内容和可见区域,在最终的窗口内部
 923         if (windowsAreFloating && !mFrame.isEmpty()) {
 924             // For pinned workspace the frame isn't limited in any particular
 925             // way since SystemUI controls the bounds. For freeform however
 926             // we want to keep things inside the content frame.
 927             final Rect limitFrame = task.inPinnedWorkspace() ? mFrame : mContentFrame;
 928             // Keep the frame out of the blocked system area, limit it in size to the content area
 929             // and make sure that there is always a minimum visible so that the user can drag it
 930             // into a usable area..
 931             final int height = Math.min(mFrame.height(), limitFrame.height());
 932             final int width = Math.min(limitFrame.width(), mFrame.width());
 933             final DisplayMetrics displayMetrics = getDisplayContent().getDisplayMetrics();
 934             final int minVisibleHeight = Math.min(height, WindowManagerService.dipToPixel(
 935                     MINIMUM_VISIBLE_HEIGHT_IN_DP, displayMetrics));
//...
 970         //step4, 设置过扫苗区域(边距)
 971         if (inFullscreenContainer && !windowsAreFloating) {
 972             // Windows that are not fullscreen can be positioned outside of the display frame,
 973             // but that is not a reason to provide them with overscan insets.
 974             mOverscanInsets.set(Math.max(mOverscanFrame.left - layoutContainingFrame.left, 0),
 975                     Math.max(mOverscanFrame.top - layoutContainingFrame.top, 0),
 976                     Math.max(layoutContainingFrame.right - mOverscanFrame.right, 0),
 977                     Math.max(layoutContainingFrame.bottom - mOverscanFrame.bottom, 0));
 978         }
//...
1023         // Offset the actual frame by the amount layout frame is off.
1024         //step5, 设置位移区域
1025         mFrame.offset(-layoutXDiff, -layoutYDiff);
1026         mCompatFrame.offset(-layoutXDiff, -layoutYDiff);
1027         mContentFrame.offset(-layoutXDiff, -layoutYDiff);
1028         mVisibleFrame.offset(-layoutXDiff, -layoutYDiff);
1029         mStableFrame.offset(-layoutXDiff, -layoutYDiff);
1030 
1031         mCompatFrame.set(mFrame);
1032         //step5, 设置屏幕内容的缩放
1033         if (mEnforceSizeCompat) {
1034             // If there is a size compatibility scale being applied to the
1035             // window, we need to apply this to its insets so that they are
1036             // reported to the app in its coordinate space.
1037             mOverscanInsets.scale(mInvGlobalScale);
1038             mContentInsets.scale(mInvGlobalScale);
1039             mVisibleInsets.scale(mInvGlobalScale);
1040             mStableInsets.scale(mInvGlobalScale);
1041             mOutsets.scale(mInvGlobalScale);
1042 
1043             // Also the scaled frame that we report to the app needs to be
1044             // adjusted to be in its coordinate space.
1045             mCompatFrame.scale(mInvGlobalScale);
1046         }
1047 
1048         //step7, 更新壁纸的位移
1049         if (mIsWallpaper && (fw != mFrame.width() || fh != mFrame.height())) {
1050             final DisplayContent displayContent = getDisplayContent();
1051             if (displayContent != null) {
1052                 final DisplayInfo displayInfo = displayContent.getDisplayInfo();
1053                 getDisplayContent().mWallpaperController.updateWallpaperOffset(
1054                         this, displayInfo.logicalWidth, displayInfo.logicalHeight, false);
1055             }
1056         }
1057 
1058         if (DEBUG_LAYOUT || localLOGV) Slog.v(TAG,
1059                 "Resolving (mRequestedWidth="
1060                 + mRequestedWidth + ", mRequestedheight="
1061                 + mRequestedHeight + ") to" + " (pw=" + pw + ", ph=" + ph
1062                 + "): frame=" + mFrame.toShortString()
1063                 + " ci=" + mContentInsets.toShortString()
1064                 + " vi=" + mVisibleInsets.toShortString()
1065                 + " si=" + mStableInsets.toShortString()
1066                 + " of=" + mOutsets.toShortString());
1067     }

computeFrameLw方法是主要Frame,这个是应用真实显示区域,确保在正确边界之内,同时计算Inset(边衬)区域。

WMS 到 SurfaceFlinger设置窗口位置

WindowSurfaceController.setPositionInTransaction --> SurfaceControl.setPosition() --> android_view_SurfaceControl.nativeSetPosition --> SurfaceComposerClient.setPosition()
Composer::closeGlobalTransactionImpl() --> SurfaceFlinger->setTransactionState() --> layer->setPosition() 设置到最终的layer上面。 layer是SurfaceFlinger最终合成图层使用。

总结:
也花了几天时间,把这个完成流程梳理了下,中间也忽略了很多细节,也参考了下写的很不错的博客,计算窗口的原理不是很复杂,就是细节特别多。后面根据自己的需求,比如我想把虚拟分辨率设置为3840x1080或3840x2160,然后随意调整activity(window)显示位置,让系统可以显示多个activity, 虽然当前分屏和多窗口可以做到,但是很容易导致UI适配不兼容问题。

参考资料

深入理解Android内核设计思想
Android 重学系列 WMS在Activity启动中的职责 计算窗体的大小(四)
Window大小位置测量以及View绘制流程
Android窗口管理服务WindowManagerService计算Activity窗口大小的过程分析

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

推荐阅读更多精彩内容