既上一篇博客,這篇我們分析WMS的relayoutWindow函數。
relayoutWindow
我們先看下relayoutWindow函數
- public int relayoutWindow(Session session, IWindow client, int seq,
- WindowManager.LayoutParams attrs, int requestedWidth,
- int requestedHeight, int viewVisibility, int flags,
- Rect outFrame, Rect outOverscanInsets, Rect outContentInsets,
- Rect outVisibleInsets, Rect outStableInsets, Rect outOutsets, Configuration outConfig,
- Surface outSurface) {
- ......
-
- synchronized(mWindowMap) {
- WindowState win = windowForClientLocked(session, client, false);
- if (win == null) {
- return 0;
- }
- WindowStateAnimator winAnimator = win.mWinAnimator;
- if (viewVisibility != View.GONE && (win.mRequestedWidth != requestedWidth
- || win.mRequestedHeight != requestedHeight)) {
- win.mLayoutNeeded = true;
- 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 = (flags&WindowManagerGlobal.RELAYOUT_INSETS_PENDING) != 0;
- configChanged = updateOrientationFromAppTokensLocked(false);
- performLayoutAndPlaceSurfacesLocked();
- .....
-
- outFrame.set(win.mCompatFrame);
- outOverscanInsets.set(win.mOverscanInsets);
- outContentInsets.set(win.mContentInsets);
- outVisibleInsets.set(win.mVisibleInsets);
- outStableInsets.set(win.mStableInsets);
- outOutsets.set(win.mOutsets);
-
- inTouchMode = mInTouchMode;
- ......
- }
- ......
-
- return (inTouchMode ? WindowManagerGlobal.RELAYOUT_RES_IN_TOUCH_MODE : 0)
- | (toBeDisplayed ? WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME : 0)
- | (surfaceChanged ? WindowManagerGlobal.RELAYOUT_RES_SURFACE_CHANGED : 0);
- }
參數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函數。
- private final void performLayoutAndPlaceSurfacesLocked() {
- int loopCount = 6;
- do {
- mTraversalScheduled = false;
- performLayoutAndPlaceSurfacesLockedLoop();
- mH.removeMessages(H.DO_TRAVERSAL);
- loopCount--;
- } while (mTraversalScheduled && loopCount > 0);
- mInnerFields.mWallpaperActionPending = false;
- }
performLayoutAndPlaceSurfacesLockedLoop函數:
- private final void performLayoutAndPlaceSurfacesLockedLoop() {
- if (mInLayout) {
- return;
- }
-
- if (mWaitingForConfig) {
- return;
- }
-
- if (!mDisplayReady) {
- // Not yet initialized, nothing to do.
- return;
- }
-
- Trace.traceBegin(Trace.TRACE_TAG_WINDOW_MANAGER, "wmLayout");
- mInLayout = true;
-
- boolean recoveringMemory = false;
- if (!mForceRemoves.isEmpty()) {
- recoveringMemory = true;
- // Wait a little bit for things to settle down, and off we go.
- while (!mForceRemoves.isEmpty()) {
- WindowState ws = mForceRemoves.remove(0);
- Slog.i(TAG, "Force removing: " + ws);
- removeWindowInnerLocked(ws);
- }
-
- Object tmp = new Object();
- synchronized (tmp) {
- try {
- tmp.wait(250);
- } catch (InterruptedException e) {
- }
- }
- }
-
- try {
- performLayoutAndPlaceSurfacesLockedInner(recoveringMemory);
-
- mInLayout = false;
-
- if (needsLayout()) {
- if (++mLayoutRepeatCount < 6) {
- requestTraversalLocked();
- } else {
- Slog.e(TAG, "Performed 6 layouts in a row. Skipping");
- mLayoutRepeatCount = 0;
- }
- } else {
- mLayoutRepeatCount = 0;
- }
-
- if (mWindowsChanged && !mWindowChangeListeners.isEmpty()) {
- mH.removeMessages(H.REPORT_WINDOWS_CHANGE);
- mH.sendEmptyMessage(H.REPORT_WINDOWS_CHANGE);
- }
- } catch (RuntimeException e) {
- mInLayout = false;
- Slog.wtf(TAG, "Unhandled exception while laying out windows", e);
- }
-
- Trace.traceEnd(Trace.TRACE_TAG_WINDOW_MANAGER);
- }
從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窗口的大小計算過程。
- 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();
-
- ......
-
- }
-
- ......
- }
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窗口的大小計算過程。
- private final void performLayoutLockedInner(final DisplayContent displayContent,
- boolean initial, boolean updateInputWindows) {
- if (!displayContent.layoutNeeded) {
- return;
- }
- displayContent.layoutNeeded = false;
- WindowList windows = displayContent.getWindowList();
- boolean isDefaultDisplay = displayContent.isDefaultDisplay;
-
- DisplayInfo displayInfo = displayContent.getDisplayInfo();
- final int dw = displayInfo.logicalWidth;
- final int dh = displayInfo.logicalHeight;
-
- if (mInputConsumer != null) {
- mInputConsumer.layout(dw, dh);
- }
-
- final int N = windows.size();
- int i;
-
- mPolicy.beginLayoutLw(isDefaultDisplay, dw, dh, mRotation);
- if (isDefaultDisplay) {
- // Not needed on non-default displays.
- mSystemDecorLayer = mPolicy.getSystemDecorLayerLw();
- mScreenRect.set(0, 0, dw, dh);
- }
-
- mPolicy.getContentRectLw(mTmpContentRect);
- displayContent.resize(mTmpContentRect);
-
- int seq = mLayoutSeq+1;
- if (seq < 0) seq = 0;
- mLayoutSeq = seq;
-
- boolean behindDream = false;
-
- // First perform layout of any root windows (not attached
- // to another window).
- int topAttached = -1;
- for (i = N-1; i >= 0; i--) {
- final WindowState win = windows.get(i);
- final boolean gone = (behindDream && mPolicy.canBeForceHidden(win, win.mAttrs))
- || win.isGoneForLayoutLw();
-
-
- if (!gone || !win.mHaveFrame || win.mLayoutNeeded
- || ((win.isConfigChanged() || win.setInsetsChanged()) &&
- ((win.mAttrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0 ||
- (win.mHasSurface && win.mAppToken != null &&
- win.mAppToken.layoutConfigChanges)))) {
- if (!win.mLayoutAttached) {
- if (initial) {
- win.mContentChanged = false;
- }
- if (win.mAttrs.type == TYPE_DREAM) {
- behindDream = true;
- }
- win.mLayoutNeeded = false;
- win.prelayout();
- mPolicy.layoutWindowLw(win, null);
- win.mLayoutSeq = seq;
- } else {
- if (topAttached < 0) topAttached = i;
- }
- }
- }
-
- boolean attachedBehindDream = false;
-
- for (i = topAttached; i >= 0; i--) {
- final WindowState win = windows.get(i);
-
- if (win.mLayoutAttached) {
- if (attachedBehindDream && mPolicy.canBeForceHidden(win, win.mAttrs)) {
- continue;
- }
- if ((win.mViewVisibility != View.GONE && win.mRelayoutCalled)
- || !win.mHaveFrame || win.mLayoutNeeded) {
- if (initial) {
- //Slog.i(TAG, "Window " + this + " clearing mContentChanged - initial");
- win.mContentChanged = false;
- }
- win.mLayoutNeeded = false;
- win.prelayout();
- mPolicy.layoutWindowLw(win, win.mAttachedWindow);
- win.mLayoutSeq = seq;
- }
- } else if (win.mAttrs.type == TYPE_DREAM) {
- // Don't layout windows behind a dream, so that if it
- // does stuff like hide the status bar we won't get a
- // bad transition when it goes away.
- attachedBehindDream = behindDream;
- }
- }
- ......
- mPolicy.finishLayoutLw();
- }
在分析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
- public void beginLayoutLw(boolean isDefaultDisplay, int displayWidth, int displayHeight,
- int displayRotation) {
- mDisplayRotation = displayRotation;
- final int overscanLeft, overscanTop, overscanRight, overscanBottom;
- if (isDefaultDisplay) {
- switch (displayRotation) {
- case Surface.ROTATION_90:
- overscanLeft = mOverscanTop;
- overscanTop = mOverscanRight;
- overscanRight = mOverscanBottom;
- overscanBottom = mOverscanLeft;
- break;
- case Surface.ROTATION_180:
- overscanLeft = mOverscanRight;
- overscanTop = mOverscanBottom;
- overscanRight = mOverscanLeft;
- overscanBottom = mOverscanTop;
- break;
- case Surface.ROTATION_270:
- overscanLeft = mOverscanBottom;
- overscanTop = mOverscanLeft;
- overscanRight = mOverscanTop;
- overscanBottom = mOverscanRight;
- break;
- default:
- overscanLeft = mOverscanLeft;
- overscanTop = mOverscanTop;
- overscanRight = mOverscanRight;
- overscanBottom = mOverscanBottom;
- break;
- }
- } else {
- overscanLeft = 0;
- overscanTop = 0;
- overscanRight = 0;
- overscanBottom = 0;
- }
- mOverscanScreenLeft = mRestrictedOverscanScreenLeft = 0;
- mOverscanScreenTop = mRestrictedOverscanScreenTop = 0;
- mOverscanScreenWidth = mRestrictedOverscanScreenWidth = displayWidth;
- mOverscanScreenHeight = mRestrictedOverscanScreenHeight = displayHeight;
- mSystemLeft = 0;
- mSystemTop = 0;
- mSystemRight = displayWidth;
- mSystemBottom = displayHeight;
- mUnrestrictedScreenLeft = overscanLeft;
- mUnrestrictedScreenTop = overscanTop;
- mUnrestrictedScreenWidth = displayWidth - overscanLeft - overscanRight;
- mUnrestrictedScreenHeight = displayHeight - overscanTop - overscanBottom;
- mRestrictedScreenLeft = mUnrestrictedScreenLeft;
- mRestrictedScreenTop = mUnrestrictedScreenTop;
- mRestrictedScreenWidth = mSystemGestures.screenWidth = mUnrestrictedScreenWidth;
- mRestrictedScreenHeight = mSystemGestures.screenHeight = mUnrestrictedScreenHeight;
- mDockLeft = mContentLeft = mVoiceContentLeft = mStableLeft = mStableFullscreenLeft
- = mCurLeft = mUnrestrictedScreenLeft;
- mDockTop = mContentTop = mVoiceContentTop = mStableTop = mStableFullscreenTop
- = mCurTop = mUnrestrictedScreenTop;
- mDockRight = mContentRight = mVoiceContentRight = mStableRight = mStableFullscreenRight
- = mCurRight = displayWidth - overscanRight;
- mDockBottom = mContentBottom = mVoiceContentBottom = mStableBottom = mStableFullscreenBottom
- = mCurBottom = displayHeight - overscanBottom;
- mDockLayer = 0x10000000;
- mStatusBarLayer = -1;
-
- // start with the current dock rect, which will be (0,0,displayWidth,displayHeight)
- final Rect pf = mTmpParentFrame;
- final Rect df = mTmpDisplayFrame;
- final Rect of = mTmpOverscanFrame;
- final Rect vf = mTmpVisibleFrame;
- final Rect dcf = mTmpDecorFrame;
- final Rect osf = mTmpOutsetFrame;
- pf.left = df.left = of.left = vf.left = mDockLeft;
- pf.top = df.top = of.top = vf.top = mDockTop;
- pf.right = df.right = of.right = vf.right = mDockRight;
- 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個變量
- private void configureDisplayPolicyLocked(DisplayContent displayContent) {
- mPolicy.setInitialDisplaySize(displayContent.getDisplay(),
- displayContent.mBaseDisplayWidth,
- displayContent.mBaseDisplayHeight,
- displayContent.mBaseDisplayDensity);
-
- DisplayInfo displayInfo = displayContent.getDisplayInfo();
- mPolicy.setDisplayOverscan(displayContent.getDisplay(),
- displayInfo.overscanLeft, displayInfo.overscanTop,
- displayInfo.overscanRight, displayInfo.overscanBottom);
- }
setDisplayOverscan函數如下:
- @Override
- public void setDisplayOverscan(Display display, int left, int top, int right, int bottom) {
- if (display.getDisplayId() == Display.DEFAULT_DISPLAY) {
- mOverscanLeft = left;
- mOverscanTop = top;
- mOverscanRight = right;
- mOverscanBottom = bottom;
- }
- }
屏幕上除了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函數
- public void layoutWindowLw(WindowState win, WindowState attached) {
- // We’ve already done the navigation bar and status bar. If the status bar can receive
- // input, we need to layout it again to accomodate for the IME window.
- if ((win == mStatusBar && !canReceiveInput(win)) || win == mNavigationBar) {
- return;
- }
- final WindowManager.LayoutParams attrs = win.getAttrs();
- final boolean isDefaultDisplay = win.isDefaultDisplay();
- final boolean needsToOffsetInputMethodTarget = isDefaultDisplay &&
- (win == mLastInputMethodTargetWindow && mLastInputMethodWindow != null);
- if (needsToOffsetInputMethodTarget) {
- if (DEBUG_LAYOUT) Slog.i(TAG, “Offset ime target window by the last ime window state”);
- offsetInputMethodWindowLw(mLastInputMethodWindow);
- }
-
- final int fl = PolicyControl.getWindowFlags(win, attrs);
- final int sim = attrs.softInputMode;
- final int sysUiFl = PolicyControl.getSystemUiVisibility(win, null);
-
- final Rect pf = mTmpParentFrame;
- final Rect df = mTmpDisplayFrame;
- final Rect of = mTmpOverscanFrame;
- final Rect cf = mTmpContentFrame;
- final Rect vf = mTmpVisibleFrame;
- final Rect dcf = mTmpDecorFrame;
- final Rect sf = mTmpStableFrame;
- Rect osf = null;
- dcf.setEmpty();
-
- final boolean hasNavBar = (isDefaultDisplay && mHasNavigationBar
- && mNavigationBar != null && mNavigationBar.isVisibleLw());
-
- final int adjust = sim & SOFT_INPUT_MASK_ADJUST;
-
- if (isDefaultDisplay) {
- sf.set(mStableLeft, mStableTop, mStableRight, mStableBottom);
- } else {
- sf.set(mOverscanLeft, mOverscanTop, mOverscanRight, mOverscanBottom);
- }
-
- if (!isDefaultDisplay) {
- 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, adjust, attached, true, pf, df, of, cf, vf);
- } else {
- // Give the window full screen.
- pf.left = df.left = of.left = cf.left = mOverscanScreenLeft;
- pf.top = df.top = of.top = cf.top = mOverscanScreenTop;
- pf.right = df.right = of.right = cf.right
- = mOverscanScreenLeft + mOverscanScreenWidth;
- pf.bottom = df.bottom = of.bottom = cf.bottom
- = mOverscanScreenTop + mOverscanScreenHeight;
- }
- } else if (attrs.type == TYPE_INPUT_METHOD) {
- pf.left = df.left = of.left = cf.left = vf.left = mDockLeft;
- pf.top = df.top = of.top = cf.top = vf.top = mDockTop;
- pf.right = df.right = of.right = cf.right = vf.right = mDockRight;
- // IM dock windows layout below the nav bar…
- pf.bottom = df.bottom = of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;
- // …with content insets above the nav bar
- cf.bottom = vf.bottom = mStableBottom;
- // IM dock windows always go to the bottom of the screen.
- attrs.gravity = Gravity.BOTTOM;
- mDockLayer = win.getSurfaceLayer();
- } …..
PhoneWindowManager的computeFrameLw函數
- final Rect mFrame = new Rect();//真實窗口大小
- final Rect mLastFrame = new Rect();
- final Rect mCompatFrame = new Rect();//兼容模式下窗口大小
-
- final Rect mContainingFrame = new Rect();
-
- final Rect mParentFrame = new Rect();// 父窗口大小
- final Rect mDisplayFrame = new Rect();//顯示設備大小
-
- final Rect mOverscanFrame = new Rect();//Overscan區域大小
-
- // The display frame minus the stable insets. This value is always constant regardless of if
- // the status bar or navigation bar is visible.
- final Rect mStableFrame = new Rect();
-
- final Rect mDecorFrame = new Rect();//Decor區域大小
-
- final Rect mContentFrame = new Rect();//內容區域大小
-
- final Rect mVisibleFrame = new Rect();//可見區域大小
- mContentFrame.set(Math.max(mContentFrame.left, mFrame.left),
- Math.max(mContentFrame.top, mFrame.top),
- Math.min(mContentFrame.right, mFrame.right),
- Math.min(mContentFrame.bottom, mFrame.bottom));
-
- mVisibleFrame.set(Math.max(mVisibleFrame.left, mFrame.left),
- Math.max(mVisibleFrame.top, mFrame.top),
- Math.min(mVisibleFrame.right, mFrame.right),
- Math.min(mVisibleFrame.bottom, mFrame.bottom));
-
- mStableFrame.set(Math.max(mStableFrame.left, mFrame.left),
- Math.max(mStableFrame.top, mFrame.top),
- Math.min(mStableFrame.right, mFrame.right),
- Math.min(mStableFrame.bottom, mFrame.bottom));
-
- mOverscanInsets.set(Math.max(mOverscanFrame.left - mFrame.left, 0),
- Math.max(mOverscanFrame.top - mFrame.top, 0),
- Math.max(mFrame.right - mOverscanFrame.right, 0),
- Math.max(mFrame.bottom - mOverscanFrame.bottom, 0));
-
- mContentInsets.set(mContentFrame.left - mFrame.left,
- mContentFrame.top - mFrame.top,
- mFrame.right - mContentFrame.right,
- mFrame.bottom - mContentFrame.bottom);
-
- mVisibleInsets.set(mVisibleFrame.left - mFrame.left,
- mVisibleFrame.top - mFrame.top,
- mFrame.right - mVisibleFrame.right,
- mFrame.bottom - mVisibleFrame.bottom);
-
- mStableInsets.set(Math.max(mStableFrame.left - mFrame.left, 0),
- Math.max(mStableFrame.top - mFrame.top, 0),
- Math.max(mFrame.right - mStableFrame.right, 0),
- Math.max(mFrame.bottom - mStableFrame.bottom, 0));
-
- mCompatFrame.set(mFrame);
PhoneWindowManager的finishLayoutLw
該函數只是一個空實現
- @Override
- public void finishLayoutLw() {
- return;
- }
設置relayoutWindow的輸出變量
- outFrame.set(win.mCompatFrame);//這個值一般和mFrame相同
- outOverscanInsets.set(win.mOverscanInsets);
- outContentInsets.set(win.mContentInsets);
- outVisibleInsets.set(win.mVisibleInsets);
- outStableInsets.set(win.mStableInsets);
- outOutsets.set(win.mOutsets);