android计算窗口大小,Android 7.1 GUI系統-窗口管理WMS-窗口大小計算(五)

窗口大小的計算

一個應用窗口,除了應用程序本身的內容外,還有狀態欄,可能還有輸入法窗口,狀態欄的大小是固定的,輸入法窗口可以在AndroidManifest.xml中配置,相關屬性如下:

以state開頭的表示當Activity成為焦點時軟鍵盤是隱藏還是可見,以adjust開頭的表示如何調整Activity窗口以容納軟鍵盤。

frameworks/base/core/res/res/values/Attrs.xml

ViewRootImpl中的performTraversals方法,是計算應用窗口大小的起點,在程序運行過程中這個方法會被調用多次。

如果是第一次(mFirst==true)執行遍歷,或者窗口內容有變化會調用dispatchApplyInsets,然后會調用到View的onApplyWindowInsets,進一步調用到fitSystemWindows(Rectinsets),來通知一個窗口的insets發生了改變,讓View有機會適應最新的變化,他會沿着ViewTree從上到下傳遞給各個View對象。通常不需要處理這個函數,但是如果使用SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN,SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION這兩個屬性除外,因為應用窗口的內容可能會在系統元素之下。

如果mFirst==false,不是第一次調用這個函數(performTraversals),那么:desiredWindowWidth=frame.width();desiredWindowHeight =frame.height();這兩個值是期望的寬高,和最終的值可能有差異。frame也就是mWinFrame它是用於記錄WMS提供的寬高,當Activity的窗口大小需要改變時,WMS會通過dispatchResized,調用Iwindow的resized函數(也就是ViewRootImpl.java的內部類W的resized函數)。如果(desiredWindowWidth!= mWidth || desiredWindowHeight !=mHeight),就是說WMS的期望值跟當前真實的寬高(mWidth,mHeight),那么變量mFullRedrawNeeded= true;mLayoutRequested= true;windowSizeMayChange =true;這三個變量在后面的流程中可能觸發invalidate,或者觸發layout請求。

performTraversals繼續往下走,如果(mFirst|| windowShouldResize|| insetsChanged ||viewVisibilityChanged ||params != null ||mForceNextWindowRelayout)條件滿足,說明窗口屬性發生了變化,會通過調用relayoutWindow,進一步調用mWindowSession.relayout,請求WMS計算窗口屬性,WMS計算的計算的結果將通過relayout的參數帶回。這些參數包括:

mWinFrame:WMS得出的應用窗口的大小,對應WMS的relayoutWindow中的outFrame。

mPendingContentInsets:WMS得出的contentinsets,對應WMS的relayoutWindow中的outContentInsets。

mPendingVisibleInsets:WMS得出的visibleinsets,對應WMS的relayoutWindow中的outVisibleInsets。

mPendingConfiguration:WMS得出的visibleinsets,對應WMS的relayoutWindow中的outConfig。

mSurface:WMS申請的有效的Surface對象,對應WMS的relayoutWindow中的outSurface。

WMS的relayoutWindow中的這些參數都是out開頭的,表明他們是出參。

WMS的relayoutWindow有關窗口大小的計算,調用流程會走到WindowSurfacePlacer.java中的applySurfaceChangesTransaction。

private void applySurfaceChangesTransaction(boolean recoveringMemory, int numDisplays,

int defaultDw, int defaultDh) @WindowSurfacePlacer.java{

int repeats = 0;

do {

repeats++;

//循環6次退出。

if (repeats > 6) {

displayContent.layoutNeeded = false;

break;

}

//重新計算config。

if (isDefaultDisplay

&& (displayContent.pendingLayoutChanges & FINISH_LAYOUT_REDO_CONFIG) != 0) {

displayContent.layoutNeeded = true;

mService.mH.sendEmptyMessage(SEND_NEW_CONFIGURATION);

}

//重新layout。

if ((displayContent.pendingLayoutChanges & FINISH_LAYOUT_REDO_LAYOUT) != 0) {

displayContent.layoutNeeded = true;

}

//循環小於4次,每次都會調用這個函數,窗口大小計算的關鍵函數。

if (repeats < LAYOUT_REPEAT_THRESHOLD) {

performLayoutLockedInner(displayContent, repeats == 1,false /* updateInputWindows */);

}

//對每個窗口應用窗口策略。

WindowState w = windows.get(i);

if (w.mHasSurface) {

mService.mPolicy.applyPostLayoutPolicyLw(w, w.mAttrs, w.mAttachedWindow);

}

// finishPostLayoutPolicyLw返回值賦給displayContent.pendingLayoutChanges ,

會影響到是退出while循環,還是根據pendingLayoutChanges 的值再次執行layout,或者重新計算configuration。

displayContent.pendingLayoutChanges |=mService.mPolicy.finishPostLayoutPolicyLw();

//這個while循環退出的另一個條件是 displayContent.pendingLayoutChanges ==0,

}while (displayContent.pendingLayoutChanges != 0);

}

