Android6.0 WMS(五) WMS計算Activity窗口大小的過程分析(一)應用進程

這篇文章很多參考博客:http://blog.csdn.net/luoshengyang/article/details/8479101/

一、窗口簡介



在Android系統中,Activity窗口的大小是由WindowManagerService服務來計算的。WindowManagerService服務會根據屏幕及其裝飾區的大小來決定Activity窗口的大小。一個Activity窗口只有知道自己的大小之後,才能對它裏面的UI元素進行測量、佈局以及繪製。本文將詳細分析WindowManagerService服務計算Activity窗口大小的過程。

        一般來說,Activity窗口的大小等於整個屏幕的大小,但是它並不佔據着整塊屏幕。爲了理解這一點,我們首先分析一下Activity窗口的區域是如何劃分的。

        我們知道,Activity窗口的上方一般會有一個狀態欄,用來顯示3G信號、電量使用等圖標,如圖1所示。

圖1 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分別用來描述內容區域與窗口區域的左右上下邊界距離。

       我們還知道,Activity窗口有時候需要顯示輸入法窗口,如圖2所示。

圖2 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元素進行測量、佈局以及繪製等操作了。


二、窗口函數調用流程

應用程序進程是從ViewRoot類的成員函數performTraversals開始,向WindowManagerService服務請求計算一個Activity窗口的大小的,因此,接下來我們就從ViewRoot類的成員函數performTraversals開始分析一個Activity窗口大小的計算過程,如圖3所示。

圖3 Activity窗口大小的計算過程


2.1 ViewRootImpl的performTraversals函數

這個函數比較複雜我們,進行分段講解:

  1. private void performTraversals() {
  2. // cache mView since it is used so much below...
  3. final View host = mView;
  4. mIsInTraversal = true;
  5. mWillDrawSoon = true;
  6. boolean windowSizeMayChange = false;
  7. boolean newSurface = false;
  8. boolean surfaceChanged = false;
  9. WindowManager.LayoutParams lp = mWindowAttributes;
  10. int desiredWindowWidth;
  11. int desiredWindowHeight;
  12. ......
  13. mWindowAttributesChangesFlag = 0;
  14. Rect frame = mWinFrame;
  15. if (mFirst) {
  16. mFullRedrawNeeded = true;
  17. mLayoutRequested = true;
  18. if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
  19. || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
  20. // NOTE -- system code, won't try to do compat mode.
  21. Point size = new Point();
  22. mDisplay.getRealSize(size);
  23. desiredWindowWidth = size.x;
  24. desiredWindowHeight = size.y;
  25. } else {
  26. DisplayMetrics packageMetrics =
  27. mView.getContext().getResources().getDisplayMetrics();
  28. desiredWindowWidth = packageMetrics.widthPixels;
  29. desiredWindowHeight = packageMetrics.heightPixels;
  30. }
  31. ......
  32. } else {
  33. desiredWindowWidth = frame.width();
  34. desiredWindowHeight = frame.height();
  35. if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
  36. mFullRedrawNeeded = true;
  37. mLayoutRequested = true;
  38. windowSizeMayChange = true;
  39. }
  40. }

