Android視圖架構(gòu)詳解

作者: ztelur
原文地址:
http://blog.csdn.net/u012422440/article/details/51173387

最近一直在研究View的繪制相關(guān)的機(jī)制,發(fā)現(xiàn)需要補(bǔ)充一下Android View Architecture的相關(guān)知識(shí)姐刁,所以就特地研究了一下這方面的代碼聂使,寫成本篇文章。

為了節(jié)約你的時(shí)間弃理,本篇文章內(nèi)容大致如下:

  • Activity屎蜓,DecorView炬转,PhoneWindowViewRoot的作用和相關(guān)關(guān)系。

Android View Architecture

先來幾張圖驻啤,大致展現(xiàn)一下Android 視圖架構(gòu)的大概:

Android View Architecture
View各類關(guān)系圖
View樹狀圖

Activity和Window

眾所周知骑冗,Activity并不負(fù)責(zé)視圖控制捍靠,它只是控制生命周期和處理事件榨婆,真正控制視圖的是Window。一個(gè)Activity包含了一個(gè)Window谊迄,Window才是真正代表一個(gè)窗口烟央,也就是說Activity可以沒有Window,那就相當(dāng)于是Service了粮呢。在ActivityThread中也有控制Service的相關(guān)函數(shù)或許正好印證了這一點(diǎn)。

ActivityWindow的第一次邂逅是在ActivityThread調(diào)用Activityattach()函數(shù)時(shí)豪硅。