看下displayContent.pendingLayoutChanges的值都有那些情況,有注釋不再翻譯了:

WindowManagerPolicy.java

/** Layout state may havechanged (soanother layout will be performed) */

static final intFINISH_LAYOUT_REDO_LAYOUT =0x0001;

/** Configuration statemay have changed */

static final intFINISH_LAYOUT_REDO_CONFIG =0x0002;

/** Wallpaper may need tomove */

static finalintFINISH_LAYOUT_REDO_WALLPAPER = 0x0004;

/** Need to recomputeanimations */

static final intFINISH_LAYOUT_REDO_ANIM =0x0008;

再次執行layout的核心代碼是performLayoutLockedInner:

final void performLayoutLockedInner(final DisplayContent displayContent,

boolean initial, boolean updateInputWindows) @WindowSurfacePlacer.java{

//所有的窗口列表。

WindowList windows = displayContent.getWindowList();

//顯示屏相關的信息。

boolean isDefaultDisplay = displayContent.isDefaultDisplay;

DisplayInfo displayInfo = displayContent.getDisplayInfo();

final int dw = displayInfo.logicalWidth;

final int dh = displayInfo.logicalHeight;

final int N = windows.size();

//循環處理每個窗口前,對顯示屏相關的變量進行初始化。

mService.mPolicy.beginLayoutLw(isDefaultDisplay, dw, dh, mService.mRotation,

mService.mCurConfiguration.uiMode);

//首先執行layout的是根窗口(沒有附加到別的窗口的那些窗口)。

int topAttached = -1;

for (i = N-1; i >= 0; i--) {

final WindowState win = windows.get(i);

//如果一個窗口不可見,就不用浪費時間去計算了。

final boolean gone = (behindDream && mService.mPolicy.canBeForceHidden(win, win.mAttrs))

|| win.isGoneForLayoutLw();

// gone表示這個窗口是不是還存在, mHaveFrame表示窗口是否已經執行過computeFrameLw,

if (!gone || !win.mHaveFrame || win.mLayoutNeeded

|| ((win.isConfigChanged() || win.setReportResizeHints())

&& !win.isGoneForLayoutLw() &&

((win.mAttrs.privateFlags & PRIVATE_FLAG_KEYGUARD) != 0 ||

(win.mHasSurface && win.mAppToken != null &&

win.mAppToken.layoutConfigChanges)))) {

if (!win.mLayoutAttached) {

//win.mLayoutAttached 為false是跟窗口,執行具體的計算。

win.prelayout();

mService.mPolicy.layoutWindowLw(win, null);

win.mLayoutSeq = seq;

}else{

//這里是子窗口的情況。

if (topAttached < 0) topAttached = i;

}

}

}

//從上一步計算的結果topAttached 開始,第一個有 attached窗口的情況,計算它的父窗口。

for (i = topAttached; i >= 0; i--) {

final WindowState win = windows.get(i);

if (win.mLayoutAttached) {

mService.mPolicy.layoutWindowLw(win, win.mAttachedWindow);

}

}

//結束layout。

mService.mPolicy.finishLayoutLw();

}

窗口的計算過程是:

這三個方法跟具體的窗口有關,手機默認的窗口策略是PhoneWindowManager.java

mService.mPolicy.beginLayoutLw(….);

mService.mPolicy.layoutWindowLw(win,win.mAttachedWindow);

mService.mPolicy.finishLayoutLw();

beginLayoutLw會做跟窗口大小相關的變量的初始化。比如:

PhoneWindowManager.java

