有關(guān)Window的愛恨情仇板熊!

window的類型

什么是window?通俗的來說察绷,window就是系統(tǒng)劃分的一個窗口干签,其本身是不可見的,是一個用來存儲view的容器拆撼。
在Android系統(tǒng)中容劳,window一共有三種類型,分別是System Window闸度、Application Window以及Sub Window


Window類型

各個類型的window如圖所示竭贩,其中值得一提的是sub window,他們必須依附于另一種window之上莺禁,我們比較常用的就有dialog以及popwindow

Activity留量、Window、View的關(guān)系

view是我們眼睛具體看到界面;window本身是不可見的肪获,本質(zhì)上它只是系統(tǒng)劃分的一個窗口寝凌,是用來放置view的容器;而activity則是一個管理者孝赫,維護整個環(huán)節(jié)的運行较木,統(tǒng)一的管理所有view的繪制流程。
如果用貼窗花來類比Android系統(tǒng)青柄,那么activity就是剪窗花的人伐债,window是窗子,view是窗花致开。

Application Window創(chuàng)建過程

Window峰锁、WindowManager、WindowManagerService

我們查看Activity的源碼双戳,發(fā)現(xiàn)其中有2個成員變量與window相關(guān)

 private Window mWindow;

 private WindowManager mWindowManager;

顧名思義虹蒋,WindowManager是Window的管理類,但是它并不是真正的管理類飒货。因為在android中魄衅,存在著很多很多的window,其管理者必然是一個系統(tǒng)級的塘辅、跨進程的服務(wù)晃虫,也就是WindowManagerService。那么WindowManager又做了什么呢扣墩?其實它可以算作一個拉皮條的哲银,是window與wms之間通信的橋梁。

Window的初始化

開始分析源碼呻惕,可以發(fā)現(xiàn)mWindow是在Activity的attach()方法中初始化的

 mWindow = new PhoneWindow(this, window);

繼續(xù)往下看荆责,在最后,可以看見wm也是在其中初始化的亚脆。

mWindow.setWindowManager((WindowManager)context.getSystemService(Context.WINDOW_SERVICE),mToken, mComponent.flattenToString(),(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
        if (mParent != null) {
            mWindow.setContainer(mParent.getWindow());
        }
        mWindowManager = mWindow.getWindowManager();

那么attach()方法又是在什么時候被執(zhí)行的呢做院?開動腦筋想一想,當然是在onCreate()之前啦型酥。前面說過window是view的容器山憨,那么在onCreate()將view添加進來之前,window肯定已經(jīng)準備就緒了弥喉。具體這部分代碼的分析就放在下篇activity啟動流程之中郁竟。

View的加載

有道友不知道setContentView()嗎?我們來看看activity中該方法的實現(xiàn)

  public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

getwindow()會返回mWindow的實例由境,也就是調(diào)用了PhoneWindow的setContentView()方法

public void setContentView(int layoutResID) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

當mContentParent為空時棚亩,首先會調(diào)用installDecor()方法蓖议。查看該方法,可以看到有一個叫mDecor的玩意兒讥蟆,這東西是什么呢勒虾?

// This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;

官方注釋是這么說的,mDecor是window中最頂層的view瘸彤,也就是根view修然,是window中的第一個容器。查看DecorView的源碼质况,也可以知道它是繼承自FrameLayout愕宋。換句說中,我們所寫的layout布局一定是放在DecorView中结榄。

繼續(xù)閱讀installDecor()方法:

private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor();
            ...
            }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
            ...
            }
    }

當mDecor為空時中贝,會調(diào)用generateDecor()方法初始化mDecor。這個方法很簡單臼朗,就是直接new了一個DecorView對象并返回邻寿。

之后當mContentParent為空時,會以剛剛創(chuàng)建的mDecor為參數(shù)视哑,通過generateLayout()方法給mContentParent賦值绣否。

到此為止,mContentParent已經(jīng)有值了黎炉,回到之前的setContentView()看到這行代碼

 mLayoutInflater.inflate(layoutResID, mContentParent);

這個layoutResID就是我們通過Layout布局資源枝秤。是不是一切都通暢了醋拧?window中有一個DecorView慷嗜,mContentParent是DecorView的一部分,最終用戶view通過mLayoutInflater的inflate()方法傳遞到DecorView中丹壕。

Window的顯示過程

到現(xiàn)在為止庆械,window已經(jīng)創(chuàng)建并與activity綁定了,view也已經(jīng)加載進window中了菌赖,是時候去看看window是如何顯示在用戶手機屏幕上了缭乘。
通過之前的分析可以知道,window是由系統(tǒng)服務(wù)WindowManagerService統(tǒng)一管理的琉用。在activity的源碼中堕绩,我們找不到它是如何通過wms添加到手機屏幕中的,既然如此邑时,不妨換個思路奴紧,去看看其他類型window的源碼。