這段代碼用來獲得Activity窗口的當前寬度desiredWindowWidth和當前高度desiredWindowHeight。
        注意,Activity窗口當前的寬度和高度是保存ViewRootImpl類的成員變量mWinFrame中的。ViewRootImpl類的另外兩個成員變量mWidth和mHeight也是用來描述Activity窗口當前的寬度和高度的,但是它們的值是由應用程序進程上一次主動請求WindowManagerService服務計算得到的,並且會一直保持不變到應用程序進程下一次再請求WindowManagerService服務來重新計算爲止。Activity窗口的當前寬度和高度有時候是被WindowManagerService服務主動請求應用程序進程修改的,修改後的值就會保存在ViewRootImpl類的成員變量mWinFrame中,它們可能會與ViewRootImpl類的成員變量mWidth和mHeight的值不同。

        如果Activity窗口是第一次被請求執行測量、佈局和繪製操作,即ViewRootImpl類的成員變量mFirst的值等於true,那麼它的當前寬度desiredWindowWidth和當前高度desiredWindowHeight就等於屏幕的寬度和高度,否則的話,它的當前寬度desiredWindowWidth和當前高度desiredWindowHeight就等於保存在ViewRootImpl類的成員變量mWinFrame中的寬度和高度值。

        如果Activity窗口不是第一次被請求執行測量、佈局和繪製操作,並且Activity窗口主動上一次請求WindowManagerService服務計算得到的寬度mWidth和高度mHeight不等於Activity窗口的當前寬度desiredWindowWidth和當前高度desiredWindowHeight,那麼就說明Activity窗口的大小發生了變化,這時候變量windowSizeMayChange的值就會被標記爲true,以便接下來可以對Activity窗口的大小變化進行處理。

        我們繼續往下閱讀代碼:

  1. boolean insetsChanged = false;
  2. boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
  3. if (layoutRequested) {
  4. final Resources res = mView.getContext().getResources();
  5. if (mFirst) {
  6. // make sure touch mode code executes by setting cached value
  7. // to opposite of the added touch mode.
  8. mAttachInfo.mInTouchMode = !mAddedTouchMode;
  9. ensureTouchModeLocally(mAddedTouchMode);
  10. } else {
  11. if (!mPendingOverscanInsets.equals(mAttachInfo.mOverscanInsets)) {
  12. insetsChanged = true;
  13. }
  14. if (!mPendingContentInsets.equals(mAttachInfo.mContentInsets)) {
  15. insetsChanged = true;
  16. }
  17. if (!mPendingStableInsets.equals(mAttachInfo.mStableInsets)) {
  18. insetsChanged = true;
  19. }
  20. if (!mPendingVisibleInsets.equals(mAttachInfo.mVisibleInsets)) {
  21. mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
  22. if (DEBUG_LAYOUT) Log.v(TAG, "Visible insets changing to: "
  23. + mAttachInfo.mVisibleInsets);
  24. }
  25. if (!mPendingOutsets.equals(mAttachInfo.mOutsets)) {
  26. insetsChanged = true;
  27. }
  28. if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
  29. || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
  30. windowSizeMayChange = true;
  31. if (lp.type == WindowManager.LayoutParams.TYPE_STATUS_BAR_PANEL
  32. || lp.type == WindowManager.LayoutParams.TYPE_INPUT_METHOD) {
  33. // NOTE -- system code, won't try to do compat mode.
  34. Point size = new Point();
  35. mDisplay.getRealSize(size);
  36. desiredWindowWidth = size.x;
  37. desiredWindowHeight = size.y;
  38. } else {
  39. DisplayMetrics packageMetrics = res.getDisplayMetrics();
  40. desiredWindowWidth = packageMetrics.widthPixels;
  41. desiredWindowHeight = packageMetrics.heightPixels;
  42. }
  43. }
  44. }
  45. // Ask host how big it wants to be
  46. windowSizeMayChange |= measureHierarchy(host, lp, res,
  47. desiredWindowWidth, desiredWindowHeight);
  48. }

