Android6.0 WMS(五) WMS計算Activity窗口大小的過程分析(二)WMS的relayoutWindow

既上一篇博客,這篇我們分析WMS的relayoutWindow函數。

relayoutWindow

我們先看下relayoutWindow函數

  1. public int relayoutWindow(Session session, IWindow client, int seq,
  2. WindowManager.LayoutParams attrs, int requestedWidth,
  3. int requestedHeight, int viewVisibility, int flags,
  4. Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
  5. Rect outVisibleInsets, Rect outStableInsets, Rect outOutsets, Configuration outConfig,
  6. Surface outSurface) {
  7. ......
  8. synchronized(mWindowMap) {
  9. WindowState win = windowForClientLocked(session, client, false);
  10. if (win == null) {
  11. return 0;
  12. }
  13. WindowStateAnimator winAnimator = win.mWinAnimator;
  14. if (viewVisibility != View.GONE && (win.mRequestedWidth != requestedWidth
  15. || win.mRequestedHeight != requestedHeight)) {
  16. win.mLayoutNeeded = true;
  17. win.mRequestedWidth = requestedWidth;
  18. win.mRequestedHeight = requestedHeight;
  19. }
  20. ......
  21. final boolean scaledWindow =
  22. ((win.mAttrs.flags & WindowManager.LayoutParams.FLAG_SCALED) != 0);
  23. if (scaledWindow) {
  24. // requested{Width|Height} Surface's physical size
  25. // attrs.{width|height} Size on screen
  26. win.mHScale = (attrs.width != requestedWidth) ?
  27. (attrs.width / (float)requestedWidth) : 1.0f;
  28. win.mVScale = (attrs.height != requestedHeight) ?
  29. (attrs.height / (float)requestedHeight) : 1.0f;
  30. } else {
  31. win.mHScale = win.mVScale = 1;
  32. }
  33. ......
  34. win.mGivenInsetsPending = (flags&WindowManagerGlobal.RELAYOUT_INSETS_PENDING) != 0;
  35. configChanged = updateOrientationFromAppTokensLocked(false);
  36. performLayoutAndPlaceSurfacesLocked();
  37. .....
  38. outFrame.set(win.mCompatFrame);
  39. outOverscanInsets.set(win.mOverscanInsets);
  40. outContentInsets.set(win.mContentInsets);
  41. outVisibleInsets.set(win.mVisibleInsets);
  42. outStableInsets.set(win.mStableInsets);
  43. outOutsets.set(win.mOutsets);
  44. inTouchMode = mInTouchMode;
  45. ......
  46. }
  47. ......
  48. return (inTouchMode ? WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE : 0)
  49. | (toBeDisplayed ? WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME : 0)
  50. | (surfaceChanged ? WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED : 0);
  51. }

參數client是一個Binder代理對象,它引用了運行在應用程序進程這一側中的一個W對象,用來標誌一個Activity窗口。在應用程序進程這一側的每一個W對象,在WindowManagerService服務這一側都有一個對應的WindowState對象,用來描述一個Activity窗口的狀態。因此,WindowManagerService類的成員函數relayoutWindow首先通過調用另外一個成員函數windowForClientLocked來獲得與參數client所對應的一個WindowState對象win,以便接下來可以對它進行操作。

        本文我們只關注WindowManagerService類的成員函數relayoutWindow中與窗口大小計算有關的邏輯,計算過程如下所示:

        1. 參數requestedWidth和requestedHeight描述的是應用程序進程請求設置Activity窗口中的寬度和高度,它們會被記錄在WindowState對象win的成員變量mRequestedWidth和mRequestedHeight中。

        2. WindowState對象win的成員變量mAttr,它指向的是一個WindowManager.LayoutParams對象,用來描述Activity窗口的佈局參數。其中,這個WindowManager.LayoutParams對象的成員變量width和height是用來描述Activity窗口的寬度和高度的。當這個WindowManager.LayoutParams對象的成員變量flags的WindowManager.LayoutParams.FLAG_SCALED位不等於0的時候,就說明需要給Activity窗口的大小設置縮放因子。縮放因子分爲兩個維度,分別是寬度縮放因子和高度縮放因子,保存在WindowState對象win的成員變量HScale和VScale中,計算方法分別是用應用程序進程請求設置Activity窗口中的寬度和高度除以Activity窗口在佈局參數中所設置的寬度和高度。

        3. 參數insetsPending用來描述Activity窗口是否有額外的內容區域邊襯和可見區域邊襯未設置,它被記錄在WindowState對象win的成員變量mGivenInsetsPending中。

        4. 調用WindowManagerService類的成員函數performLayoutAndPlaceSurfacesLocked來計算Activity窗口的大小。計算完成之後,參數client所描述的Activity窗口的大小、內容區域邊襯大小和可見區域邊邊襯大小就會分別保存在WindowState對象win的成員變量mFrame、mContentInsets和mVisibleInsets中。

        5. 將WindowState對象win的成員變量mFrame、mContentInsets和mVisibleInsets的值分別拷貝到參數出數outFrame、outContentInsets和outVisibleInsets中,以便可以返回給應用程序進程。

        經過上述五個操作後,Activity窗口的大小計算過程就完成了,接下來我們繼續分析WindowManagerService類的成員函數performLayoutAndPlaceSurfacesLocked的實現,以便可以詳細瞭解Activity窗口的大小計算過程。


