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

前言

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

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

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

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

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

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


640.png

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

setContentView是如何起作用的?

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

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

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

當(dāng)setContentView()執(zhí)行完畢后,此時PhoneWindow和DecorView都已經(jīng)創(chuàng)建完成,但是DecorView并沒有添加到PhoneWindow上,這個操作需要在onResume()才會觸發(fā),ActivityThread在執(zhí)行完performLaunchActivity后,便會執(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)建了好多臨時變量,其實仔細分析的話,只是兩個變量在執(zhí)行操作,一個就是wm(PhoneWindow自身的WindowManager),一個是decor(setContentView()創(chuàng)建出來的DecorView)。兩個變量的最終交互就是wm.addView(decor, l)。同時我們還會發(fā)現(xiàn)addView()執(zhí)行的大前提是等待onResume()執(zhí)行完畢,如果我們在onResume()中處理耗時操作,那就意味著應(yīng)用頁面的顯示時間被延后,為了保障頁面盡快進入繪制階段,onResume中不要處理耗時任務(wù)。

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


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分別存儲著View鏈表和ViewRootImpl的鏈表,ViewRootImpl就是一個ViewParent視圖管理類。每個傳入的DecorView都會創(chuàng)建一個對應(yīng)的ViewRootImpl來管理。它將實際控制著DecorView的繪制周期,同時還可以與WWM進行Binder通信。ViewRootImpl在調(diào)用setView后,即向WWM發(fā)起了添加請求,WWM便會將當(dāng)前的PhoneWindow放入自身管理的Window列表中,將DecorView添加到PhoneWindow上,同時通知ViewRootImpl進行繪制操作(繪制操作將涉及到SurfaceFlinger,在這里暫不探討),代碼走到這里時,View和Window之間便真正的結(jié)合起來了。其完成流程圖如下:


640-3.png

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

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


640-4.png

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

總結(jié)

閱讀源碼時發(fā)現(xiàn),在setContentView()中,頻繁用到了inflate()方法,源碼中使用的是兩參數(shù)形式的,而我們在使用inflate()時,更多的是用三參數(shù)的,在這列就順便提一下,inflate(layoutResID, mContentParent)實際上等價于inflate(layoutResID, mContentParent, mContentParent !=null)。mContentParent設(shè)置的意義在于協(xié)助第一個參數(shù)layoutResID所指定布局的根節(jié)點生成布局參數(shù),避免寬高設(shè)置等屬性失效。屬性表示一個控件在容器中的大小,就是說這個控件必須在容器中,這個屬性才有意義。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容