Android 重學系列 WMS在Activity啟動中的職責 添加窗體(三)

前言

經(jīng)過上一篇章的討論曹阔,我們理清楚了ActivityRecord,TaskRecord和窗體容器之間的關系蚁趁。同時達到了應用啟動時干发,啟動的第一個啟動窗口,StartingWindow叙甸。這個時候颖医,我們可以看到一個直指核心的代碼段:

            wm = (WindowManager) context.getSystemService(WINDOW_SERVICE);
            view = win.getDecorView();

...
            wm.addView(view, params);

這個方法聯(lián)通了WMS中的addView方法。

上一篇:Android 重學系列 WMS在Activity啟動中的職責(二)

正文

Context 獲取系統(tǒng)服務

在正式聊WMS之前裆蒸,我們先來看看context.getSystemService其核心原理熔萧,才能找到WindowManager的實現(xiàn)類:
文件:/frameworks/base/core/java/android/app/ContextImpl.java

    @Override
    public Object getSystemService(String name) {
        return SystemServiceRegistry.getSystemService(this, name);
    }

文件: /frameworks/base/core/java/android/app/SystemServiceRegistry.java

private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
            new HashMap<String, ServiceFetcher<?>>();

public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }

能看到是實際上所有的我們通過Context獲取系統(tǒng)服務,是通過SYSTEM_SERVICE_FETCHERS這個提前存放在HashMap的服務集合中光戈。這個服務是在靜態(tài)代碼域中提前注冊哪痰。

        registerService(Context.WINDOW_SERVICE, WindowManager.class,
                new CachedServiceFetcher<WindowManager>() {
            @Override
            public WindowManager createService(ContextImpl ctx) {
                return new WindowManagerImpl(ctx);
            }});

能看到此時實際上WindowManager的interface是由WindowManagerImpl實現(xiàn)的。

這里先上一個WindowManager的UML類圖久妆。


WindowManager.png

我們能夠從這個UML圖能夠看到,其實所有的事情都委托給WindowManagerGlobal工作跷睦。因此我們只需要看WindowManagerGlobal中做了什么筷弦。

因此我們要尋求WindowManager的addView的方法,實際上就是看WindowManagerGlobal的addView方法。

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 {
            // If there's no parent, then hardware acceleration for this view is
            // set from the application's hardware acceleration setting.
            final Context context = view.getContext();
            if (context != null
                    && (context.getApplicationInfo().flags
                            & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
                wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
            }
        }

        ViewRootImpl root;
        View panelParentView = null;

        synchronized (mLock) {
            // Start watching for system property changes.
            ...
            int index = findViewLocked(view, false);
            if (index >= 0) {
                if (mDyingViews.contains(view)) {
                    // Don't wait for MSG_DIE to make it's way through root's queue.
                    mRoots.get(index).doDie();
                } else {
                    throw new IllegalStateException("View " + view
                            + " has already been added to the window manager.");
                }
                // The previous removeView() had not completed executing. Now it has.
            }

            // 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) {
                // BadTokenException or InvalidDisplayException, clean up.
                if (index >= 0) {
                    removeViewLocked(index, true);
                }
                throw e;
            }
        }
    }

這里能夠看到一個新的addView的時候烂琴,會找到是否有父Window爹殊。沒有則繼續(xù)往后走,判斷新建窗體的type是否是子窗口類型奸绷,是則查找傳進來的Binder對象和存儲在緩存中的Binder對象又沒有對應的Window梗夸。有則作為本次新建窗口的復窗口。

最后能夠看到我們熟悉的類ViewRootImpl号醉。這個類可以說是所有View繪制的根部核心反症,這個類會在后面View繪制流程聊聊。最后會調用ViewRootImpl的setView進一步的溝通系統(tǒng)應用端畔派。

這里涉及到了幾個有趣的宏铅碍,如WindowManager.LayoutParams.FIRST_SUB_WINDOW 。它們象征這當前Window處于什么層級线椰。

Window的層級

Window的層級胞谈,我們大致可以分為3大類:System Window(系統(tǒng)窗口),Application Window(應用窗口)憨愉,Sub Window(子窗口)

Application Window(應用窗口)

Application值得注意的有這么幾個宏:

type 描述
FIRST_APPLICATION_WINDOW = 1 應用程序窗口初始值
TYPE_BASE_APPLICATION = 1 應用窗口類型初始值烦绳,其他窗口以此為基準
TYPE_APPLICATION = 2 普通應用程序窗口類型
TYPE_APPLICATION_STARTING = 3 應用程序的啟動窗口類型,不是應用進程支配配紫,當?shù)谝粋€應用進程誕生了啟動窗口就會銷毀
TYPE_DRAWN_APPLICATION = 4 應用顯示前WindowManager會等待這種窗口類型繪制完畢爵嗅,一般在多用戶使用
LAST_APPLICATION_WINDOW = 99 應用窗口類型最大值

因此此時我們能夠清楚,應用窗口的范圍在1~99之間笨蚁。

Sub Window(子窗口)

type 描述
FIRST_SUB_WINDOW = 1000 子窗口初始值
TYPE_APPLICATION_PANEL = FIRST_SUB_WINDOW 應用的panel窗口睹晒,在父窗口上顯示
TYPE_APPLICATION_MEDIA = FIRST_SUB_WINDOW + 1 多媒體內容子窗口,在父窗口之下
TYPE_APPLICATION_SUB_PANEL = FIRST_SUB_WINDOW + 2 也是一種panel子窗口括细,位于所有TYPE_APPLICATION_PANEL之上
TYPE_APPLICATION_ATTACHED_DIALOG = FIRST_SUB_WINDOW + 3 dialog彈窗
TYPE_APPLICATION_MEDIA_OVERLAY = FIRST_SUB_WINDOW + 4 多媒體內容窗口的覆蓋層
TYPE_APPLICATION_ABOVE_SUB_PANEL = FIRST_SUB_WINDOW + 5 位于子panel之上窗口
LAST_SUB_WINDOW = 1999 子窗口類型最大值

能夠看到子窗口的范圍從1000~1999

System Window(系統(tǒng)窗口)

type 描述
FIRST_SYSTEM_WINDOW = 2000 系統(tǒng)窗口初始值
TYPE_STATUS_BAR = FIRST_SYSTEM_WINDOW 系統(tǒng)狀態(tài)欄
TYPE_SEARCH_BAR = FIRST_SYSTEM_WINDOW+1 搜索條窗口
TYPE_PHONE = FIRST_SYSTEM_WINDOW+2 通話窗口
TYPE_SYSTEM_ALERT = FIRST_SYSTEM_WINDOW+3 alert窗口伪很,電量不足時警告
TYPE_KEYGUARD = FIRST_SYSTEM_WINDOW+4 屏保窗口
TYPE_TOAST = FIRST_SYSTEM_WINDOW+5 Toast提示窗口
TYPE_SYSTEM_OVERLAY = FIRST_SYSTEM_WINDOW+6 系統(tǒng)覆蓋層窗口,這個層不會響應點擊事件
TYPE_PRIORITY_PHONE = FIRST_SYSTEM_WINDOW+7 電話優(yōu)先層奋单,在屏保狀態(tài)下顯示通話
TYPE_SYSTEM_DIALOG = FIRST_SYSTEM_WINDOW+8 系統(tǒng)層級的dialog锉试,比如RecentAppDialog
TYPE_KEYGUARD_DIALOG= FIRST_SYSTEM_WINDOW+9 屏保時候對話框(如qq屏保時候的聊天框)
TYPE_SYSTEM_ERROR= FIRST_SYSTEM_WINDOW+10 系統(tǒng)錯誤窗口
TYPE_INPUT_METHOD= FIRST_SYSTEM_WINDOW+11 輸入法窗口
TYPE_INPUT_METHOD_DIALOG= FIRST_SYSTEM_WINDOW+12 輸入法窗口上的對話框
TYPE_WALLPAPER= FIRST_SYSTEM_WINDOW+13 壁紙窗口
TYPE_STATUS_BAR_PANEL = FIRST_SYSTEM_WINDOW+14 滑動狀態(tài)欄窗口
LAST_SYSTEM_WINDOW = 2999 系統(tǒng)窗口最大值