performLayoutAndPlaceSurfacesLocked

我們再來看performLayoutAndPlaceSurfacesLocked函數,直接調用了performLayoutAndPlaceSurfacesLockedLoop函數。

  1. private final void performLayoutAndPlaceSurfacesLocked() {
  2. int loopCount = 6;
  3. do {
  4. mTraversalScheduled = false;
  5. performLayoutAndPlaceSurfacesLockedLoop();
  6. mH.removeMessages(H.DO_TRAVERSAL);
  7. loopCount--;
  8. } while (mTraversalScheduled && loopCount > 0);
  9. mInnerFields.mWallpaperActionPending = false;
  10. }

performLayoutAndPlaceSurfacesLockedLoop函數:

  1. private final void performLayoutAndPlaceSurfacesLockedLoop() {
  2. if (mInLayout) {
  3. return;
  4. }
  5. if (mWaitingForConfig) {
  6. return;
  7. }
  8. if (!mDisplayReady) {
  9. // Not yet initialized, nothing to do.
  10. return;
  11. }
  12. Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "wmLayout");
  13. mInLayout = true;
  14. boolean recoveringMemory = false;
  15. if (!mForceRemoves.isEmpty()) {
  16. recoveringMemory = true;
  17. // Wait a little bit for things to settle down, and off we go.
  18. while (!mForceRemoves.isEmpty()) {
  19. WindowState ws = mForceRemoves.remove(0);
  20. Slog.i(TAG, "Force removing: " + ws);
  21. removeWindowInnerLocked(ws);
  22. }
  23. Object tmp = new Object();
  24. synchronized (tmp) {
  25. try {
  26. tmp.wait(250);
  27. } catch (InterruptedException e) {
  28. }
  29. }
  30. }
  31. try {
  32. performLayoutAndPlaceSurfacesLockedInner(recoveringMemory);
  33. mInLayout = false;
  34. if (needsLayout()) {
  35. if (++mLayoutRepeatCount < 6) {
  36. requestTraversalLocked();
  37. } else {
  38. Slog.e(TAG, "Performed 6 layouts in a row. Skipping");
  39. mLayoutRepeatCount = 0;
  40. }
  41. } else {
  42. mLayoutRepeatCount = 0;
  43. }
  44. if (mWindowsChanged && !mWindowChangeListeners.isEmpty()) {
  45. mH.removeMessages(H.REPORT_WINDOWS_CHANGE);
  46. mH.sendEmptyMessage(H.REPORT_WINDOWS_CHANGE);
  47. }
  48. } catch (RuntimeException e) {
  49. mInLayout = false;
  50. Slog.wtf(TAG, "Unhandled exception while laying out windows", e);
  51. }
  52. Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
  53. }

從WindowManagerService類的成員函數performLayoutAndPlaceSurfacesLocked的名稱可以推斷出,它執行的操作絕非是計算窗口大小這麼簡單。計算窗口大小隻是其中的一個小小功能點,它主要的功能是用來刷新系統的UI。在我們這個情景中,爲什麼需要刷新系統的UI呢?Activity窗口在其屬性發生了變化,例如,可見性、大小發生了變化,又或者它新增、刪除了子視圖,都需要重新計算大小,而這些變化都是要求WindowManagerService服務重新刷新系統的UI的。事實上,刷新系統的UI是WindowManagerService服務的主要任務,在新增和刪除了窗口、窗口動畫顯示過程、窗口切換過程中,WindowManagerService服務都需要不斷地刷新系統的UI。

       WindowManagerService類的成員函數performLayoutAndPlaceSurfacesLocked主要是通過調用另外一個成員函數performLayoutAndPlaceSurfacesLockedInner來刷新系統的UI的,而在刷新的過程中,就會對系統中的各個窗口的大小進行計算。

       在調用成員函數performLayoutAndPlaceSurfacesLockedInner來刷新系統UI的前後,WindowManagerService類的成員函數performLayoutAndPlaceSurfacesLocked還會執行以下兩個操作:

       1. 調用前,檢查系統中是否存在強制刪除的窗口。有內存不足的情況下,有一些窗口就會被回收,即要從系統中刪除,這些窗口會保存在WindowManagerService類的成員變量mForceRemoves所描述的一個ArrayList中。如果存在這些窗口,那麼WindowManagerService類的成員函數performLayoutAndPlaceSurfacesLocked就會調用另外一個成員函數removeWindowInnerLocked來刪除它們,以便可以回收它們所佔用的內存。

       2. 調用後,檢查系統中是否有窗口需要移除。如果有的話,那麼WindowManagerService類的成員變量mPendingRemove所描述的一個ArrayList的大小就會大於0。這種情況下,WindowManagerService類的成員函數performLayoutAndPlaceSurfacesLocked就會調用另外一個成員函數removeWindowInnerLocked來移除這些窗口。注意,WindowManagerService類的成員函數removeWindowInnerLocked只是用來移除窗口,但是並沒有回收這些窗口所佔用的內存。等到合適的時候,例如,內存不足時,纔會考慮回收這些窗口所佔用的內存。移除一個窗口的操作也是很複雜的,除了要將窗口從WindowManagerService類的相關成員變量中移除之外,還要考慮重新調整輸入法窗口和壁紙窗口,因爲被移除的窗口可能要求顯示壁紙和輸入法窗口,當它被移除之後,就要將壁紙窗口和輸入法窗口調整到合適的Z軸位置上去,以便可以交給下一個需要顯示壁紙和輸入法窗口的窗口使用。此外,在移除了窗口之後,WindowManagerService服務還需要重新計算現存的其它窗口的Z軸位置,以便可以正確地反映系統當前的UI狀態,這是通過調用WindowManagerService類的成員函數assignLayersLocked來實現的。重新計算了現存的其它窗口的Z軸位置之後,又需要再次刷新系統的UI,即要對WindowManagerService類的成員函數performLayoutAndPlaceSurfacesLocked進行遞歸調用,並且在調用前,將WindowManagerService類的成員變量mLayoutNeeded的值設置爲true。由此就可見,系統UI的刷新過程是非常複雜的。

       注意,爲了防止在刷新系統UI的過程中被重複調用,WindowManagerService類的成員函數performLayoutAndPlaceSurfacesLocked在刷新系統UI之前,即調用成員函數performLayoutAndPlaceSurfacesLockedInner之前,會將WindowManagerService類的成員變量mInLayout的值設置爲true,並且在調用之後,重新將這個成員變量的值設置爲false。這樣,WindowManagerService類的成員函數performLayoutAndPlaceSurfacesLocked就可以在一開始的時候檢查成員變量mInLayout的值是否等於true,如果等於的話,那麼就說明WindowManagerService服務正在刷新系統UI的過程中,於是就不用往下執行了。


