Android DecorView 一窺全貌(上)

前言

我們都知道DecorView是最頂層View(根View),它是怎么創(chuàng)建和使用的呢?
通過本篇文章黄橘,你將了解到:

1、DecorView創(chuàng)建過程屈溉。
2塞关、DecorView與Window/Activity關(guān)系
3、DecorView各個子View創(chuàng)建

DecorView創(chuàng)建過程

來回顧一下Activity創(chuàng)建過程:


image.png

AMS管理著Activity生命周期子巾,每當(dāng)切換Activity狀態(tài)時通過Binder告訴ActivityThread帆赢,ActivityThread通過Handler切換到主線程(UI線程)執(zhí)行小压,最終分別調(diào)用到我們熟知的onCreate(xx)/onResume()方法。
更多細(xì)節(jié)請移步:Android Activity創(chuàng)建到View的顯示過程
本次關(guān)注的重點是setContentView(xx)方法椰于。

簡單布局分析

相信大家都知道怠益,該方法是將我們布局(layout)文件添加到一個id為“android.R.id.content”的ViewGroup里,我們只需要關(guān)心layout的內(nèi)容廉羔。那么R.id.content是整個View樹的根布局嗎溉痢?來看看一個最簡單的Activity的布局:

my_layout.xml
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:background="@color/colorGreen"
    android:id="@+id/my_root"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    <Button
        android:id="@+id/btn"
        android:text="hello"
        android:onClick="onClick"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content">
    </Button>
</FrameLayout>

效果圖:


image.png

把布局觀察器打開:


image.png

可以看出,"content"布局對應(yīng)的是ContentFrameLayout憋他,它并不是View樹的根布局孩饼,根布局是DecorView,DecorView和ContentFrameLayout之間還有幾個View竹挡,接下來我們就來了解上圖的這些View是怎么確定的镀娶。

setContentView(xx)源碼解析

從Activity onCreate(xx)看起

AppCompatDelegateImpl

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        //先調(diào)用父類構(gòu)造函數(shù)
        //初始化一些必要變量
        super.onCreate(savedInstanceState);
        setContentView(R.layout.layout_path);
    }

創(chuàng)建及初始化AppCompatDelegateImpl

AppCompatActivity.java
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        final AppCompatDelegate delegate = getDelegate();
        delegate.installViewFactory();
        //交給AppCompatDelegate代理
        delegate.onCreate(savedInstanceState);
        super.onCreate(savedInstanceState);
    }

AppCompatActivity將工作交給了AppCompatDelegate處理,而AppCompatDelegate是抽象類揪罕,真正工作的是其子類:AppCompatDelegateImpl梯码。

    @Override
    public void onCreate(Bundle savedInstanceState) {
        ensureWindow();
    }
    private void ensureWindow() {
        //省略
        //mHost為創(chuàng)建此代理的Activity
        if (mWindow == null && mHost instanceof Activity) {
            attachToWindow(((Activity) mHost).getWindow());
        }
    }
    private void attachToWindow(@NonNull Window window) {
        if (mWindow != null) {
            throw new IllegalStateException(
                    "AppCompat has already installed itself into the Window");
        }
        //接收各種事件的window callback,實際就是將callback再包了一層
        mAppCompatWindowCallback = new AppCompatWindowCallback(callback);
        window.setCallback(mAppCompatWindowCallback);
        //省略
        //使用activity的window
        mWindow = window;
    }

上面代碼的主要工作是關(guān)聯(lián)AppCompatDelegateImpl和Activity的window變量好啰。

接著來看看setContentView(R.layout.layout_path)本尊
Activity的setContentView(xx)最終調(diào)用了AppCompatDelegateImpl里的setContentView(xx):

AppCompatDelegateImpl.java
    @Override
    public void setContentView(int resId) {
        //創(chuàng)建SubDecor轩娶,從名字可以猜測一下是DecorView的子View
        ensureSubDecor();
        //找到R.id.content布局
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
        //清空content里的子View
        contentParent.removeAllViews();
        //加載在activity里設(shè)置的layout,并添加到content里
        LayoutInflater.from(mContext).inflate(resId, contentParent);
    }