常見的系統(tǒng)級別窗口主要是這幾個。能夠注意到系統(tǒng)窗口層級是從2000~2999览濒。

這些層級有什么用的呆盖?這些層級會作為參考,將會插入到顯示棧的位置贷笛,層級值越高应又,越靠近用戶。這個邏輯之后會聊到乏苦。

ViewRootImpl setView

ViewRootImpl里面包含了許多事情株扛,主要是包含了我們熟悉的View的繪制流程尤筐,以及添加Window實例的流程。

本文是關于WMS洞就,因此我們只需要看下面這個核心函數(shù)

public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;

                mAttachInfo.mDisplayState = mDisplay.getState();
//注冊屏幕變換監(jiān)聽
                mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);

                mViewLayoutDirectionInitial = mView.getRawLayoutDirection();
//點擊事件分發(fā)
                mFallbackEventHandler.setView(view);
                mWindowAttributes.copyFrom(attrs);
                if (mWindowAttributes.packageName == null) {
                    mWindowAttributes.packageName = mBasePackageName;
                }
                attrs = mWindowAttributes;
                setTag();

              ...
                // 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();
                }
                mForceDecorViewVisibility = (mWindowAttributes.privateFlags
                        & PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;
                try {
                    mOrigWindowType = mWindowAttributes.type;
                    mAttachInfo.mRecomputeGlobalAttributes = true;
                    collectViewAttributes();
                    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(), mWinFrame,
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel);
                } catch (RemoteException e) {
                    mAdded = false;
                    mView = null;
                    mAttachInfo.mRootView = null;
                    mInputChannel = null;
                    mFallbackEventHandler.setView(null);
                    unscheduleTraversals();
                    setAccessibilityFocus(null, null);
                    throw new RuntimeException("Adding window failed", e);
                } finally {
                    if (restore) {
                        attrs.restore();
                    }
                }

         ...

        }
    }

這個方法有兩個核心requestLayout以及addToDisplay盆繁。

  • 1.requestLayout實際上就是指View的繪制流程,并且最終會把像素數(shù)據(jù)發(fā)送到Surface底層旬蟋。
  • 2.mWindowSession.addToDisplay 添加Window實例到WMS中油昂。

本文主要討論WMS,requestLayout的方法暫時不談倾贰。

WindowManager的Session設計思想

先來看看Session類:

class Session extends IWindowSession.Stub implements IBinder.DeathRecipient

得知此時Session實現(xiàn)了一個IWindowSession的Binder對象冕碟。并且實現(xiàn)了Binder的死亡監(jiān)聽。

那么這個Session是從哪里來的呢躁染?實際上是通過WMS通過跨進程通信把數(shù)據(jù)這個Binder對象傳遞過來的:

    @Override
    public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
            IInputContext inputContext) {
        if (client == null) throw new IllegalArgumentException("null client");
        if (inputContext == null) throw new IllegalArgumentException("null inputContext");
        Session session = new Session(this, callback, client, inputContext);
        return session;
    }

通著這種方式鸣哀,就能把一個Session帶上WMS相關的環(huán)境送給客戶端操作。這種方式和什么很相似吞彤,實際上和servicemanager查詢服務Binder的思路幾乎一模一樣我衬。

@Override
    public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,
            int viewVisibility, int displayId, Rect outFrame, Rect outContentInsets,
            Rect outStableInsets, Rect outOutsets,
            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) {
        return mService.addWindow(this, window, seq, attrs, viewVisibility, displayId, outFrame,
                outContentInsets, outStableInsets, outOutsets, outDisplayCutout, outInputChannel);
    }

很有趣的是,我們能夠看到饰恕,按照道理我們需要添加窗體實例到WMS中挠羔。從邏輯上來講,我們只需要做一次跨進程通信即可埋嵌。但是為什么需要一個Session作為中轉站呢破加?

image.png

能夠看到實際上Session(會話)做的事情不僅僅只有溝通WMS這么簡單。實際上它還同時處理了窗口上的拖拽雹嗦,輸入法等邏輯范舀,更加重要的是Session面對著系統(tǒng)多個服務,但是通過這個封裝了罪,應用程序只需要面對這個Sesion接口锭环,真的是名副其實的"會話"。

這種設計想什么泊藕?實際上就是我們常說的門面設計模式辅辩。

IWindow對象

注意,這里面除了IWindowSession之外,當我們調用addWindow添加Window到WMS中的時候娃圆,其實還存在一個IWindow接口.這個IWindow是指PhoneWindow嗎玫锋?

很遺憾。并不是讼呢。PhoneWindow基礎的接口只有Window接口撩鹿。它并不是一個IBinder對象。我們轉過頭看看ViewRootImpl.

public ViewRootImpl(Context context, Display display) {
        mContext = context;
        mWindowSession = WindowManagerGlobal.getWindowSession();
        mDisplay = display;
        mBasePackageName = context.getBasePackageName();
        mThread = Thread.currentThread();
        mLocation = new WindowLeaked(null);
        mLocation.fillInStackTrace();
        mWidth = -1;
        mHeight = -1;
        mDirty = new Rect();
        mTempRect = new Rect();
        mVisRect = new Rect();
        mWinFrame = new Rect();
        mWindow = new W(this);
        mTargetSdkVersion = context.getApplicationInfo().targetSdkVersion;
        mViewVisibility = View.GONE;
        mTransparentRegion = new Region();
        mPreviousTransparentRegion = new Region();
        mFirst = true; // true for the first time the view is added
        mAdded = false;
        mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this,
                context);
...
        mViewConfiguration = ViewConfiguration.get(context);
        mDensity = context.getResources().getDisplayMetrics().densityDpi;
        mNoncompatDensity = context.getResources().getDisplayMetrics().noncompatDensityDpi;
        mFallbackEventHandler = new PhoneFallbackEventHandler(context);
        mChoreographer = Choreographer.getInstance();
        mDisplayManager = (DisplayManager)context.getSystemService(Context.DISPLAY_SERVICE);

        if (!sCompatibilityDone) {
            sAlwaysAssignFocus = mTargetSdkVersion < Build.VERSION_CODES.P;

            sCompatibilityDone = true;
        }

        loadSystemProperties();
    }

能看到此時吝岭,實際上在ViewRootImpl的構造函數(shù)會對應當前生成一個W的內部類三痰。這個內部類:

static class W extends IWindow.Stub

這個內部類實際上就是一個Binder類吧寺,里面回調了很多方法來操作當前的ViewRootImpl窜管。換句話說散劫,就是把當前的ViewRootImpl的代理W交給WMS去管理。

那么我們可以總結幕帆,IWindow是WMS用來間接操作ViewRootImpl中的View获搏,IWindowSession是App用來間接操作WMS。

WMS.addWindow

WMS的addWindow很長失乾,因此我這邊拆開成3部分聊

添加窗體的準備步驟