performLayoutAndPlaceSurfacesLockedInner刷新UI

       接下來,我們就繼續分析WindowManagerService類的成員函數performLayoutAndPlaceSurfacesLockedInner的實現,以便可以瞭解Activity窗口的大小計算過程。

  1. private final void performLayoutAndPlaceSurfacesLockedInner(
  2. boolean recoveringMemory) {
  3. ......
  4. Surface.openTransaction();
  5. ......
  6. try {
  7. ......
  8. int repeats = 0;
  9. int changes = 0;
  10. do {
  11. repeats++;
  12. if (repeats > 6) {
  13. ......
  14. break;
  15. }
  16. // FIRST LOOP: Perform a layout, if needed.
  17. if (repeats < 4) {
  18. changes = performLayoutLockedInner();
  19. if (changes != 0) {
  20. continue;
  21. }
  22. } else {
  23. Slog.w(TAG, "Layout repeat skipped after too many iterations");
  24. changes = 0;
  25. }
  26. // SECOND LOOP: Execute animations and update visibility of windows.
  27. ......
  28. } while (changes != 0);
  29. // THIRD LOOP: Update the surfaces of all windows.
  30. ......
  31. } catch (RuntimeException e) {
  32. ......
  33. }
  34. ......
  35. Surface.closeTransaction();
  36. ......
  37. }
  38. ......
  39. }