public void beginLayoutLw(boolean isDefaultDisplay, int displayWidth, int displayHeight,

int displayRotation, int uiMode) @PhoneWindowManager.java{

//overscan是過掃描,根據屏幕的角度賦值。有些顯示屏可能存在失真現象,且越靠近邊緣越嚴重,為了避開這個問題,廠商可能把掃描調整到畫面的5%-10%,這樣造成的結果是畫面可能顯示不全,損失這部分成為overscan。

final int overscanLeft, overscanTop, overscanRight, overscanBottom;

//屏幕的真實大小,包含了overscan區域。

mOverscanScreenLeft = mRestrictedOverscanScreenLeft = 0;

mOverscanScreenTop = mRestrictedOverscanScreenTop = 0;

mOverscanScreenWidth = mRestrictedOverscanScreenWidth = displayWidth;

mOverscanScreenHeight = mRestrictedOverscanScreenHeight = displayHeight;

//所有可見的系統UI元素區域。

mSystemLeft = 0;

mSystemTop = 0;

mSystemRight = displayWidth;

mSystemBottom = displayHeight;

//屏幕的真實大小,不管狀態欄是否可見,不包含overscan區域。

mUnrestrictedScreenLeft = overscanLeft;

mUnrestrictedScreenTop = overscanTop;

mUnrestrictedScreenWidth = displayWidth - overscanLeft - overscanRight;

mUnrestrictedScreenHeight = displayHeight - overscanTop – overscanBottom;

//跟mOverscanScreen類似,適當的時候可以移動到overscan區域。

int mRestrictedOverscanScreenLeft, mRestrictedOverscanScreenTop;

int mRestrictedOverscanScreenWidth, mRestrictedOverscanScreenHeight;

//屏幕大小,如果狀態欄不隱藏,他和其他的變量是不同的。

mRestrictedScreenLeft = mUnrestrictedScreenLeft;

mRestrictedScreenTop = mUnrestrictedScreenTop;

mRestrictedScreenWidth = mSystemGestures.screenWidth = mUnrestrictedScreenWidth;

mRestrictedScreenHeight = mSystemGestures.screenHeight = mUnrestrictedScreenHeight;

//包括狀態欄、輸入法窗口在內的外圍尺寸。

int mCurLeft, mCurTop, mCurRight, mCurBottom;

//內容區域。

int mContentLeft, mContentTop, mContentRight, mContentBottom;

//輸入法窗口區域。

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;

//計算導航欄的大小。

boolean updateSysUiVisibility = layoutNavigationBar(displayWidth, displayHeight,

displayRotation, uiMode, overscanLeft, overscanRight, overscanBottom, dcf, navVisible, navTranslucent,

navAllowedHidden, statusBarExpandedNotKeyguard);

//計算狀態欄的大小。

updateSysUiVisibility |= layoutStatusBar(pf, df, of, vf, dcf, sysui, isKeyguardShowing);

if (updateSysUiVisibility) {

updateSystemUiVisibilityLw();

}

}

初始化后,根據具體的配置執行layoutWindowLw,計算父窗口大小,屏幕大小,可見區域,內容區域,overscan區域,decor區域等。