可以看出框往,我們自定義的layout最后被添加到contentParent里鳄抒,而contentParent是從mSubDecor里找尋的,因此我們重點來看看ensureSubDecor()方法椰弊。
ensureSubDecor里調(diào)用的是createSubDecor()方法來創(chuàng)建subDecor许溅。

subDecor創(chuàng)建

AppCompatDelegateImpl.java
    private ViewGroup createSubDecor() {
        //根據(jù)Activity的主題設(shè)置不同設(shè)置window的feature
        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
        //確保window已經(jīng)關(guān)聯(lián)
        ensureWindow();
        //獲取DecorView,沒有則創(chuàng)建
        mWindow.getDecorView();
        ViewGroup subDecor = null;
        //有標(biāo)題
        if (!mWindowNoTitle) {
            //根據(jù)條件給subDecor加載不同的布局文件
            if (mIsFloating) {
                subDecor = (ViewGroup) inflater.inflate(
                        R.layout.abc_dialog_title_material, null);
            } else if (mHasActionBar) {
                //加載subDecor布局
                subDecor = (ViewGroup) LayoutInflater.from(themedContext)
                        .inflate(R.layout.abc_screen_toolbar, null);

                //尋找subDecor子布局
                mDecorContentParent = (DecorContentParent) subDecor
                        .findViewById(R.id.decor_content_parent);
                mDecorContentParent.setWindowCallback(getWindowCallback());
            }
        } else {
            //省略
        }
        //標(biāo)題欄標(biāo)題
        if (mDecorContentParent == null) {
            mTitleView = (TextView) subDecor.findViewById(R.id.title);
        }
        //尋找subDecor子布局秉版,命名為contentView
        final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
                R.id.action_bar_activity_content);
        //找到window里content布局贤重,實際上找的是DecorView里名為content的布局
        final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
        if (windowContentView != null) {
            //挨個移除windowContentView的子View,并將之添加到contentView里
            while (windowContentView.getChildCount() > 0) {
                final View child = windowContentView.getChildAt(0);
                windowContentView.removeViewAt(0);
                contentView.addView(child);
            }
            //把windowContentView id去掉清焕,之前名為content
            windowContentView.setId(View.NO_ID);
            //將"content"名賦予contentView
            contentView.setId(android.R.id.content);
        }
        //把subDecor添加為Window的contentView并蝗,實際上添加為DecorView的子View。該方法后面再具體分析
        mWindow.setContentView(subDecor);
        return subDecor;
    }

該方法較長秸妥,省略了一些細(xì)枝末節(jié)借卧,主體功能是:

  • 根據(jù)不同的前置條件,加載不同的布局文件作為subDecor
  • 將subDecor加入到DecorView里筛峭,subDecor本身是ViewGroup

subDecor創(chuàng)建了铐刘,那么DecorView啥時候創(chuàng)建呢?createSubDecor()有段代碼:

mWindow.getDecorView()

DecorView創(chuàng)建

mWindow之前分析過是Activity的window變量影晓,它的實現(xiàn)類是PhoneWindow镰吵。

PhoneWindow.java
    View getDecorView() {
        if (mDecor == null || mForceDecorInstall) {
            installDecor();
        }
        return mDecor;
    }

    private void installDecor() {
        //mDecor作為PhoneWindow變量
        if (mDecor == null) {
            //新建DecorView檩禾,并關(guān)聯(lián)window
            mDecor = generateDecor(-1);
        }
        if (mContentParent == null) {
            //創(chuàng)建DecorView子布局
            mContentParent = generateLayout(mDecor);
        }
    }

    protected DecorView generateDecor(int featureId) {
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                //applicationContext是Application
                //getContext 是Activity
                //DecorContext 繼承自ContextThemeWrapper
                context = new DecorContext(applicationContext, getContext());
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
        //this 指的是phoneWindow,賦值給mWindow變量
        return new DecorView(context, featureId, this, getAttributes());
    }

從上可知:

DecorView繼承自FrameLayout