這段代碼用來在Activity窗口主動請求WindowManagerService服務計算大小之前,對它的頂層視圖進行一次測量操作。
        在分析這段代碼之前,我們首先解釋一下ViewRootImpl類的成員變量mAttachInfo和mPendingContentInsets、mPendingVisibleInsets。ViewRoot類的成員變量mAttachInfo指向的一個AttachInfo對象,這個AttachInfo對象用來描述Activity窗口的屬性,例如,這個AttachInfo對象的成員變量mContentInsets和mVisibleInsets分別用來描述Activity窗口上一次主動請求WindowManagerService服務計算得到的內容邊襯大小和可見邊襯大小,即Activity窗口的當前內容邊襯大小和可見邊襯大小。ViewRootImpl類的成員變量mPendingContentInsets和mPendingVisibleInsets也是用來描述Activity窗口的內容邊襯大小和可見邊襯大小的,不過它們是由WindowManagerService服務主動請求Activity窗口設置的,但是尚未生效。

        我們分兩種情況來分析這段代碼。

        第一種情況是Activity窗口是第一次被請求執行測量、佈局和繪製操作,即ViewRootImpl類的成員變量mFirst的值等於true,那麼調用ensureTouchModeLocally函數。

        第二種情況是Activity窗口不是第一次被請求執行測量、佈局和繪製操作,即ViewRoot類的成員變量mFirst的值等於false,那麼這段代碼就會檢查Activity窗口是否被WindowManagerService服務主動請求設置了一個新的內容邊襯大小mPendingContentInsets和一個新的可見邊襯大小mPendingVisibleInsets。如果是的話,那麼就會分別將它們保存在ViewRoot類的成員變量mAttachInfo所指向的一個AttachInfo對象的成員變量mContentInsets和成員變量mVisibleInsets中。注意,如果Activity窗口被WindowManagerService服務主動請求設置了一個新的內容邊襯大小mPendingContentInsets,那麼這段代碼同時還需要同步調用Activity窗口的頂層視圖host的成員函數fitSystemWindows來將它的四個內邊距(mPaddingLeft,mPaddingTop,mPaddingRight,mPaddingBottom)的大小設置爲新的內容邊襯大小,並且將變量insetsChanged的值設置爲true,表明Activity窗口的內容邊襯大小發生了變化。

        在第二種情況下,如果Activity窗口的寬度被設置爲ViewGroup.LayoutParams.WRAP_CONTENT或者高度被設置爲ViewGroup.LayoutParams.WRAP_CONTENT,那麼就意味着Activity窗口的大小要等於內容區域的大小。但是由於Activity窗口的大小是需要覆蓋整個屏幕的,因此,這時候就會Activity窗口的當前寬度desiredWindowWidth和當前高度desiredWindowHeight設置爲屏幕的寬度和高度。也就是說,如果我們將Activity窗口的寬度和高度設置爲ViewGroup.LayoutParams.WRAP_CONTENT,實際上就意味着它的寬度和高度等於屏幕的寬度和高度。這種情況也意味着Acitivity窗口的大小發生了變化,因此,就將變量windowSizeMayChange的值設置爲true。

        經過上面的一系列處理之後,這段代碼就會調用ViewRoot類的成員函數getRootMeasureSpec來根據Activity窗口的當前寬度和寬度測量規範以及高度和高度測量規範來計算得到它的頂層視圖host的寬度測量規範childWidthMeasureSpec和高度測量規範childHeightMeasureSpec。有了這兩個規範之後,就可以調用measureHierarchy來執行大小測量的工作了。

 我們繼續往下閱讀代碼:

  1. boolean windowShouldResize = layoutRequested && windowSizeMayChange
  2. && ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight())
  3. || (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
  4. frame.width() < desiredWindowWidth && frame.width() != mWidth)
  5. || (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
  6. frame.height() < desiredWindowHeight && frame.height() != mHeight));
  7. // Determine whether to compute insets.
  8. // If there are no inset listeners remaining then we may still need to compute
  9. // insets in case the old insets were non-empty and must be reset.
  10. final boolean computesInternalInsets =
  11. mAttachInfo.mTreeObserver.hasComputeInternalInsetsListeners()
  12. || mAttachInfo.mHasNonEmptyGivenInternalInsets;

 這段代碼主要是做兩件事情。

       第一件事情是檢查是否需要處理Activity窗口的大小變化事件。如果滿足以下條件,那麼就需要處理,即將變量windowShouldResize的值設置爲true:

       1. ViewRootImpl類的成員變量mLayoutRequest的值等於true,這說明應用程序進程正在請求對Activity窗口執行一次測量、佈局和繪製操作;

       2. 變量windowSizeMayChange的值等於true,這說明前面檢測到了Activity窗口的大小發生了變化;

       3. 前面我們已經Activity窗口的頂層視圖host的大小重新進行了測量。如果測量出來的寬度host.mMeasuredWidth和高度host.mMeasuredHeight和Activity窗口的當前寬度mWidth和高度mHeight一樣,那麼即使條件1和條件2能滿足,那麼也是可以認爲是Activity窗口的大小是沒有發生變化的。換句話說,只有當測量出來的大小和當前大小不一致時,才認爲Activity窗口大小發生了變化。另一方面,如果測量出來的大小和當前大小一致,但是Activity窗口的大小被要求設置成WRAP_CONTENT,即設置成和屏幕的寬度desiredWindowWidth和高度desiredWindowHeight一致,但是WindowManagerService服務請求Activity窗口設置的寬度frame.width()和高度frame.height()與它們不一致,而且與Activity窗口上一次請求WindowManagerService服務計算的寬度mWidth和高度mHeight也不一致,那麼也是認爲Activity窗口大小發生了變化的。

        第二件事情是檢查Activity窗口是否需要指定有額外的內容邊襯區域和可見邊襯區域。如果有的話,那麼變量attachInfo所指向的一個AttachInfo對象的成員變量mTreeObserver所描述的一個TreeObserver對象的成員函數hasComputeInternalInsetsListerner的返回值ComputeInternalInsets就會等於true。Activity窗口指定額外的內容邊襯區域和可見邊襯區域是爲了放置一些額外的東西。

        我們繼續往下閱讀代碼:

  1. if (mFirst || windowShouldResize || insetsChanged ||
  2. viewVisibilityChanged || params != null) {
  3. if (viewVisibility == View.VISIBLE) {
  4. // If this window is giving internal insets to the window
  5. // manager, and it is being added or changing its visibility,
  6. // then we want to first give the window manager "fake"
  7. // insets to cause it to effectively ignore the content of
  8. // the window during layout. This avoids it briefly causing
  9. // other windows to resize/move based on the raw frame of the
  10. // window, waiting until we can finish laying out this window
  11. // and get back to the window manager with the ultimately
  12. // computed insets.
  13. insetsPending = computesInternalInsets && (mFirst || viewVisibilityChanged);
  14. }

這段代碼以及接下來的兩段代碼都是在滿足下面的條件之一的情況下執行的:
        1. Activity窗口是第一次執行測量、佈局和繪製操作,即ViewRoot類的成員變量mFirst的值等於true。

        2. 前面得到的變量windowShouldResize的值等於true,即Activity窗口的大小的確是發生了變化。

        3. 前面得到的變量insetsChanged的值等於true,即Activity窗口的內容區域邊襯發生了變化。

        4. Activity窗口的可見性發生了變化,即變量viewVisibilityChanged的值等於true。

        5. Activity窗口的屬性發生了變化,即變量params指向了一個WindowManager.LayoutParams對象。

        在滿足上述條件之一,並且Activity窗口處於可見狀態,即變量viewVisibility的值等於View.VISIBLE,那麼就需要檢查接下來請求WindowManagerService服務計算大小時,是否要告訴WindowManagerService服務它指定了額外的內容區域邊襯和可見區域邊襯,但是這些額外的內容區域邊襯和可見區域邊襯又還有確定。這種情況發生在Activity窗口第一次執行測量、佈局和繪製操作或者由不可見變化可見時。因此,當前面得到的變量computesInternalInsets等於true時,即Activity窗口指定了額外的內容區域邊襯和可見區域邊襯,那麼就需要檢查ViewRoot類的成員變量mFirst或者變量viewVisibilityChanged的值是否等於true。如果這些條件能滿足,那麼變量insetsPending的值就會等於true,表示Activity窗口有額外的內容區域邊襯和可見區域邊襯等待指定。

        我們繼續往下閱讀代碼:

  1. try {
  2. ......
  3. relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
  4. if (mPendingConfiguration.seq != 0) {
  5. updateConfiguration(mPendingConfiguration, !mFirst);
  6. mPendingConfiguration.seq = 0;
  7. }
  8. final boolean overscanInsetsChanged = !mPendingOverscanInsets.equals(
  9. mAttachInfo.mOverscanInsets);
  10. contentInsetsChanged = !mPendingContentInsets.equals(
  11. mAttachInfo.mContentInsets);
  12. final boolean visibleInsetsChanged = !mPendingVisibleInsets.equals(
  13. mAttachInfo.mVisibleInsets);
  14. final boolean stableInsetsChanged = !mPendingStableInsets.equals(
  15. mAttachInfo.mStableInsets);
  16. final boolean outsetsChanged = !mPendingOutsets.equals(mAttachInfo.mOutsets);
  17. if (contentInsetsChanged) {
  18. if (mWidth > 0 && mHeight > 0 && lp != null &&
  19. ((lp.systemUiVisibility|lp.subtreeSystemUiVisibility)
  20. & View.SYSTEM_UI_LAYOUT_FLAGS) == 0 &&
  21. mSurface != null && mSurface.isValid() &&
  22. !mAttachInfo.mTurnOffWindowResizeAnim &&
  23. mAttachInfo.mHardwareRenderer != null &&
  24. mAttachInfo.mHardwareRenderer.isEnabled() &&
  25. lp != null && !PixelFormat.formatHasAlpha(lp.format)
  26. && !mBlockResizeBuffer) {
  27. disposeResizeBuffer();
  28. }
  29. mAttachInfo.mContentInsets.set(mPendingContentInsets);
  30. }
  31. if (overscanInsetsChanged) {
  32. mAttachInfo.mOverscanInsets.set(mPendingOverscanInsets);
  33. contentInsetsChanged = true;
  34. }
  35. if (stableInsetsChanged) {
  36. mAttachInfo.mStableInsets.set(mPendingStableInsets);
  37. contentInsetsChanged = true;
  38. }
  39. if (contentInsetsChanged || mLastSystemUiVisibility !=
  40. mAttachInfo.mSystemUiVisibility || mApplyInsetsRequested
  41. || mLastOverscanRequested != mAttachInfo.mOverscanRequested
  42. || outsetsChanged) {
  43. mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
  44. mLastOverscanRequested = mAttachInfo.mOverscanRequested;
  45. mAttachInfo.mOutsets.set(mPendingOutsets);
  46. mApplyInsetsRequested = false;
  47. dispatchApplyInsets(host);
  48. }
  49. if (visibleInsetsChanged) {
  50. mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets);
  51. }
  52. ......
  53. } catch (RemoteException e) {
  54. }
  55. mAttachInfo.mWindowLeft = frame.left;
  56. mAttachInfo.mWindowTop = frame.top;
  57. if (mWidth != frame.width() || mHeight != frame.height()) {
  58. mWidth = frame.width();
  59. mHeight = frame.height();
  60. }