PopupWindow

popupwindow是通過showAtLocation()顯示在界面上的晶丘,直接查看該方法源碼:

public void showAtLocation(IBinder token, int gravity, int x, int y) {
       ...

        TransitionManager.endTransitions(mDecorView);

        detachFromAnchor();

        mIsShowing = true;
        mIsDropdown = false;
        mGravity = gravity;

        final WindowManager.LayoutParams p = createPopupLayoutParams(token);
        preparePopup(p);

        p.x = x;
        p.y = y;

        invokePopup(p);
    }

代碼中間有一個叫WindowManager.LayoutParams的玩意兒黍氮,它繼承自ViewGroup.LayoutParams唐含,其存放了window在屏幕上顯示時的各種參數(shù)。

在文章最開始時說window有三種類型沫浆,其實就是在WindowManager.LayoutParams中看到的捷枯。在此處我只是將三種大類列出來,每一個類別還有很多子類別专执,其中有一些是我們可以使用的淮捆,另一些是系統(tǒng)級別的。

        /**
         * Start of window types that represent normal application windows.
         */
        public static final int FIRST_APPLICATION_WINDOW = 1;
        /**
         * Start of types of sub-windows.  The {@link #token} of               these windows
         * must be set to the window they are attached to.  These types of
         * windows are kept next to their attached window in Z-order, and their
         * coordinate space is relative to their attached window.
         */
        public static final int FIRST_SUB_WINDOW = 1000;
        /**
         * Start of system-specific window types.  These are not normally
         * created by applications.
         */
        public static final int FIRST_SYSTEM_WINDOW     = 2000;

WindowManager.LayoutParams通過createPopupLayoutParams(token)方法進行初始化本股。這個方法很簡單争剿,就是將LayoutParams中的一系列參數(shù)進行賦值。

        p.gravity = computeGravity();
        p.flags = computeFlags(p.flags);
        p.type = mWindowLayoutType;
        p.token = token;
        p.softInputMode = mSoftInputMode;
        p.windowAnimations = computeAnimationResource();

傳進去的參數(shù)token又是什么呢痊末?

private WindowManager.LayoutParams createPopupLayoutParams(IBinder token)

原來token是一個IBinder對象蚕苇,這里就很好理解了,window通過跨進程的windowManagerService進行統(tǒng)一的管理凿叠,那么wms必然需要某個標識來區(qū)分每一個window涩笤,而token顯然就是這個標識。

接著看 preparePopup(p)方法盒件。

if (mBackground != null) {
            mBackgroundView = createBackgroundView(mContentView);
            mBackgroundView.setBackground(mBackground);
        } else {
            mBackgroundView = mContentView;
        }

最開始就說過蹬碧,sub window必須依附在父window之上,而preparePopup(p)就是用來給popupwindow創(chuàng)建根view的炒刁。到此為止恩沽,window已經(jīng)創(chuàng)建并被賦值了各種參數(shù),根view也創(chuàng)建好了翔始,剩下應(yīng)該就是通過wms進行管理了罗心,我們看最后一個方法invokePopup(p)

private void invokePopup(WindowManager.LayoutParams p) {
        ...
        final PopupDecorView decorView = mDecorView;
        decorView.setFitsSystemWindows(mLayoutInsetDecor);
        ...
        mWindowManager.addView(decorView, p);
        ...
    }

顯而易見,最終讓window顯示在屏幕上的城瞎,是mWindowManager.addView(decorView, p)

mWindowManager是一個接口渤闷,找到其被實例化的地方

mWindowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);

系統(tǒng)在開啟的時候,會初始化很多的SystemService脖镀,此處通過aidl獲取相關(guān)的系統(tǒng)服務(wù)飒箭,即WindowManagerImpl。

接著去找WindowManagerImpl的addView()蜒灰,可以看到其最終調(diào)用了WindowManagerGlobal的addView()弦蹂,這個方法很長,我挑重點展示一下:

 public void addView(View view, ViewGroup.LayoutParams params,
                        Display display, Window parentWindow) {
        ...
//注釋1
            // 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);
//注釋2
            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);
        }

        // do this last because it fires off messages to start doing things
        try {
//注釋3        
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
        ...
        }
    }

首先看注釋2强窖,其中有三個集合凸椿, mViews、mRoots毕骡、mParams分別用來存放某個window的decorView削饵,ViewRootImpl渲染器以及LayoutParams參數(shù)岩瘦,且這三者的順序是一一對應(yīng)的。