注:DecorContext 有關(guān)請移步:Android各種Context的前世今生

重點來看看

mContentParent = generateLayout(mDecor);

PhoneWindow.java
    protected ViewGroup generateLayout(DecorView decor) {
        //獲取WindowStyle
        TypedArray a = getWindowStyle();
        //根據(jù)style設(shè)置各種flags和feature
        //省略...

        WindowManager.LayoutParams params = getAttributes();
        //待加載的布局資源id
        int layoutResource;
        int features = getLocalFeatures();
        //根據(jù)不同的feature確定不同的布局
        if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss;
            setCloseOnSwipeEnabled(true);
        } else if (features) {
            //省略
        } else {
            //默認(rèn)加載該布局
            layoutResource = R.layout.screen_simple; 
        }
        //實例化上面確定的布局疤祭,并將該布局添加到DecorView里
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        //獲取名為ID_ANDROID_CONTENT的子View
        //public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
        //實際上就是content
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        return contentParent;
    }

將確定的布局加載盼产,并添加到DecorView里。

    void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        //實例化布局
        final View root = inflater.inflate(layoutResource, null);
        if (mDecorCaptionView != null) {
            //省略
        } else {
            //添加到DecorView里
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        //記錄root
        mContentRoot = (ViewGroup) root;
    }

總結(jié)來說勺馆,mWindow.getDecorView()做了哪些事呢戏售?

  • 創(chuàng)建DecorView,并關(guān)聯(lián)PhoneWindow與DecorView(互相持有)
  • 根據(jù)feature確定DecorView子布局草穆,并添加填充滿DecorView

可能看到上面的分析還是一頭霧水灌灾,沒關(guān)系先來理一下DecorView和SubDecor關(guān)系。

1悲柱、首先創(chuàng)建DecorView锋喜,并添加其子View,該子View在DecorView里稱為:mContentRoot豌鸡。當(dāng)前場景下使用的子View資源id:R.layout.screen_simple;
2嘿般、mContentRoot里有名為"R.id.content"的子View,其在PhoneWindow里稱作為:mContentParent
3涯冠、當(dāng)前場景下使用的subDecor資源id:R.layout.abc_screen_toolbar炉奴,實例化該資源文件獲得subDecor實例
4、該subDecor里有子View資源id:R.id.action_bar_activity_content蛇更,在AppCompatDelegateImpl里命名為:contentView
5瞻赶、DecorView添加subDecor過程:取出DecorView里的mContentParent,并移除里面的子View械荷,并將移除的子View添加到subDecor里的contentView里共耍。
6虑灰、將contentView更名為“R.id.content”
7吨瞎、最后通過mWindow.setContentView(subDecor),將subDecor添加到DecorView里

繼續(xù)分析mWindow.setContentView(subDecor)

PhoneWindow.java
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        if (mContentParent == null) {
            //為空穆咐,說明DecorView沒構(gòu)造
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            //清空子View
            mContentParent.removeAllViews();
        }
        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            //省略
        } else {
            //View添加到mContentParent
            mContentParent.addView(view, params);
        }
        //省略
    }

以上就是DecorView的創(chuàng)建過程颤诀。
DecorView創(chuàng)建完成后,再將自定義布局添加到DecorView里名為R.id.content布局里对湃。至此setContentView分析完畢崖叫,老規(guī)矩用圖表示整個流程。

setContentView圖示

image.png

DecorView與Window/Activity關(guān)系