這段代碼主要就是調用ViewRootImpl類的另外一個成員函數relayoutWindow來請求WindowManagerService服務計算Activity窗口的大小以及內容區域邊襯大小和可見區域邊襯大小。計算完畢之後,Activity窗口的大小就會保存在ViewRootImpl類的成員變量mWinFrame中,而Activity窗口的內容區域邊襯大小和可見區域邊襯大小分別保存在ViewRootImpl類的成員變量mPendingContentInsets和mPendingVisibleInsets中。
        如果這次計算得到的Activity窗口的內容區域邊襯大小mPendingContentInsets和可見區域邊襯大小mPendingVisibleInsets與上一次計算得到的不一致,即與ViewRootImpl類的成員變量mAttachInfo所指向的一個AttachInfo對象的成員變量mContentInsets和mVisibleInsets所描述的大小不一致,那麼變量contentInsetsChanged和visibleInsetsChanged的值就會等於true,表示Activity窗口的內容區域邊襯大小和可見區域邊襯大小發生了變化。

        由於變量frame和ViewRootImpl類的成員變量mWinFrame引用的是同一個Rect對象,因此,這時候變量frame描述的也是Activity窗口請求WindowManagerService服務計算之後得到的大小。這段代碼分別將計算得到的Activity窗口的左上角座標保存在變量attachInfo所指向的一個AttachInfo對象的成員變量mWindowLeft和mWindowTop中,並且將計算得到的Activity窗口的寬度和高度保存在ViewRootImpl類的成員變量mWidth和mHeight中。

        我們繼續往下閱讀代碼:

  1. if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth()
  2. || mHeight != host.getMeasuredHeight() || contentInsetsChanged) {
  3. int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
  4. int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
  5. // Ask host how big it wants to be
  6. performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
  7. // Implementation of weights from WindowManager.LayoutParams
  8. // We just grow the dimensions as needed and re-measure if
  9. // needs be
  10. int width = host.getMeasuredWidth();
  11. int height = host.getMeasuredHeight();
  12. boolean measureAgain = false;
  13. if (lp.horizontalWeight > 0.0f) {
  14. width += (int) ((mWidth - width) * lp.horizontalWeight);
  15. childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
  16. MeasureSpec.EXACTLY);
  17. measureAgain = true;
  18. }
  19. if (lp.verticalWeight > 0.0f) {
  20. height += (int) ((mHeight - height) * lp.verticalWeight);
  21. childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
  22. MeasureSpec.EXACTLY);
  23. measureAgain = true;
  24. }
  25. if (measureAgain) {
  26. if (DEBUG_LAYOUT) Log.v(TAG,
  27. "And hey let's measure once more: width=" + width
  28. + " height=" + height);
  29. performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
  30. }
  31. layoutRequested = true;//代表可以進行應用佈局了
  32. }