接著看注釋1窿撬,if語句中判斷當前window的類型是否是sub window启昧,如果是的話,就遍歷mRoots集合劈伴,找到其中與當前window參數(shù)中token相同的window密末,并將其設(shè)置為當前window的父window。
之前說過這個token是一個IBinder對象跛璧,是用來在跨進程通信中確定window身份的严里。那么它具體是個什么玩意兒呢?

public void showAtLocation(View parent, int gravity, int x, int y) {
        showAtLocation(parent.getWindowToken(), gravity, x, y);
    }

從上面的代碼可以知道追城,token就是當前window的父window的標識刹碾。原來如此,所以在windowManagerGlobal.addView()中座柱,只要mRoots中某個window的token與當前window的layoutParams中的token相同迷帜,那么它就是當前window的父window。

最后看注釋3色洞,在前面已經(jīng)獲取了decorview戏锹,layoutParams以及panelParentView,所以現(xiàn)在就可通過ViewRootImpl的setView方法對屏幕進行渲染繪制了火诸!

ViewRootImpl

setView這個方法比較長锦针,選擇其中跟window相關(guān)的最關(guān)鍵的代碼

res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(),mDisplay.getDisplayId(),mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,mAttachInfo.mOutsets, mInputChannel);

這里有一個叫做mWindowSession的東西。熟悉web的同學一定知道置蜀,session和cookie是服務(wù)器端與客戶端用來進行身份識別的奈搜,其中session存放在服務(wù)器端,cookie存放在客戶端盾碗。那么為啥在android系統(tǒng)里還會出現(xiàn)session呢媚污?

在文章最開始就說過了舀瓢,整個window的管理是一個進程間的管理廷雅,系統(tǒng)的windowManagerService就相當于服務(wù)器端,用戶的每一個window所對應(yīng)的windowManager就相當于客戶端京髓。因此這個IWindowSession就是wms用來識別不同wm的“身份證”航缀。

再來看看IWindowSession初始化的地方,在ViewRootImpl的構(gòu)造方法中堰怨,有這樣一段

 mWindowSession = WindowManagerGlobal.getWindowSession();

OK芥玉,回去找WindowManagerGlobal

public static IWindowSession getWindowSession() {
            ...
                    IWindowManager windowManager = getWindowManagerService();
                    sWindowSession = windowManager.openSession(
                            new IWindowSessionCallback.Stub() {
                                @Override
                                public void onAnimatorScaleChanged(float scale) {
                                    ValueAnimator.setDurationScale(scale);
            ...
            return sWindowSession;
        }
    }

艾瑪,這不就是典型的AIDL嘛备图,首先看這里的windowManager

 IWindowManager windowManager = getWindowManagerService();

不要被它的名字所欺騙灿巧,其實它是WindowManagerService的IBinder引用赶袄,不信就點進去看看getWindowManagerService,其中有這樣一行代碼

 sWindowManagerService = IWindowManager.Stub.asInterface(
                        ServiceManager.getService("window"));

這兒就不多做解釋了抠藕,對AIDL不太清楚的同學移步Binder恐怖如斯

現(xiàn)在我們已經(jīng)獲取了WindowManagerService的IBinder引用饿肺,接下來就是通過AIDL調(diào)用遠程端的openSession方法。顯而易見盾似,我們要去wms中查看這個方法到底做了什么:

 @Override
    public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
            IInputContext inputContext) {
        ...
        Session session = new Session(this, callback, client, inputContext);
        return session;
    }

很簡單敬辣,就是根據(jù)客戶端創(chuàng)建了一個session并返回。順帶一提零院,wms是通過ArraySet來存放客戶端的所有session信息的溉跃,有詩為證:

    final ArraySet<Session> mSessions = new ArraySet<>();

回過神,現(xiàn)在我們帶著獲取到的session回到最開始的ViewRootImpl中告抄,也就是res = mWindowSession.addToDisplay()撰茎。既然mWindowSession是服務(wù)端wms的一個IBinder引用,那么同上打洼,我們要去wms中尋找addToDisplay這個方法乾吻。
遺憾的是,addToDisplay不見了拟蜻!幸好天無絕人之路绎签,雖然沒有了addToDisplay,但是我們可以在wms中找到一個叫addWindow的方法酝锅。來對比一下這兩家伙的參數(shù)诡必。

addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(),mDisplay.getDisplayId(),mAttachInfo.mContentInsets, 
mAttachInfo.mStableInsets,mAttachInfo.mOutsets, mInputChannel);
addWindow(Session session, IWindow client, int seq,
            LayoutParams attrs, int viewVisibility, int displayId,
            Rect outContentInsets, Rect outStableInsets, Rect outOutsets,
            InputChannel outInputChannel)