WindowManagerService類的成員函數performLayoutAndPlaceSurfacesLockedInner是一個巨無霸的函數,承載了WindowManagerService服務的核心功能。對於這樣一個巨無霸函數,要逐行地分析它的實現是很困難的,因爲要理解各種上下文信息,纔可以清楚地知道它的執行過程。這裏我們就大概地分析它的實現框架,以後再逐步地分析它的具體實現:

        1. 在一個最多執行7次的while循環中,做兩件事情:第一件事情是計算各個窗品的大小,這是通過調用另外一個成員函數performLayoutLockedInner來實現的;第二件事情是執行窗口的動畫,主要是處理窗口的啓動窗口顯示動畫和窗口切換過程中的動畫,以及更新各個窗口的可見性。注意,每一次while循環執行之後,如果發現系統中的各個窗口的相應佈局屬性不再發生變化,那麼就不行執行下一次的while循環了,即該while循環可能不用執行7次就結束了。窗口的動畫顯示過程和窗口的可見性更新過程是相當複雜的,它們也是WindowManagerService服務最爲核的地方,在後面的文章中,我們再詳細分析。

        2. 經過第1點的操作之後,接下來就可以將各個窗口的屬性,例如,大小、位置等屬性,通知SurfaceFlinger服務了,也就是讓SurfaceFlinger服務更新它裏面的各個Layer的屬性值,以便可以對這些Layer執行可見性計算、合成等操作,最後渲染到硬件幀緩衝區中去。SurfaceFlinger服務計算系統中各個窗口,即各個Layer的可見性,以便將它們合成、渲染到硬件幀緩衝區。注意,各個窗口的屬性更新操作是被包含在SurfaceFlinger服務的一個事務中的,即一個Transaction中,這樣做是爲了避免每更新一個窗口的一個屬性就觸發SurfaceFlinger服務重新計算各個Layer的可見性,以及對各個Layer進行合併和渲染的操作。啓動SurfaceFlinger服務的一個事務可以通過調用Surface類的靜態成員函數openTransaction來實現,而關閉SurfaceFlinger服務的一個事務可以通過調用Surface類的靜態成員函數closeTransaction來實現。到SurfaceFlinger的各個Layer設置屬性是在WindowState的WindowStateAnimator的setSurfaceBoundariesLocked函數中調用mSurfaceControl變量設置一些屬性。

       3. 經過第1點和第2點的操作之後,一次系統UI的刷新過程就完成了,這時候就會將系統中的那些不會再顯示的窗口的繪圖表面銷燬掉,並且將那些已經完成退出了的窗口令牌WindowToken移除掉,以及將那些已經退出了的Activity窗口令牌AppWindowToken也移除掉。這一步實際執行的是窗口清理操作。

       上述三個操作是WindowManagerService類的成員函數performLayoutAndPlaceSurfacesLockedInner的實現關鍵所在,理解了這三個操作,基本也就可以理解WindowManagerService服務刷新系統UI的過程了。

       接下來,我們繼續分析WindowManagerService類的成員函數performLayoutLockedInner的實現,以便可以繼續瞭解Activity窗口的大小計算過程。

  1. private final void performLayoutLockedInner(final DisplayContent displayContent,
  2. boolean initial, boolean updateInputWindows) {
  3. if (!displayContent.layoutNeeded) {
  4. return;
  5. }
  6. displayContent.layoutNeeded = false;
  7. WindowList windows = displayContent.getWindowList();
  8. boolean isDefaultDisplay = displayContent.isDefaultDisplay;
  9. DisplayInfo displayInfo = displayContent.getDisplayInfo();
  10. final int dw = displayInfo.logicalWidth;
  11. final int dh = displayInfo.logicalHeight;
  12. if (mInputConsumer != null) {
  13. mInputConsumer.layout(dw, dh);
  14. }
  15. final int N = windows.size();
  16. int i;
  17. mPolicy.beginLayoutLw(isDefaultDisplay, dw, dh, mRotation);
  18. if (isDefaultDisplay) {
  19. // Not needed on non-default displays.
  20. mSystemDecorLayer = mPolicy.getSystemDecorLayerLw();
  21. mScreenRect.set(0, 0, dw, dh);
  22. }
  23. mPolicy.getContentRectLw(mTmpContentRect);
  24. displayContent.resize(mTmpContentRect);
  25. int seq = mLayoutSeq+1;
  26. if (seq < 0) seq = 0;
  27. mLayoutSeq = seq;
  28. boolean behindDream = false;
  29. // First perform layout of any root windows (not attached
  30. // to another window).
  31. int topAttached = -1;
  32. for (i = N-1; i >= 0; i--) {
  33. final WindowState win = windows.get(i);
  34. final boolean gone = (behindDream && mPolicy.canBeForceHidden(win, win.mAttrs))
  35. || win.isGoneForLayoutLw();
  36. if (!gone || !win.mHaveFrame || win.mLayoutNeeded
  37. || ((win.isConfigChanged() || win.setInsetsChanged()) &&
  38. ((win.mAttrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0 ||
  39. (win.mHasSurface && win.mAppToken != null &&
  40. win.mAppToken.layoutConfigChanges)))) {
  41. if (!win.mLayoutAttached) {
  42. if (initial) {
  43. win.mContentChanged = false;
  44. }
  45. if (win.mAttrs.type == TYPE_DREAM) {
  46. behindDream = true;
  47. }
  48. win.mLayoutNeeded = false;
  49. win.prelayout();
  50. mPolicy.layoutWindowLw(win, null);
  51. win.mLayoutSeq = seq;
  52. } else {
  53. if (topAttached < 0) topAttached = i;
  54. }
  55. }
  56. }
  57. boolean attachedBehindDream = false;
  58. for (i = topAttached; i >= 0; i--) {
  59. final WindowState win = windows.get(i);
  60. if (win.mLayoutAttached) {
  61. if (attachedBehindDream && mPolicy.canBeForceHidden(win, win.mAttrs)) {
  62. continue;
  63. }
  64. if ((win.mViewVisibility != View.GONE && win.mRelayoutCalled)
  65. || !win.mHaveFrame || win.mLayoutNeeded) {
  66. if (initial) {
  67. //Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
  68. win.mContentChanged = false;
  69. }
  70. win.mLayoutNeeded = false;
  71. win.prelayout();
  72. mPolicy.layoutWindowLw(win, win.mAttachedWindow);
  73. win.mLayoutSeq = seq;
  74. }
  75. } else if (win.mAttrs.type == TYPE_DREAM) {
  76. // Don't layout windows behind a dream, so that if it
  77. // does stuff like hide the status bar we won't get a
  78. // bad transition when it goes away.
  79. attachedBehindDream = behindDream;
  80. }
  81. }
  82. ......
  83. mPolicy.finishLayoutLw();
  84. }

在分析WindowManagerService類的成員函數performLayoutLockedInner的實現之前,我們首先介紹WindowManagerService類的兩個成員變量mPolicy和mWindows:

        1. mPolicy指向的是一個窗口管理策略類,它是通過調用PolicyManager類的靜態成員函數makeNewWindowManager來初始化的,在Phone平臺中,它指向的是便是一個PhoneWindowManager對象,主要是用來制定窗口的大小計算策略。

        2. mWindows指向的是一個類型爲WindowState的ArrayList,它裏面保存的就是系統中的所有窗口,這些窗口是按照Z軸位置從小到大的順序保存在這個ArrayList中的,也就是說,第i個窗口位於第i-1個窗口的上面,其中,i > 0。

        理解了這兩個成員變量的含義之後,我們就分析WindowManagerService類的成員函數performLayoutLockedInner的執行過程,主要是分三個階段:

        1. 準備階段:調用PhoneWindowManager類的成員函數beginLayoutLw來設置屏幕的大小。屏幕的大小可以通過調用WindowManagerService類的成員變量mDisplay所描述的一個Display對象的成員函數getWidth和getHeight來獲得。

        2. 計算階段:調用PhoneWindowManager類的成員函數layoutWindowLw來計算各個窗口的大小、內容區域邊襯大小以及可見區域邊襯大小。

        3. 結束階段:調用PhoneWindowManager類的成員函數finishLayoutLw來執行一些清理工作。

        按照父子關係來劃分,系統中的窗口可以分爲父窗口和子窗口兩種。如果一個WindowState對象的成員變量mLayoutAttached的值等於false,那麼它所描述的窗口就可以作爲一個父窗口,否則的話,它所描述的窗口就是一個子窗口。由於子窗口的大小計算是依賴於其父窗口的,因此,在計算各個窗口的大小的過程中,即在上述的第2階段中,按照以下方式來進行:
        1.  先計算父窗口的大小。一般來說,能夠作爲父窗口的,是那些Activity窗口。如果一個窗口是Activity窗口,那麼用來描述它的一個WindowState對象的成員變量mAppToken就不等於null,並且指向的是一個AppWindowToken對象。這個AppWindowToken對象主要是用來描述一個Activity,即與ActivityManagerService服務中的一個ActivityRecord對象對應。一個Activity窗口只有在兩種情況下才會被計算大小:第一種情況是窗口不是處於不可見狀態的;第二種情況是窗口從來還沒有被計算過大小,即用來描述該Activity窗口的WindowState對象的成員變量mHaveFrame的值等於false,這種情況一般發生在窗口剛剛被添加到WindowManagerService的過程中。一個Activity窗口的不可見狀態由它本身的狀態、它所在的窗口結構樹狀態以及它所屬的Activity的狀態有關,也就是說,如果一個Activity窗口本身是可見的,但是由於它的父窗口、它所在的窗口組的根窗口或者它所屬的Activity是不可見的,那麼該Activity窗口也是不可見的。一個Activity窗口的不可見狀態由以下因素決定:

        1). 它本身處於不可見狀態,即對應的WindowState對象的成員變量mViewVisibility的值等於View.GONE;

        2). 它本身處於正在退出的狀態,即對應的WindowState對象的成員變量mExiting的值等於true;

        3). 它本身處於正在銷燬的狀態,即對應的WindowState對象的成員變量mDestroying的值等於true;

        4). 它的父窗口處於不可見狀態,即對應的WindowState對象的成員變量mAttachedHidden的值等於true;

        5). 它所在窗口結構樹中的根窗口處於不可見狀態,即對應的WindowState對象的成員變量mRootToken所描述的一個WindowToken對象的成員變量hidden的值等於true;

        6). 它所屬的Activity處於不可見狀態,即對應的WindowState對象的成員變量mAppToken所描述的一個AppWindowToken對象的成員變量hiddenRequested的值等於true。

        除了上述六個因素之外,如果一個Activity窗口沒有被它所運行在的應用程序進程主動請求WindowManagerService服務對它進行佈局,即對應的WindowState對象的成員變量mRelayoutCalled的值等於false,那麼此時也是不需要計算Activity窗口的大小的。

       一個Activity窗口的大小一旦確定是需要計算大小之後,PhoneWindowManager類的成員函數layoutWindowLw就被調用來計算它的大小。

       2. 接着計算子窗口的大小。前面在計算父窗口的大小過程中,會記錄位於系統最上面的一個子窗口在mWindows所描述的一個ArrayList的位置topAttached,接下來就可以從這個位置開始向下計算每一個子窗口的大小。一個子窗口在以下兩種情況下,纔會被計算大小:

       1). 它本身處於可見狀態,即對應的WindowState對象的成員變量mViewVisibility的值不等於View.GONE,並且它所運行在的應用程序進程主動請求WindowManagerService服務對它進行佈局,即對應的WindowState對象的成員變量mRelayoutCalled的值等於true。

       2). 它從來還沒有被計算過大小,即用來描述該子窗口的WindowState對象的成員變量mHaveFrame的值等於false,這種情況一般發生在子窗口剛剛被添加到WindowManagerService的過程中。

       接下來,我們就分別分析PhoneWindowManager類的成員函數beginLayoutLw、layoutWindowLw和finishLayoutLw的實現,以便可以瞭解Activity窗口的大小計算過程。


