淺談Android之App視窗檢視管理
5 App視窗檢視管理
WindowManagerGlobal負責管理App所有要新增到WMS的視窗,介面即為上頭的addView
首先,對於App本地視窗來說,其最核心的資料無非就兩個,一個是Window Parameters,另一個就是視窗的DécorView,一個負責描述視窗屬性,另外一個描述視窗檢視
視窗有很多種型別,比如Activity關聯Window,PopupWindow, Dialog等等,它們主要通過
Window.LayoutParam.Type欄位做型別區分,Window.LayoutParam.Token欄位則儲存視窗關聯
Parent Window的Token,這三個視窗型別的區別後面會做介紹
視窗跟WMS建立連線,其實就是將Window Parameters傳送到WMS並對應建立
WindowState, 接著連線SurfaceFlinger建立Surface,最後將Surface返回到App端供視窗Décor View繪製圖形介面的過程, 這一切都被封裝到了ViewRootImpl中
當WindowManagerGlobal.addView被呼叫時,傳入Décor View和WindowParameters,其內部會對應建立ViewRootImpl,然後WindowManagerGlobal內部會對三者繫結關聯
接下去看下簡單的關係圖:
從圖中可以看出,PopUpWindow是Activity Base Window的子視窗,Dialog在預設情況下,它跟Activity Base Window是同級的
Activity在WMS中對應的AppWindowToken之前已經介紹過,是AMS在建立ActivityRecord後然後通知WMS建立的,剩下WindowState的對應關係,跟App這邊一致
三個視窗大體流程都差不多,所以接下去先基於ActivityBase Window來做介紹,最後再簡單介紹PopupWindow和Dialog與其存在的差異點
5.1 Activity Base Window介紹
Activity Base Window的建立上頭已經介紹過,是在Activity.attach時被建立的,所以我們接著4.3 Activity啟動之Activity和視窗的建立的wm.addView來繼續介紹
r.window = r.activity.getWindow(); View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION; l.softInputMode |= forwardBit; if (a.mVisibleFromClient) { a.mWindowAdded = true; wm.addView(decor, l); } |
Window type為TYPE_BASE_APPLICATION,這意味這個視窗在WMS對應的Parent 為
AppWindowToken,所以LayoutParams.token必須被設定為Activity對應的AppToken
wm對應的是WindowManagerImpl例項:
//WindowManagerImpl.java public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mDisplay, mParentWindow); } |
上頭說過,Activity basewindow在設定WindowManager時會建立本地視窗管理物件,即ParentWindow是Activity BaseWindow的WindowManagerImpl物件例項,所以這邊直接呼叫WindowManagerGlobal.addView,傳入的mParentWindow即為Activity BaseWindow,接著看程式碼:
//ViewRootImpl.java public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { …… Final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params; if (parentWindow != null) { parentWindow.adjustLayoutParamsForSubWindow(wparams); } else { …… } ViewRootImpl root; View panelParentView = null; …… // If this is a panel window, then find the window it is being // attached to for future reference. if (wparams.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && wparams.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { final int count = mViews.size(); for (int i = 0; i < count; i++) { if (mRoots.get(i).mWindow.asBinder() == wparams.token) { panelParentView = mViews.get(i); } } } root = new ViewRootImpl(view.getContext(), display); view.setLayoutParams(wparams); mViews.add(view); mRoots.add(root); mParams.add(wparams); } // do this last because it fires off messages to start doing things try { root.setView(view, wparams, panelParentView); } catch (RuntimeException e) { …… } } |
parentWindow肯定不為空,呼叫adjustLayoutParamsForSubWindow調整引數,接著建立
ViewRootImpl,然後再將三者對應的新增到mViews,mRoots,mParams陣列中,最後呼叫
setView新增視窗檢視
先來看adjustLayoutParamsForSubWindow的實現:
void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) { CharSequence curTitle = wp.getTitle(); if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW && wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) { …… } else { if (wp.token == null) { wp.token = mContainer == null ? mAppToken : mContainer.mAppToken; } if ((curTitle == null || curTitle.length() == 0) && mAppName != null) { wp.setTitle(mAppName); } } if (wp.packageName == null) { wp.packageName = mContext.getPackageName(); } if (mHardwareAccelerated) { wp.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED; } } |
wp.type為TYPE_BASE_APPLICATION,再加上mContainer為空,所以wp.token最終被設定為
mAppToken
ViewRootImpl在核心控制類,所以他除了儲存設定的View以及對應的LayoutParams資料外,最重要的,就是跟WMS建立資料通訊。
接著看其建構函式:
public ViewRootImpl(Context context, Display display) { mContext = context; // These can be accessed by any thread, must be protected with a lock. // Surface can never be reassigned or cleared (use Surface.clear()). Surface mSurface = new Surface(); mThread = Thread.currentThread(); mWindowSession = WindowManagerGlobal.getWindowSession(); …… mWindow = new W(this); …… mFirst = true; // true for the first time the view is added mAdded = false; mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this); …… } |
建立Surface儲存到mSurface,不過目前這個Surface是空的,還未與SurfaceControl繫結,還有Surface本身是不限制訪問執行緒的;接著將當前執行緒資訊儲存到mThread,這個才是用於對UI操作進行執行緒限制的
接著與WMS建立會話並儲存到mWindowSession,接著建立mWindow物件,對應類為W:
static class W extends IWindow.Stub |
可以看出,它是一個native binder,用來跟當前新增的檢視在WMS進行繫結,供WMS向App端進行視窗的相關操作
最後是mAttachInfo,這個類儲存了很多全域性資料,它會在View被成功新增到WMS後,在view.dispatchAttachedToWindow(mAttachInfo, 0)被呼叫時作為引數傳到View
接著看setView的程式碼:
* We have one child */ public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { mView = view; mAttachInfo.mDisplayState = mDisplay.getState(); mDisplayManager.registerDisplayListener(mDisplayListener, mHandler); mViewLayoutDirectionInitial = mView.getRawLayoutDirection(); mFallbackEventHandler.setView(view); mWindowAttributes.copyFrom(attrs); if (mWindowAttributes.packageName == null) { mWindowAttributes.packageName = mBasePackageName; } attrs = mWindowAttributes; // Keep track of the actual window flags supplied by the client. mClientWindowLayoutFlags = attrs.flags; setAccessibilityFocus(null, null); …… mSoftInputMode = attrs.softInputMode; mWindowAttributesChanged = true; mWindowAttributesChangesFlag = WindowManager.LayoutParams.EVERYTHING_CHANGED; mAttachInfo.mRootView = view; mAttachInfo.mScalingRequired = mTranslator != null; mAttachInfo.mApplicationScale = mTranslator == null ? 1.0f : mTranslator.applicationScale; if (panelParentView != null) { mAttachInfo.mPanelParentWindowToken = panelParentView.getApplicationWindowToken(); } mAdded = true; int res; /* = WindowManagerImpl.ADD_OKAY; */ // Schedule the first layout -before- adding to the window // manager, to make sure we do the relayout before receiving // any other events from the system. requestLayout(); if ((mWindowAttributes.inputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) { mInputChannel = new InputChannel(); } try { mOrigWindowType = mWindowAttributes.type; mAttachInfo.mRecomputeGlobalAttributes = true; collectViewAttributes(); res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mInputChannel); } …… } |
先requestLayout,由於這個呼叫是非同步的,而mWindowSession.addToDisplay同步呼叫後,如果requestLayout在mWindowSession.addToDisplay之後被呼叫,由於執行緒排程的原因,就有可能會導致WMS通過binder往mWindow傳送的事件會先於requestLayout被執行,因為UI操作都會被post到主執行緒執行,這就導致,誰先入列,誰就先被執行
所以,為了能夠按照註釋說的,確保requestLayout操作在收到所有系統事件之前被執行,其必須在mWindowSession.addToDisplay之前被呼叫
先看requestLayout的程式碼:
public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { checkThread(); mLayoutRequested = true; scheduleTraversals(); } } |
先呼叫checkThread確保操作在主執行緒進行:
void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } } |
接著呼叫scheduleTraversals:
void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); if (!mUnbufferedInputDispatch) { scheduleConsumeBatchedInput(); } notifyRendererOfFramePending(); } } |
最終mTraversalRunnable這個runnable 被post,它稍後會被立即執行
回過頭繼續看mWindowSession.addToDisplay,這個是一個RPC呼叫,主要傳入三個引數,一個是mWindow ,作為WMS向本地視窗傳送事件的入口,第二個是視窗引數,還有就是InputChannel,用於從WMS接收視窗事件
接著將程式碼執行環境切到WMS
//Session.java public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, InputChannel outInputChannel) { return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outContentInsets, outStableInsets, outInputChannel); } |
接著看WMS.addWindow:
public int addWindow(Session session, IWindow client, int seq, WindowManager.LayoutParams attrs, int viewVisibility, int displayId, Rect outContentInsets, Rect outStableInsets, InputChannel outInputChannel) { int[] appOp = new int[1]; int res = mPolicy.checkAddPermission(attrs, appOp); if (res != WindowManagerGlobal.ADD_OKAY) { return res; } boolean reportNewConfig = false; WindowState attachedWindow = null; WindowState win = null; long origId; final int type = attrs.type; synchronized(mWindowMap) { …… if (mWindowMap.containsKey(client.asBinder())) { Slog.w(TAG, "Window " + client + " is already added"); return WindowManagerGlobal.ADD_DUPLICATE_ADD; } if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) { …… } …… boolean addToken = false; WindowToken token = mTokenMap.get(attrs.token); if (token == null) { …… } else if (type >= FIRST_APPLICATION_WINDOW && type <= LAST_APPLICATION_WINDOW) { AppWindowToken atoken = token.appWindowToken; …… } …… win = new WindowState(this, session, client, token, attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent); …… mPolicy.adjustWindowParamsLw(win.mAttrs); win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs)); res = mPolicy.prepareAddWindowLw(win, attrs); if (res != WindowManagerGlobal.ADD_OKAY) { return res; } …… res = WindowManagerGlobal.ADD_OKAY; …… win.attach(); mWindowMap.put(client.asBinder(), win); …… final WindowStateAnimator winAnimator = win.mWinAnimator; winAnimator.mEnterAnimationPending = true; winAnimator.mEnteringAnimation = true; …… return res; } |
在這裡又看到了mPolicy,在WMS端,其實它對應的是PhoneWindowManager,跟App端的
PhoneWindow一樣,它也是通過Policy來建立的,PhoneWindow負責App端視窗一些預設事件或操作的處理,PhoneWindowManager則負責在WMS端對視窗的類似操作和處理
這裡呼叫mPolicy.checkAddPermission(attrs,appOp)進行許可權檢查,其實這個主要是針對一些系統視窗和特殊視窗型別進行許可權檢查,對於TYPE_BASE_APPLICATION這些app的視窗型別,檢查肯定是OK的
接著呼叫mWindowMap.containsKey(client.asBinder())檢查視窗是否已經被新增過,如果新增過,則返回重複新增錯誤
attrs.token儲存的是ActivityRecord對應的AppWindowToken,通過attrs.token從mTokenMap拿到對應的AppWindowToken物件儲存到token
TYPE_BASE_APPLICATION的值為1,位於FIRST_APPLICATION_WINDOW和
LAST_APPLICATION_WINDOW之間,接著建立WindowState儲存到win,然後呼叫win.attach,
最後將win新增到mWindowMap
WindowState是視窗在WMS對應的例項,它跟ViewRootImpl的mWindow是一對一繫結的,先看其建構函式:
WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token, WindowState attachedWindow, int appOp, int seq, WindowManager.LayoutParams a, int viewVisibility, final DisplayContent displayContent) { mService = service; mSession = s; mClient = c; mAppOp = appOp; mToken = token; mOwnerUid = s.mUid; mWindowId = new IWindowId.Stub() { @Override public void registerFocusObserver(IWindowFocusObserver observer) { WindowState.this.registerFocusObserver(observer); } @Override public void unregisterFocusObserver(IWindowFocusObserver observer) { WindowState.this.unregisterFocusObserver(observer); } @Override public boolean isFocused() { return WindowState.this.isFocused(); } }; mAttrs.copyFrom(a); …… if ((mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW)) { …… } else { // The multiplier here is to reserve space for multiple // windows in the same type layer. mBaseLayer = mPolicy.windowTypeToLayerLw(a.type) * WindowManagerService.TYPE_LAYER_MULTIPLIER + WindowManagerService.TYPE_LAYER_OFFSET; mSubLayer = 0; mAttachedWindow = null; mLayoutAttached = false; mIsImWindow = mAttrs.type == TYPE_INPUT_METHOD || mAttrs.type == TYPE_INPUT_METHOD_DIALOG; mIsWallpaper = mAttrs.type == TYPE_WALLPAPER; mIsFloatingLayer = mIsImWindow || mIsWallpaper; } WindowState appWin = this; while (appWin.mAttachedWindow != null) { appWin = appWin.mAttachedWindow; } WindowToken appToken = appWin.mToken; while (appToken.appWindowToken == null) { WindowToken parent = mService.mTokenMap.get(appToken.token); if (parent == null || appToken == parent) { break; } appToken = parent; } mRootToken = appToken; mAppToken = appToken.appWindowToken; if (mAppToken != null) { final DisplayContent appDisplay = getDisplayContent(); mNotOnAppsDisplay = displayContent != appDisplay; } mWinAnimator = new WindowStateAnimator(this); mWinAnimator.mAlpha = a.alpha; mRequestedWidth = 0; mRequestedHeight = 0; mLastRequestedWidth = 0; mLastRequestedHeight = 0; mXOffset = 0; mYOffset = 0; mLayer = 0; mInputWindowHandle = new InputWindowHandle( mAppToken != null ? mAppToken.mInputApplicationHandle : null, this, displayContent.getDisplayId()); } |
儲存關聯的IWindow(即ViewRootImpl.mWindow)到mClient,將parent token儲存到mToken,對於App Top Level WindowState來說,mToken就對應AppWindowToken,對於很多sub window
則需要通過:
WindowState appWin = this; while (appWin.mAttachedWindow != null) { appWin = appWin.mAttachedWindow; |
用迴圈根據層級關係往上爬,直到找到TopLevel WindowState,迴圈結束後,將其儲存到appWin,最後將其儲存到mRootToken和mAppToken中,最後建立WindowState關聯的
WindowStateAnimator
WindowState建立結束後,接著其attach函式被呼叫:
void attach() { if (WindowManagerService.localLOGV) Slog.v( TAG, "Attaching " + this + " token=" + mToken + ", list=" + mToken.windows); mSession.windowAddedLocked(); } |
接著看mSession.windowAddedLocked:
void windowAddedLocked() { if (mSurfaceSession == null) { …… mSurfaceSession = new SurfaceSession(); …… mService.mSessions.add(this); if (mLastReportedAnimatorScale != mService.getCurrentAnimatorScale()) { mService.dispatchNewAnimatorScaleLocked(this); } } mNumWindow++; } |
這個函式負責建立了SurfaceSession並儲存到mSurfaceSession
從這裡可以看出,WMS為每一個Window Session對應建立了一個SurfaceSession
SurfaceSession建立好了,剩下唯一要做的就是為視窗建立SurfaceControl了
WMS.addWindow結束後,程式碼執行環境重新切回到App ViewRootImpl
接著mTraversalRunnable被呼叫:
//ViewRootImpl.java final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } } |
doTraversal:
void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; …… try { performTraversals(); } finally { Trace.traceEnd(Trace.TRACE_TAG_VIEW); } …… } } |
performTraversals:
private void performTraversals() { // cache mView since it is used so much below... final View host = mView; …… if (host == null || !mAdded) return; mIsInTraversal = true; mWillDrawSoon = true; boolean windowSizeMayChange = false; boolean newSurface = false; boolean surfaceChanged = false; WindowManager.LayoutParams lp = mWindowAttributes; …… final int viewVisibility = getHostVisibility(); boolean viewVisibilityChanged = mViewVisibility != viewVisibility || mNewSurfaceNeeded; WindowManager.LayoutParams params = null; if (mWindowAttributesChanged) { mWindowAttributesChanged = false; surfaceChanged = true; params = lp; } …… if (mFirst) { mFullRedrawNeeded = true; mLayoutRequested = true; …… host.dispatchAttachedToWindow(mAttachInfo, 0); …… } else { …… } …… if (mApplyInsetsRequested) { mApplyInsetsRequested = false; mLastOverscanRequested = mAttachInfo.mOverscanRequested; dispatchApplyInsets(host); if (mLayoutRequested) { // Short-circuit catching a new layout request here, so // we don't need to go through two layout passes when things // change due to fitting system windows, which can happen a lot. windowSizeMayChange |= measureHierarchy(host, lp, mView.getContext().getResources(), desiredWindowWidth, desiredWindowHeight); } } if (layoutRequested) { // Clear this now, so that if anything requests a layout in the // rest of this function we will catch it and re-run a full // layout pass. mLayoutRequested = false; } …… try { …… relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); }catch{ …… } …… final boolean didLayout = layoutRequested && !mStopped; boolean triggerGlobalLayoutListener = didLayout || mAttachInfo.mRecomputeGlobalAttributes; if (didLayout) { performLayout(lp, desiredWindowWidth, desiredWindowHeight); …… } …… boolean cancelDraw = mAttachInfo.mTreeObserver.dispatchOnPreDraw() || viewVisibility != View.VISIBLE; if (!cancelDraw && !newSurface) { if (!skipDraw || mReportNextDraw) { if (mPendingTransitions != null && mPendingTransitions.size() > 0) { for (int i = 0; i < mPendingTransitions.size(); ++i) { mPendingTransitions.get(i).startChangingAnimations(); } mPendingTransitions.clear(); } performDraw(); } } else { …… } mIsInTraversal = false; } |
這個函式巨長無比,大約有700多行程式碼,簡單介紹如下:
1) 如果是第一次執行,也就是mFirst為true,會呼叫
host.dispatchAttachedToWindow(mAttachInfo,0)將mAttachInfo dispatch給host décor view以及其所有的child views
2) 呼叫measureHierarchy()->performMeasure基於螢幕真實寬高來測量décor view的大小
3) 呼叫relayoutWindow-> mWindowSession.relayout,傳入第二步獲取的décor view大小,向WMS說明你期望的視窗大小,然後WMS會根據當前狀況視窗情況,比如是否有彈出輸入法介面,以及是否顯示狀態列等等,返回視窗真正被分配到的大小
4) 如果WMS給當前視窗分配的大小和第二步期望的不一致,則需再次呼叫
performMeasure基於新分配的視窗大小來對décor view重新測量
5) 接著呼叫performLayout-> mView.layout開始對décor view進行重新佈局
6) 最後呼叫performDraw->draw-> drawSoftware->mView.draw(canvas)對View進行重新繪製
繼續看drawSoftware函式程式碼:
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff, boolean scalingRequired, Rect dirty) { // Draw with software renderer. final Canvas canvas; try { final int left = dirty.left; final int top = dirty.top; final int right = dirty.right; final int bottom = dirty.bottom; canvas = mSurface.lockCanvas(dirty); …… try { canvas.translate(-xoff, -yoff); if (mTranslator != null) { mTranslator.translateCanvas(canvas); } canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0); attachInfo.mSetIgnoreDirtyState = false; mView.draw(canvas); drawAccessibilityFocusedDrawableIfNeeded(canvas); } finally { if (!attachInfo.mSetIgnoreDirtyState) { // Only clear the flag if it was not set during the mView.draw() call attachInfo.mIgnoreDirtyState = false; } } } finally { try { surface.unlockCanvasAndPost(canvas); } …… } return true; } |
先通過mSurface.lockCanvas拿到畫布,然後呼叫mView.draw將view圖形資料繪製到畫布上,接著呼叫surface.unlockCanvasAndPost(canvas)將圖形資料post到SurfaceFlinger
看到這裡很多人會有疑問,mSurface不是空的嗎?怎麼直接就用了,它在哪初始化的?回過頭來看第三步mWindowSession.relayout的呼叫,最後一個引數就是mSurface,不出意外的話,肯定是這次呼叫在WMS端被初始化的,接下去把程式碼執行環境切到WMS:
//Session.java public int relayout(IWindow window, int seq, WindowManager.LayoutParams attrs, int requestedWidth, int requestedHeight, int viewFlags, int flags, Rect outFrame, Rect outOverscanInsets, Rect outContentInsets, Rect outVisibleInsets, Rect outStableInsets, Configuration outConfig, Surface outSurface) { int res = mService.relayoutWindow(this, window, seq, attrs, requestedWidth, requestedHeight, viewFlags, flags, outFrame, outOverscanInsets, outContentInsets, outVisibleInsets, outStableInsets, outConfig, outSurface); return res; } |
繼續看mService.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, 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; } if (attrs != null) { mPolicy.adjustWindowParamsLw(attrs); } …… if ((attrChanges&WindowManager.LayoutParams.FORMAT_CHANGED) != 0) { // To change the format, we need to re-build the surface. winAnimator.destroySurfaceLocked(); toBeDisplayed = true; surfaceChanged = true; } try { if (!win.mHasSurface) { surfaceChanged = true; } SurfaceControl surfaceControl = winAnimator.createSurfaceLocked(); if (surfaceControl != null) { outSurface.copyFrom(surfaceControl); } else { // For some reason there isn't a surface. Clear the // caller's object so they see the same state. outSurface.release(); } } catch (Exception e) { …… return 0; } …… } else { …… } …… } |
先通過client拿到對應的WindowState, 然後獲取其內部關聯的WindowStateAnimator物件,由於WindowState還未建立過Surface,所以這裡mHasSurface肯定為false,接著呼叫
winAnimator.createSurfaceLocked()建立SurfaceControl, 最後呼叫
outSurface.copyFrom(surfaceControl)初始化outSurface,也就是App端傳過來的mSurface
接著看winAnimator.createSurfaceLocked()的內部實現:
SurfaceControl createSurfaceLocked() { final WindowState w = mWin; if (mSurfaceControl == null) { …… // Set up surface control with initial size. try { mSurfaceW = width; mSurfaceH = height; final boolean isHwAccelerated = (attrs.flags & WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED) != 0; final int format = isHwAccelerated ? PixelFormat.TRANSLUCENT : attrs.format; if (!PixelFormat.formatHasAlpha(attrs.format) && attrs.surfaceInsets.left == 0 && attrs.surfaceInsets.top == 0 && attrs.surfaceInsets.right == 0 && attrs.surfaceInsets.bottom == 0) { flags |= SurfaceControl.OPAQUE; } if (DEBUG_SURFACE_TRACE) { mSurfaceControl = new SurfaceTrace( mSession.mSurfaceSession, attrs.getTitle().toString(), width, height, format, flags); } else { mSurfaceControl = new SurfaceControl( mSession.mSurfaceSession, attrs.getTitle().toString(), width, height, format, flags); } w.mHasSurface = true; …… } …… // Start a new transaction and apply position & offset. SurfaceControl.openTransaction(); try { mSurfaceX = left; mSurfaceY = top; try { mSurfaceControl.setPosition(left, top); mSurfaceLayer = mAnimLayer; final DisplayContent displayContent = w.getDisplayContent(); if (displayContent != null) { mSurfaceControl.setLayerStack(displayContent.getDisplay().getLayerStack()); } mSurfaceControl.setLayer(mAnimLayer); mSurfaceControl.setAlpha(0); mSurfaceShown = false; } catch (RuntimeException e) { Slog.w(TAG, "Error creating surface in " + w, e); mService.reclaimSomeSurfaceMemoryLocked(this, "create-init", true); } mLastHidden = true; } finally { SurfaceControl.closeTransaction(); if (SHOW_LIGHT_TRANSACTIONS) Slog.i(TAG, "<<< CLOSE TRANSACTION createSurfaceLocked"); } if (WindowManagerService.localLOGV) Slog.v( TAG, "Created surface " + this); } return mSurfaceControl; } |
如果mSurfaceControl為null,則呼叫new SurfaceControl並傳入關聯SurfaceSession建立後儲存到mSurfaceControl,最後通過SurfaceControl.openTransaction()和closeTransaction()修改配置資料
對SurfaceControl相關不熟悉的,建議先閱讀第三章節
至此,Activity的整個建立流程跑步跑完,View已經成功被show出來了^_^
5.2 Dialog和PopupWindow介紹
不管Activity Base Window,PopupWindow還是Dialog,其最終都是通過呼叫
WindowManageGlobal.addView新增到WMS,所以,它們三個呼叫addView之後的流程都是一樣的
這裡主要對它們的建立,以及對應的視窗型別和parenttoken做下簡單介紹
Dialog:
Dialog我們都知道,它構造的時候,傳入的Context必須是Activity物件例項,這是為什麼?
先看其建構函式:
Dialog(Context context, int theme, boolean createContextThemeWrapper) { if (createContextThemeWrapper) { if (theme == 0) { TypedValue outValue = new TypedValue(); context.getTheme().resolveAttribute(com.android.internal.R.attr.dialogTheme, outValue, true); theme = outValue.resourceId; } mContext = new ContextThemeWrapper(context, theme); } else { mContext = context; } mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); Window w = PolicyManager.makeNewWindow(mContext); mWindow = w; w.setCallback(this); w.setOnWindowDismissedCallback(this); w.setWindowManager(mWindowManager, null, null); w.setGravity(Gravity.CENTER); mListenersHandler = new ListenersHandler(this); } |
先呼叫context.getSystemService獲取WindowManager例項儲存到mWindowManager,接著建立Window物件儲存到mWindow,這裡有點注意下,這裡雖然呼叫w.setWindowManager建立了Window關聯的本地視窗管理例項,但是最終Dialog show的時候,並沒有用Window內部關聯的WindowManager,而是用的mWindowManager
那構造時,傳入的Context是Activity還是非Activity,唯一不同的,就是getSystemService的實現了,所以沒猜錯的話,Activity肯定也實現了這個函式:
//Activity.java public Object getSystemService(@ServiceName @NonNull String name) { if (getBaseContext() == null) { throw new IllegalStateException( "System services not available to Activities before onCreate()"); } if (WINDOW_SERVICE.equals(name)) { return mWindowManager; } else if (SEARCH_SERVICE.equals(name)) { ensureSearchManager(); return mSearchManager; } return super.getSystemService(name); } |
看到了嗎?如果傳入的是Activity,那Dialog使用的視窗管理其實就是Activity basewindow關聯是本地視窗管理。
那Context如果是非Activity為什麼又不行?
繼續看Dialog.show:
public void show() { …… WindowManager.LayoutParams l = mWindow.getAttributes(); …… try { mWindowManager.addView(mDecor, l); mShowing = true; sendShowMessage(); } finally { } } |
直接獲取預設的視窗屬性,WindowManager.LayoutParams預設建構函式:
public LayoutParams() { super(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT); type = TYPE_APPLICATION; format = PixelFormat.OPAQUE; } |
也就是說,Dialog預設的視窗型別就是TYPE_APPLICATION,那它的parent token就必須要被設定成Activity的app token
這就是Dialog構造時要傳入Activity的原因,要不啟動會報無法找到token的錯誤
如果你想要脫離Activity,在後臺通過Context啟動Dialog,唯一的辦法就是修改Dialog的視窗型別,當然這麼做太暴力,需要有對應的許可權.
PopupWindow:
先看建構函式:
public PopupWindow(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) { mContext = context; mWindowManager = (WindowManager)context.getSystemService(Context.WINDOW_SERVICE); …… } |
初始化mWindowManager,這裡對Context對應例項沒要求,只要是Context就行
接著看showAsDropDown,PopupWindow顯示的時候,必須指定anchorview, 用於設定其顯示位置:
public void showAsDropDown(View anchor, int xoff, int yoff, int gravity) { if (isShowing() || mContentView == null) { return; } registerForScrollChanged(anchor, xoff, yoff, gravity); mIsShowing = true; mIsDropdown = true; WindowManager.LayoutParams p = createPopupLayout(anchor.getWindowToken()); preparePopup(p); updateAboveAnchor(findDropDownPosition(anchor, p, xoff, yoff, gravity)); if (mHeightMode < 0) p.height = mLastHeight = mHeightMode; if (mWidthMode < 0) p.width = mLastWidth = mWidthMode; p.windowAnimations = computeAnimationResource(); invokePopup(p); } |
先呼叫createPopupLayout並傳入anchor.getWindowToken()的值建立視窗屬性:
private WindowManager.LayoutParams createPopupLayout(IBinder token) { WindowManager.LayoutParams p = new WindowManager.LayoutParams(); p.gravity = Gravity.START | Gravity.TOP; p.width = mLastWidth = mWidth; p.height = mLastHeight = mHeight; if (mBackground != null) { p.format = mBackground.getOpacity(); } else { p.format = PixelFormat.TRANSLUCENT; } p.flags = computeFlags(p.flags); p.type = mWindowLayoutType; p.token = token; p.softInputMode = mSoftInputMode; p.setTitle("PopupWindow:" + Integer.toHexString(hashCode())); return p; } |
設定視窗屬性型別為mWindowLayoutType,mWindowLayoutType預設的值為:
WindowManager.LayoutParams.TYPE_APPLICATION_PANEL 對應定義為: public static final int TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW; |
也就是說,PopupWindow對應的視窗型別為sub window,那p.token就必須設定為其父視窗的token,也就是anchor.getWindowToken拿到的值。
接著看invokePopup的程式碼:
private void invokePopup(WindowManager.LayoutParams p) { if (mContext != null) { p.packageName = mContext.getPackageName(); } mPopupView.setFitsSystemWindows(mLayoutInsetDecor); setLayoutDirectionFromAnchor(); mWindowManager.addView(mPopupView, p); } |
很簡單,就是呼叫mWindowManager將mPopupView和對應LayoutParams新增到
WindowManagerGlobal。