public int addWindow(Session session, IWindow client, int seq,
            LayoutParams attrs, int viewVisibility, int displayId, Rect outFrame,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            DisplayCutout.ParcelableWrapper outDisplayCutout, InputChannel outInputChannel) {
        int[] appOp = new int[1];
        int res = mPolicy.checkAddPermission(attrs, appOp);
        if (res != WindowManagerGlobal.ADD_OKAY) {
            return res;
        }

        boolean reportNewConfig = false;
        WindowState parentWindow = null;
        long origId;
        final int callingUid = Binder.getCallingUid();
        final int type = attrs.type;

        synchronized(mWindowMap) {
            if (!mDisplayReady) {
                throw new IllegalStateException("Display has not been initialialized");
            }

            final DisplayContent displayContent = getDisplayContentOrCreate(displayId);

            if (displayContent == null) {
...
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }
            if (!displayContent.hasAccess(session.mUid)
                    && !mDisplayManagerInternal.isUidPresentOnDisplay(session.mUid, displayId)) {
...
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }

            if (mWindowMap.containsKey(client.asBinder())) {
  ...
                return WindowManagerGlobal.ADD_DUPLICATE_ADD;
            }

            if (type >= FIRST_SUB_WINDOW && type <= LAST_SUB_WINDOW) {
//如果是子窗口常熙,則通過Binder找父窗口
                parentWindow = windowForClientLocked(null, attrs.token, false);
                if (parentWindow == null) {
...
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
                if (parentWindow.mAttrs.type >= FIRST_SUB_WINDOW
                        && parentWindow.mAttrs.type <= LAST_SUB_WINDOW) {
...
                    return WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN;
                }
            }

            if (type == TYPE_PRIVATE_PRESENTATION && !displayContent.isPrivate()) {
...
                return WindowManagerGlobal.ADD_PERMISSION_DENIED;
            }

            AppWindowToken atoken = null;
            final boolean hasParent = parentWindow != null;
//從DisplayContent找到對應的WIndowToken
            WindowToken token = displayContent.getWindowToken(
                    hasParent ? parentWindow.mAttrs.token : attrs.token);

            final int rootType = hasParent ? parentWindow.mAttrs.type : type;

....
    }

我們拋開大部分的校驗邏輯。實際上可以把這個過程總結為以下幾點:

  • 1.判斷又沒有相關的權限
  • 2.嘗試著獲取當前displayId對應的DisplayContent碱茁,沒有則創(chuàng)建裸卫。其邏輯實際上和我上一篇說的創(chuàng)建DisplayContent一摸一樣
  • 3.通過mWindowMap,判斷當前IWindow是否被添加過纽竣,是的話說明已經(jīng)存在這個Window墓贿,不需要繼續(xù)添加
  • 4.如果當前窗口類型是子窗口,則會通過WindowToken.attrs參數(shù)中的token去查找當前窗口的父窗口是什么蜓氨。
  • 5.如果有父窗口聋袋,則從DisplayContent中以父窗口的IWindow獲取父窗口WindowToken的對象,否則嘗試的獲取當前窗口對應的WindowToken對象穴吹。

我們稍微探索一下其中的幾個核心:

通過windowForClientLocked查找父窗口的WindowState

final WindowState windowForClientLocked(Session session, IBinder client, boolean throwOnError) {
        WindowState win = mWindowMap.get(client);
        if (localLOGV) Slog.v(TAG_WM, "Looking up client " + client + ": " + win);
        if (win == null) {
            if (throwOnError) {
                throw new IllegalArgumentException(
                        "Requested window " + client + " does not exist");
            }
            Slog.w(TAG_WM, "Failed looking up window callers=" + Debug.getCallers(3));
            return null;
        }
        if (session != null && win.mSession != session) {
            if (throwOnError) {
                throw new IllegalArgumentException("Requested window " + client + " is in session "
                        + win.mSession + ", not " + session);
            }
            Slog.w(TAG_WM, "Failed looking up window callers=" + Debug.getCallers(3));
            return null;
        }

        return win;
    }

實際上可以看到這里面是從mWindowMap通過IWindow獲取WindowState對象幽勒。還記得我上篇說過很重要的數(shù)據(jù)結構嗎?mWindowMap實際上是保存著WMS中IWindow對應WindowState對象港令。IWindow本質上是WMS控制ViewRootImpl的Binder接口啥容。因此我們可以把WindowState看成應用進程的對應的對象也未嘗不可。

獲取對應的WindowToken

            AppWindowToken atoken = null;
            final boolean hasParent = parentWindow != null;
//從DisplayContent找到對應的WIndowToken
            WindowToken token = displayContent.getWindowToken(
                    hasParent ? parentWindow.mAttrs.token : attrs.token);

從這里面我們能夠看到WindowToken顷霹,是通過DisplayContent獲取到的咪惠。

WindowToken getWindowToken(IBinder binder) {
        return mTokenMap.get(binder);
    }

這樣就能看到我前兩篇提到過的很重要的數(shù)據(jù)結構:mTokenMap以及mWindowMap。這兩者要稍微區(qū)分一下:
mWindowMap是以IWindow為key泼返,WindowState為value硝逢。
mTokenMap是以WindowState的IBinder(一般為IApplicationToken)為key,WindowToken為value

還記得mTokenMap在Activity的啟動流程中做的事情嗎绅喉?在創(chuàng)建AppWIndowContainer的時候渠鸽,會同時創(chuàng)建AppWindowToken,AppWIndowToken的構造會把當前的IBinder作為key柴罐,AppWindowToken作為value添加到mTokenMap中徽缚。

也就是說,如果系統(tǒng)想要通過應用進程給的IWindow找到真正位于WMS中Window的句柄革屠,必須通過這兩層變換才能真正找到凿试。

拆分情況獲取對應的WindowToken和AppWindowToken

這個時候就分為兩種情況排宰,一種是存在WindowToken,一種是不存在WindowToken那婉。

            boolean addToastWindowRequiresToken = false;

            if (token == null) {
            //校驗窗口參數(shù)是否合法  
            ...
                
                final IBinder binder = attrs.token != null ? attrs.token : client.asBinder();
                final boolean isRoundedCornerOverlay =
                        (attrs.privateFlags & PRIVATE_FLAG_IS_ROUNDED_CORNERS_OVERLAY) != 0;
                token = new WindowToken(this, binder, type, false, displayContent,
                        session.mCanAddInternalSystemWindow, isRoundedCornerOverlay);
            } else if (rootType >= FIRST_APPLICATION_WINDOW && rootType <= LAST_APPLICATION_WINDOW) {
                atoken = token.asAppWindowToken();
                  if (atoken == null) {
                    return WindowManagerGlobal.ADD_NOT_APP_TOKEN;
                } 
...
                } else if (atoken.removed) {
...
                } else if (type == TYPE_APPLICATION_STARTING && atoken.startingWindow != null) {
...
                 
                }
            } else if (rootType == TYPE_INPUT_METHOD) {
...
                   
            } else if (rootType == TYPE_VOICE_INTERACTION) {
...
            } else if (rootType == TYPE_WALLPAPER) {
 ...
            } else if (rootType == TYPE_DREAM) {
...
            } else if (rootType == TYPE_ACCESSIBILITY_OVERLAY) {
...
            } else if (type == TYPE_TOAST) {
....
           } else if (type == TYPE_QS_DIALOG) {
...
            } else if (token.asAppWindowToken() != null) {

                attrs.token = null;
                token = new WindowToken(this, client.asBinder(), type, false, displayContent,
                        session.mCanAddInternalSystemWindow);
            }

當我們通過mTokenMap獲取WindowToken的時候板甘,大致分為四種情況。WindowToken會嘗試的獲取父窗口對應的Token详炬,找不到則使用WindowManager.LayoutParams中的WindowToken盐类。一般來說我們找到的都有父親的WindowToken。

  • 1.無關應用的找不到WindowToken
  • 2.有關應用找不到WindowToken呛谜。
  • 3.無關應用找到WindowToken
  • 4.有關應用找到WindowToken

前兩種情況解析

實際上前兩種情況在跳,一旦發(fā)現(xiàn)找不到WindowToken,如果當前的窗口和應用相關的隐岛,就一定爆錯誤猫妙。如Toast,輸入法聚凹,應用窗口等等割坠。

因此在Android 8.0開始,當我們想要顯示Toast的時候元践,加入傳入的Context是Application而不是Activity韭脊,此時一旦發(fā)現(xiàn)mTokenMap中找不到IApplicationToken對應的WindowToken就爆出了錯誤。正確的做法應該是需要獲取Activity當前的Context单旁。

在上面的情況應用啟動窗口沪羔,此時并沒有啟動Activity。因此不可能會被校驗攔下象浑,因此并沒有異常拋出蔫饰。就會自己創(chuàng)建一個WindowToken。

后兩種的解析

當找到WindowToken愉豺,一般是指Activity啟動之后篓吁,在AppWindowToken初始化后,自動加入了mTokenMap中蚪拦。此時的情況稍微復雜了點杖剪。

當是子窗口的時候,則會判斷當前的WindowToken是不是AppWindowToken驰贷。不是盛嘿,或者被移除等異常情況則報錯。

如果是壁紙括袒,輸入法次兆,系統(tǒng)彈窗,toast等窗口模式锹锰,子窗口和父窗口的模式必須一致芥炭。

當此時的AppWindowToken不為空的時候漓库,說明在New的時候已經(jīng)生成,且沒有移除园蝠,將會生成一個新的WindowToken渺蒿。

為什么要生成一個新的windowToken?可以翻閱之前我寫的文章,只要每一次調用一次構造函數(shù)將會把當前的WindowToken添加到mTokenMap中砰琢,實際上也是擔心蘸嘶,對應的AppWindowToken出現(xiàn)的重新綁定的問題良瞧。

添加WindowState實例到數(shù)據(jù)結構

但是別忘了陪汽,我們這個時候還需要把相關的數(shù)據(jù)結構存儲到全局。

            final WindowState win = new WindowState(this, session, client, token, parentWindow,
                    appOp[0], seq, attrs, viewVisibility, session.mUid,
                    session.mCanAddInternalSystemWindow);
            if (win.mDeathRecipient == null) {
...
                return WindowManagerGlobal.ADD_APP_EXITING;
            }

            if (win.getDisplayContent() == null) {
...
                return WindowManagerGlobal.ADD_INVALID_DISPLAY;
            }

            final boolean hasStatusBarServicePermission =
                    mContext.checkCallingOrSelfPermission(permission.STATUS_BAR_SERVICE)
                            == PackageManager.PERMISSION_GRANTED;
            mPolicy.adjustWindowParamsLw(win, win.mAttrs, hasStatusBarServicePermission);
            win.setShowToOwnerOnlyLocked(mPolicy.checkShowToOwnerOnly(attrs));

            res = mPolicy.prepareAddWindowLw(win, attrs);
            if (res != WindowManagerGlobal.ADD_OKAY) {
                return res;
            }
            // From now on, no exceptions or errors allowed!

            res = WindowManagerGlobal.ADD_OKAY;
            if (mCurrentFocus == null) {
                mWinAddedSinceNullFocus.add(win);
            }

            if (excludeWindowTypeFromTapOutTask(type)) {
                displayContent.mTapExcludedWindows.add(win);
            }

            origId = Binder.clearCallingIdentity();

            win.attach();
//以IWindow為key褥蚯,WindowState為value存放到WindowMap中
            mWindowMap.put(client.asBinder(), win);

            win.initAppOpsState();

....
            win.mToken.addWindow(win);

因為完全可能出現(xiàn)新的WindowToken挚冤,因此干脆會創(chuàng)建一個新的WindowState。此時會對調用WindowState.attach方法

    void attach() {
        mSession.windowAddedLocked(mAttrs.packageName);
    }

這方法挺重要的赞庶,Session做了一次添加鎖定训挡。
文件:/frameworks/base/services/core/java/com/android/server/wm/Session.java

void windowAddedLocked(String packageName) {
        mPackageName = packageName;
        mRelayoutTag = "relayoutWindow: " + mPackageName;
        if (mSurfaceSession == null) {
            if (WindowManagerService.localLOGV) Slog.v(
                TAG_WM, "First window added to " + this + ", creating SurfaceSession");
            mSurfaceSession = new SurfaceSession();
            if (SHOW_TRANSACTIONS) Slog.i(
                    TAG_WM, "  NEW SURFACE SESSION " + mSurfaceSession);
            mService.mSessions.add(this);
            if (mLastReportedAnimatorScale != mService.getCurrentAnimatorScale()) {
                mService.dispatchNewAnimatorScaleLocked(this);
            }
        }
        mNumWindow++;
    }

此時的工作是什么?聯(lián)系上下文歧强,當我們新增了PhoneWindow澜薄,就會一個ViewRootImpl,也因此新增了Session摊册。此時說明誕生一個新界面肤京,此時已經(jīng)誕生了相關的容器對象,但是相關的繪制到底層對象還沒有創(chuàng)建出來茅特。

命名邏輯和Session很相似忘分。Session是WMS給應用App的會話對象,SurfaceSession是SurfaceFlinger面向上層每一個WIndow需要繪制內容對象白修。

這個SurfaceSession和SurfaceControl都是重點,聯(lián)通到SurfaceFlinger很重要的對象肯骇。

最后再添加到mWindowMap中祖很。并且把WindowState添加到WindowToken中,讓每一個WindowToken賦予狀態(tài)的信息若债。我們稍微探索一下addWindow的方法。

WindowState 添加Window的策略

有沒有考慮過WindowManager.LayoutParams是從哪里來的token拆融?

當我們沒有指定當前窗口的type,則會自動設置為TYPE_APPLICATION = 2蓝牲,同時token將會是原來的appwindowtoken.當我們在addView傳入了父親窗口時候例衍,則會通過adjustLayoutParamsForSubWindow先不設定application的值,而是先拿到父親窗口的token:
文件:/frameworks/base/core/java/android/view/Window.java

void adjustLayoutParamsForSubWindow(WindowManager.LayoutParams wp) {
        CharSequence curTitle = wp.getTitle();
        if (wp.type >= WindowManager.LayoutParams.FIRST_SUB_WINDOW &&
                wp.type <= WindowManager.LayoutParams.LAST_SUB_WINDOW) {
            if (wp.token == null) {
                View decor = peekDecorView();
                if (decor != null) {
                    wp.token = decor.getWindowToken();
                }
            }
           ...
        } else if (wp.type >= WindowManager.LayoutParams.FIRST_SYSTEM_WINDOW &&
                wp.type <= WindowManager.LayoutParams.LAST_SYSTEM_WINDOW) {
            //設置title
        } else {
            if (wp.token == null) {
                wp.token = mContainer == null ? mAppToken : mContainer.mAppToken;
            }
           //設置title
        }
        if (wp.packageName == null) {
            wp.packageName = mContext.getPackageName();
        }
        if (mHardwareAccelerated ||
                (mWindowAttributes.flags & FLAG_HARDWARE_ACCELERATED) != 0) {
            wp.flags |= FLAG_HARDWARE_ACCELERATED;
        }
    }

能夠看到此時將會初始化WindowManager.LayoutParams的Token。此時Token在Activity啟動流程中已經(jīng)先一步初始化AppWindowToken奥吩。

在聊WindowState的添加窗口的策略之前霞赫,我們先來看看WindowState的構造函數(shù)端衰。

WindowState(WindowManagerService service, Session s, IWindow c, WindowToken token,
            WindowState parentWindow, int appOp, int seq, WindowManager.LayoutParams a,
            int viewVisibility, int ownerId, boolean ownerCanAddInternalSystemWindow,
            PowerManagerWrapper powerManagerWrapper) {
        super(service);
....
        try {
            c.asBinder().linkToDeath(deathRecipient, 0);
        } catch (RemoteException e) {
...
            return;
        }
        mDeathRecipient = deathRecipient;

        if (mAttrs.type >= FIRST_SUB_WINDOW && mAttrs.type <= LAST_SUB_WINDOW) {
            // The multiplier here is to reserve space for multiple
            // windows in the same type layer.
            mBaseLayer = mPolicy.getWindowLayerLw(parentWindow)
                    * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
            mSubLayer = mPolicy.getSubWindowLayerFromTypeLw(a.type);
            mIsChildWindow = true;


            parentWindow.addChild(this, sWindowSubLayerComparator);

            mLayoutAttached = mAttrs.type !=
                    WindowManager.LayoutParams.TYPE_APPLICATION_ATTACHED_DIALOG;
            mIsImWindow = parentWindow.mAttrs.type == TYPE_INPUT_METHOD
                    || parentWindow.mAttrs.type == TYPE_INPUT_METHOD_DIALOG;
            mIsWallpaper = parentWindow.mAttrs.type == TYPE_WALLPAPER;
        } else {
            // The multiplier here is to reserve space for multiple
            // windows in the same type layer.
            mBaseLayer = mPolicy.getWindowLayerLw(this)
                    * TYPE_LAYER_MULTIPLIER + TYPE_LAYER_OFFSET;
            mSubLayer = 0;
            mIsChildWindow = false;
            mLayoutAttached = false;
            mIsImWindow = mAttrs.type == TYPE_INPUT_METHOD
                    || mAttrs.type == TYPE_INPUT_METHOD_DIALOG;
            mIsWallpaper = mAttrs.type == TYPE_WALLPAPER;
        }
        mIsFloatingLayer = mIsImWindow || mIsWallpaper;

        if (mAppToken != null && mAppToken.mShowForAllUsers) {
            // Windows for apps that can show for all users should also show when the device is
            // locked.
            mAttrs.flags |= FLAG_SHOW_WHEN_LOCKED;
        }

...
    }

我們把目光幾種在mBaseLayer和mSubLayer的初始化上。我們能夠看到在初始化WindowState的時候央渣,會獲取WindowState的type是子窗口還是不是子窗口。

此時我們把這個問題分為兩種情況:

1.是子窗口

當我們發(fā)現(xiàn)當前窗口子窗口参淫,會分為如下2個層級作為基準值涎才。獲取當前傳進進來的層級type:
符合如下公式:

mBaselayer = 父窗口層級type(見上文Window層級的表格) * 10000 + 1000邑闺;
mSubLayer = 子窗口本身的層級type(見上文Window層級的表格)

    private static final Comparator<WindowState> sWindowSubLayerComparator =
            new Comparator<WindowState>() {
                @Override
                public int compare(WindowState w1, WindowState w2) {
                    final int layer1 = w1.mSubLayer;
                    final int layer2 = w2.mSubLayer;
                    if (layer1 < layer2 || (layer1 == layer2 && layer2 < 0 )) {
                        return -1;
                    }
                    return 1;
                };
            };

此時就會直接添加到parentWindow當中。會不斷的比對比當前mSubLayer大的值靶衍,直到找到第一個插入颅眶。

2.不是子窗口

此時也會根據(jù)當前傳進來的層級去計算當前window應該插入的地方帚呼。
符合如下公式:

mBaselayer = 當前的窗口層級type(見上文Window層級的表格) * 10000 + 1000眷蜈;
mSubLayer = 0;

將會在接下來通過WindowState的addWindow做進一步調整酌儒。

文件:/frameworks/base/services/core/java/com/android/server/wm/WindowToken.java

    private final Comparator<WindowState> mWindowComparator =
            (WindowState newWindow, WindowState existingWindow) -> {
        final WindowToken token = WindowToken.this;
        if (newWindow.mToken != token) {
            throw new IllegalArgumentException("newWindow=" + newWindow
                    + " is not a child of token=" + token);
        }

        if (existingWindow.mToken != token) {
            throw new IllegalArgumentException("existingWindow=" + existingWindow
                    + " is not a child of token=" + token);
        }

        return isFirstChildWindowGreaterThanSecond(newWindow, existingWindow) ? 1 : -1;
    };

    protected boolean isFirstChildWindowGreaterThanSecond(WindowState newWindow,
            WindowState existingWindow) {
        // New window is considered greater if it has a higher or equal base layer.
        return newWindow.mBaseLayer >= existingWindow.mBaseLayer;
    }
    void addWindow(final WindowState win) {
        if (DEBUG_FOCUS) Slog.d(TAG_WM,
                "addWindow: win=" + win + " Callers=" + Debug.getCallers(5));

        if (win.isChildWindow()) {
            // Child windows are added to their parent windows.
            return;
        }
        if (!mChildren.contains(win)) {
            if (DEBUG_ADD_REMOVE) Slog.v(TAG_WM, "Adding " + win + " to " + this);
            addChild(win, mWindowComparator);
            mService.mWindowsChanged = true;
            // TODO: Should we also be setting layout needed here and other places?
        }
    }

這一段都是繼上面調整非子窗口邏輯,能夠很輕松的看出來孽惰,實際上此時會去不斷的比對mBaseLayer直到找到一個大于等于的層級添加到上面勋功。

層級初步計算總結

還記得此時在DisplayContent中,把整個WindowContainer的集合拆分成幾個層次嗎骚揍?棧區(qū)域纤掸,statusbar區(qū)域借跪,壁紙區(qū)域,輸入法區(qū)域果港。

每當我們new了一個WindowToken,將會自動的根據(jù)此時窗口類型綁定到對應的區(qū)域的末尾萝衩。這個時候,當我們addWindow要添加WindowState的時候牌捷,將會根據(jù)這個句柄去查找WindowToken中的層級暗甥,插入到對應的層級中。

用一幅圖總結如下:


Window的層級插入.png

層級第二次計算

經(jīng)過上面的區(qū)域劃分,把窗體大致上區(qū)分到了幾個區(qū)域當中剥懒,并且有了大致的順序,但是實際上保檐,我們只是粗略的處理了Window夜只。實際上在App應用中不是簡單的擺好,我們在平時使用的時候并非如此场躯。

還有一種情況需要特殊處理,當我們嘗試著執(zhí)行窗口動畫的時候签舞,一般很少遇到有什么東西把Activity的Window動畫給遮擋住。實際上也是得益于第二次的調整师妙。

            

            if (type == TYPE_INPUT_METHOD) {
                win.mGivenInsetsPending = true;
                setInputMethodWindowLocked(win);
                imMayMove = false;
            } else if (type == TYPE_INPUT_METHOD_DIALOG) {
                displayContent.computeImeTarget(true /* updateImeTarget */);
                imMayMove = false;
            } else {
                if (type == TYPE_WALLPAPER) {
                    displayContent.mWallpaperController.clearLastWallpaperTimeoutTime();
                    displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                } else if ((attrs.flags&FLAG_SHOW_WALLPAPER) != 0) {
                    displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                } else if (displayContent.mWallpaperController.isBelowWallpaperTarget(win)) {

                    displayContent.pendingLayoutChanges |= FINISH_LAYOUT_REDO_WALLPAPER;
                }
            }
...
            if (imMayMove) {
                displayContent.computeImeTarget(true /* updateImeTarget */);
            }
            // Don't do layout here, the window must call
            // relayout to be displayed, so we'll do it there.
            win.getParent().assignChildLayers();
....

此時褪秀,會從DisplayContent頂部向下重新對層級進行排序媒吗。能看到核心方法是就是computeImeTarget以及assignChildLayers。

computeImeTarget

    WindowState computeImeTarget(boolean updateImeTarget) {
        if (mService.mInputMethodWindow == null) {
            if (updateImeTarget) {
                setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim);
            }
            return null;
        }

        final WindowState curTarget = mService.mInputMethodTarget;
        if (!canUpdateImeTarget()) {
            return curTarget;
        }

        mUpdateImeTarget = updateImeTarget;
        WindowState target = getWindow(mComputeImeTargetPredicate);


        if (target != null && target.mAttrs.type == TYPE_APPLICATION_STARTING) {
            final AppWindowToken token = target.mAppToken;
            if (token != null) {
                final WindowState betterTarget = token.getImeTargetBelowWindow(target);
                if (betterTarget != null) {
                    target = betterTarget;
                }
            }
        }

        if (curTarget != null && curTarget.isDisplayedLw() && curTarget.isClosing()
                && (target == null || target.isActivityTypeHome())) {
            return curTarget;
        }


        if (target == null) {
            if (updateImeTarget) {
                setInputMethodTarget(null, mService.mInputMethodTargetWaitingAnim);
            }

            return null;
        }

        if (updateImeTarget) {
            AppWindowToken token = curTarget == null ? null : curTarget.mAppToken;
            if (token != null) {

                WindowState highestTarget = null;
                if (token.isSelfAnimating()) {
                    highestTarget = token.getHighestAnimLayerWindow(curTarget);
                }

                if (highestTarget != null) {
                    final AppTransition appTransition = mService.mAppTransition;
                    if (appTransition.isTransitionSet()) {
                        setInputMethodTarget(highestTarget, true);
                        return highestTarget;
                    } else if (highestTarget.mWinAnimator.isAnimationSet() &&
                            highestTarget.mWinAnimator.mAnimLayer > target.mWinAnimator.mAnimLayer) {

                        setInputMethodTarget(highestTarget, true);
                        return highestTarget;
                    }
                }
            }

            setInputMethodTarget(target, false);
        }

        return target;
    }