PhoneWindowManager的beginLayoutLw

  1. public void beginLayoutLw(boolean isDefaultDisplay, int displayWidth, int displayHeight,
  2. int displayRotation) {
  3. mDisplayRotation = displayRotation;
  4. final int overscanLeft, overscanTop, overscanRight, overscanBottom;
  5. if (isDefaultDisplay) {
  6. switch (displayRotation) {
  7. case Surface.ROTATION_90:
  8. overscanLeft = mOverscanTop;
  9. overscanTop = mOverscanRight;
  10. overscanRight = mOverscanBottom;
  11. overscanBottom = mOverscanLeft;
  12. break;
  13. case Surface.ROTATION_180:
  14. overscanLeft = mOverscanRight;
  15. overscanTop = mOverscanBottom;
  16. overscanRight = mOverscanLeft;
  17. overscanBottom = mOverscanTop;
  18. break;
  19. case Surface.ROTATION_270:
  20. overscanLeft = mOverscanBottom;
  21. overscanTop = mOverscanLeft;
  22. overscanRight = mOverscanTop;
  23. overscanBottom = mOverscanRight;
  24. break;
  25. default:
  26. overscanLeft = mOverscanLeft;
  27. overscanTop = mOverscanTop;
  28. overscanRight = mOverscanRight;
  29. overscanBottom = mOverscanBottom;
  30. break;
  31. }
  32. } else {
  33. overscanLeft = 0;
  34. overscanTop = 0;
  35. overscanRight = 0;
  36. overscanBottom = 0;
  37. }
  38. mOverscanScreenLeft = mRestrictedOverscanScreenLeft = 0;
  39. mOverscanScreenTop = mRestrictedOverscanScreenTop = 0;
  40. mOverscanScreenWidth = mRestrictedOverscanScreenWidth = displayWidth;
  41. mOverscanScreenHeight = mRestrictedOverscanScreenHeight = displayHeight;
  42. mSystemLeft = 0;
  43. mSystemTop = 0;
  44. mSystemRight = displayWidth;
  45. mSystemBottom = displayHeight;
  46. mUnrestrictedScreenLeft = overscanLeft;
  47. mUnrestrictedScreenTop = overscanTop;
  48. mUnrestrictedScreenWidth = displayWidth - overscanLeft - overscanRight;
  49. mUnrestrictedScreenHeight = displayHeight - overscanTop - overscanBottom;
  50. mRestrictedScreenLeft = mUnrestrictedScreenLeft;
  51. mRestrictedScreenTop = mUnrestrictedScreenTop;
  52. mRestrictedScreenWidth = mSystemGestures.screenWidth = mUnrestrictedScreenWidth;
  53. mRestrictedScreenHeight = mSystemGestures.screenHeight = mUnrestrictedScreenHeight;
  54. mDockLeft = mContentLeft = mVoiceContentLeft = mStableLeft = mStableFullscreenLeft
  55. = mCurLeft = mUnrestrictedScreenLeft;
  56. mDockTop = mContentTop = mVoiceContentTop = mStableTop = mStableFullscreenTop
  57. = mCurTop = mUnrestrictedScreenTop;
  58. mDockRight = mContentRight = mVoiceContentRight = mStableRight = mStableFullscreenRight
  59. = mCurRight = displayWidth - overscanRight;
  60. mDockBottom = mContentBottom = mVoiceContentBottom = mStableBottom = mStableFullscreenBottom
  61. = mCurBottom = displayHeight - overscanBottom;
  62. mDockLayer = 0x10000000;
  63. mStatusBarLayer = -1;
  64. // start with the current dock rect, which will be (0,0,displayWidth,displayHeight)
  65. final Rect pf = mTmpParentFrame;
  66. final Rect df = mTmpDisplayFrame;
  67. final Rect of = mTmpOverscanFrame;
  68. final Rect vf = mTmpVisibleFrame;
  69. final Rect dcf = mTmpDecorFrame;
  70. final Rect osf = mTmpOutsetFrame;
  71. pf.left = df.left = of.left = vf.left = mDockLeft;
  72. pf.top = df.top = of.top = vf.top = mDockTop;
  73. pf.right = df.right = of.right = vf.right = mDockRight;
  74. pf.bottom = df.bottom = of.bottom = vf.bottom = mDockBottom;

Android能夠支持智能電視作爲顯示設備、電視或者大的液晶顯示器四周有一圈黑色的區域,稱爲overscan(過掃描)區域,這個區域也是顯示屏的一部分,但是通常不能顯示。
在PhoneWindowManager中如下表示
    int mOverscanLeft = 0;
    int mOverscanTop = 0;
    int mOverscanRight = 0;
    int mOverscanBottom = 0;
這4個變量分別表示overscan區距離四個方向的距離。這4個變量的初始化在配置顯示設備是完成的。這個函數在WMS中,該函數使用了保存在顯示設備對象中的overscan數據來調用PhoneWindowManager的setDisplayOverscan函數來設置這4個變量

  1. private void configureDisplayPolicyLocked(DisplayContent displayContent) {
  2. mPolicy.setInitialDisplaySize(displayContent.getDisplay(),
  3. displayContent.mBaseDisplayWidth,
  4. displayContent.mBaseDisplayHeight,
  5. displayContent.mBaseDisplayDensity);
  6. DisplayInfo displayInfo = displayContent.getDisplayInfo();
  7. mPolicy.setDisplayOverscan(displayContent.getDisplay(),
  8. displayInfo.overscanLeft, displayInfo.overscanTop,
  9. displayInfo.overscanRight, displayInfo.overscanBottom);
  10. }
setDisplayOverscan函數如下:

  1. @Override
  2. public void setDisplayOverscan(Display display, int left, int top, int right, int bottom) {
  3. if (display.getDisplayId() == Display.DEFAULT_DISPLAY) {
  4. mOverscanLeft = left;
  5. mOverscanTop = top;
  6. mOverscanRight = right;
  7. mOverscanBottom = bottom;
  8. }
  9. }

屏幕上除了overscan區域還有狀態欄,導航欄、輸入法區域,PhoneWindowManager定義各種區域代表屏幕上不同區域的組合。

屏幕窗口的區域劃分

1.overscanScreen區域,這個區域包括屏幕的overscan區域,相當於整個屏幕

2.RestrictedOverScanScreen區域,包括overscan區,不包含導航欄、因此這個區域上面到屏幕的頂部,下面就到導航欄的頂部。

3.RestrictedScreen區域,這個區域不包含overscan區域不包含導航欄

4.UnRestrictedScreen區域,不包含屏幕的overscan區域、包含狀態欄和導航欄

5.stableFullScreen區域,包含狀態欄、輸入法、不包含導航欄

6.Decor區域,不包含狀態欄、不包含導航欄、包含輸入法區域

7.Curren區域、不包含狀態欄、不包含導航欄、不包含輸入法區域

PhoneWindowManager一共定義了10種區域,剩下的system區域、Stable區域、Content區域的範圍和Decor區域相同。這些區域在不同場合含義不同,但是值是相同的。

beginLayoutLw方法邏輯很簡單,就是做了下面工作。

首先根據各個區域是否延伸到屏幕的overscan區域來對各個區域進行初始化賦值。這個時候各個區域的大小或者等於整個屏幕的大小或者等於不包含overscan區域的大小。

然後計算出導航欄的尺寸,再調整和導航欄有關係的區域大小,這些區域要去掉導航欄所佔的區域。

對狀態欄也是類似,先計算狀態欄的尺寸,在調整和狀態欄有關係區域的大小。


PhoneWindowManager的layoutWindowLw函數

  1. public void layoutWindowLw(WindowState win, WindowState attached) {
  2. // We’ve already done the navigation bar and status bar. If the status bar can receive
  3. // input, we need to layout it again to accomodate for the IME window.
  4. if ((win == mStatusBar && !canReceiveInput(win)) || win == mNavigationBar) {
  5. return;
  6. }
  7. final WindowManager.LayoutParams attrs = win.getAttrs();
  8. final boolean isDefaultDisplay = win.isDefaultDisplay();
  9. final boolean needsToOffsetInputMethodTarget = isDefaultDisplay &&
  10. (win == mLastInputMethodTargetWindow && mLastInputMethodWindow != null);
  11. if (needsToOffsetInputMethodTarget) {
  12. if (DEBUG_LAYOUT) Slog.i(TAG, “Offset ime target window by the last ime window state”);
  13. offsetInputMethodWindowLw(mLastInputMethodWindow);
  14. }
  15. final int fl = PolicyControl.getWindowFlags(win, attrs);
  16. final int sim = attrs.softInputMode;
  17. final int sysUiFl = PolicyControl.getSystemUiVisibility(win, null);
  18. final Rect pf = mTmpParentFrame;
  19. final Rect df = mTmpDisplayFrame;
  20. final Rect of = mTmpOverscanFrame;
  21. final Rect cf = mTmpContentFrame;
  22. final Rect vf = mTmpVisibleFrame;
  23. final Rect dcf = mTmpDecorFrame;
  24. final Rect sf = mTmpStableFrame;
  25. Rect osf = null;
  26. dcf.setEmpty();
  27. final boolean hasNavBar = (isDefaultDisplay && mHasNavigationBar
  28. && mNavigationBar != null && mNavigationBar.isVisibleLw());
  29. final int adjust = sim & SOFT_INPUT_MASK_ADJUST;
  30. if (isDefaultDisplay) {
  31. sf.set(mStableLeft, mStableTop, mStableRight, mStableBottom);
  32. } else {
  33. sf.set(mOverscanLeft, mOverscanTop, mOverscanRight, mOverscanBottom);
  34. }
  35. if (!isDefaultDisplay) {
  36. if (attached != null) {
  37. // If this window is attached to another, our display
  38. // frame is the same as the one we are attached to.
  39. setAttachedWindowFrames(win, fl, adjust, attached, true, pf, df, of, cf, vf);
  40. } else {
  41. // Give the window full screen.
  42. pf.left = df.left = of.left = cf.left = mOverscanScreenLeft;
  43. pf.top = df.top = of.top = cf.top = mOverscanScreenTop;
  44. pf.right = df.right = of.right = cf.right
  45. = mOverscanScreenLeft + mOverscanScreenWidth;
  46. pf.bottom = df.bottom = of.bottom = cf.bottom
  47. = mOverscanScreenTop + mOverscanScreenHeight;
  48. }
  49. } else if (attrs.type == TYPE_INPUT_METHOD) {
  50. pf.left = df.left = of.left = cf.left = vf.left = mDockLeft;
  51. pf.top = df.top = of.top = cf.top = vf.top = mDockTop;
  52. pf.right = df.right = of.right = cf.right = vf.right = mDockRight;
  53. // IM dock windows layout below the nav bar…
  54. pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
  55. // …with content insets above the nav bar
  56. cf.bottom = vf.bottom = mStableBottom;
  57. // IM dock windows always go to the bottom of the screen.
  58. attrs.gravity = Gravity.BOTTOM;
  59. mDockLayer = win.getSurfaceLayer();
  60. } …..

計算窗口大小是在layoutWindowLw中完成的,該方法不會直接計算出窗口大小,而是先計算出窗口能夠擴展的最大空間,然後再調用WindowState的computeFrameLw來計算出窗口的最終大小。
layoutWindowLw方法、主要是計算6個矩形區域的大小,然後根據計算出來的值調用WindowState的computeFrameLw方法來計算窗口大小。
6個區域如下:
pf(parentFrame):表示窗口父窗口的大小
df(deviceFrame):設備的屏幕大小
of(OverScanFrame):設備屏幕大小、和df值一樣
cf(ContentFrame):窗口內容區域大小
vf(VisibleFrame):窗口可見內容區域大小
dcf(DecorFrame):裝飾區域大小。除去狀態欄和導航欄的區域。
這6個區域和上面介紹的屏幕區域有關,但是他們只是一些臨時值,爲計算窗口的大小服務,因此它們的值並不完全等於上一節介紹的屏幕區域的值。
在layoutWindowLw方法中,如果當前顯示設備不是缺省的設備、對於子窗口,則調用setAttachedWindowFrames來處理,如果是非子窗口,將pf、df、of、cf設置爲全屏大小。
對於是缺省的設備,如果當前是輸入法窗口,pf、df、of大小被設置成不包含狀態欄,但是包含導航欄的區域大小,cf、vf設置爲不包含狀態欄也不包含導航欄的區域大小。窗口屬性的gravity設置爲Gravity.BOTTOM(底部對齊)。
下面也是根據窗口的類型決定窗口所佔的區域是否包含狀態欄和導航欄,而且有些窗口還要擴展到OverScan區域。(這裏不詳細介紹)
得到了窗口的6個區域後就調用computeFrameLw函數來計算窗口的大小了。

PhoneWindowManager的computeFrameLw函數

我們先來看下WindowState描述窗口大小相關的變量,當然我們主要還是要算mFrame變量
  1. final Rect mFrame = new Rect();//真實窗口大小
  2. final Rect mLastFrame = new Rect();
  3. final Rect mCompatFrame = new Rect();//兼容模式下窗口大小
  4. final Rect mContainingFrame = new Rect();
  5. final Rect mParentFrame = new Rect();// 父窗口大小
  6. final Rect mDisplayFrame = new Rect();//顯示設備大小
  7. final Rect mOverscanFrame = new Rect();//Overscan區域大小
  8. // The display frame minus the stable insets. This value is always constant regardless of if
  9. // the status bar or navigation bar is visible.
  10. final Rect mStableFrame = new Rect();
  11. final Rect mDecorFrame = new Rect();//Decor區域大小
  12. final Rect mContentFrame = new Rect();//內容區域大小
  13. final Rect mVisibleFrame = new Rect();//可見區域大小
WindowState類還有一個成員變量mHaveFrame用來描述一個窗口的大小是否計算過了。當WindowState類的成員函數computeFrameLw被調用的時候,就說明一個相應的窗口的大小得到計算了,因此,WindowState類的成員函數computeFrameLw一開始就會將成員變量mHaveFrame的值設置爲true。
我們來看下下面一些變量的賦值:
  1. mContentFrame.set(Math.max(mContentFrame.left, mFrame.left),
  2. Math.max(mContentFrame.top, mFrame.top),
  3. Math.min(mContentFrame.right, mFrame.right),
  4. Math.min(mContentFrame.bottom, mFrame.bottom));
  5. mVisibleFrame.set(Math.max(mVisibleFrame.left, mFrame.left),
  6. Math.max(mVisibleFrame.top, mFrame.top),
  7. Math.min(mVisibleFrame.right, mFrame.right),
  8. Math.min(mVisibleFrame.bottom, mFrame.bottom));
  9. mStableFrame.set(Math.max(mStableFrame.left, mFrame.left),
  10. Math.max(mStableFrame.top, mFrame.top),
  11. Math.min(mStableFrame.right, mFrame.right),
  12. Math.min(mStableFrame.bottom, mFrame.bottom));
  13. mOverscanInsets.set(Math.max(mOverscanFrame.left - mFrame.left, 0),
  14. Math.max(mOverscanFrame.top - mFrame.top, 0),
  15. Math.max(mFrame.right - mOverscanFrame.right, 0),
  16. Math.max(mFrame.bottom - mOverscanFrame.bottom, 0));
  17. mContentInsets.set(mContentFrame.left - mFrame.left,
  18. mContentFrame.top - mFrame.top,
  19. mFrame.right - mContentFrame.right,
  20. mFrame.bottom - mContentFrame.bottom);
  21. mVisibleInsets.set(mVisibleFrame.left - mFrame.left,
  22. mVisibleFrame.top - mFrame.top,
  23. mFrame.right - mVisibleFrame.right,
  24. mFrame.bottom - mVisibleFrame.bottom);
  25. mStableInsets.set(Math.max(mStableFrame.left - mFrame.left, 0),
  26. Math.max(mStableFrame.top - mFrame.top, 0),
  27. Math.max(mFrame.right - mStableFrame.right, 0),
  28. Math.max(mFrame.bottom - mStableFrame.bottom, 0));
  29. mCompatFrame.set(mFrame);

PhoneWindowManager的finishLayoutLw

該函數只是一個空實現

  1. @Override
  2. public void finishLayoutLw() {
  3. return;
  4. }

設置relayoutWindow的輸出變量

最後我們會在WMS中的relayoutWindow函數中把一些輸出設置到相關輸出變量中。
  1. outFrame.set(win.mCompatFrame);//這個值一般和mFrame相同
  2. outOverscanInsets.set(win.mOverscanInsets);
  3. outContentInsets.set(win.mContentInsets);
  4. outVisibleInsets.set(win.mVisibleInsets);
  5. outStableInsets.set(win.mStableInsets);
  6. outOutsets.set(win.mOutsets);




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