Android中將View添加至窗口的源碼分析

本文主要內(nèi)容是講解一個(gè)視圖View或者一個(gè)ViewGroup對(duì)象是如何添加至應(yīng)用程序窗口中的。下文中提到的窗口可泛指我們能看到的界面孕似,包括一個(gè)Activity呈現(xiàn)的界面(我們可以將之理解為應(yīng)用程序窗口),一個(gè)Dialog,一個(gè)Toast,一個(gè)Menu菜單等澳叉。
  首先先來(lái)看一個(gè)UI框架的關(guān)系圖


  然后再來(lái)對(duì)相關(guān)類(lèi)的作用進(jìn)行一下簡(jiǎn)單介紹,這樣你的印象才深刻:
Window 類(lèi)
  位于/frameworks/base/core/java/android/view/Window.java

說(shuō)明:該類(lèi)是一個(gè)抽象類(lèi)沐悦,提供了繪制窗口的一組通用API成洗。可以將之理解為一個(gè)載體藏否,各種View在這個(gè)載體上顯示瓶殃。

源文件(部分)如下:

public abstract class Window {    
    //...  
    //指定Activity窗口的風(fēng)格類(lèi)型  
    public static final int FEATURE_NO_TITLE = 1;  
    public static final int FEATURE_INDETERMINATE_PROGRESS = 5;  
      
    //設(shè)置布局文件  
    public abstract void setContentView(int layoutResID);  
  
    public abstract void setContentView(View view);  
  
    //請(qǐng)求指定Activity窗口的風(fēng)格類(lèi)型  
    public boolean requestFeature(int featureId) {  
        final int flag = 1<<featureId;  
        mFeatures |= flag;  
        mLocalFeatures |= mContainer != null ? (flag&~mContainer.mFeatures) : flag;  
        return (mFeatures&flag) != 0;  
    }      
    //...  
}  

PhoneWindow類(lèi)
  /frameworks/policies/base/phone/com/android/internal/policy/impl/PhoneWindow.java

說(shuō)明:該類(lèi)繼承于Window類(lèi),是Window類(lèi)的具體實(shí)現(xiàn)副签,即我們可以通過(guò)該類(lèi)具體去繪制窗口遥椿。該類(lèi)內(nèi)部包含了一個(gè)DecorView對(duì)象,該DecorView對(duì)象是所有應(yīng)用窗口(Activity界面)的根View淆储。PhoneWindow類(lèi):把一個(gè)FrameLayout類(lèi)即DecorView對(duì)象進(jìn)行一定的包裝冠场,將其作為應(yīng)用窗口的根View,并提供一組通用的窗口操作接口本砰。

源文件(部分)如下:

public class PhoneWindow extends Window implements MenuBuilder.Callback {  
    //...  
    // This is the top-level view of the window, containing the window decor.  
    private DecorView mDecor;  //該對(duì)象是所有應(yīng)用窗口的根視圖 碴裙, 是FrameLayout的子類(lèi)  
      
    //該對(duì)象是Activity布局文件的父視圖,一般來(lái)說(shuō)是一個(gè)FrameLayout型的ViewGroup   
    // 同時(shí)也是DecorView對(duì)象的一個(gè)子視圖  
    // This is the view in which the window contents are placed. It is either  
    // mDecor itself, or a child of mDecor where the contents go.  
    private ViewGroup mContentParent;   
      
    //設(shè)置標(biāo)題  
    @Override  
    public void setTitle(CharSequence title) {  
        if (mTitleView != null) {  
            mTitleView.setText(title);  
        }  
        mTitle = title;  
    }  
    //設(shè)置背景圖片  
    @Override  
    public final void setBackgroundDrawable(Drawable drawable) {  
        if (drawable != mBackgroundDrawable || mBackgroundResource != 0) {  
            mBackgroundResource = 0;  
            mBackgroundDrawable = drawable;  
            if (mDecor != null) {  
                mDecor.setWindowBackground(drawable);  
            }  
        }  
    }  
    //...      
}  

DecorView類(lèi)

