<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                合規國際互聯網加速 OSASE為企業客戶提供高速穩定SD-WAN國際加速解決方案。 廣告
                原文出處——>[Android窗口管理服務WindowManagerService計算Activity窗口大小的過程分析](http://blog.csdn.net/luoshengyang/article/details/8479101) 在Android系統中,Activity窗口的大小是由WindowManagerService服務來計算的。WindowManagerService服務會根據屏幕及其裝飾區的大小來決定Activity窗口的大小。一個Activity窗口只有知道自己的大小之后,才能對它里面的UI元素進行測量、布局以及繪制。本文將詳細分析WindowManagerService服務計算Activity窗口大小的過程。 一般來說,Activity窗口的大小等于整個屏幕的大小,但是它并不占據著整塊屏幕。為了理解這一點,我們首先分析一下Activity窗口的區域是如何劃分的。 我們知道,Activity窗口的上方一般會有一個狀態欄,用來顯示3G信號、電量使用等圖標,如圖1所示。 :-: ![](https://box.kancloud.cn/9f013fcb6fe9deb70b705a57fd531432_915x424.jpg) 圖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所示。 :-: ![](https://box.kancloud.cn/e5eb637fd699eb073c8e4f042eb39f9b_913x447.jpg) 圖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元素進行測量、布局以及繪制等操作了。 從前面Android應用程序窗口(Activity)的繪圖表面(Surface)的創建過程分析一文可以知道,應用程序進程是從ViewRoot類的成員函數performTraversals開始,向WindowManagerService服務請求計算一個Activity窗口的大小的,因此,接下來我們就從ViewRoot類的成員函數performTraversals開始分析一個Activity窗口大小的計算過程,如圖3所示。 :-: ![](https://box.kancloud.cn/bfa34d76af1770110cabd614ad49f8cf_729x933.jpg) 圖3 Activity窗口大小的計算過程 這個過程可以分為11個步驟,接下來我們就詳細分析每一個步驟。 **Step 1. ViewRoot.performTraversals** 這個函數定義在文件frameworks/base/core/java/android/view/ViewRoot.java中,它的實現很復雜,一共有600-行,不過大部分代碼都是用來計算Activity窗口的大小的,我們分段來閱讀: ~~~ public final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Callbacks { ...... private void performTraversals() { ...... final View host = mView; ...... int desiredWindowWidth; int desiredWindowHeight; int childWidthMeasureSpec; int childHeightMeasureSpec; ...... Rect frame = mWinFrame; if (mFirst) { ...... DisplayMetrics packageMetrics = mView.getContext().getResources().getDisplayMetrics(); desiredWindowWidth = packageMetrics.widthPixels; desiredWindowHeight = packageMetrics.heightPixels; } else { desiredWindowWidth = frame.width(); desiredWindowHeight = frame.height(); if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) { ...... windowResizesToFitContent = true; } } ~~~ 這段代碼用來獲得Activity窗口的當前寬度desiredWindowWidth和當前高度desiredWindowHeight。 注意,Activity窗口當前的寬度和高度是保存ViewRoot類的成員變量mWinFrame中的。ViewRoot類的另外兩個成員變量mWidth和mHeight也是用來描述Activity窗口當前的寬度和高度的,但是它們的值是由應用程序進程上一次主動請求WindowManagerService服務計算得到的,并且會一直保持不變到應用程序進程下一次再請求WindowManagerService服務來重新計算為止。Activity窗口的當前寬度和高度有時候是被WindowManagerService服務主動請求應用程序進程修改的,修改后的值就會保存在ViewRoot類的成員變量mWinFrame中,它們可能會與ViewRoot類的成員變量mWidth和mHeight的值不同。 如果Activity窗口是第一次被請求執行測量、布局和繪制操作,即ViewRoot類的成員變量mFirst的值等于true,那么它的當前寬度desiredWindowWidth和當前高度desiredWindowHeight就等于屏幕的寬度和高度,否則的話,它的當前寬度desiredWindowWidth和當前高度desiredWindowHeight就等于保存在ViewRoot類的成員變量mWinFrame中的寬度和高度值。 如果Activity窗口不是第一次被請求執行測量、布局和繪制操作,并且Activity窗口主動上一次請求WindowManagerService服務計算得到的寬度mWidth和高度mHeight不等于Activity窗口的當前寬度desiredWindowWidth和當前高度desiredWindowHeight,那么就說明Activity窗口的大小發生了變化,這時候變量windowResizesToFitContent的值就會被標記為true,以便接下來可以對Activity窗口的大小變化進行處理。 我們繼續往下閱讀代碼: ~~~ boolean insetsChanged = false; if (mLayoutRequested) { ...... if (mFirst) { host.fitSystemWindows(mAttachInfo.mContentInsets); ...... } else { if (!mAttachInfo.mContentInsets.equals(mPendingContentInsets)) { mAttachInfo.mContentInsets.set(mPendingContentInsets); host.fitSystemWindows(mAttachInfo.mContentInsets); insetsChanged = true; ...... } if (!mAttachInfo.mVisibleInsets.equals(mPendingVisibleInsets)) { mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets); ...... } if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT || lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) { windowResizesToFitContent = true; DisplayMetrics packageMetrics = mView.getContext().getResources().getDisplayMetrics(); desiredWindowWidth = packageMetrics.widthPixels; desiredWindowHeight = packageMetrics.heightPixels; } } childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width); childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height); ...... host.measure(childWidthMeasureSpec, childHeightMeasureSpec); ...... } ~~~ 這段代碼用來在Activity窗口主動請求WindowManagerService服務計算大小之前,對它的頂層視圖進行一次測量操作。 在分析這段代碼之前,我們首先解釋一下ViewRoot類的成員變量mAttachInfo和mPendingContentInsets、mPendingVisibleInsets。ViewRoot類的成員變量mAttachInfo指向的一個AttachInfo對象,這個AttachInfo對象用來描述Activity窗口的屬性,例如,這個AttachInfo對象的成員變量mContentInsets和mVisibleInsets分別用來描述Activity窗口上一次主動請求WindowManagerService服務計算得到的內容邊襯大小和可見邊襯大小,即Activity窗口的當前內容邊襯大小和可見邊襯大小。ViewRoot類的成員變量mPendingContentInsets和mPendingVisibleInsets也是用來描述Activity窗口的內容邊襯大小和可見邊襯大小的,不過它們是由WindowManagerService服務主動請求Activity窗口設置的,但是尚未生效。 我們分兩種情況來分析這段代碼。 第一種情況是Activity窗口是第一次被請求執行測量、布局和繪制操作,即ViewRoot類的成員變量mFirst的值等于true,那么這段代碼在測量Activity窗口的頂層視圖host的大小之前,首先會調用這個頂層視圖host的成員函數fitSystemWindows來設置它的四個內邊距(mPaddingLeft,mPaddingTop,mPaddingRight,mPaddingBottom)的大小設置為Activity窗口的初始化內容邊襯大小。這樣做的目的是可以在Activity窗口的四周留下足夠的區域來放置可能會出現的系統窗口,也就是狀態欄和輸入法窗口。 第二種情況是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窗口的大小發生了變化,因此,就將變量windowResizesToFitContent的值設置為true。 經過上面的一系列處理之后,這段代碼就會調用ViewRoot類的成員函數getRootMeasureSpec來根據Activity窗口的當前寬度和寬度測量規范以及高度和高度測量規范來計算得到它的頂層視圖host的寬度測量規范childWidthMeasureSpec和高度測量規范childHeightMeasureSpec。有了這兩個規范之后,就可以調用Activity窗口的頂層視圖host的成員函數measure來執行大小測量的工作了。這個大小測量的過程可以參考前面Android應用程序窗口(Activity)的測量(Measure)、布局(Layout)和繪制(Draw)過程分析一文。 我們繼續往下閱讀代碼: ~~~ boolean windowShouldResize = mLayoutRequested && windowResizesToFitContent && ((mWidth != host.mMeasuredWidth || mHeight != host.mMeasuredHeight) || (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT && frame.width() < desiredWindowWidth && frame.width() != mWidth) || (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT && frame.height() < desiredWindowHeight && frame.height() != mHeight)); final boolean computesInternalInsets = attachInfo.mTreeObserver.hasComputeInternalInsetsListeners(); ~~~ 這段代碼主要是做兩件事情。 第一件事情是檢查是否需要處理Activity窗口的大小變化事件。如果滿足以下條件,那么就需要處理,即將變量windowShouldResize的值設置為true: 1. ViewRoot類的成員變量mLayoutRequest的值等于true,這說明應用程序進程正在請求對Activity窗口執行一次測量、布局和繪制操作; 2. 變量windowResizesToFitContent的值等于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窗口指定額外的內容邊襯區域和可見邊襯區域是為了放置一些額外的東西。 我們繼續往下閱讀代碼: ~~~ if (mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null) { if (viewVisibility == View.VISIBLE) { // If this window is giving internal insets to the window // manager, and it is being added or changing its visibility, // then we want to first give the window manager "fake" // insets to cause it to effectively ignore the content of // the window during layout. This avoids it briefly causing // other windows to resize/move based on the raw frame of the // window, waiting until we can finish laying out this window // and get back to the window manager with the ultimately // computed insets. insetsPending = computesInternalInsets && (mFirst || viewVisibilityChanged); ...... } ~~~ 這段代碼以及接下來的兩段代碼都是在滿足下面的條件之一的情況下執行的: 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窗口有額外的內容區域邊襯和可見區域邊襯等待指定。 我們繼續往下閱讀代碼: ~~~ boolean contentInsetsChanged = false; boolean visibleInsetsChanged; ...... try { ...... relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); contentInsetsChanged = !mPendingContentInsets.equals( mAttachInfo.mContentInsets); visibleInsetsChanged = !mPendingVisibleInsets.equals( mAttachInfo.mVisibleInsets); if (contentInsetsChanged) { mAttachInfo.mContentInsets.set(mPendingContentInsets); host.fitSystemWindows(mAttachInfo.mContentInsets); ...... } if (visibleInsetsChanged) { mAttachInfo.mVisibleInsets.set(mPendingVisibleInsets); ...... } ...... } catch (RemoteException e) { } ...... attachInfo.mWindowLeft = frame.left; attachInfo.mWindowTop = frame.top; // !!FIXME!! This next section handles the case where we did not get the // window size we asked for. We should avoid this by getting a maximum size from // the window session beforehand. mWidth = frame.width(); mHeight = frame.height(); ~~~ 這段代碼主要就是調用ViewRoot類的另外一個成員函數relayoutWindow來請求WindowManagerService服務計算Activity窗口的大小以及內容區域邊襯大小和可見區域邊襯大小。計算完畢之后,Activity窗口的大小就會保存在ViewRoot類的成員變量mWinFrame中,而Activity窗口的內容區域邊襯大小和可見區域邊襯大小分別保存在ViewRoot類的成員變量mPendingContentInsets和mPendingVisibleInsets中。 如果這次計算得到的Activity窗口的內容區域邊襯大小mPendingContentInsets和可見區域邊襯大小mPendingVisibleInsets與上一次計算得到的不一致,即與ViewRoot類的成員變量mAttachInfo所指向的一個AttachInfo對象的成員變量mContentInsets和mVisibleInsets所描述的大小不一致,那么變量contentInsetsChanged和visibleInsetsChanged的值就會等于true,表示Activity窗口的內容區域邊襯大小和可見區域邊襯大小發生了變化。 由于變量frame和ViewRoot類的成員變量mWinFrame引用的是同一個Rect對象,因此,這時候變量frame描述的也是Activity窗口請求WindowManagerService服務計算之后得到的大小。這段代碼分別將計算得到的Activity窗口的左上角坐標保存在變量attachInfo所指向的一個AttachInfo對象的成員變量mWindowLeft和mWindowTop中,并且將計算得到的Activity窗口的寬度和高度保存在ViewRoot類的成員變量mWidth和mHeight中。 我們繼續往下閱讀代碼: ~~~ boolean focusChangedDueToTouchMode = ensureTouchModeLocally( (relayoutResult&WindowManagerImpl.RELAYOUT_IN_TOUCH_MODE) != 0); if (focusChangedDueToTouchMode || mWidth != host.mMeasuredWidth || mHeight != host.mMeasuredHeight || contentInsetsChanged) { childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width); childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height); ...... // Ask host how big it wants to be host.measure(childWidthMeasureSpec, childHeightMeasureSpec); // Implementation of weights from WindowManager.LayoutParams // We just grow the dimensions as needed and re-measure if // needs be int width = host.mMeasuredWidth; int height = host.mMeasuredHeight; boolean measureAgain = false; if (lp.horizontalWeight > 0.0f) { width += (int) ((mWidth - width) * lp.horizontalWeight); childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY); measureAgain = true; } if (lp.verticalWeight > 0.0f) { height += (int) ((mHeight - height) * lp.verticalWeight); childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY); measureAgain = true; } if (measureAgain) { ...... host.measure(childWidthMeasureSpec, childHeightMeasureSpec); } mLayoutRequested = true; } ~~~ 這段代碼用來檢查是否需要重新測量Activity窗口的大小。如果滿足以下條件之一,那么就需要重新測量: 1. Activity窗口的觸摸模式發生了變化,并且由此引發了Activity窗口當前獲得焦點的控件發生了變化,即變量focusChangedDueToTouchMode的值等于true。這個檢查是通過調用ViewRoot類的成員函數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的最大可用空間進行擴展后再進行一次測量工作。 我們繼續往下閱讀最后一段代碼: ~~~ final boolean didLayout = mLayoutRequested; ...... if (didLayout) { ...... host.layout(0, 0, host.mMeasuredWidth, host.mMeasuredHeight); ...... } if (computesInternalInsets) { ViewTreeObserver.InternalInsetsInfo insets = attachInfo.mGivenInternalInsets; final Rect givenContent = attachInfo.mGivenInternalInsets.contentInsets; final Rect givenVisible = attachInfo.mGivenInternalInsets.visibleInsets; givenContent.left = givenContent.top = givenContent.right = givenContent.bottom = givenVisible.left = givenVisible.top = givenVisible.right = givenVisible.bottom = 0; attachInfo.mTreeObserver.dispatchOnComputeInternalInsets(insets); Rect contentInsets = insets.contentInsets; Rect visibleInsets = insets.visibleInsets; if (mTranslator != null) { contentInsets = mTranslator.getTranslatedContentInsets(contentInsets); visibleInsets = mTranslator.getTranslatedVisbileInsets(visibleInsets); } if (insetsPending || !mLastGivenInsets.equals(insets)) { mLastGivenInsets.set(insets); try { sWindowSession.setInsets(mWindow, insets.mTouchableInsets, contentInsets, visibleInsets); } catch (RemoteException e) { } } } ...... } ...... ~~~ 經過前面漫長的操作后,Activity窗口的大小測量工作終于塵埃落定,這時候就可以對Activity窗口的內容進行布局了,前提是ViewRoot類的成員變量mLayoutRequest的值等于true。對Activity窗口的內容進行布局是通過調用它的頂層視圖host的成員函數layout來實現的,這個過程可以參考前面Android應用程序窗口(Activity)的測量(Measure)、布局(Layout)和繪制(Draw)過程分析一文。 從前面的描述可以知道,當變量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了,這個過程同樣可以參考前面Android應用程序窗口(Activity)的測量(Measure)、布局(Layout)和繪制(Draw)過程分析一文。 接下來,我們繼續分析ViewRoot類的成員函數relayoutWindow的實現,以便可以了解它是如何請求WindowManagerService服務計算Activity窗口的大小的。 **Step 2. ViewRoot.relayoutWindow** ~~~ public final class ViewRoot extends Handler implements ViewParent, View.AttachInfo.Callbacks { ...... private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility, boolean insetsPending) throws RemoteException { float appScale = mAttachInfo.mApplicationScale; ...... int relayoutResult = sWindowSession.relayout( mWindow, params, (int) (mView.mMeasuredWidth * appScale + 0.5f), (int) (mView.mMeasuredHeight * appScale + 0.5f), viewVisibility, insetsPending, mWinFrame, mPendingContentInsets, mPendingVisibleInsets, mPendingConfiguration, mSurface); ...... if (mTranslator != null) { mTranslator.translateRectInScreenToAppWinFrame(mWinFrame); mTranslator.translateRectInScreenToAppWindow(mPendingContentInsets); mTranslator.translateRectInScreenToAppWindow(mPendingVisibleInsets); } return relayoutResult; } ...... } ~~~ 這個函數定義在文件frameworks/base/core/java/android/view/ViewRoot.java中。 從前面Android應用程序窗口(Activity)與WindowManagerService服務的連接過程分析一文可以知道,ViewRoot類的靜態成員變量sWindowSession是一個Binder代理對象,它引用了運行在WindowManagerService服務這一側的一個Session對象,ViewRoot類的成員函數relayoutWindow通過調用這個Session對象的成員函數relayout來請求WindowManagerService服務計算Activity窗口的大小,其中,傳遞給WindowManagerService服務的參數包括: 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窗口的大小的。 **Step 3. Session.relayout** ~~~ public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor { ...... private final class Session extends IWindowSession.Stub implements IBinder.DeathRecipient { ...... public int relayout(IWindow window, WindowManager.LayoutParams attrs, int requestedWidth, int requestedHeight, int viewFlags, boolean insetsPending, Rect outFrame, Rect outContentInsets, Rect outVisibleInsets, Configuration outConfig, Surface outSurface) { //Log.d(TAG, ">>>>>> ENTERED relayout from " + Binder.getCallingPid()); int res = relayoutWindow(this, window, attrs, requestedWidth, requestedHeight, viewFlags, insetsPending, outFrame, outContentInsets, outVisibleInsets, outConfig, outSurface); //Log.d(TAG, "<<<<<< EXITING relayout to " + Binder.getCallingPid()); return res; } ...... } ...... } ~~~ 這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。 Session類的成員函數relayout的實現很簡單,它只是調用了WindowManagerService類的成員函數relayoutWindow來進一步計算參數window所描述的一個Activity窗品的大小,接下來我們就繼續分析WindowManagerService類的成員函數relayoutWindow的實現。 **Step 4. WindowManagerService.relayoutWindow** ~~~ public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor { ...... public int relayoutWindow(Session session, IWindow client, WindowManager.LayoutParams attrs, int requestedWidth, int requestedHeight, int viewVisibility, boolean insetsPending, Rect outFrame, Rect outContentInsets, Rect outVisibleInsets, Configuration outConfig, Surface outSurface) { ...... synchronized(mWindowMap) { WindowState win = windowForClientLocked(session, client, false); ...... win.mRequestedWidth = requestedWidth; win.mRequestedHeight = requestedHeight; ...... final boolean scaledWindow = ((win.mAttrs.flags & WindowManager.LayoutParams.FLAG_SCALED) != 0); if (scaledWindow) { // requested{Width|Height} Surface's physical size // attrs.{width|height} Size on screen win.mHScale = (attrs.width != requestedWidth) ? (attrs.width / (float)requestedWidth) : 1.0f; win.mVScale = (attrs.height != requestedHeight) ? (attrs.height / (float)requestedHeight) : 1.0f; } else { win.mHScale = win.mVScale = 1; } ...... win.mGivenInsetsPending = insetsPending; ...... performLayoutAndPlaceSurfacesLocked(); ...... outFrame.set(win.mFrame); outContentInsets.set(win.mContentInsets); outVisibleInsets.set(win.mVisibleInsets); ...... } return (inTouchMode ? WindowManagerImpl.RELAYOUT_IN_TOUCH_MODE : 0) | (displayed ? WindowManagerImpl.RELAYOUT_FIRST_TIME : 0); } ...... } ~~~ 這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。 參數client是一個Binder代理對象,它引用了運行在應用程序進程這一側中的一個W對象,用來標志一個Activity窗口。從前面Android應用程序窗口(Activity)與WindowManagerService服務的連接過程分析一文可以知道,在應用程序進程這一側的每一個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窗口的大小計算過程。 **Step 5. WindowManagerService.performLayoutAndPlaceSurfacesLocked** ~~~ public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor { ...... private final void performLayoutAndPlaceSurfacesLocked() { if (mInLayout) { ...... return; } ...... boolean recoveringMemory = false; if (mForceRemoves != null) { recoveringMemory = true; // Wait a little it for things to settle down, and off we go. for (int i=0; i<mForceRemoves.size(); i++) { WindowState ws = mForceRemoves.get(i); Slog.i(TAG, "Force removing: " + ws); removeWindowInnerLocked(ws.mSession, ws); } mForceRemoves = null; ...... } mInLayout = true; try { performLayoutAndPlaceSurfacesLockedInner(recoveringMemory); int i = mPendingRemove.size()-1; if (i >= 0) { while (i >= 0) { WindowState w = mPendingRemove.get(i); removeWindowInnerLocked(w.mSession, w); i--; } mPendingRemove.clear(); mInLayout = false; assignLayersLocked(); mLayoutNeeded = true; performLayoutAndPlaceSurfacesLocked(); } else { mInLayout = false; ...... } ...... } catch (RuntimeException e) { mInLayout = false; ...... } } ...... } ~~~ 這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。 從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的過程中,于是就不用往下執行了。 接下來,我們就繼續分析WindowManagerService類的成員函數performLayoutAndPlaceSurfacesLockedInner的實現,以便可以了解Activity窗口的大小計算過程。 **Step 6. WindowManagerService.performLayoutAndPlaceSurfacesLockedInner** ~~~ public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor { ...... private final void performLayoutAndPlaceSurfacesLockedInner( boolean recoveringMemory) { ...... Surface.openTransaction(); ...... try { ...... int repeats = 0; int changes = 0; do { repeats++; if (repeats > 6) { ...... break; } // FIRST LOOP: Perform a layout, if needed. if (repeats < 4) { changes = performLayoutLockedInner(); if (changes != 0) { continue; } } else { Slog.w(TAG, "Layout repeat skipped after too many iterations"); changes = 0; } // SECOND LOOP: Execute animations and update visibility of windows. ...... } while (changes != 0); // THIRD LOOP: Update the surfaces of all windows. ...... } catch (RuntimeException e) { ...... } ...... Surface.closeTransaction(); ...... // Destroy the surface of any windows that are no longer visible. ...... // Time to remove any exiting tokens? ...... // Time to remove any exiting applications? ...... } ...... } ~~~ 這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。 WindowManagerService類的成員函數performLayoutAndPlaceSurfacesLockedInner是一個巨無霸的函數,它一共有1200+行代碼,承載了WindowManagerService服務的核心功能。對于這樣一個巨無霸函數,要逐行地分析它的實現是很困難的,因為要理解各種上下文信息,才可以清楚地知道它的執行過程。這里我們就大概地分析它的實現框架,以后再逐步地分析它的具體實現: 1. 在一個最多執行7次的while循環中,做兩件事情:第一件事情是計算各個窗品的大小,這是通過調用另外一個成員函數performLayoutLockedInner來實現的;第二件事情是執行窗口的動畫,主要是處理窗口的啟動窗口顯示動畫和窗口切換過程中的動畫,以及更新各個窗口的可見性。注意,每一次while循環執行之后,如果發現系統中的各個窗口的相應布局屬性不再發生變化,那么就不行執行下一次的while循環了,即該while循環可能不用執行7次就結束了。窗口的動畫顯示過程和窗口的可見性更新過程是相當復雜的,它們也是WindowManagerService服務最為核的地方,在后面的文章中,我們再詳細分析。 2. 經過第1點的操作之后,接下來就可以將各個窗口的屬性,例如,大小、位置等屬性,通知SurfaceFlinger服務了,也就是讓SurfaceFlinger服務更新它里面的各個Layer的屬性值,以便可以對這些Layer執行可見性計算、合成等操作,最后渲染到硬件幀緩沖區中去。SurfaceFlinger服務計算系統中各個窗口,即各個Layer的可見性,以便將它們合成、渲染到硬件幀緩沖區的過程可以參考前面Android系統Surface機制的SurfaceFlinger服務渲染應用程序UI的過程分析一文。注意,各個窗口的屬性更新操作是被包含在SurfaceFlinger服務的一個事務中的,即一個Transaction中,這樣做是為了避免每更新一個窗口的一個屬性就觸發SurfaceFlinger服務重新計算各個Layer的可見性,以及對各個Layer進行合并和渲染的操作。啟動SurfaceFlinger服務的一個事務可以通過調用Surface類的靜態成員函數openTransaction來實現,而關閉SurfaceFlinger服務的一個事務可以通過調用Surface類的靜態成員函數closeTransaction來實現。 3. 經過第1點和第2點的操作之后,一次系統UI的刷新過程就完成了,這時候就會將系統中的那些不會再顯示的窗口的繪圖表面銷毀掉,并且將那些已經完成退出了的窗口令牌,即將我們在前面Android應用程序窗口(Activity)與WindowManagerService服務的連接過程分析一文中所提到的WindowToken移除掉,以及將那些已經退出了的Activity窗口令牌,即將我們在前面Android應用程序窗口(Activity)與WindowManagerService服務的連接過程分析一文中所提到的AppWindowToken也移除掉。這一步實際執行的是窗口清理操作。 上述三個操作是WindowManagerService類的成員函數performLayoutAndPlaceSurfacesLockedInner的實現關鍵所在,理解了這三個操作,基本也就可以理解WindowManagerService服務刷新系統UI的過程了。 接下來,我們繼續分析WindowManagerService類的成員函數performLayoutLockedInner的實現,以便可以繼續了解Activity窗口的大小計算過程。 **Step 7. WindowManagerService.performLayoutLockedInner** ~~~ public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor { ...... final WindowManagerPolicy mPolicy = PolicyManager.makeNewWindowManager(); ...... /** * Z-ordered (bottom-most first) list of all Window objects. */ final ArrayList<WindowState> mWindows = new ArrayList<WindowState>(); ...... private final int performLayoutLockedInner() { ...... final int dw = mDisplay.getWidth(); final int dh = mDisplay.getHeight(); final int N = mWindows.size(); int i; ...... mPolicy.beginLayoutLw(dw, dh); int seq = mLayoutSeq+1; if (seq < 0) seq = 0; mLayoutSeq = seq; // First perform layout of any root windows (not attached // to another window). int topAttached = -1; for (i = N-1; i >= 0; i--) { WindowState win = mWindows.get(i); ...... final AppWindowToken atoken = win.mAppToken; final boolean gone = win.mViewVisibility == View.GONE || !win.mRelayoutCalled || win.mRootToken.hidden || (atoken != null && atoken.hiddenRequested) || win.mAttachedHidden || win.mExiting || win.mDestroying; ...... if (!gone || !win.mHaveFrame) { if (!win.mLayoutAttached) { mPolicy.layoutWindowLw(win, win.mAttrs, null); win.mLayoutSeq = seq; ...... } else { if (topAttached < 0) topAttached = i; } } } ...... for (i = topAttached; i >= 0; i--) { WindowState win = mWindows.get(i); // If this view is GONE, then skip it -- keep the current // frame, and let the caller know so they can ignore it // if they want. (We do the normal layout for INVISIBLE // windows, since that means "perform layout as normal, // just don't display"). if (win.mLayoutAttached) { ...... if ((win.mViewVisibility != View.GONE && win.mRelayoutCalled) || !win.mHaveFrame) { mPolicy.layoutWindowLw(win, win.mAttrs, win.mAttachedWindow); win.mLayoutSeq = seq; ...... } } } ...... return mPolicy.finishLayoutLw(); } ...... } ~~~ 這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。 在分析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窗口。從前面Android應用程序窗口(Activity)與WindowManagerService服務的連接過程分析一文可以知道,如果一個窗口是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窗口的大小計算過程。 **Step 8. PhoneWindowManager.beginLayoutLw** ~~~ public class PhoneWindowManager implements WindowManagerPolicy { ...... WindowState mStatusBar = null; ...... // The current size of the screen. int mW, mH; // During layout, the current screen borders with all outer decoration // (status bar, input method dock) accounted for. int mCurLeft, mCurTop, mCurRight, mCurBottom; // 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. int mContentLeft, mContentTop, mContentRight, mContentBottom; // During layout, the current screen borders along with input method // windows are placed. int mDockLeft, mDockTop, mDockRight, mDockBottom; // During layout, the layer at which the doc window is placed. int mDockLayer; static final Rect mTmpParentFrame = new Rect(); static final Rect mTmpDisplayFrame = new Rect(); static final Rect mTmpContentFrame = new Rect(); static final Rect mTmpVisibleFrame = new Rect(); ...... public void beginLayoutLw(int displayWidth, int displayHeight) { mW = displayWidth; mH = displayHeight; mDockLeft = mContentLeft = mCurLeft = 0; mDockTop = mContentTop = mCurTop = 0; mDockRight = mContentRight = mCurRight = displayWidth; mDockBottom = mContentBottom = mCurBottom = displayHeight; mDockLayer = 0x10000000; // decide where the status bar goes ahead of time if (mStatusBar != null) { final Rect pf = mTmpParentFrame; final Rect df = mTmpDisplayFrame; final Rect vf = mTmpVisibleFrame; pf.left = df.left = vf.left = 0; pf.top = df.top = vf.top = 0; pf.right = df.right = vf.right = displayWidth; pf.bottom = df.bottom = vf.bottom = displayHeight; mStatusBar.computeFrameLw(pf, df, vf, vf); if (mStatusBar.isVisibleLw()) { // If the status bar is hidden, we don't want to cause // windows behind it to scroll. mDockTop = mContentTop = mCurTop = mStatusBar.getFrameLw().bottom; ...... } } } ...... } ~~~ 這個函數定義在文件frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java中。 在分析PhoneWindowManager類的成員函數beginLayoutLw的實現之前,我們首先介紹PhoneWindowManager類的五組成員變量。 * 第一組成員變量是mW和mH,它們分別用來描述當前這輪窗口大小計算過程的屏幕寬度和高度。 * 第二組成員變量是mCurLeft、mCurTop、mCurRight和mCurBottom,它們組成一個四元組(mCurLeft, mCurTop, mCurRight, mCurBottom),用來描述當前這輪窗口大小計算過程的屏幕裝飾區,它對應于前面所提到的Activity窗口的可見區域邊襯。 * 第三組成員變量是mContentLeft、mContentTop、mContentRight和mContentBottom,它們組成一個四元組(mContentLeft, mContentTop, mContentRight, mContentBottom),也是用來描述當前這輪窗口大小計算過程的屏幕裝飾區,不過它對應的是前面所提到的Activity窗口的內容區域邊襯。 * 第四組成員變量是mDockLeft、mDockTop、mDockRight、mDockBottom和mDockLayer,其中,前四個成員變量組成一個四元組(mDockLeft, mDockTop, mDockRight, mDockBottom),用來描述當前這輪窗口大小計算過程中的輸入法窗口所占據的位置,后一個成員變量mDockLayer用來描述輸入法窗品的Z軸位置。 * 第五組成員變量是mTmpParentFrame、mTmpDisplayFrame、mTmpContentFrame和mTmpVisibleFrame,它們是一組臨時Rect區域,用來作為參數傳遞給具體的窗口計算大小的,避免每次都創建一組新的Rect區域來作來參數傳遞窗口。 除了這五組成員變量之外,PhoneWindowManager類還有一個成員變量mStatusBar,它的類型為WindowState,用來描述系統的狀態欄。 理解了這些成員變量的含義之后,PhoneWindowManager類的成員函數beginLayoutLw的實現就容易理解了,它主要做了以下兩件事情: 1. 初始化前面所提到的四組成員變量,其中,mW和mH設置為參數displayWidth和displayHeight所指定的屏幕寬度和高度,并且使得(mCurLeft, mCurTop, mCurRight, mCurBottom)、(mContentLeft, mContentTop, mContentRight, mContentBottom)和(mDockLeft, mDockTop, mDockRight, mDockBottom)這三個區域的大小等于屏幕的大小。 2. 計算狀態欄的大小。狀態欄的大小一經確定,并且它是可見的,那么就會修改成員變量mCurLeft、mContentLeft和mDockLeft的值為狀態欄的所占據的區域的下邊界位置,這樣就可以將(mCurLeft, mCurTop, mCurRight, mCurBottom)、(mContentLeft, mContentTop, mContentRight, mContentBottom)和(mDockLeft, mDockTop, mDockRight, mDockBottom)這三個區域限制為剔除狀態欄區域之后所得到的屏幕區域。 還有另外一個地方需要注意的是,輸入法窗口的Z軸被初始化為0x10000000,這個值是相當大的了,可以保證輸入法窗口作為頂層窗口出現。 這一步執行完成之后,返回到前面的Step 7中,即WindowManagerService類的成員函數performLayoutLockedInner,接下來就會調用PhoneWindowManager類的成員函數layoutWindowLw來計算系統中各個可見窗口的大小。 **Step 9. PhoneWindowManager.layoutWindowLw** ~~~ public class PhoneWindowManager implements WindowManagerPolicy { ...... public void layoutWindowLw(WindowState win, WindowManager.LayoutParams attrs, WindowState attached) { // we've already done the status bar if (win == mStatusBar) { return; } ...... final int fl = attrs.flags; final int sim = attrs.softInputMode; final Rect pf = mTmpParentFrame; final Rect df = mTmpDisplayFrame; final Rect cf = mTmpContentFrame; final Rect vf = mTmpVisibleFrame; if (attrs.type == TYPE_INPUT_METHOD) { pf.left = df.left = cf.left = vf.left = mDockLeft; pf.top = df.top = cf.top = vf.top = mDockTop; pf.right = df.right = cf.right = vf.right = mDockRight; pf.bottom = df.bottom = cf.bottom = vf.bottom = mDockBottom; // IM dock windows always go to the bottom of the screen. attrs.gravity = Gravity.BOTTOM; mDockLayer = win.getSurfaceLayer(); } else { if ((fl & (FLAG_LAYOUT_IN_SCREEN | FLAG_FULLSCREEN | FLAG_LAYOUT_INSET_DECOR)) == (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) { // This is the case for a normal activity window: we want it // to cover all of the screen space, and it can take care of // moving its contents to account for screen decorations that // intrude into that space. if (attached != null) { // If this window is attached to another, our display // frame is the same as the one we are attached to. setAttachedWindowFrames(win, fl, sim, attached, true, pf, df, cf, vf); } else { pf.left = df.left = 0; pf.top = df.top = 0; pf.right = df.right = mW; pf.bottom = df.bottom = mH; if ((sim & SOFT_INPUT_MASK_ADJUST) != SOFT_INPUT_ADJUST_RESIZE) { cf.left = mDockLeft; cf.top = mDockTop; cf.right = mDockRight; cf.bottom = mDockBottom; } else { cf.left = mContentLeft; cf.top = mContentTop; cf.right = mContentRight; cf.bottom = mContentBottom; } vf.left = mCurLeft; vf.top = mCurTop; vf.right = mCurRight; vf.bottom = mCurBottom; } } ...... } win.computeFrameLw(pf, df, cf, vf); // Dock windows carve out the bottom of the screen, so normal windows // can't appear underneath them. if (attrs.type == TYPE_INPUT_METHOD && !win.getGivenInsetsPendingLw()) { int top = win.getContentFrameLw().top; top += win.getGivenContentInsetsLw().top; if (mContentBottom > top) { mContentBottom = top; } top = win.getVisibleFrameLw().top; top += win.getGivenVisibleInsetsLw().top; if (mCurBottom > top) { mCurBottom = top; } ...... } } ...... } ~~~ 這個函數定義在文件frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java中。 第一個參數win描述的是當前要計算大小的窗口,第二個參數attrs描述的是窗口win的布局參數,第三個參數attached描述的是窗口win的父窗口,如果它的值等于null,就表示窗口win沒有父窗口。 PhoneWindowManager類的成員函數layoutWindowLw會根據窗口win的是子窗口還是全屏窗口及其軟鍵盤顯示模式來決定它的大小如何計算。這里我們只關注輸入法窗口和非全屏的Activity窗口的大小計算方式,其它類型的窗口大小計算方式是差不多的。 從前面的Step 8可以知道,系統的狀態欄大小已經計算過了,因此,PhoneWindowManager類的成員函數layoutWindowLw如果發現參數win描述的正好是狀態欄的話,它就什么也不做就返回了。 在計算一個窗口的大小的時候,我們需要四個參數。第一個參數是父窗口的大小pf,第二個參數是屏幕的大小df,第三個參數是內容區域邊襯大小cf,第四個參數是可見區域邊襯大小vf。 如果參數win描述的是輸入法窗口,即參數attrs所描述的一個WindowManager.LayoutParams對象的成員變量type的值等于TYPE_INPUT_METHOD,那么上述四個用來計算窗口大小的區域pf、df、cf和vf就等于PhoneWindowManager類的成員變量mDockLeft、mDockTop、mDockRight和mDockBottom所組成的區域的大小。 如果參數win描述的是一個非全屏的Activity窗口,即參數attrs所描述的一個WindowManager.LayoutParams對象的成員變量flags的FLAG_LAYOUT_IN_SCREEN位和FLAG_LAYOUT_INSET_DECOR位等于1,那么PhoneWindowManager類的成員函數layoutWindowLw就會繼續檢查參數attached的值是否不等于null。如果不等于null的話,那么就說明參數win所描述的一個非全屏的Activity窗口附加在其它窗口上,即它具有一個父窗口,這時候就會調用另外一個成員函數setAttachedWindowFrames來計算它的大小。 接下來我們就只關注參數win描述的是一個非全屏的、并且沒有附加到其它窗口的Activity窗口的大小計算過程。 * 首先,父窗口大小pf和屏幕大小df都會被設置為整個屏幕區域的大小。 * 其次,可見區域邊襯大小vf被設置為PhoneWindowManager類的成員變量mCurLeft、mCurTop、mCurRight和mCurBottom所組成的區域的大小。 * 第三,內容區域邊襯大小cf的計算相對復雜一些,需要考慮窗口win的軟鍵盤顯示模式sim的值。如果變量sim的SOFT_INPUT_ADJUST_RESIZE位等于1,那么就意味著窗口win在出向輸入法窗口的時候,它的內容要重新進行排布,避免被輸入法窗口擋住,因此,這時候窗口win的內容區域大小就會等于PhoneWindowManager類的成員變量mContentLeft、mContentTop、mContentRight和mContentBottom所組成的區域的大小。另一方面,如果變量sim的SOFT_INPUT_ADJUST_RESIZE位等于0,那么就意味著窗口win在出向輸入法窗口的時候,它的內容不需要重新進行排布,這時候它的內容區域大小就會等于PhoneWindowManager類的成員變量mDockLeft、mDockTop、mDockRight和mDockBottom所組成的區域的大小。注意,PhoneWindowManager類的成員變量mDockLeft、mDockTop、mDockRight和mDockBottom所組成的區域的大小并不是等于輸入法窗口的大小的,而是包含了輸入法窗口所占據的區域的大小,這就意味著輸入法窗口與窗口win會有重疊的部分,或者說輸入法窗口覆蓋了窗口win的一部分。 得到了用來計算窗口win四個參數pf、 df、cf和vf之后,就可以調用參數win所描述的一個WindowState對象的成員函數computeFrameLw來計算窗口win的具體大小了。計算的結果便得到了窗口win的大小,以及它的內容區域邊襯大小和可見區域邊襯大小。注意,窗口經過計算后得到的內容區域邊襯大小和可見區域邊襯大小并不一定是等于參數cf和vf所指定的大小的。 計算完成窗口win的大小之后,PhoneWindowManager類的成員函數layoutWindowLw還會檢查窗口win是否是一個輸入法窗口,并且它是否指定了額外的內容區域邊襯和可見區域邊襯。如果這兩個條件都成立的話,那么就需要相應地調整PhoneWindowManager類的成員變量mContentBottom和mCurBottom的值,以便使得PhoneWindowManager類的成員變量是mContentLeft、mContentTop、mContentRight和mContentBottom所圍成的內容區域和成員變量mCurLeft、mCurTop、mCurRight和mCurBottom所圍成的可見區域不會覆蓋到輸入法窗口額外指定的內容區域邊襯和可見區域邊襯。 接下來,我們就繼續分析WindowState類的成員函數computeFrameLw的實現,以便可以了解Activity窗口的大小計算的具體過程。 **Step 10. WindowState.computeFrameLw** ~~~ public class WindowManagerService extends IWindowManager.Stub implements Watchdog.Monitor { ...... private final class WindowState implements WindowManagerPolicy.WindowState { ...... boolean mHaveFrame; ...... // "Real" frame that the application sees. final Rect mFrame = new Rect(); ...... final Rect mContainingFrame = new Rect(); final Rect mDisplayFrame = new Rect(); final Rect mContentFrame = new Rect(); final Rect mVisibleFrame = new Rect(); public void computeFrameLw(Rect pf, Rect df, Rect cf, Rect vf) { mHaveFrame = true; final Rect container = mContainingFrame; container.set(pf); final Rect display = mDisplayFrame; display.set(df); if ((mAttrs.flags & FLAG_COMPATIBLE_WINDOW) != 0) { container.intersect(mCompatibleScreenFrame); if ((mAttrs.flags & FLAG_LAYOUT_NO_LIMITS) == 0) { display.intersect(mCompatibleScreenFrame); } } final int pw = container.right - container.left; final int ph = container.bottom - container.top; int w,h; if ((mAttrs.flags & mAttrs.FLAG_SCALED) != 0) { w = mAttrs.width < 0 ? pw : mAttrs.width; h = mAttrs.height< 0 ? ph : mAttrs.height; } else { w = mAttrs.width == mAttrs.MATCH_PARENT ? pw : mRequestedWidth; h = mAttrs.height== mAttrs.MATCH_PARENT ? ph : mRequestedHeight; } final Rect content = mContentFrame; content.set(cf); final Rect visible = mVisibleFrame; visible.set(vf); final Rect frame = mFrame; final int fw = frame.width(); final int fh = frame.height(); ...... Gravity.apply(mAttrs.gravity, w, h, container, (int) (mAttrs.x + mAttrs.horizontalMargin * pw), (int) (mAttrs.y + mAttrs.verticalMargin * ph), frame); ...... // Now make sure the window fits in the overall display. Gravity.applyDisplay(mAttrs.gravity, df, frame); // Make sure the content and visible frames are inside of the // final window frame. if (content.left < frame.left) content.left = frame.left; if (content.top < frame.top) content.top = frame.top; if (content.right > frame.right) content.right = frame.right; if (content.bottom > frame.bottom) content.bottom = frame.bottom; if (visible.left < frame.left) visible.left = frame.left; if (visible.top < frame.top) visible.top = frame.top; if (visible.right > frame.right) visible.right = frame.right; if (visible.bottom > frame.bottom) visible.bottom = frame.bottom; final Rect contentInsets = mContentInsets; contentInsets.left = content.left-frame.left; contentInsets.top = content.top-frame.top; contentInsets.right = frame.right-content.right; contentInsets.bottom = frame.bottom-content.bottom; final Rect visibleInsets = mVisibleInsets; visibleInsets.left = visible.left-frame.left; visibleInsets.top = visible.top-frame.top; visibleInsets.right = frame.right-visible.right; visibleInsets.bottom = frame.bottom-visible.bottom; if (mIsWallpaper && (fw != frame.width() || fh != frame.height())) { updateWallpaperOffsetLocked(this, mDisplay.getWidth(), mDisplay.getHeight(), false); } ...... } ...... } ...... } ~~~ 這個函數定義在文件frameworks/base/services/java/com/android/server/WindowManagerService.java中。 WindowState類的成員變量mHaveFrame用來描述一個窗口的大小是否計算過了。當WindowState類的成員函數computeFrameLw被調用的時候,就說明一個相應的窗口的大小得到計算了,因此,WindowState類的成員函數computeFrameLw一開始就會將成員變量mHaveFrame的值設置為true。 回憶一下,在前面的Step 9中提到,參數pf描述的是父窗口的大小,參數df描述的是屏幕的大小,參數cf描述的內容區域大小,參數vf描述的是可見區域大小,接下來我們就分析WindowState類的成員函數computeFrameLw是如何利用這些參數來計算一個窗口的大小的。 WindowState類的成員變量mContainingFrame和mDisplayFrame描述的是當前正在處理的窗口的父窗口和屏幕的大小,它們剛好就分別等于參數pf和df的大小,因此,函數就直接將參數pf和df的值分別保存在WindowState類的成員變量mContainingFrame和mDisplayFrame中。如果當前正在處理的窗口運行在兼容模式,即WindowState類的成員變量mAttrs所指向的一個WindowManager.LayoutParams對象的成員變量flags的FLAG_COMPATIBLE_WINDOW位等于1,那么就需要將其父窗口的大小限制mContainingFrame在兼容模式下的屏幕區域中。兼容模式下的屏幕區域保存在WindowManagerService類的成員變量mCompatibleScreenFrame中,將父窗口的大小mContainingFrame與它執行一個相交操作,就可以將父窗品的大小限制兼容模式下的屏幕區域中。在當前正在處理的窗口運行在兼容模式的情況下,如果它的大小被限制在了兼容模式下的屏幕區域之中,即WindowState類的成員變量mAttrs所指向的一個WindowManager.LayoutParams對象的成員變量flags的FLAG_LAYOUT_NO_LIMITS位等于0,那么同樣需要將屏幕大小mDisplayFrame限制在兼容模式下的屏幕區域mCompatibleScreenFrame,這也是通過執行一個相交操作來完成的。 WindowState類的成員變量mContentFrame和mVisibleFrame描述的是當前正在處理的窗口的內容區域和可見區域大小,它們剛好就分別等于參數cf和vf的大小,因此,函數就直接將參數cf和vf的值分別保存在WindowState類的成員變量mContainingFrame和mDisplayFrame中。現在,就剩下窗口的大小還沒有計算。一旦窗口大小確定下來之后,就可以繼續計算窗口的內容區域邊襯和可見區域邊襯大小了。接下來我們就繼續分析窗口大小的計算過程。 WindowState類的成員變量mFrame描述的就是當前正在處理的窗品的大小,我們的目標就是計算它的值。一個窗口的大小是受以下因素影響的: 1. 是否指定了縮放因子。如果一個窗口的大小被指定了縮放因子,即WindowState類的成員變量mAttrs所指向的一個WindowManager.LayoutParams對象的成員變量flags的FLAG_SCALED位等于1,那么該窗口的大小就是在它的布局參數中指定的,即是由WindowState類的成員變量mAttrs所指向的一個WindowManager.LayoutParams對象的成員變量width和height所指定的。但是,如果在布局參數中指定的窗口寬度或者高度小于0,那么就會使用其父窗口的大小來作為當前窗口的大小。當前窗口的父窗口的寬度和高度分別保存在變量pw和ph中。 2. 是否指定了等于父窗口的大小。如果一個窗口的大小被指定為其父窗口的大小,即WindowState類的成員變量mAttrs所指向的一個WindowManager.LayoutParams對象的成員變量width和height的值等于mAttrs.MATCH_PARENT,那么該窗口的大小就會等于其父窗口的大小,即等于變量pw和ph所描述的寬度和高度。另一方面,如果一個窗口的大小沒有指定為其父窗口的大小的話,那么它的大小就會等于應用程序進程請求WindowManagerService所設置的大小,即等于WindowState類的成員變量mRequestedWidth和mRequestedHeight所描述的寬度和高度。 經過上述2個操作之后,我們就初步地得到了窗口的寬度w和高度h,但是,它們還不是最終的窗口大小,還要進一步地根據窗口的Gravity屬性來作調整,這個調整分兩步進行: 1. 根據窗口的Gravity值,以及位置、初始大小和父窗口大小,來計算窗口的大小,并且保存在變量frame中,即保存在WindowState類的成員變量mFrame中,這是通過調用Gravity類的靜態成員函數apply來實現的。其中,窗口的初始大小保存在變量w和h中,父窗口大小保存在變量container中,即WindowState類的成員變量mContainingFrame中,位置保存在WindowState類的成員變量mAttrs所指向的一個WindowManager.LayoutParams對象的成員變量x和y中。注意,如果窗口指定了相對父窗口的margin值,那么還需要相應的調整其位置值,即要在指定的位置值的基礎上,再加上相對父窗口的margin值。一個窗口相對父窗口的margion是通過一個百分比來表示的,用這個百分比乘以父窗口的大小就可以得到絕對值。這個百分比又分為在水平方向和垂直方向兩個值,分別保存在WindowState類的成員變量mAttrs所指向的一個WindowManager.LayoutParams對象的成員變量horizontalMargin和verticalMargin中。 2. 前面計算得到的窗口大小沒有考慮在屏幕的大小,因此,接下來還需要繼續調用Gravity類的靜態成員函數applyDisplay來將前面計算得到的窗口大小限制在屏幕區域df中,即限制在WindowState類的成員變量mDisplayFrame所描述的區域中。 經過上述2個操作之后,窗口的最終大小就保存在變量frame中了,即WindowState類的成員變量mFrame中,接下來就可以計算窗品的內容區域邊襯和可見區域邊襯大小了。 內容區域邊襯和可見區域邊襯大小的計算很簡單的,只要將窗口的大小frame,即WindowState類的成員變量mFrame所描述的區域,分別減去變量content和visible,即WindowState類的成員變量mContentFrame和mVisibleFrame所描述的區域,就可以得到窗口的內容區域邊襯和可見區域邊襯大小,它們分別保存在WindowState類的成員變量mContentInsets和mVisibleInsets中。注意,在計算窗口的內容區域邊襯和可見區域邊襯大小之前,首先要保證窗口的內容區域和可見區域包含在整個窗口區域中,這一點是由中間的8個if語句來保證的。 窗口上一次的大小保存在變量fw和fh中。如果當前正在處理的窗口是一個壁紙窗口,即WindowState類的成員變量mIsWallpaper的值等于true,并且該窗口的大小發生了變化,即變量fw和fh的所描述的窗口大小不等于變量frame描述的窗口大小,那么就需要調用WindowManagerService類的成員函數updateWallpaperOffsetLocked來更新壁紙的位置。在后面的文章中,我們再詳細描述系統的壁紙窗口的位置是如何計算的。 這一步執行完成之后,一個窗口的大小就計算完成了。從計算的過程可以知道,整個窗口大小保存在WindowState類的成員變量mFrame中,而窗品的內容區域邊襯大小和可見區域邊襯大小分別保在WindowState類的成員變量mContentInsets和mVisibleInsets中。這些值最終會通過前面的Step 4返回給應用程序進程。 返回到前面的Step 7中,即WindowManagerService類的成員函數performLayoutLockedInner,接下來就會調用PhoneWindowManager類的成員函數finishLayoutLw來結束當前這輪窗口大小的計算工作。 **Step 11. PhoneWindowManager.finishLayoutLw** ~~~ public class PhoneWindowManager implements WindowManagerPolicy { ...... public int finishLayoutLw() { return 0; } ...... } ~~~ 這個函數定義在文件frameworks/base/policy/src/com/android/internal/policy/impl/PhoneWindowManager.java中。 PhoneWindowManager類的成員函數finishLayoutLw是設計來結束一輪窗口大小的計算過程中,不過目前它什么也不做,只是一個空實現。 至此,我們就分析完成Activity窗口的大小計算過程了。從這個計算過程中,我們就可以知道一個Activity窗口除了有一個整體大小之外,還有一個內容區域邊襯大小和一個可見區域邊襯大小。此外,我們還知道,一個Activity窗口的內容區域邊襯大小和可見區域邊襯大小是可能會受到與其所關聯的輸入法窗口的影響的,因為輸入法窗口會疊加在該Activity窗口上面,這就涉及到了系統中的窗口的組織方式。在接下來的一篇文章中,我們就將繼續分析WindowManagerService服務是如何組織系統中的窗口的。敬請關注!
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看