這段代碼用來檢查是否需要重新測量Activity窗口的大小。如果滿足以下條件之一,那麼就需要重新測量:
        1. Activity窗口的觸摸模式發生了變化,並且由此引發了Activity窗口當前獲得焦點的控件發生了變化,即變量focusChangedDueToTouchMode的值等於true。這個檢查是通過調用ViewRootImpl類的成員函數ensureTouchModeLocally來實現的。

        2. Activity窗口前面測量出來的寬度host.mMeasuredWidth和高度host.mMeasuredHeight不等於WindowManagerService服務計算出來的寬度mWidth和高度mHeight。

        3. Activity窗口的內容區域邊襯大小和可見區域邊襯大小發生了變化,即前面得到的變量contentInsetsChanged的值等於true。

        重新計算了一次之後,如果Activity窗口的屬性lp表明需要對測量出來的寬度width和高度height進行擴展,即變量lp所指向的一個WindowManager.LayoutParams對象的成員變量horizontalWeight和verticalWeight的值大於0.0,那麼就需要對Activity窗口的頂層視圖host的最大可用空間進行擴展後再進行一次測量工作。

        我們繼續往下閱讀最後一段代碼:

  1. final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
  2. boolean triggerGlobalLayoutListener = didLayout
  3. || mAttachInfo.mRecomputeGlobalAttributes;
  4. if (didLayout) {
  5. performLayout(lp, desiredWindowWidth, desiredWindowHeight);
  6. ......
  7. }
  8. if (triggerGlobalLayoutListener) {
  9. mAttachInfo.mRecomputeGlobalAttributes = false;
  10. mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
  11. }
  12. if (computesInternalInsets) {
  13. // Clear the original insets.
  14. final ViewTreeObserver.InternalInsetsInfo insets = mAttachInfo.mGivenInternalInsets;
  15. insets.reset();
  16. // Compute new insets in place.
  17. mAttachInfo.mTreeObserver.dispatchOnComputeInternalInsets(insets);
  18. mAttachInfo.mHasNonEmptyGivenInternalInsets = !insets.isEmpty();
  19. // Tell the window manager.
  20. if (insetsPending || !mLastGivenInsets.equals(insets)) {
  21. mLastGivenInsets.set(insets);
  22. // Translate insets to screen coordinates if needed.
  23. final Rect contentInsets;
  24. final Rect visibleInsets;
  25. final Region touchableRegion;
  26. if (mTranslator != null) {
  27. contentInsets = mTranslator.getTranslatedContentInsets(insets.contentInsets);
  28. visibleInsets = mTranslator.getTranslatedVisibleInsets(insets.visibleInsets);
  29. touchableRegion = mTranslator.getTranslatedTouchableArea(insets.touchableRegion);
  30. } else {
  31. contentInsets = insets.contentInsets;
  32. visibleInsets = insets.visibleInsets;
  33. touchableRegion = insets.touchableRegion;
  34. }
  35. try {
  36. mWindowSession.setInsets(mWindow, insets.mTouchableInsets,
  37. contentInsets, visibleInsets, touchableRegion);
  38. } catch (RemoteException e) {
  39. }
  40. }
  41. }