說(shuō)明:該類(lèi)是FrameLayout的子類(lèi),并且是PhoneWindow的內(nèi)部類(lèi)舔株。該類(lèi)就是對(duì)普通的FrameLayout進(jìn)行功能的擴(kuò)展莺琳,更確切點(diǎn)可以說(shuō)是修飾(Decor的英文全稱(chēng)是Decoration,即“修飾”的意思)督笆,比如說(shuō)添加TitleBar(標(biāo)題欄),以及 TitleBar上的滾動(dòng)條等 诱贿。**最重要的一點(diǎn)是娃肿,它是所有應(yīng)用窗口的根View 。

如下所示:


**DecorView根視圖結(jié)構(gòu)**
** DecorView 根視圖形式**

源文件(部分)如下:

private final class DecorView extends FrameLayout {  
    //...  
    //觸摸事件處理  
    @Override  
    public boolean onTouchEvent(MotionEvent event) {  
        return onInterceptTouchEvent(event);  
    }  
    //...  
}  

打個(gè)不恰當(dāng)比喻吧珠十,Window類(lèi)相當(dāng)于一幅畫(huà)(抽象概念料扰,什么畫(huà)我們未知) ,PhoneWindow為一副齊白石先生的山水畫(huà)(具體概念焙蹭,我們知道了是誰(shuí)的晒杈、什么性質(zhì)的畫(huà)),DecorView則為該山水畫(huà)的具體內(nèi)容(有山孔厉、有水拯钻、有樹(shù),各種界面)撰豺。DecorView呈現(xiàn)在PhoneWindow上粪般。
  當(dāng)系統(tǒng)(一般是ActivityManagerService)配置好啟動(dòng)一個(gè)Activity(activity對(duì)象和window對(duì)象信息)的相關(guān)參數(shù)后,就會(huì)調(diào)用Activity的onCreate()方法污桦,在其中通過(guò)設(shè)置setContentView()方法設(shè)置該Activity的顯示界面亩歹,整個(gè)調(diào)用環(huán)節(jié)由此鋪開(kāi)。setContentView()的三個(gè)構(gòu)造方法調(diào)用流程本質(zhì)上一樣凡橱,我們分析其中一個(gè)setContentView(int resId)方法小作。
  ###Step1、Activity.setContentView(int resId)該方法在Activity類(lèi)中稼钩,該方法只是簡(jiǎn)單的回調(diào)Window對(duì)象顾稀,具體為PhoneWindow對(duì)象的setContentView()方法實(shí)現(xiàn) 。

public void setContentView(int layoutResID) {  
    getWindow().setContentView(layoutResID);  
}  
  
public Window getWindow() {  
    return mWindow;   //Window對(duì)象坝撑,本質(zhì)上是一個(gè)PhoneWindow對(duì)象  
}  

###Step2础拨、PhoneWindow.setContentView() 該方法在PhoneWindow類(lèi)中

@Override  
public void setContentView(int layoutResID) {  
    //是否是第一次調(diào)用setContentView方法, 如果是第一次調(diào)用绍载,則mDecor和mContentParent對(duì)象都為空  
    if (mContentParent == null) {  
        installDecor();  
    } else {  
        mContentParent.removeAllViews();  
    }  
    mLayoutInflater.inflate(layoutResID, mContentParent);  
    final Callback cb = getCallback();  
    if (cb != null) {  
        cb.onContentChanged();  
    }  
}  

首先判斷是否已經(jīng)由setContentView()獲取了mContentParent(即View)對(duì)象诡宗,即是否第一次調(diào)用PhoneWindow對(duì)象的setContentView()方法。 如果第一次調(diào)用击儡,則調(diào)用installDecor()方法塔沃,否則移除該View內(nèi)的所有子View。最后將我們的資源文件(一般為XML文件)通過(guò)LayoutInflater對(duì)象轉(zhuǎn)化成View樹(shù)阳谍,并且添加到mContentParent視圖中蛀柴。
  ###Step3螃概、PhoneWindow.installDecor()