public void layoutWindowLw(WindowState win, WindowState attached) @PhoneWindowManager.java{

//在beginLayout時已經處理過導航欄、狀態欄,如果狀態欄收到input事件,就需要再次layout以適應輸入法窗口。

if ((win == mStatusBar && !canReceiveInput(win)) || win == mNavigationBar) {

return;

}

final WindowManager.LayoutParams attrs = win.getAttrs();

//窗口屬性。

final int fl = PolicyControl.getWindowFlags(win, attrs);

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;

//是否有導航條,手機底部有物理按鍵的,通常就不用導航條了。

final boolean hasNavBar = (isDefaultDisplay && mHasNavigationBar

&& mNavigationBar != null && mNavigationBar.isVisibleLw());

只關注應用窗口的計算,其他窗口如輸入法、狀態欄、wallpaper等略過。

final boolean isAppWindow =

attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW &&

attrs.type <= WindowManager.LayoutParams.LAST_APPLICATION_WINDOW;

if (isAppWindow && !inheritTranslucentDecor && !topAtRest) {

//設置了全屏,確保decorView包含狀態欄。

if ((sysUiFl & View.SYSTEM_UI_FLAG_FULLSCREEN) == 0

&& (fl & WindowManager.LayoutParams.FLAG_FULLSCREEN) == 0

&& (fl & WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS) == 0

&& (fl & WindowManager.LayoutParams.

FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0

&& (pfl & PRIVATE_FLAG_FORCE_DRAW_STATUS_BAR_BACKGROUND) == 0) {

// Ensure policy decor includes status bar

dcf.top = mStableTop;

}

//設置了導航欄透明,確保DecorView包含導航欄。

if ((fl & WindowManager.LayoutParams.FLAG_TRANSLUCENT_NAVIGATION) == 0

&& (sysUiFl & View.SYSTEM_UI_FLAG_HIDE_NAVIGATION) == 0

&& (fl & WindowManager.LayoutParams.

FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0) {

// Ensure policy decor includes navigation bar

dcf.bottom = mStableBottom;

dcf.right = mStableRight;

}

}

//這是常見的Activity窗口的情況,想要覆蓋所有的屏幕空間,要移動窗口的內容以適應屏幕裝飾條在內的系統元素。

if ((fl & (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR))

== (FLAG_LAYOUT_IN_SCREEN | FLAG_LAYOUT_INSET_DECOR)) {

//如果是子窗口,各個變量的值跟其父窗口一致

if (attached != null) {

setAttachedWindowFrames(win, fl, adjust, attached, true, pf, df, of, cf, vf);

}else{ //非子窗口的情況

//狀態欄的下拉面板,這是唯一可以在狀態欄之上的窗口,他們受STATUS_BAR_SERVICE權限保護,跟狀態欄有相同的優先級。

if (attrs.type == TYPE_STATUS_BAR_PANEL

|| attrs.type == TYPE_STATUS_BAR_SUB_PANEL) {

pf.left = df.left = of.left = hasNavBar

? mDockLeft : mUnrestrictedScreenLeft;

pf.top = df.top = of.top = mUnrestrictedScreenTop;

pf.right = df.right = of.right = hasNavBar

? mRestrictedScreenLeft+mRestrictedScreenWidth

: mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;

pf.bottom = df.bottom = of.bottom = hasNavBar

? mRestrictedScreenTop+mRestrictedScreenHeight

: mUnrestrictedScreenTop + mUnrestrictedScreenHeight;

}

}else if ((fl & FLAG_LAYOUT_IN_OVERSCAN) != 0

&& attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW

&& attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {

//要求占據overscan區域,所以給定的是真正不受限制的區域。

pf.left = df.left = of.left = mOverscanScreenLeft;

pf.top = df.top = of.top = mOverscanScreenTop;

pf.right = df.right = of.right = mOverscanScreenLeft + mOverscanScreenWidth;

pf.bottom = df.bottom = of.bottom = mOverscanScreenTop

+ mOverscanScreenHeight;

}else if (canHideNavigationBar()

&& (sysUiFl & View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION) != 0

&& attrs.type >= WindowManager.LayoutParams.FIRST_APPLICATION_WINDOW

&& attrs.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {

//假設導航條是隱藏的,讓應用窗口可以布局到無限制的overscan區域。只對應用窗口這么做,是要確保沒有別的窗口能在導航條之上。

pf.left = df.left = mOverscanScreenLeft;

pf.top = df.top = mOverscanScreenTop;

pf.right = df.right = mOverscanScreenLeft + mOverscanScreenWidth;

pf.bottom = df.bottom = mOverscanScreenTop + mOverscanScreenHeight;

//我們需要告知應用程序過掃描的內側在哪里,以便根據這個值嵌入它的內容,而不至於實際的超出overscan的范圍。

of.left = mUnrestrictedScreenLeft;

of.top = mUnrestrictedScreenTop;

of.right = mUnrestrictedScreenLeft + mUnrestrictedScreenWidth;

of.bottom = mUnrestrictedScreenTop + mUnrestrictedScreenHeight;

}else{

//然后是其他情況。

}

}else if ((fl & FLAG_LAYOUT_IN_SCREEN) != 0 || (sysUiFl

& (View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN

| View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION)) != 0) {

//窗口希望全屏顯示,可能是有系統UI的全屏,也可能是沒有系統UI的全屏。

}else if (attached != null) {

//子窗口的情況。

setAttachedWindowFrames(win, fl, adjust, attached, false, pf, df, of, cf, vf);

}else{

//其他的窗口類型,如:TYPE_STATUS_BAR_PANEL,TYPE_VOLUME_OVERLAY,TYPE_TOAST,TYPE_SYSTEM_ALERT等。

}

// pf, df, of, cf, vf, dcf, sf, osf這些都是layoutWindowLw函數中的臨時變量,最終還有通過windowState的 computeFrameLw的確認。

win.computeFrameLw(pf, df, of, cf, vf, dcf, sf, osf);

}

layoutWindowLw中的pf,df, of, cf, vf, dcf, sf, osf這些都是臨時變量,最終還有通過windowState的computeFrameLw的確認。也就是分別設置到WindowState中的變量mOverscanFrame,mContentFrame,mVisibleFrame,mDecorFrame,mStableFrame中,最后把WindowState中的這些變量在relayoutWindow(WindowManagerService.java)函數中通過outFrame.set(win.mCompatFrame);outOverscanInsets.set(win.mOverscanInsets);outContentInsets.set(win.mContentInsets);outVisibleInsets.set(win.mVisibleInsets);outStableInsets.set(win.mStableInsets);outOutsets.set(win.mOutsets);這些出參告知ViewRootImpl。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值