經過前面漫長的操作後,Activity窗口的大小測量工作終於塵埃落定,這時候就可以對Activity窗口的內容進行佈局了,前提是ViewRoot類的成員變量layoutRequested 的值等於true。對Activity窗口的內容進行佈局是通過調用performLayout來實現的。

從前面的描述可以知道,當變量computesInternalInsets的值等於true時,就表示Activity窗口指定有額外的內容區域邊襯和可見區域邊襯,這時候就是時候把它們告訴給WindowManagerService服務了,以便WindowManagerService服務下次可以知道Activity窗口的真實佈局。Activity窗口額外指定的內容區域邊襯大小和可見區域邊襯大小是通過調用變量attachInfo所指向的一個AttachInfo對象的成員變量mTreeObserver所描述的一個TreeObserver對象的成員函數dispatchOnComputeInternalInsets來計算的。計算完成之後,就會保存在變量attachInfo所指向的一個AttachInfo對象的成員變量mGivenInternalInsets中,並且會通過ViewRoot類的靜態成員變量sWindowSession所指向一個Binder代理對象來設置到WindowManagerService服務中去。

        注意,如果ViewRoot類的成員變量mTranslator指向了一個Translator對象,那麼就說明Activity窗口是運行兼容模式中,這時候就需要將前面計算得到的內容區域邊襯大小和可見區域邊襯大小轉化到兼容模式下,然後纔可以保存在變量attachInfo所指向的一個AttachInfo對象的成員變量mGivenInternalInsets中,以及設置到WindowManagerService服務中去。

        另外,只有前面得到的變量insetsPending的值等於true,即Activity窗口正在等待告訴WindowManagerService服務它有額外指定的內容區域邊襯和可見區域邊襯,或者Activty窗口額外指定的內容區域邊襯和可見區域邊襯發生了變化,即Activty窗口上一次額外指定的內容區域邊襯和可見區域邊襯mLastGivenInsets不等於當前這次指定的內容區域邊襯和可見區域邊襯insets,Activity窗口額外指定的內容區域邊襯和可見區域邊襯纔會被設置到WindowManagerService服務中去。

        ViewRoot類的成員函數再接下來的工作就是繪製Activity窗口的UI了,這個之前我們的博客分析過了。