這里面有兩個對象需要區(qū)分以下遇伞,一個是curTarget巍耗,一個是Target炬太。

  • curTarget是來自WMS的mInputMethodTarget炒考。也就意味著此時是WMS預定的輸入法窗口層級票腰。也就代表當前的輸入法窗口。
  • target 是來自DisplayContent對自己的孩子進行搜索到最頂部能夠稱為輸入法窗口的WIndow缘滥。也就代表著下一個層級最高(可見的)輸入法窗口。根據(jù)之前的文章,我們可以推斷出來此時就是找添加到DisplayContent層級最高的NonMagnifiableWindowContainers的彈窗搂捧。也就是下一個要彈出的窗口

  • 1.如果此時WMS中沒有IME(輸入法)的Window,此時就沒有生成頂部的Window聋丝,就直接獲取mInputMethodTargetWaitingAnim (因為此時需要做輸入法窗口動畫)作為新的輸入法彈窗,并不需要調整每篷。

  • 2.通過getWindow找到最頂層能夠成為輸入法彈窗層級的DisplayContent的子窗口子库。也就是NonMagnifiableWindowContainers。

  • 3.如果當前可以作為輸入法彈窗是啟動窗口類型仓技,因為啟動窗口本身很特殊,類似中轉站的角色地沮。則會自動找到下面那一層的窗口,判斷是否能夠作為彈窗雷袋。

  • 4.如果當前的輸入法彈窗不為空,同時當前的進程還存在,并且下一個要啟動的窗口是Home。則直接返回當前進程的輸入法彈窗每界。避免屏幕閃動眨层。這里也就解釋為什么馒闷,我們在自己應用啟動了輸入法彈窗,點擊回退鍵盤回退Home之后疏虫,有些時候,輸入法還留在Home上。

  • 5.下一個目標輸入法彈窗為空蚯涮,則獲取上一個。

  • 6.輸入法動畫播放,會根據(jù)方法參數(shù)updateImeTarget這個標志位是否打開嗦哆,來判斷是否處理彈窗動畫。因為是做需要做動畫,所以需要找到當前輸入法彈窗下旁舰,最高層級(可見)的窗口。

