從setContentView探討View,Window與Activity的關(guān)系

前言

Activity生命周期的調(diào)用時(shí)通過(guò)ActivityThread管控的娜扇,我們?cè)谠O(shè)置應(yīng)用頁(yè)面時(shí),都是在onCreate()中調(diào)用setContentView()加載布局栅组,這樣就產(chǎn)生了三個(gè)疑惑:

1:為什么要在onCreate()中設(shè)置setContentView()雀瓢。
2: setContentView是如何起作用的。
3: DecorView和PhoneWindow如何結(jié)合玉掸。

我么利用android studio 查看布局結(jié)構(gòu)樹(shù)發(fā)現(xiàn):
E3C27C6A-D864-4C1C-922D-9023369ADCFD.png

通過(guò)布局結(jié)構(gòu)樹(shù)我們發(fā)現(xiàn):應(yīng)用布局的外層有一個(gè)根視圖DecorView,那么這個(gè)DecorView是如何出現(xiàn)在我們的布局中的呢刃麸?

我們發(fā)現(xiàn),setContentView調(diào)用了PhoneWindow中的setContentView方法司浪。
public void setContentView(int layoutResID) {  
     getWindow().setContentView(layoutResID);                 
     initActionBar();  
} 

Window是一個(gè)抽象類泊业,注解中聲明Window是一個(gè)管理窗口外觀和屬性策略的抽象類,它的實(shí)現(xiàn)類將會(huì)以頂層視圖的形式添加到窗口管理器中啊易。它提供了標(biāo)準(zhǔn)的UI策略吁伺。且有一個(gè)唯一的實(shí)現(xiàn)類:PhoneWindow。重新回到Activity源碼中搜索PhoneWindow租谈,確實(shí)找到了這個(gè)類篮奄,同時(shí)也是getWindow()的返回值類型。注解中聲明PhoneView所在包為android.view割去,但實(shí)際上通過(guò)檢索PhoneView已經(jīng)被移到了android.internal.policy下窟却。


640.png

在一個(gè)Activity對(duì)象被創(chuàng)建的初期,會(huì)首先依靠WindowManagerGlobal和WMS建立通信關(guān)系呻逆,WindowManagerGlobal用來(lái)向WindowManagerService注冊(cè)夸赫,主要是獲取到 WindowManagerService 代理對(duì)象。對(duì)外提供與WindowManagerService(WMS)的底層通信咖城。隨后ActivityThread通過(guò)performLaunchActivity調(diào)用Activity生命周期茬腿。
Activity.attach()是Activity實(shí)例化后最先被調(diào)用的呼奢,這就保證了Window實(shí)例化對(duì)象的可用性。而onCreate()和onStart()是初始階段唯一可以重寫的方法滓彰,其他的都是final類型控妻,鑒于Activity本質(zhì)是管理頁(yè)面交互,布局加載時(shí)機(jī)越早越有益于頁(yè)面的展示揭绑。所以此時(shí)不設(shè)弓候,更待何時(shí)呢。setConteneView(int layoutID)就在onCreate()中調(diào)用了他匪。這樣第一個(gè)問(wèn)題就回答完了菇存。

setContentView是如何起作用的?

Activity在attach()中實(shí)例化了PhoneWindow對(duì)象,并且進(jìn)行了綁定操作邦蜜,操作如下:
mWindow = new PhoneWindow(this, window);  
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);  
mWindow.setOnWindowDismissedCallback(this);

根據(jù)分析可知:
手機(jī)展示的頁(yè)面實(shí)際上是一個(gè)層層嵌套的樣式依鸥,一個(gè)Activity啟動(dòng)后,首先實(shí)例化PhoneWindow對(duì)象悼沈,調(diào)用setContentView時(shí)贱迟,首先執(zhí)行installDecor(),通過(guò)generateDecor()實(shí)例化一個(gè)DecorView對(duì)象絮供,將PhoneWindow和DecorView進(jìn)行了關(guān)聯(lián)綁定衣吠,通過(guò)generateLayout()加載系統(tǒng)布局到DecorView上,并將ID為content的FrameLayout賦值給mContentParent壤靶,最后執(zhí)行inflate()將我們的布局文件自動(dòng)添加到mContentParent缚俏。

View和Window如何結(jié)合?

當(dāng)setContentView()執(zhí)行完畢后,此時(shí)PhoneWindow和DecorView都已經(jīng)創(chuàng)建完成贮乳,但是DecorView并沒(méi)有添加到PhoneWindow上忧换,這個(gè)操作需要在onResume()才會(huì)觸發(fā),ActivityThread在執(zhí)行完performLaunchActivity后向拆,便會(huì)執(zhí)行handlerResumeActivity()亚茬,具體流程和源碼如下圖所示:


640.png
 final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) {  
//執(zhí)行到 onResume()  
ActivityClientRecord r = performResumeActivity(token, clearHide);  

if (r != null) {  
    final Activity a = r.activity;  
    boolean willBeVisible = !a.mStartedActivity;  
    ...  
    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);  
        }  
    }  
     ...  
    if (!r.activity.mFinished && willBeVisible  
            && r.activity.mDecor != null && !r.hideForNow) {  
        ...  
        mNumVisibleActivities++;  
        if (r.activity.mVisibleFromClient) {  
            r.activity.makeVisible();   
        }  
    }  
    ...  
}   

在這段代碼中,內(nèi)部創(chuàng)建了好多臨時(shí)變量浓恳,其實(shí)仔細(xì)分析的話才写,只是兩個(gè)變量在執(zhí)行操作,一個(gè)就是wm(PhoneWindow自身的WindowManager)奖蔓,一個(gè)是decor(setContentView()創(chuàng)建出來(lái)的DecorView)赞草。兩個(gè)變量的最終交互就是wm.addView(decor, l)。同時(shí)我們還會(huì)發(fā)現(xiàn)addView()執(zhí)行的大前提是等待onResume()執(zhí)行完畢吆鹤,如果我們?cè)趏nResume()中處理耗時(shí)操作厨疙,那就意味著應(yīng)用頁(yè)面的顯示時(shí)間被延后,為了保障頁(yè)面盡快進(jìn)入繪制階段疑务,onResume中不要處理耗時(shí)任務(wù)沾凄。

理解完這些梗醇,我們?cè)賮?lái)看一下addView()到底做了什么,WindowManager是一個(gè)接口類撒蟀,PhoneWindow的WindowManager對(duì)象是WindowManagerImpl叙谨,WindowManagerImpl其內(nèi)部方法始終持有WindowManagerGlobal的引用,我們?cè)贏ctivityThread的handlerLanuchActivity()中已經(jīng)知道WindowManagerGlobal是用來(lái)和WindowManagerService(WWM)進(jìn)行通信保屯,在WindowManagerImpl.addView中其實(shí)質(zhì)是把DecorView對(duì)象交付給WindowManagerGlobal的視圖鏈中手负,并通知WWM對(duì)當(dāng)前Window進(jìn)行管理。引用流程及源碼如下:


640-2.png
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {  
...  
ViewRootImpl root = new ViewRootImpl(view.getContext(), display);          
view.setLayoutParams(wparams);      
mViews.add(view);      
mRoots.add(root);      
mParams.add(wparams);          
root.setView(view, wparams, panelParentView);  
...  
} 

WindowManagerGlobal分別存儲(chǔ)著View鏈表和ViewRootImpl的鏈表姑尺,ViewRootImpl就是一個(gè)ViewParent視圖管理類竟终。每個(gè)傳入的DecorView都會(huì)創(chuàng)建一個(gè)對(duì)應(yīng)的ViewRootImpl來(lái)管理。它將實(shí)際控制著DecorView的繪制周期切蟋,同時(shí)還可以與WWM進(jìn)行Binder通信统捶。ViewRootImpl在調(diào)用setView后,即向WWM發(fā)起了添加請(qǐng)求柄粹,WWM便會(huì)將當(dāng)前的PhoneWindow放入自身管理的Window列表中喘鸟,將DecorView添加到PhoneWindow上,同時(shí)通知ViewRootImpl進(jìn)行繪制操作(繪制操作將涉及到SurfaceFlinger驻右,在這里暫不探討)迷守,代碼走到這里時(shí),View和Window之間便真正的結(jié)合起來(lái)了旺入。其完成流程圖如下:


640-3.png

需要注意的是,當(dāng)獲得DecorView對(duì)象后凯力,先執(zhí)行了一次setVisibility(View.INVISIBLE)操作茵瘾,執(zhí)行完addView()操作后才會(huì)重新設(shè)置為VISIBLE,我覺(jué)得此處的做法類似于SurfaceView繪制過(guò)程對(duì)Canvas的鎖操作咐鹤,頁(yè)面的顯示需要由過(guò)渡動(dòng)畫管理器TranslateManager進(jìn)行控制拗秘,如果直接在可見(jiàn)狀態(tài)下進(jìn)行頁(yè)面繪制,會(huì)給用戶一種頁(yè)面加載卡頓的感覺(jué)祈惶,而等待頁(yè)面全部加載繪制完畢后再整體展示給用戶可以有效的避免這個(gè)問(wèn)題雕旨。

通過(guò)對(duì)以上三個(gè)問(wèn)題的探究,明確的了解了應(yīng)用布局的加載過(guò)程捧请,一個(gè)應(yīng)用展示在手持設(shè)備上時(shí)凡涩,其布局結(jié)構(gòu)實(shí)際如下圖所示:


640-4.png

一個(gè)Activity對(duì)應(yīng)一個(gè)PhoneWindow,一個(gè)PhoneWindow對(duì)應(yīng)一個(gè)DecorView疹蛉。
布局加載的整個(gè)過(guò)程中系統(tǒng)布局對(duì)外提供的都是FrameLayout活箕,所以當(dāng)你看到有些性能優(yōu)化書籍提出的合并布局方案,建議用<merge>代替FrameLayout作父布局的原因就在這里可款。同時(shí)應(yīng)用頁(yè)面視圖只會(huì)添加在ID為content的FrameLayout中育韩,即系統(tǒng)布局的內(nèi)容部分克蚂。不論開(kāi)發(fā)者配置的樣式或者主題有何區(qū)別,系統(tǒng)布局中必定會(huì)有一個(gè)ID為content的控件筋讨。

總結(jié)

閱讀源碼時(shí)發(fā)現(xiàn)埃叭,在setContentView()中,頻繁用到了inflate()方法悉罕,源碼中使用的是兩參數(shù)形式的赤屋,而我們?cè)谑褂胕nflate()時(shí),更多的是用三參數(shù)的蛮粮,在這列就順便提一下益缎,inflate(layoutResID, mContentParent)實(shí)際上等價(jià)于inflate(layoutResID, mContentParent, mContentParent !=null)然想。mContentParent設(shè)置的意義在于協(xié)助第一個(gè)參數(shù)layoutResID所指定布局的根節(jié)點(diǎn)生成布局參數(shù)莺奔,避免寬高設(shè)置等屬性失效。屬性表示一個(gè)控件在容器中的大小变泄,就是說(shuō)這個(gè)控件必須在容器中令哟,這個(gè)屬性才有意義。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末妨蛹,一起剝皮案震驚了整個(gè)濱河市屏富,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌蛙卤,老刑警劉巖狠半,帶你破解...
    沈念sama閱讀 218,284評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異颤难,居然都是意外死亡神年,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,115評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門行嗤,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)已日,“玉大人,你說(shuō)我怎么就攤上這事栅屏∑В” “怎么了?”我有些...
    開(kāi)封第一講書人閱讀 164,614評(píng)論 0 354
  • 文/不壞的土叔 我叫張陵栈雳,是天一觀的道長(zhǎng)护奈。 經(jīng)常有香客問(wèn)我,道長(zhǎng)哥纫,這世上最難降的妖魔是什么逆济? 我笑而不...
    開(kāi)封第一講書人閱讀 58,671評(píng)論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮,結(jié)果婚禮上奖慌,老公的妹妹穿的比我還像新娘抛虫。我一直安慰自己,他們只是感情好简僧,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,699評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布建椰。 她就那樣靜靜地躺著,像睡著了一般岛马。 火紅的嫁衣襯著肌膚如雪棉姐。 梳的紋絲不亂的頭發(fā)上,一...
    開(kāi)封第一講書人閱讀 51,562評(píng)論 1 305
  • 那天啦逆,我揣著相機(jī)與錄音伞矩,去河邊找鬼。 笑死夏志,一個(gè)胖子當(dāng)著我的面吹牛乃坤,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播沟蔑,決...
    沈念sama閱讀 40,309評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼湿诊,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了瘦材?” 一聲冷哼從身側(cè)響起厅须,我...
    開(kāi)封第一講書人閱讀 39,223評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎食棕,沒(méi)想到半個(gè)月后朗和,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,668評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡簿晓,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,859評(píng)論 3 336
  • 正文 我和宋清朗相戀三年眶拉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片抢蚀。...
    茶點(diǎn)故事閱讀 39,981評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖镰禾,靈堂內(nèi)的尸體忽然破棺而出皿曲,到底是詐尸還是另有隱情,我是刑警寧澤吴侦,帶...
    沈念sama閱讀 35,705評(píng)論 5 347
  • 正文 年R本政府宣布屋休,位于F島的核電站,受9級(jí)特大地震影響备韧,放射性物質(zhì)發(fā)生泄漏劫樟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,310評(píng)論 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望叠艳。 院中可真熱鬧奶陈,春花似錦、人聲如沸附较。這莊子的主人今日做“春日...
    開(kāi)封第一講書人閱讀 31,904評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)拒课。三九已至徐勃,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間早像,已是汗流浹背僻肖。 一陣腳步聲響...
    開(kāi)封第一講書人閱讀 33,023評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留卢鹦,地道東北人臀脏。 一個(gè)月前我還...
    沈念sama閱讀 48,146評(píng)論 3 370
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像法挨,于是被迫代替她去往敵國(guó)和親谁榜。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,933評(píng)論 2 355

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