下面就是看ViewRootImpl的relayoutWindow了。

2.2 relayoutWindow

我們先看ViewRootImpl的relayoutWindow函數:

  1. private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
  2. boolean insetsPending) throws RemoteException {
  3. ......
  4. int relayoutResult = mWindowSession.relayout(
  5. mWindow, mSeq, params,
  6. (int) (mView.getMeasuredWidth() * appScale + 0.5f),
  7. (int) (mView.getMeasuredHeight() * appScale + 0.5f),
  8. viewVisibility, insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0,
  9. mWinFrame, mPendingOverscanInsets, mPendingContentInsets, mPendingVisibleInsets,
  10. mPendingStableInsets, mPendingOutsets, mPendingConfiguration, mSurface);
  11. if (restore) {
  12. params.restore();
  13. }
  14. if (mTranslator != null) {
  15. mTranslator.translateRectInScreenToAppWinFrame(mWinFrame);
  16. mTranslator.translateRectInScreenToAppWindow(mPendingOverscanInsets);
  17. mTranslator.translateRectInScreenToAppWindow(mPendingContentInsets);
  18. mTranslator.translateRectInScreenToAppWindow(mPendingVisibleInsets);
  19. mTranslator.translateRectInScreenToAppWindow(mPendingStableInsets);
  20. }
  21. return relayoutResult;
  22. }

我們來看下參數:

     1. ViewRoot類的成員變量mWindow,用來標誌要計算的是哪一個Activity窗口的大小。

       2. Activity窗口的頂層視圖經過測量後得到的寬度和高度。注意,傳遞給WindowManagerService服務的寬度和高度是已經考慮了Activity窗口所設置的縮放因子了的。

       3. Activity窗口的可見狀態,即參數viewVisibility。

       4. Activity窗口是否有額外的內容區域邊襯和可見區域邊襯等待告訴給WindowManagerService服務,即參數insetsPending。

       5. ViewRoot類的成員變量mWinFrame,這是一個輸出參數,用來保存WindowManagerService服務計算後得到的Activity窗口的大小。

       6. ViewRoot類的成員變量mPendingContentInsets,這是一個輸出參數,用來保存WindowManagerService服務計算後得到的Activity窗口的內容區域邊襯大小。

       7. ViewRoot類的成員變量mPendingVisibleInsets,這是一個輸出參數,用來保存WindowManagerService服務計算後得到的Activity窗口的可見區域邊襯大小。

       8. ViewRoot類的成員變量mPendingConfiguration,這是一個輸出參數,用來保存WindowManagerService服務返回來的Activity窗口的配置信息。

       9. ViewRoot類的成員變量mSurface,這是一個輸出參數,用來保存WindowManagerService服務返回來的Activity窗口的繪圖表面。

       得到了Activity窗口的大小以及內容區域邊襯大小和可見區域邊襯大小之後,如果Activity窗口是運行在兼容模式中,即ViewRoot類的成員變量mTranslator指向了一個Translator對象,那麼就需要調用它的成員函數translateRectInScreenToAppWindow來對它們進行轉換。

       接下來,我們繼續分析Session類的成員函數relayout,以便可以瞭解WindowManagerService服務是如何計算一個Activity窗口的大小的。


最後就到WMS的relayoutWindow函數了,我們放到下篇博客再分析。



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