前言
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下窟却。
在一個(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()亚茬,具體流程和源碼如下圖所示:
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)行管理。引用流程及源碼如下:
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)了旺入。其完成流程圖如下:
需要注意的是,當(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í)際如下圖所示:
一個(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è)屬性才有意義。