這個方法到處都調用了另一個比較核心的方法setInputMethodTarget纳猫,去設定當前的輸入法彈窗目標。

setInputMethodTarget

    private void setInputMethodTarget(WindowState target, boolean targetWaitingAnim) {
        if (target == mService.mInputMethodTarget
                && mService.mInputMethodTargetWaitingAnim == targetWaitingAnim) {
            return;
        }

        mService.mInputMethodTarget = target;
        mService.mInputMethodTargetWaitingAnim = targetWaitingAnim;
        assignWindowLayers(false /* setLayoutNeeded */);
    }

能看到除了賦值之外物遇,還做一個和我上面提過十分相似的方法assignWindowLayers。

assignWindowLayers

/** Updates the layer assignment of windows on this display. */
    void assignWindowLayers(boolean setLayoutNeeded) {

        assignChildLayers(getPendingTransaction());
        if (setLayoutNeeded) {
            setLayoutNeeded();
        }

        scheduleAnimation();
    }

能看到此時會調用assignChildLayers這個方法,并且執(zhí)行窗口動畫。

assignChildLayers

    void assignChildLayers(Transaction t) {
        int layer = 0;

        // We use two passes as a way to promote children which
        // need Z-boosting to the end of the list.
        for (int j = 0; j < mChildren.size(); ++j) {
            final WindowContainer wc = mChildren.get(j);
            wc.assignChildLayers(t);
            if (!wc.needsZBoost()) {
                wc.assignLayer(t, layer++);
            }
        }
        for (int j = 0; j < mChildren.size(); ++j) {
            final WindowContainer wc = mChildren.get(j);
            if (wc.needsZBoost()) {
                wc.assignLayer(t, layer++);
            }
        }
    }

    void assignChildLayers() {
        assignChildLayers(getPendingTransaction());
        scheduleAnimation();
    }