巧不巧?你就說巧不巧搔扁!addWindow就比addToDisplay多了一個session參數(shù)爸舒。原來如此,客戶端調(diào)用了IBinder的addToDisplay之后稿蹲,會傳入當前客戶端的session,最終通過AIDL在服務(wù)端調(diào)用了addWindow方法扭勉。

那么這個addWindow又干了些什么呢?這就涉及到具體的繪制流程了苛聘,代碼太復雜沒法細看涂炎,總結(jié)起來就是一句話,判斷各種權(quán)限设哗、window的狀態(tài)以及window的類型唱捣,之后對window進行繪制,最終將window綁定的結(jié)果返回給ViewRootImpl网梢。

要注意的是震缭,到此為止,window繪制完畢了战虏,但是window中的view還是空空如也拣宰。而ViewRootImpl在獲取到window的綁定結(jié)果后党涕,如果成功,就會開始進行一系列的view的繪制流程巡社。這部分的內(nèi)容相當變態(tài)遣鼓,咱們有緣再見吧。

Application Window顯示過程

經(jīng)過前面一大段的嗶嗶重贺,Sub Window的顯示過程已經(jīng)說清楚了骑祟。還記得為什么我們要去看sub window的顯示過程嗎?因為Application Window的顯示過程在activity里面找不到啊气笙。但是在文章的末尾次企,這種坑怎么能不填呢?
遺憾的是潜圃,Application Window的顯示過程和activity的生命周期有關(guān)缸棵,這部分的內(nèi)容也是相當變態(tài),需要有緣再見谭期。所以先不要知道為什么堵第,只要記住在ActivityThread中有一個叫handleResumeActivity()的方法,其中有這樣一段:

  if (r.window == null && !a.mFinished && willBeVisible) {
                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的類型設(shè)置為TYPE_BASE_APPLICATION踏志,然后通過wm.addView(decor, l)開始和windowManagerService通信。

完結(jié)撒花~

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末胀瞪,一起剝皮案震驚了整個濱河市针余,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌凄诞,老刑警劉巖圆雁,帶你破解...
    沈念sama閱讀 217,509評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異帆谍,居然都是意外死亡伪朽,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,806評論 3 394
  • 文/潘曉璐 我一進店門汛蝙,熙熙樓的掌柜王于貴愁眉苦臉地迎上來烈涮,“玉大人,你說我怎么就攤上這事患雇≡炯梗” “怎么了?”我有些...
    開封第一講書人閱讀 163,875評論 0 354
  • 文/不壞的土叔 我叫張陵苛吱,是天一觀的道長。 經(jīng)常有香客問我器瘪,道長翠储,這世上最難降的妖魔是什么绘雁? 我笑而不...
    開封第一講書人閱讀 58,441評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮援所,結(jié)果婚禮上庐舟,老公的妹妹穿的比我還像新娘。我一直安慰自己住拭,他們只是感情好挪略,可當我...
    茶點故事閱讀 67,488評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著滔岳,像睡著了一般杠娱。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上谱煤,一...
    開封第一講書人閱讀 51,365評論 1 302
  • 那天摊求,我揣著相機與錄音,去河邊找鬼刘离。 笑死室叉,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的硫惕。 我是一名探鬼主播茧痕,決...
    沈念sama閱讀 40,190評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼恼除!你這毒婦竟也來了凿渊?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,062評論 0 276
  • 序言:老撾萬榮一對情侶失蹤缚柳,失蹤者是張志新(化名)和其女友劉穎埃脏,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體秋忙,經(jīng)...
    沈念sama閱讀 45,500評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡彩掐,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,706評論 3 335
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了灰追。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片堵幽。...
    茶點故事閱讀 39,834評論 1 347
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖弹澎,靈堂內(nèi)的尸體忽然破棺而出朴下,到底是詐尸還是另有隱情,我是刑警寧澤苦蒿,帶...
    沈念sama閱讀 35,559評論 5 345
  • 正文 年R本政府宣布殴胧,位于F島的核電站,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏团滥。R本人自食惡果不足惜竿屹,卻給世界環(huán)境...
    茶點故事閱讀 41,167評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望灸姊。 院中可真熱鬧拱燃,春花似錦、人聲如沸力惯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,779評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽父晶。三九已至哮缺,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間诱建,已是汗流浹背蝴蜓。 一陣腳步聲響...
    開封第一講書人閱讀 32,912評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留俺猿,地道東北人茎匠。 一個月前我還...
    沈念sama閱讀 47,958評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像押袍,于是被迫代替她去往敵國和親诵冒。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 44,779評論 2 354

推薦閱讀更多精彩內(nèi)容