Android DecorView 一窺全貌(上)

前言

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

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

DecorView創(chuàng)建過(guò)程

來(lái)回顧一下Activity創(chuàng)建過(guò)程:


image.png

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

簡(jiǎn)單布局分析

相信大家都知道,該方法是將我們布局(layout)文件添加到一個(gè)id為“android.R.id.content”的ViewGroup里,我們只需要關(guān)心layout的內(nèi)容。那么R.id.content是整個(gè)View樹(shù)的根布局嗎?來(lái)看看一個(gè)最簡(jiǎn)單的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

把布局觀察器打開(kāi):


image.png

可以看出,"content"布局對(duì)應(yīng)的是ContentFrameLayout,它并不是View樹(shù)的根布局,根布局是DecorView,DecorView和ContentFrameLayout之間還有幾個(gè)View,接下來(lái)我們就來(lái)了解上圖的這些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,實(shí)際就是將callback再包了一層
        mAppCompatWindowCallback = new AppCompatWindowCallback(callback);
        window.setCallback(mAppCompatWindowCallback);
        //省略
        //使用activity的window
        mWindow = window;
    }

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

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

AppCompatDelegateImpl.java
    @Override
    public void setContentView(int resId) {
        //創(chuàng)建SubDecor,從名字可以猜測(cè)一下是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里找尋的,因此我們重點(diǎn)來(lái)看看ensureSubDecor()方法。
ensureSubDecor里調(diào)用的是createSubDecor()方法來(lái)創(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,沒(méi)有則創(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布局,實(shí)際上找的是DecorView里名為content的布局
        final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
        if (windowContentView != null) {
            //挨個(gè)移除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,實(shí)際上添加為DecorView的子View。該方法后面再具體分析
        mWindow.setContentView(subDecor);
        return subDecor;
    }

該方法較長(zhǎng),省略了一些細(xì)枝末節(jié),主體功能是:

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

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

mWindow.getDecorView()

DecorView創(chuàng)建

mWindow之前分析過(guò)是Activity的window變量,它的實(shí)現(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)請(qǐng)移步:Android各種Context的前世今生

重點(diǎn)來(lái)看看

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; 
        }
        //實(shí)例化上面確定的布局,并將該布局添加到DecorView里
        mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
        //獲取名為ID_ANDROID_CONTENT的子View
        //public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
        //實(shí)際上就是content
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        return contentParent;
    }

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

    void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        //實(shí)例化布局
        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é)來(lái)說(shuō),mWindow.getDecorView()做了哪些事呢?

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

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

1、首先創(chuàng)建DecorView,并添加其子View,該子View在DecorView里稱為:mContentRoot。當(dāng)前場(chǎng)景下使用的子View資源id:R.layout.screen_simple;
2、mContentRoot里有名為"R.id.content"的子View,其在PhoneWindow里稱作為:mContentParent
3、當(dāng)前場(chǎng)景下使用的subDecor資源id:R.layout.abc_screen_toolbar,實(shí)例化該資源文件獲得subDecor實(shí)例
4、該subDecor里有子View資源id:R.id.action_bar_activity_content,在AppCompatDelegateImpl里命名為:contentView
5、DecorView添加subDecor過(guò)程:取出DecorView里的mContentParent,并移除里面的子View,并將移除的子View添加到subDecor里的contentView里。
6、將contentView更名為“R.id.content”
7、最后通過(guò)mWindow.setContentView(subDecor),將subDecor添加到DecorView里

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

PhoneWindow.java
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        if (mContentParent == null) {
            //為空,說(shuō)明DecorView沒(méi)構(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)建過(guò)程。
DecorView創(chuàng)建完成后,再將自定義布局添加到DecorView里名為R.id.content布局里。至此setContentView分析完畢,老規(guī)矩用圖表示整個(gè)流程。

setContentView圖示

image.png

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

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

  • Activity管理著Window,Window管理著DecorView,Activity間接管理著DecorView
  • Activity處在“create"狀態(tài)的時(shí)候創(chuàng)建對(duì)應(yīng)的Window,并在setContentView里創(chuàng)建DecorView,最終添加自定義布局到DecorView里。此時(shí),Activity創(chuàng)建完畢,Window創(chuàng)建完畢,DecorView創(chuàng)建完畢,ViewTree構(gòu)建成功,三者關(guān)系已建立。
  • Acitivity處在“resume"狀態(tài)的時(shí)候,通過(guò)Window的管理類將DecorView添加到Window里,并提交更新DecorView的請(qǐng)求。當(dāng)下次屏幕刷新信號(hào)到來(lái)之時(shí),執(zhí)行ViewTree三大流程(測(cè)量,布局,繪制),最終的效果就是我們的自定義布局顯示在屏幕上。
    “注”:指的狀態(tài)執(zhí)行時(shí)機(jī)是處在ActivityThread里的。

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


image.png

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


image.png

注:這圖只是便于理解抽象關(guān)系,實(shí)際上對(duì)于Activity來(lái)說(shuō)并沒(méi)有尺寸的說(shuō)法。

后續(xù)

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

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

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

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