//[window]:通過PolicyManager創(chuàng)建window,實(shí)現(xiàn)callback函數(shù),所以,當(dāng)window接收到
//外界狀態(tài)改變時(shí),會(huì)調(diào)用activity的方法,
final void attach(Context context, ActivityThread aThread,
        Instrumentation instr, IBinder token, int ident,
        Application application, Intent intent, ActivityInfo info,
        CharSequence title, Activity parent, String id,
        NonConfigurationInstances lastNonConfigurationInstances,
        Configuration config, String referrer, IVoiceInteractor voiceInteractor) {
    ....
    mWindow = PolicyManager.makeNewWindow(this);
    //當(dāng)window接收系統(tǒng)發(fā)送給它的IO輸入事件時(shí),例如鍵盤和觸摸屏事件,就可以轉(zhuǎn)發(fā)給相應(yīng)的Activity
    mWindow.setCallback(this);
    .....
    //設(shè)置本地窗口管理器
    mWindow.setWindowManager(
            (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
            mToken, mComponent.flattenToString(),
            (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
    .....
}

attach()中懒浮,新建一個(gè)Window實(shí)例作為自己的成員變量识藤,它的類型為PhoneWindow痴昧,這是抽象類Window的一個(gè)子類。然后設(shè)置mWindowWindowManager秧骑。

Window,Activity和DecorView

DecorViewFrameLayout的子類扣囊,它可以被認(rèn)為是Android視圖樹的根節(jié)點(diǎn)視圖绒疗。DecorView作為頂級(jí)View,一般情況下它內(nèi)部包含一個(gè)豎直方向的LinearLayout吓蘑,在這個(gè)LinearLayout里面有上下兩個(gè)部分(具體情況和Android版本及主體有關(guān))磨镶,上面的是標(biāo)題欄,下面的是內(nèi)容欄伟叛。在Activity中通過setContentView所設(shè)置的布局文件其實(shí)就是被加到內(nèi)容欄之中的脐嫂,而內(nèi)容欄的id是content,在代碼中可以通過ViewGroup content = (ViewGroup)findViewById(R.android.id.content)來得到content對(duì)應(yīng)的layout侥蒙。

Window中有幾個(gè)視圖相關(guān)的比較重要的成員變量如下所示:

  • mDecor:DecorView的實(shí)例鞭衩,標(biāo)示Window內(nèi)部的頂級(jí)視圖;
  • mContentParent:setContetView所設(shè)置的布局文件就加到這個(gè)視圖中恒水;
  • mContentRoot:是DecorView的唯一子視圖饲齐,內(nèi)部包含mContentParent,標(biāo)題欄和狀態(tài)欄御雕。

Activity中不僅持有一個(gè)Window實(shí)例滥搭,還有一個(gè)類型為ViewmDecor實(shí)例瑟匆。這個(gè)實(shí)例和Window中的mDecor實(shí)例有什么關(guān)系呢?它又是什么時(shí)候被創(chuàng)建的呢疾嗅?

二者其實(shí)指向同一個(gè)對(duì)象冕象,這個(gè)對(duì)象是在Activity調(diào)用setContentView時(shí)創(chuàng)建的。我們都知道ActivitysetContentView實(shí)際上是調(diào)用了WindowsetContentView方法论悴。

@Override
public void setContentView(int layoutResID) {
    if (mContentParent == null) { //[window]如何沒有DecorView,那么就新建一個(gè)
        installDecor(); //[window]
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }
    ....
    //[window]第二步,將layout添加到mContentParent
    mLayoutInflater.inflate(layoutResID, mContentParent);
    .....
}

代碼很清楚的顯示了布局文件的視圖是添加到mContentParent中膀估,而且Window通過installDecor來新建DecorView耻讽。

//[window]創(chuàng)建一個(gè)decorView
private void installDecor() {
    if (mDecor == null) {
        mDecor = generateDecor(); //直接new出一個(gè)DecorView返回
        ....
    }
    if (mContentParent == null) {
        //[window] 這一步也是很重要的.
        mContentParent = generateLayout(mDecor); //mContentParent是setContentVIew的關(guān)鍵啊
        .....
    }
    ....
}
protected ViewGroup generateLayout(DecorView decor) {
    // Apply data from current theme.
    .......
    //[window] 根據(jù)不同的style生成不同的decorview啊
    View in = mLayoutInflater.inflate(layoutResource, null);
    // 加入到deco中,所以應(yīng)該是其第一個(gè)child
    decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    mContentRoot = (ViewGroup) in; //給DecorView的第一個(gè)child是mContentView
    // 這是獲得所謂的content 
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    }
    .....
    return contentParent;
}

從上述的代碼中齐饮,我們可以清楚的看到mDecormContentParentmContentRoot的關(guān)系握恳。

那么捺僻,Activity中的mDecor是何時(shí)被賦值的?我們?nèi)绾未_定它和Widnow中的mDecor指向同一個(gè)對(duì)象呢拔稳?我們可以查看ActivityThreadhandleResumeActivity函數(shù)锹雏,它負(fù)責(zé)處理Activityresume階段。在這個(gè)函數(shù)中轻绞,Android直接將Window中的DecorView實(shí)例賦值給Activity佣耐。

final Activity a = r.activity;
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;

Window兼砖,DecorView 和 ViewRoot

ViewRoot對(duì)應(yīng)ViewRootImpl類,它是連接WindowManagerServiceDecorView的紐帶懒叛,View的三大流程(測量(measure)戏挡,布局(layout)褐墅,繪制(draw))均通過ViewRoot來完成洪己。ViewRoot并不屬于View樹的一份子。從源碼實(shí)現(xiàn)上來看逝钥,它既非View的子類拱镐,也非View的父類沃琅,但是,它實(shí)現(xiàn)了ViewParent接口晌柬,這讓它可以作為View的名義上的父視圖。RootView繼承了Handler類澈歉,可以接收事件并分發(fā)屿衅,Android的所有觸屏事件、按鍵事件凯砍、界面刷新等事件都是通過ViewRoot進(jìn)行分發(fā)的拴竹。ViewRoot可以被理解為“View樹的管理者”——它有一個(gè)mView成員變量栓拜,它指向的對(duì)象和上文中WindowActivitymDecor指向的對(duì)象是同一個(gè)對(duì)象

我們來先看一下ViewRoot的創(chuàng)建過程挑势。由于ViewRoot作為WindowMangerServiceDecorView的紐帶啦鸣,只有在WindowManager將持有DecorViewWindow添加進(jìn)窗口管理器才創(chuàng)建诫给。我們可以查看WindowMangerGlobal中的addView函數(shù)。對(duì)WindowManager不太熟悉的同學(xué)可以參考《Window和WindowManager解析》

public void addView(View view, ViewGroup.LayoutParams params,
        Display display, Window parentWindow) {
        // 創(chuàng)建ViewRootImpl,然后將下述對(duì)象添加到列表中
    ....
    root = new ViewRootImpl(view.getContext(), display);

    view.setLayoutParams(wparams);

    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);
    ....
    try {
        // 添加啦!!!!!!!!這是通過ViewRootImpl的setView來完成凫碌,這個(gè)View就是DecorView實(shí)例
        root.setView(view, wparams, panelParentView);
    } catch (RuntimeException e) {
      ....
    }
    ....
}