這里做了什么事情呢?實際上很巧妙族扰,首先對整個WindowContainer的子窗體做一次調整,接著打開了needsZBoost標志位的窗口再添加到上面。

這樣就分離了需要做動畫的層級,以及普通層級巩检。保證了做動畫的窗口一定再普通窗口之上领舰。

添加窗口層級調整總結

從這里我們看到舍咖,Android 9.0對窗口層級的管理窍株,比起過去的Android4.4的窗口層級調整有了十足的進步。

Android 4.4的窗口管理通過復雜的循環(huán)對窗口進行管理,這里就不分析了开睡。到了Android 9.0 先把窗口層級大致劃分出幾個區(qū)域之后凶杖,再對每個區(qū)域進行循環(huán)管理蝗茁,最后再調整動畫的窗口。這么做的有點很顯然易見饭寺,那就是抽象出了WIndowContainer提高了擴展性,并且減少了循環(huán)次數(shù)叫挟。

到這里窗口添加的大致上的邏輯艰匙,大體上已經(jīng)弄透徹了,但是還有其他內容抹恳。

接下來讓我們繼續(xù)聊聊WMS面向應用暴露的三個接口,剩下的兩個接口奋献,updateViewLayout,removeView.以及Window如何計算Window的邊緣健霹。

updateViewLayout

從上一篇文章看到ViewManager還有另外一個很重要的方法updateViewLayout旺上。我們直奔WindowManagerGlobal看看真正的實現(xiàn)類做了什么:
文件:/frameworks/base/core/java/android/view/WindowManagerGlobal.java

public void updateViewLayout(View view, ViewGroup.LayoutParams params) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }
        if (!(params instanceof WindowManager.LayoutParams)) {
            throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
        }

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;

        view.setLayoutParams(wparams);

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            ViewRootImpl root = mRoots.get(index);
            mParams.remove(index);
            mParams.add(index, wparams);
            root.setLayoutParams(wparams, false);
        }
    }

能看到其核心十分簡單,就是獲取Windowmanager.LayoutParams需要更新的ViewRootImpl糖埋,最后調用setLayoutParams把新的LayoutParams設置到ViewRootImpl中宣吱,最后通過requestLayout做一次更新。

由于這是基于ViewRootImpl做一次一個邏輯屏幕上所有View的更新瞳别,因此使用的地方并不多征候。

removeView

removeView是最后一個ViewManager的接口。這個接口使用的次數(shù)很多祟敛。我們接著啟動窗口繼續(xù)聊聊疤坝,既然我們在啟動我們自己的真正的窗口之前會現(xiàn)有一個啟動窗口顯示,那么當Activity準備好下一步創(chuàng)建的時候垒棋,就必定會移除這個啟動窗口卒煞,讓我們找找看,是哪里移除的叼架。