Activity:
我們平時把Activity當(dāng)做一個頁面拍柒,直觀上看這個頁面承載著我們的布局顯示以及相應(yīng)的邏輯處理心傀。Activity有生命周期,在不同的狀態(tài)下做不同的處理拆讯。
Window
顧名思義脂男,就是我們所見的窗口养叛,Activity顯示的內(nèi)容實際上是展示在Window上的,對應(yīng)的是PhoneWindow宰翅。
DecorView
Window本身是比較抽象的弃甥,可以想象成為一個管理的東西,它管理的就是ViewTree汁讼,而DecorView是ViewTree的根淆攻。我們具體的界面展示是通過View完成的,把設(shè)計好的View添加到DecorView里嘿架,整個ViewTree就構(gòu)建起來了瓶珊,最終添加到Window里。
因此:

  • Activity管理著Window眶明,Window管理著DecorView艰毒,Activity間接管理著DecorView
  • Activity處在“create"狀態(tài)的時候創(chuàng)建對應(yīng)的Window,并在setContentView里創(chuàng)建DecorView搜囱,最終添加自定義布局到DecorView里丑瞧。此時,Activity創(chuàng)建完畢蜀肘,Window創(chuàng)建完畢绊汹,DecorView創(chuàng)建完畢,ViewTree構(gòu)建成功扮宠,三者關(guān)系已建立西乖。
  • Acitivity處在“resume"狀態(tài)的時候,通過Window的管理類將DecorView添加到Window里坛增,并提交更新DecorView的請求获雕。當(dāng)下次屏幕刷新信號到來之時,執(zhí)行ViewTree三大流程(測量收捣,布局届案,繪制),最終的效果就是我們的自定義布局顯示在屏幕上罢艾。
    “注”:指的狀態(tài)執(zhí)行時機(jī)是處在ActivityThread里的楣颠。

這么看起來還是不太順,我們所說的三者的聯(lián)系實際上在代碼里來看就是:“不是你持有我就是我引用你”咐蚯,通過類圖來看看三者之間的引用情況:


image.png

可以看出童漩,Window作為Activity和DecorView的聯(lián)結(jié)者。
注:Activity也持有DecorView的引用春锋,只是不對外提供獲取的接口矫膨。因此一般通過Activity獲取Window,再獲取DecorView。
在UI上看來侧馅,可以這么理解:


image.png

注:這圖只是便于理解抽象關(guān)系直奋,實際上對于Activity來說并沒有尺寸的說法。

后續(xù)

限于當(dāng)前篇幅施禾,后續(xù)內(nèi)容重開一篇:
Android DecorView 一窺全貌(下)

注:以上關(guān)于DecorView脚线、subDecor、標(biāo)題欄弥搞、布局文件和區(qū)塊尺寸的選擇是基于當(dāng)前的demo的邮绿。可能你所使用的主題攀例、設(shè)置的屬性和本文不同導(dǎo)致布局效果差異船逮,請注意甄別。
源碼基于:Android 10.0

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末粤铭,一起剝皮案震驚了整個濱河市挖胃,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌梆惯,老刑警劉巖酱鸭,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異垛吗,居然都是意外死亡凹髓,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進(jìn)店門怯屉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來蔚舀,“玉大人,你說我怎么就攤上這事锨络《奶桑” “怎么了?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵羡儿,是天一觀的道長礼患。 經(jīng)常有香客問我,道長失受,這世上最難降的妖魔是什么讶泰? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任咏瑟,我火速辦了婚禮拂到,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘码泞。我一直安慰自己兄旬,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著领铐,像睡著了一般悯森。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上绪撵,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天瓢姻,我揣著相機(jī)與錄音,去河邊找鬼音诈。 笑死幻碱,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的细溅。 我是一名探鬼主播褥傍,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼喇聊!你這毒婦竟也來了恍风?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤誓篱,失蹤者是張志新(化名)和其女友劉穎朋贬,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體窜骄,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡兄世,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了啊研。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片御滩。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖党远,靈堂內(nèi)的尸體忽然破棺而出削解,到底是詐尸還是另有隱情,我是刑警寧澤沟娱,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布氛驮,位于F島的核電站,受9級特大地震影響济似,放射性物質(zhì)發(fā)生泄漏矫废。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一砰蠢、第九天 我趴在偏房一處隱蔽的房頂上張望蓖扑。 院中可真熱鬧,春花似錦台舱、人聲如沸律杠。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽柜去。三九已至灰嫉,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嗓奢,已是汗流浹背讼撒。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點兒被人妖公主榨干…… 1. 我叫王不留股耽,地道東北人椿肩。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像豺谈,于是被迫代替她去往敵國和親郑象。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,786評論 2 345

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