private void installDecor() {  
    if (mDecor == null) {  
        //mDecor為空,則創(chuàng)建一個(gè)Decor對(duì)象  
        mDecor = generateDecor();  
        mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);  
        mDecor.setIsRootNamespace(true);  
    }  
    if (mContentParent == null) {  
        //generateLayout()方法會(huì)根據(jù)窗口的風(fēng)格修飾鸽疾,選擇對(duì)應(yīng)的修飾布局文件  
        //并且將id為content(android:id="@+id/content")的FrameLayout賦值給mContentParent  
        mContentParent = generateLayout(mDecor);  
          
        //...  
}  

首先判斷mDecor對(duì)象是否為空吊洼,不為空,則調(diào)用generateDecor()創(chuàng)建一個(gè)DecorView(該類(lèi)是FrameLayout子類(lèi)制肮,即一個(gè)ViewGroup視圖)
generateDecor()方法原型:

proteced DecorView generateDecor(){
  return new DecorView(getContext(), -1);
}

其次繼續(xù)判斷mContentParent對(duì)象是否為空冒窍,如果不為空,則調(diào)用generateLayout()方法去創(chuàng)建mContentParent對(duì)象豺鼻。
generateLayout()方法:

protected ViewGroup generateLayout(DecorView decor) {  
    // Apply data from current theme.  
  
    //...1综液、根據(jù)requestFreature()和Activity節(jié)點(diǎn)的android:theme="" 設(shè)置好 features值  
      
    //2 根據(jù)設(shè)定好的 features值,即特定風(fēng)格屬性儒飒,選擇不同的窗口修飾布局文件  
    int layoutResource;  //窗口修飾布局文件    
    int features = getLocalFeatures();  
    // System.out.println("Features: 0x" + Integer.toHexString(features));  
    if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {  
        if (mIsFloating) {  
            layoutResource = com.android.internal.R.layout.dialog_title_icons;  
        } else {  
            layoutResource = com.android.internal.R.layout.screen_title_icons;  
        }  
        // System.out.println("Title Icons!");  
    } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0) {  
        // Special case for a window with only a progress bar (and title).  
        // XXX Need to have a no-title version of embedded windows.  
        layoutResource = com.android.internal.R.layout.screen_progress;  
        // System.out.println("Progress!");  
    }   
    //...  
      
    //3 選定了窗口修飾布局文件 谬莹,添加至DecorView對(duì)象里,并且指定mcontentParent值  
    View in = mLayoutInflater.inflate(layoutResource, null);  
    decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));  
  
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);  
    if (contentParent == null) {  
        throw new RuntimeException("Window couldn't find content container view");  
    }  
  
    if ((features & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {  
        ProgressBar progress = getCircularProgressBar(false);  
        if (progress != null) {  
            progress.setIndeterminate(true);  
        }  
    }  
    //...  
    return contentParent;  
}  

該方法會(huì)做如下事情:
  1桩了、根據(jù)窗口的風(fēng)格修飾類(lèi)型為該窗口選擇不同的窗口布局文件(根視圖)附帽。這些窗口修飾布局文件指定一個(gè)用來(lái)存放Activity自定義布局文件的ViewGroup視圖,一般為FrameLayout 其id 為:

android:id="@android:id/content"

例如窗口修飾類(lèi)型包括FullScreen(全屏)井誉、NoTitleBar(不含標(biāo)題欄)等士葫。選定窗口修飾類(lèi)型有兩種:
  ①送悔、指定requestFeature()窗口修飾符慢显,PhoneWindow對(duì)象調(diào)用getLocalFeature()方法獲取值;
 ∏菲 ②荚藻、為我們的Activity配置相應(yīng)屬性,即android:theme=“”洁段,PhoneWindow對(duì)象調(diào)用getWindowStyle()方法獲取值应狱。
  舉例如下,隱藏標(biāo)題欄有如下方法:requestWindowFeature(Window.FEATURE_NO_TITLE);或者 為Activity配置xml屬性:android:theme=”@android:style/Theme.NoTitleBar”祠丝。

PS:因此疾呻,在Activity中必須在setContentView之前調(diào)用requestFeature()方法。

確定好窗口風(fēng)格之后写半,選定該風(fēng)格對(duì)應(yīng)的布局文件岸蜗,這些布局文件位于 frameworks/base/core/res/layout/ ,
典型的窗口布局文件有:
R.layout.dialog_titile_icons
R.layout.screen_title_icons
R.layout.screen_progress
R.layout.dialog_custom_title
R.layout.dialog_title
R.layout.screen_title// 最常用的Activity窗口修飾布局文件
R.layout.screen_simple//全屏的Activity窗口布局文件

** 分析Activity最常用的一種窗口布局文件叠蝇,R.layout.screen_title :**

<!--  
This is an optimized layout for a screen, with the minimum set of features  
enabled.  
-->  
  
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:orientation="vertical"  
    android:fitsSystemWindows="true">  
    <FrameLayout  
        android:layout_width="match_parent"   
        android:layout_height="?android:attr/windowTitleSize"  
        style="?android:attr/windowTitleBackgroundStyle">  
        <TextView android:id="@android:id/title"   
            style="?android:attr/windowTitleStyle"  
            android:background="@null"  
            android:fadingEdge="horizontal"  
            android:gravity="center_vertical"  
            android:layout_width="match_parent"  
            android:layout_height="match_parent" />  
    </FrameLayout>  
    <FrameLayout android:id="@android:id/content"  
        android:layout_width="match_parent"   
        android:layout_height="0dip"  
        android:layout_weight="1"  
        android:foregroundGravity="fill_horizontal|top"  
        android:foreground="?android:attr/windowContentOverlay" />  
</LinearLayout>  

該布局文件很簡(jiǎn)單璃岳,一個(gè)LinearLayout下包含了兩個(gè)子FrameLayout視圖,第一個(gè)FrameLayout用來(lái)顯示標(biāo)題欄(TitleBar),該TextView 視圖id為title(android:id="@android:id/title")铃慷;第二個(gè)FrameLayout用來(lái)顯示我們Activity的布局文件的父視圖单芜,該FrameLayoutid為content(android:id="@android:id/content") 。
全屏的窗口布局文件 R.layout.screen_simple:\

<--This is an optimized layout for a screen, with the minimum set of features  
enabled.  
-->  
  
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"  
    android:id="@android:id/content"  
    android:fitsSystemWindows="true"  
    android:foregroundInsidePadding="false"  
    android:foregroundGravity="fill_horizontal|top"  
    android:foreground="?android:attr/windowContentOverlay" />  

該布局文件只有一個(gè)FrameLayout犁柜,用來(lái)顯示我們Activity的布局文件洲鸠,該FrameLayoutid為 android:id="@android:id/content"
  2、前面我們確定窗口修飾布局文件后馋缅,mDecor作為根視圖將窗口布局對(duì)應(yīng)的視圖(view)添加進(jìn)去扒腕,并且獲取id為content的View,將其賦值給mContentParent對(duì)象股囊,即我們前面講的第二個(gè)FrameLayout.
  
  ###At Last袜匿、產(chǎn)生了mDecor和mContentParent對(duì)象后更啄,將Activity布局文件直接添加到mContentParent父View中稚疹。即step2代碼中的如下所示:

mLayoutInflater.inflate(layoutResID, mContentParent);  

###整理:整個(gè)過(guò)程主要是如何把Activity的布局文件添加至窗口中(即PhoneWindow的DecorView中),上面過(guò)程概括為:
  1祭务、創(chuàng)建一個(gè)DecorView對(duì)象内狗,作為應(yīng)用窗口的根View。
  2义锥、創(chuàng)建不同的窗口修飾布局文件柳沙,并且獲取Activity的布局文件該存放的地方,由該窗口修飾布局文件內(nèi)id為content的FrameLayout指定 拌倍。
  3赂鲤、將Activity的布局文件加至id為content的FrameLayout內(nèi)。
  4柱恤、最后数初,當(dāng)ActivityManagerService準(zhǔn)備resume一個(gè)Activity時(shí),會(huì)回調(diào)該Activity的handleResumeActivity()方法梗顺, 該方法會(huì)調(diào)用Activity的makeVisible方法 ,顯示我們剛才創(chuàng)建的mDecor 視圖族泡孩。

//系統(tǒng)resume一個(gè)Activity時(shí),調(diào)用此方法  
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {  
    ActivityRecord r = performResumeActivity(token, clearHide);  
    //...  
     if (r.activity.mVisibleFromClient) {  
         r.activity.makeVisible();  
     }  
}  

handleResumeActivity()方法原型如下:位于ActivityThread類(lèi)中

void makeVisible() {  
    if (!mWindowAdded) {  
        ViewManager wm = getWindowManager();   // 獲取WindowManager對(duì)象  
        wm.addView(mDecor, getWindow().getAttributes());  
        mWindowAdded = true;  
    }  
    mDecor.setVisibility(View.VISIBLE); //使其處于顯示狀況  
}  

接下來(lái)就是寺谤,如何把我們已經(jīng)創(chuàng)建好的窗口通知給WindowManagerService 仑鸥,以便它能夠把這個(gè)窗口顯示在屏幕上。關(guān)于這方面內(nèi)容大家可以去看鄧凡平老師的這篇博客《Android深入淺出之Surface[1]

PS:本文是根據(jù)http://blog.csdn.net/qinjuning/article/details/7226787整理出來(lái)的变屁。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末眼俊,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子粟关,更是在濱河造成了極大的恐慌泵琳,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,723評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異获列,居然都是意外死亡谷市,警方通過(guò)查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,485評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門(mén)击孩,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái)迫悠,“玉大人,你說(shuō)我怎么就攤上這事巩梢〈葱梗” “怎么了?”我有些...
    開(kāi)封第一講書(shū)人閱讀 152,998評(píng)論 0 344
  • 文/不壞的土叔 我叫張陵括蝠,是天一觀的道長(zhǎng)鞠抑。 經(jīng)常有香客問(wèn)我,道長(zhǎng)忌警,這世上最難降的妖魔是什么搁拙? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 55,323評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮法绵,結(jié)果婚禮上箕速,老公的妹妹穿的比我還像新娘。我一直安慰自己朋譬,他們只是感情好盐茎,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,355評(píng)論 5 374
  • 文/花漫 我一把揭開(kāi)白布。 她就那樣靜靜地躺著徙赢,像睡著了一般字柠。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上狡赐,一...
    開(kāi)封第一講書(shū)人閱讀 49,079評(píng)論 1 285
  • 那天窑业,我揣著相機(jī)與錄音,去河邊找鬼阴汇。 笑死数冬,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的搀庶。 我是一名探鬼主播拐纱,決...
    沈念sama閱讀 38,389評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼哥倔!你這毒婦竟也來(lái)了秸架?” 一聲冷哼從身側(cè)響起,我...
    開(kāi)封第一講書(shū)人閱讀 37,019評(píng)論 0 259
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤咆蒿,失蹤者是張志新(化名)和其女友劉穎东抹,沒(méi)想到半個(gè)月后蚂子,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,519評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡缭黔,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,971評(píng)論 2 325
  • 正文 我和宋清朗相戀三年食茎,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片馏谨。...
    茶點(diǎn)故事閱讀 38,100評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡别渔,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出惧互,到底是詐尸還是另有隱情哎媚,我是刑警寧澤,帶...
    沈念sama閱讀 33,738評(píng)論 4 324
  • 正文 年R本政府宣布喊儡,位于F島的核電站拨与,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏艾猜。R本人自食惡果不足惜买喧,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,293評(píng)論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望箩朴。 院中可真熱鬧岗喉,春花似錦秋度、人聲如沸炸庞。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 30,289評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)埠居。三九已至,卻和暖如春事期,著一層夾襖步出監(jiān)牢的瞬間滥壕,已是汗流浹背。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 31,517評(píng)論 1 262
  • 我被黑心中介騙來(lái)泰國(guó)打工兽泣, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留绎橘,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 45,547評(píng)論 2 354
  • 正文 我出身青樓唠倦,卻偏偏與公主長(zhǎng)得像称鳞,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子稠鼻,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,834評(píng)論 2 345

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