文件:/frameworks/base/services/core/java/com/android/server/am/ActivityStack.java
讓我們把焦點放在:resumeTopActivityInnerLocked方法上.只有這個方法畔裕,才是真正開始跨進程通信,準備啟動應用的Activity乖订。

 private boolean resumeTopActivityInnerLocked(ActivityRecord prev, ActivityOptions options) {
        if (!mService.mBooting && !mService.mBooted) {
            // Not ready yet!
            return false;
        }

        // Find the next top-most activity to resume in this stack that is not finishing and is
        // focusable. If it is not focusable, we will fall into the case below to resume the
        // top activity in the next focusable task.
        final ActivityRecord next = topRunningActivityLocked(true /* focusableOnly */);

        final boolean hasRunningActivity = next != null;

        // TODO: Maybe this entire condition can get removed?
        if (hasRunningActivity && !isAttached()) {
            return false;
        }

        mStackSupervisor.cancelInitializingActivities();

  ....
        return true;
    }

銷毀啟動窗口就是通過ActivityStackSupervisor.cancelInitializingActivities扮饶。

    void cancelInitializingActivities() {
        for (int displayNdx = mActivityDisplays.size() - 1; displayNdx >= 0; --displayNdx) {
            final ActivityDisplay display = mActivityDisplays.valueAt(displayNdx);
            for (int stackNdx = display.getChildCount() - 1; stackNdx >= 0; --stackNdx) {
                final ActivityStack stack = display.getChildAt(stackNdx);
                stack.cancelInitializingActivities();
            }
        }
    }

能看到會獲取ActivityDisplay中ActivityStack中所有的啟動窗口,進行銷毀乍构。

    void cancelInitializingActivities() {
        final ActivityRecord topActivity = topRunningActivityLocked();
        boolean aboveTop = true;
        // We don't want to clear starting window for activities that aren't behind fullscreen
        // activities as we need to display their starting window until they are done initializing.
        boolean behindFullscreenActivity = false;

        if (!shouldBeVisible(null)) {
            // The stack is not visible, so no activity in it should be displaying a starting
            // window. Mark all activities below top and behind fullscreen.
            aboveTop = false;
            behindFullscreenActivity = true;
        }

        for (int taskNdx = mTaskHistory.size() - 1; taskNdx >= 0; --taskNdx) {
            final ArrayList<ActivityRecord> activities = mTaskHistory.get(taskNdx).mActivities;
            for (int activityNdx = activities.size() - 1; activityNdx >= 0; --activityNdx) {
                final ActivityRecord r = activities.get(activityNdx);
                if (aboveTop) {
                    if (r == topActivity) {
                        aboveTop = false;
                    }
                    behindFullscreenActivity |= r.fullscreen;
                    continue;
                }

                r.removeOrphanedStartingWindow(behindFullscreenActivity);
                behindFullscreenActivity |= r.fullscreen;
            }
        }
    }

aboveTop默認是true甜无。換句話說,從Task歷史棧中獲取所有Activity的啟動窗口親切銷毀哥遮,如果當前的要銷毀啟動窗口的Activity和本次要啟動的Activity是同一個對象岂丘,說明沒有必要再去銷毀。

文件:/frameworks/base/services/core/java/com/android/server/am/ActivityRecord.java

    void removeOrphanedStartingWindow(boolean behindFullscreenActivity) {
        if (mStartingWindowState == STARTING_WINDOW_SHOWN && behindFullscreenActivity) {
            if (DEBUG_VISIBILITY) Slog.w(TAG_VISIBILITY, "Found orphaned starting window " + this);
            mStartingWindowState = STARTING_WINDOW_REMOVED;
            mWindowContainerController.removeStartingWindow();
        }
    }

behindFullscreenActivity 這個標志位代表著是否真的執(zhí)行銷毀啟動窗體眠饮,只要有一個Activity是全屏模式奥帘,就一定會去銷毀。

AppWindowContainerController.removeStartingWindow

public void removeStartingWindow() {
        synchronized (mWindowMap) {
        final StartingSurface surface;
....
            // Use the same thread to remove the window as we used to add it, as otherwise we end up
            // with things in the view hierarchy being called from different threads.
            mService.mAnimationHandler.post(() -> {
                if (DEBUG_STARTING_WINDOW) Slog.v(TAG_WM, "Removing startingView=" + surface);
                try {
                    surface.remove();
                } catch (Exception e) {
                    Slog.w(TAG_WM, "Exception when removing starting window", e);
                }
            });
        }
    }

能看到此時的操作和addStartingWindow相似仪召,也是把操作丟給WMS的動畫處理Handler mAnimationHandler完成寨蹋,對這個StartingSurface 進行移除。

而這個StartingSurface 在上一篇文章我們就能看到扔茅,實際上是一個SplashScreenSurface對象已旧。
文件:/frameworks/base/services/core/java/com/android/server/policy/SplashScreenSurface.java

    public void remove() {
        final WindowManager wm = mView.getContext().getSystemService(WindowManager.class);
        wm.removeView(mView);
    }

能看到此時就是通過WindowManagerService對啟動窗體進行銷毀。

WindowManagerGlobal removeView

文件:/frameworks/base/core/java/android/view/WindowManagerGlobal.java

    private int findViewLocked(View view, boolean required) {
        final int index = mViews.indexOf(view);
        if (required && index < 0) {
            throw new IllegalArgumentException("View=" + view + " not attached to window manager");
        }
        return index;
    }

    public void removeView(View view, boolean immediate) {
        if (view == null) {
            throw new IllegalArgumentException("view must not be null");
        }

        synchronized (mLock) {
            int index = findViewLocked(view, true);
            View curView = mRoots.get(index).getView();
            removeViewLocked(index, immediate);
            if (curView == view) {
                return;
            }

            throw new IllegalStateException("Calling with view " + view
                    + " but the ViewAncestor is attached to " + curView);
        }
    }

在removeView的時候召娜,我們需要注意view為空有異常运褪,當我們要銷毀的view,從mViews中找到和mRoot中找到的不一致,則會報錯吐句。這兩個對象都是在addView胁后,同步添加到mRoots和mView中。

因為此時是WindowManager的銷毀嗦枢,那么必定會去銷毀當前對應的ViewRootImpl,換句話屯断,我們要銷毀ViewGroup中的View的時候當然不會使用這個方法銷毀文虏,這個方法銷毀的是整個窗體對應的根部View。

removeViewLocked

    private void removeViewLocked(int index, boolean immediate) {
        ViewRootImpl root = mRoots.get(index);
        View view = root.getView();

        if (view != null) {
            InputMethodManager imm = InputMethodManager.getInstance();
            if (imm != null) {
                imm.windowDismissed(mViews.get(index).getWindowToken());
            }
        }
        boolean deferred = root.die(immediate);
        if (view != null) {
            view.assignParent(null);
            if (deferred) {
                mDyingViews.add(view);
            }
        }
    }

能看到在清除的行為中殖演,首先獲取對應的ViewRootImpl氧秘,先通過windowDismissed銷毀輸入法。通過ViewRootImpl做一次相關的銷毀行為趴久,再通過assignParent清除其View指定的父View丸相,如果不是立即銷毀則把view對象添加到正在死亡的View集合中,等到做完所有的清除操作后彼棍,再清除這個集合中的view灭忠。