那么盛险,Window是什么時(shí)候被添加到WindowManager中的呢勋又?我們回到ActivityThreadhandleResumeActivity函數(shù)。我們都知道Activityresume階段就是要顯示到屏幕上的階段鹤啡,在Activity也就是DecorView將要顯示到屏幕時(shí)挺邀,系統(tǒng)才會(huì)調(diào)用addView方法或链。

我們?cè)?code>handleResumeActivity函數(shù)中找到了下面一段代碼练般,它調(diào)用了ActivitymakeVisible()函數(shù)。

// ActivityThread
r.activity.makeVisible();

//Activity
    //[windows] DecorView正式添加并顯示
    void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }

我們通過源代碼發(fā)現(xiàn)狂丝,正式在makeVisible函數(shù)中几颜,系統(tǒng)進(jìn)行了Window的添加讯屈。

引用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末涮母,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子沪蓬,更是在濱河造成了極大的恐慌跷叉,老刑警劉巖营搅,帶你破解...
    沈念sama閱讀 211,042評(píng)論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件剧防,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)鸡挠,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,996評(píng)論 2 384
  • 文/潘曉璐 我一進(jìn)店門拣展,熙熙樓的掌柜王于貴愁眉苦臉地迎上來缔逛,“玉大人姓惑,你說我怎么就攤上這事按脚。” “怎么了唯沮?”我有些...
    開封第一講書人閱讀 156,674評(píng)論 0 345
  • 文/不壞的土叔 我叫張陵介蛉,是天一觀的道長溶褪。 經(jīng)常有香客問我猿妈,道長,這世上最難降的妖魔是什么毁葱? 我笑而不...
    開封第一講書人閱讀 56,340評(píng)論 1 283
  • 正文 為了忘掉前任倾剿,我火速辦了婚禮,結(jié)果婚禮上蚌成,老公的妹妹穿的比我還像新娘前痘。我一直安慰自己,他們只是感情好担忧,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,404評(píng)論 5 384
  • 文/花漫 我一把揭開白布芹缔。 她就那樣靜靜地躺著,像睡著了一般瓶盛。 火紅的嫁衣襯著肌膚如雪最欠。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,749評(píng)論 1 289
  • 那天惩猫,我揣著相機(jī)與錄音芝硬,去河邊找鬼轧房。 笑死拌阴,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的奶镶。 我是一名探鬼主播迟赃,決...
    沈念sama閱讀 38,902評(píng)論 3 405
  • 文/蒼蘭香墨 我猛地睜開眼陪拘,長吁一口氣:“原來是場噩夢(mèng)啊……” “哼!你這毒婦竟也來了纤壁?” 一聲冷哼從身側(cè)響起左刽,我...
    開封第一講書人閱讀 37,662評(píng)論 0 266
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎摄乒,沒想到半個(gè)月后悠反,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,110評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡馍佑,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,451評(píng)論 2 325
  • 正文 我和宋清朗相戀三年斋否,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片拭荤。...
    茶點(diǎn)故事閱讀 38,577評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡茵臭,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出舅世,到底是詐尸還是另有隱情旦委,我是刑警寧澤,帶...
    沈念sama閱讀 34,258評(píng)論 4 328
  • 正文 年R本政府宣布雏亚,位于F島的核電站缨硝,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏罢低。R本人自食惡果不足惜查辩,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,848評(píng)論 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望网持。 院中可真熱鬧宜岛,春花似錦、人聲如沸功舀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,726評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽辟汰。三九已至列敲,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間莉擒,已是汗流浹背酿炸。 一陣腳步聲響...
    開封第一講書人閱讀 31,952評(píng)論 1 264
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留涨冀,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 46,271評(píng)論 2 360
  • 正文 我出身青樓麦萤,卻偏偏與公主長得像鹿鳖,于是被迫代替她去往敵國和親扁眯。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,452評(píng)論 2 348

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