ViewRootImpl die

    boolean die(boolean immediate) {
        // Make sure we do execute immediately if we are in the middle of a traversal or the damage
        // done by dispatchDetachedFromWindow will cause havoc on return.
        if (immediate && !mIsInTraversal) {
            doDie();
            return false;
        }

        if (!mIsDrawing) {
            destroyHardwareRenderer();
        } else {
            Log.e(mTag, "Attempting to destroy the window while drawing!\n" +
                    "  window=" + this + ", title=" + mWindowAttributes.getTitle());
        }
        mHandler.sendEmptyMessage(MSG_DIE);
        return true;
    }

    void doDie() {
        checkThread();
        if (LOCAL_LOGV) Log.v(mTag, "DIE in " + this + " of " + mSurface);
        synchronized (this) {
            if (mRemoved) {
                return;
            }
            mRemoved = true;
            if (mAdded) {
                dispatchDetachedFromWindow();
            }

            if (mAdded && !mFirst) {
                destroyHardwareRenderer();

                if (mView != null) {
                    int viewVisibility = mView.getVisibility();
                    boolean viewVisibilityChanged = mViewVisibility != viewVisibility;
                    if (mWindowAttributesChanged || viewVisibilityChanged) {
                        // If layout params have been changed, first give them
                        // to the window manager to make sure it has the correct
                        // animation info.
                        try {
                            if ((relayoutWindow(mWindowAttributes, viewVisibility, false)
                                    & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
                                mWindowSession.finishDrawing(mWindow);
                            }
                        } catch (RemoteException e) {
                        }
                    }

                    mSurface.release();
                }
            }

            mAdded = false;
        }
        WindowManagerGlobal.getInstance().doRemoveView(this);
    }

能看到,如果需要理解銷毀則會直接執(zhí)行doDie的方法座硕,否則會把doDie委托到handler中完成弛作。

doDie做了幾件事情,一個是分發(fā)DetachedFromWindow事件給下面的View华匾,接著銷毀所有的硬件加速的渲染線程內該View的資源(這里不做更多討論映琳,之后會有專門的文章討論),釋放繪制對象Surface蜘拉,如果當前的View是可見的則通過Session溝通WMS進行結束繪制萨西,最后調用WindowManagerGlobal的doRemoveView。

我們這里暫時只關心兩個方法finishDrawing以及doRemoveView旭旭。讓我們一個個的看一遍里面做了什么東西谎脯。

WMS finishDrawingWindow

void finishDrawingWindow(Session session, IWindow client) {
        final long origId = Binder.clearCallingIdentity();
        try {
            synchronized (mWindowMap) {
                WindowState win = windowForClientLocked(session, client, false);
                if (DEBUG_ADD_REMOVE) Slog.d(TAG_WM, "finishDrawingWindow: " + win + " mDrawState="
                        + (win != null ? win.mWinAnimator.drawStateToString() : "null"));
                if (win != null && win.mWinAnimator.finishDrawingLocked()) {
                    if ((win.mAttrs.flags & FLAG_SHOW_WALLPAPER) != 0) {
                        win.getDisplayContent().pendingLayoutChanges |=
                                WindowManagerPolicy.FINISH_LAYOUT_REDO_WALLPAPER;
                    }
                    win.setDisplayLayoutNeeded();
                    mWindowPlacerLocked.requestTraversal();
                }
            }
        } finally {
            Binder.restoreCallingIdentity(origId);
        }
    }

此時先找到IWindow對應的WindowState,設置對應WindowState中的DisplayContent標志位設置為true您机。并且重新測量窗體邊緣穿肄。稍后會稍微深入WindowPlacerLocked.requestTraversal中做了什么事情。

WindowManagerGlobal doRemoveView

    void doRemoveView(ViewRootImpl root) {
        synchronized (mLock) {
            final int index = mRoots.indexOf(root);
            if (index >= 0) {
                mRoots.remove(index);
                mParams.remove(index);
                final View view = mViews.remove(index);
                mDyingViews.remove(view);
            }
        }
        if (ThreadedRenderer.sTrimForeground && ThreadedRenderer.isAvailable()) {
            doTrimForeground();
        }
    }

    private void doTrimForeground() {
        boolean hasVisibleWindows = false;
        synchronized (mLock) {
            for (int i = mRoots.size() - 1; i >= 0; --i) {
                final ViewRootImpl root = mRoots.get(i);
                if (root.mView != null && root.getHostVisibility() == View.VISIBLE
                        && root.mAttachInfo.mThreadedRenderer != null) {
                    hasVisibleWindows = true;
                } else {
                    root.destroyHardwareResources();
                }
            }
        }
        if (!hasVisibleWindows) {
            ThreadedRenderer.trimMemory(
                    ComponentCallbacks2.TRIM_MEMORY_COMPLETE);
        }
    }

能看到际看,經(jīng)過doDie釋放了必須的資源咸产,如硬件渲染啟動時候的渲染線程和Surface。并且重新計算窗體邊緣仲闽。
最后再把WindowManagerGlobal 中的mRoots中的對象和mDyingView的對象全部移除脑溢。doTrimForeground則是清除那些看不見的Window中的view對應的渲染線程的資源。

removeView的核心邏輯就是這么多了。

小結

updateViewLayout本質上是設置WindowParams屑彻,重新測量繪制整個Window中的view验庙。
removeView做了以下幾個事情:

  • 1.關閉鍵盤
  • 2.ViewRootImpl調用die方法,清除硬件加速渲染線程中對應的view資源社牲,重新執(zhí)行Window的大小計算粪薛。
  • 3.清空殘留在WindowManagerGlobal中的對象。
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末搏恤,一起剝皮案震驚了整個濱河市违寿,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌熟空,老刑警劉巖藤巢,帶你破解...
    沈念sama閱讀 216,372評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異息罗,居然都是意外死亡掂咒,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,368評論 3 392
  • 文/潘曉璐 我一進店門迈喉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來绍刮,“玉大人,你說我怎么就攤上這事弊添÷嫉” “怎么了?”我有些...
    開封第一講書人閱讀 162,415評論 0 353
  • 文/不壞的土叔 我叫張陵油坝,是天一觀的道長嫉戚。 經(jīng)常有香客問我,道長澈圈,這世上最難降的妖魔是什么彬檀? 我笑而不...
    開封第一講書人閱讀 58,157評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮瞬女,結果婚禮上窍帝,老公的妹妹穿的比我還像新娘。我一直安慰自己诽偷,他們只是感情好坤学,可當我...
    茶點故事閱讀 67,171評論 6 388
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著报慕,像睡著了一般深浮。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上眠冈,一...
    開封第一講書人閱讀 51,125評論 1 297
  • 那天飞苇,我揣著相機與錄音,去河邊找鬼。 笑死布卡,一個胖子當著我的面吹牛雨让,可吹牛的內容都是我干的。 我是一名探鬼主播忿等,決...
    沈念sama閱讀 40,028評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼栖忠,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了贸街?” 一聲冷哼從身側響起娃闲,我...
    開封第一講書人閱讀 38,887評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎匾浪,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體卷哩,經(jīng)...
    沈念sama閱讀 45,310評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡蛋辈,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 37,533評論 2 332
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了将谊。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片冷溶。...
    茶點故事閱讀 39,690評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖尊浓,靈堂內的尸體忽然破棺而出逞频,到底是詐尸還是另有隱情,我是刑警寧澤栋齿,帶...
    沈念sama閱讀 35,411評論 5 343
  • 正文 年R本政府宣布苗胀,位于F島的核電站,受9級特大地震影響瓦堵,放射性物質發(fā)生泄漏基协。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,004評論 3 325
  • 文/蒙蒙 一菇用、第九天 我趴在偏房一處隱蔽的房頂上張望澜驮。 院中可真熱鬧,春花似錦惋鸥、人聲如沸杂穷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,659評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽耐量。三九已至,卻和暖如春迎卤,著一層夾襖步出監(jiān)牢的瞬間拴鸵,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,812評論 1 268
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留劲藐,地道東北人八堡。 一個月前我還...
    沈念sama閱讀 47,693評論 2 368
  • 正文 我出身青樓,卻偏偏與公主長得像聘芜,于是被迫代替她去往敵國和親兄渺。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,577評論 2 353

推薦閱讀更多精彩內容