Android學(xué)習(xí)筆記---深入理解View#01

上次的兩篇文章,我們討論了創(chuàng)建自定義View的基本流程.對(duì)View有了基本的了解后,有好奇心的同學(xué)可能會(huì)對(duì)View的基本原理充滿好奇(其實(shí)我也非常好奇View在Android系統(tǒng)下是怎樣實(shí)現(xiàn)的),所以我就本著好奇心看了很多關(guān)于View的實(shí)現(xiàn)原理和View的基本工作流程的文章,也看了一些源碼,對(duì)View有了更加深入的理解.在這我就跟大家分享一下我對(duì)View的理解,希望能對(duì)大家的學(xué)習(xí)有所幫助.若有什么錯(cuò)誤的地方還希望大家?guī)臀抑赋?

setContentView開始

寫過Android應(yīng)用的都知道,我們最開始接觸View是在布局文件activity_main.xml中.在我們的MainActivityonCreate()方法中,通過setContentView(R.layout.activity_main)設(shè)置我們的Activity布局.View的所有工作都是從這里開始的,為了一探究竟,我們就進(jìn)到setContentView()中去,看看它究竟是怎樣實(shí)現(xiàn)的.下面就是Activity的setContentView的代碼.

    /**
     * Set the activity content from a layout resource.  The resource will be
     * inflated, adding all top-level views to the activity.
     *
     * @param layoutResID Resource ID to be inflated.
     *
     * @see #setContentView(android.view.View)
     * @see #setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
     */
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

注意:我們這里的代碼是Activity的代碼,而不是AppCompatActivity的代碼.AppCompatActivity是Android為使用兼容庫的應(yīng)用所提供的一個(gè)Activity基類,這里為了研究方便,減少其他無關(guān)代碼的干擾,我們使用Activity類進(jìn)行討論.

我們可以看到這里最終調(diào)用的是getWindow().setContentView(layoutID)進(jìn)行布局設(shè)置.

// Window 類下的setContentView()
public abstract void setContentView(View view);

可以看到這是一個(gè)抽象方法,需要由子類來實(shí)現(xiàn).這就需要我們找到Window的實(shí)現(xiàn)類了.我們再來看getWindow().

    /**
     * Retrieve the current {@link android.view.Window} for the activity.
     * This can be used to directly access parts of the Window API that
     * are not available through Activity/Screen.
     *
     * @return Window The current window, or null if the activity is not
     *         visual.
     */
    public Window getWindow() {
        return mWindow;
    }

返回的是Activity的mWindow成員對(duì)象.但我們還是沒找到Window的實(shí)現(xiàn)類啊,先別急,我們在Activity類里搜索mWindow關(guān)鍵字,看看它是在哪里被實(shí)例化的.經(jīng)過搜索后,發(fā)現(xiàn)了在mWindow是在Activity類下的attach()函數(shù)里被實(shí)例化的.下面是相關(guān)的代碼:

    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在這里被實(shí)例化
        mWindow = new PhoneWindow(this);
       ........
    }

由于attach()的代碼比較長,我只貼出了我們關(guān)心的代碼,就是上面那一句.這里很容易就看出了mWindow就是一個(gè)PhoneWindow的一個(gè)實(shí)例.這樣我們就可以查看mWindow中的setContentView()的實(shí)現(xiàn)了.

一切都在PhoneWindow

PhoneWindow并不是Android SDK內(nèi)的類,我們在Android Studio中無法看到其代碼.遇到這種情況肯定要到Internet上搜索一下,果然我們可以到一些在線的Android源碼網(wǎng)站上查看它的源代碼,我們可以找到PhoneWindow的源碼地址.然后經(jīng)過網(wǎng)頁內(nèi)的搜索找到了setContentView(int)的代碼.

    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();// 標(biāo)注1
        } else {
            mContentParent.removeAllViews();
        }
        mLayoutInflater.inflate(layoutResID, mContentParent);// 標(biāo)注2
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

我在代碼中標(biāo)注了兩個(gè)地方,我們先來看標(biāo)注2.這里可以清除的看到Android將我們傳進(jìn)來的xml布局文件進(jìn)行了inflate并將它添加到mContentParent這個(gè)容器中.我們找到mContentParent的聲明,發(fā)現(xiàn)它是一個(gè)ViewGroup.

    // This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;
    
    // 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;

這里的注釋說了,這個(gè)mContentParent是存放窗口內(nèi)容的一個(gè)View,它要不就是mDecor自己,要不就是一個(gè)存放窗口內(nèi)容的mDecorchild.而在這行代碼的上面正好是mDecor的聲明,它是一個(gè)DecorView對(duì)象,是window的頂層view,里面有window decor.
看到這里是不是有點(diǎn)暈?不要緊,先接著看,我馬上讓你的頭腦清晰起來.我們先轉(zhuǎn)到DecorView這個(gè)PhoneWindow的內(nèi)部類的聲明:

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
    ......
}

這里可以看到DecorView繼承了FrameLayout,而我們知道FrameLayout繼承了ViewGroup.所以說這里的DecorView也是一個(gè)ViewGroup對(duì)象,看到這里,上面關(guān)于mContentParentmDecor的注釋也就清楚是什么意思了.mContentParent就是mDecor,或者就是mDecor里的一個(gè)子View,而且里面的內(nèi)容就是頂層窗口的內(nèi)容,就是我們activity_main.xml布局的父布局(可以說是Activity的根布局).
知道了mCOntentParentmDecor是什么之后,我就在想:既然mContentParentmDecor有關(guān)系,那究竟它倆是怎樣聯(lián)系起來的呢? 抱著這個(gè)問題先回到setContentView()的代碼中,為了方便,我在這重新將代碼再貼一遍.

    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();// 標(biāo)注1
        } else {
            mContentParent.removeAllViews();
        }
        mLayoutInflater.inflate(layoutResID, mContentParent);// 標(biāo)注2
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

我們來看標(biāo)注1的地方,這里是當(dāng)mContentParent為空的時(shí)候才會(huì)執(zhí)行,我猜這個(gè)installDecor()函數(shù)是為了對(duì)mDecor進(jìn)行初始化設(shè)置并對(duì)mContentParent進(jìn)行賦值的,這里就是mContentParentmDecor產(chǎn)生聯(lián)系的地方.為了看一下我的猜測是否正確,我們找到installDecor()的代碼.

private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor(); //標(biāo)注1
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
        }
        if (mContentParent == null) { //標(biāo)注2
            mContentParent = generateLayout(mDecor); 
            mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
            if (mTitleView != null) {
                if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
                    View titleContainer = findViewById(com.android.internal.R.id.title_container);
                    if (titleContainer != null) {
                        titleContainer.setVisibility(View.GONE);
                    } else {
                        mTitleView.setVisibility(View.GONE);
                    }
                    if (mContentParent instanceof FrameLayout) {
                        ((FrameLayout)mContentParent).setForeground(null);
                    }
                } else {
                    mTitleView.setText(mTitle);
                }
            } else {
                mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
                if (mActionBar != null) {
                    if (mActionBar.getTitle() == null) {
                        mActionBar.setWindowTitle(mTitle);
                    }
                    final int localFeatures = getLocalFeatures();
                    if ((localFeatures & (1 << FEATURE_PROGRESS)) != 0) {
                        mActionBar.initProgress();
                    }
                    if ((localFeatures & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
                        mActionBar.initIndeterminateProgress();
                    }
                    // Post the panel invalidate for later; avoid application onCreateOptionsMenu
                    // being called in the middle of onCreate or similar.
                    mDecor.post(new Runnable() {
                        public void run() {
                            if (!isDestroyed()) {
                                invalidatePanelMenu(FEATURE_ACTION_BAR);
                            }
                        }
                    });
                }
            }
        }
    }

這里的代碼比較長,但這并不影響我們的閱讀,因?yàn)樵诤瘮?shù)的前面幾行,我看到了關(guān)鍵的部分,這些關(guān)鍵的部分我都在上面的代碼標(biāo)注了起來.我們先來看標(biāo)注1.

if (mDecor == null) {
            mDecor = generateDecor(); //標(biāo)注1
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
}

這里對(duì)mDecor使用了generateDecor()進(jìn)行賦值,可以從函數(shù)名看出這是一個(gè)創(chuàng)建DecorView對(duì)象的函數(shù),我們來看一下是否如此.

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

果然如此,這里直接new了一個(gè)DecorView對(duì)象并返回.但是參數(shù)列表里的-1是什么呢?我們來到DecorView的構(gòu)造函數(shù).

/** The feature ID of the panel, or -1 if this is the application's DecorView */
private final int mFeatureId;

public DecorView(Context context, int featureId) {
            super(context);
            mFeatureId = featureId;
}

可以看到,第二個(gè)構(gòu)造參數(shù)是一個(gè)featureID,而且將此賦值到了mFeatureId.這個(gè)mFeatureId是DecorView的feature ID,-1表示這個(gè)DecorView是applicationDecorView.這里的mFeatureId應(yīng)該是用來標(biāo)注DecorView的類型的,或者是用來設(shè)置窗口的具體樣式的.我也搞不太清楚,在Internet上搜了很久也沒找到答案,如果有同學(xué)知道答案希望能分享一下.雖然在這里遇到了一點(diǎn)問題,但這并不會(huì)對(duì)我們的后續(xù)分析有太大的影響.
我們清楚了generateDecor()是用于初始化mDecor對(duì)象,我們接著分析標(biāo)注1后面的代碼,這里

  • mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
  • mDecor.setIsRootNamespace(true);

兩句代碼可以看得出是對(duì)mDecor進(jìn)行的一些初始化的操作.然后我們來看標(biāo)注2的代碼.

        if (mContentParent == null) { //標(biāo)注2
            mContentParent = generateLayout(mDecor); 
            mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
            if (mTitleView != null) {
                if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
                    View titleContainer = findViewById(com.android.internal.R.id.title_container);
                    if (titleContainer != null) {
                        titleContainer.setVisibility(View.GONE);
                    } else {
                        mTitleView.setVisibility(View.GONE);
                    }
                    if (mContentParent instanceof FrameLayout) {
                        ((FrameLayout)mContentParent).setForeground(null);
                    }
                } else {
                    mTitleView.setText(mTitle);
                }
            }

這里同樣的也調(diào)用了generateLayout(mDecor)函數(shù)為mContentParent賦值,注意這里將初始化好的mDecor作為參數(shù)傳入.按照慣例,轉(zhuǎn)到generateLayout()的代碼.由于代碼有點(diǎn)長,為了方便分析,我就分開幾部分別貼出來.首先我們先看第一部分的代碼:

    protected ViewGroup generateLayout(DecorView decor) {
        // Apply data from current theme.
        // 獲取window的style屬性
        TypedArray a = getWindowStyle();
        if (false) {
            System.out.println("From style:");
            String s = "Attrs:";
            for (int i = 0; i < com.android.internal.R.styleable.Window.length; i++) {
                s = s + " " + Integer.toHexString(com.android.internal.R.styleable.Window[i]) + "="
                        + a.getString(i);
            }
            System.out.println(s);
        }
        mIsFloating = a.getBoolean(com.android.internal.R.styleable.Window_windowIsFloating, false);
        int flagsToUpdate = (FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR)
                & (~getForcedWindowFlags());
        if (mIsFloating) {
            setLayout(WRAP_CONTENT, WRAP_CONTENT);
            setFlags(0, flagsToUpdate);
        } else {
            setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
        }
        if (a.getBoolean(com.android.internal.R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            requestFeature(FEATURE_ACTION_BAR);
        }
        if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionBarOverlay, false)) {
            requestFeature(FEATURE_ACTION_BAR_OVERLAY);
        }
        if (a.getBoolean(com.android.internal.R.styleable.Window_windowActionModeOverlay, false)) {
            requestFeature(FEATURE_ACTION_MODE_OVERLAY);
        }
        if (a.getBoolean(com.android.internal.R.styleable.Window_windowFullscreen, false)) {
            setFlags(FLAG_FULLSCREEN, FLAG_FULLSCREEN&(~getForcedWindowFlags()));
        }
        if (a.getBoolean(com.android.internal.R.styleable.Window_windowShowWallpaper, false)) {
            setFlags(FLAG_SHOW_WALLPAPER, FLAG_SHOW_WALLPAPER&(~getForcedWindowFlags()));
        }
        if (a.getBoolean(com.android.internal.R.styleable.Window_windowEnableSplitTouch,
                getContext().getApplicationInfo().targetSdkVersion
                        >= android.os.Build.VERSION_CODES.HONEYCOMB)) {
            setFlags(FLAG_SPLIT_TOUCH, FLAG_SPLIT_TOUCH&(~getForcedWindowFlags()));
        }
        a.getValue(com.android.internal.R.styleable.Window_windowMinWidthMajor, mMinWidthMajor);
        a.getValue(com.android.internal.R.styleable.Window_windowMinWidthMinor, mMinWidthMinor);
        if (getContext().getApplicationInfo().targetSdkVersion
                < android.os.Build.VERSION_CODES.HONEYCOMB) {
            addFlags(WindowManager.LayoutParams.FLAG_NEEDS_MENU_KEY);
        }
        
        if (mAlwaysReadCloseOnTouchAttr || getContext().getApplicationInfo().targetSdkVersion
                >= android.os.Build.VERSION_CODES.HONEYCOMB) {
            if (a.getBoolean(
                    com.android.internal.R.styleable.Window_windowCloseOnTouchOutside,
                    false)) {
                setCloseOnTouchOutsideIfNotSet(true);
            }
        }
//........
}

第一部分的代碼是通過getWindowStyle()得到Window的主題樣式屬性并進(jìn)行相應(yīng)設(shè)置,就是處理我們在AndroidManifest.xml或在style.xml對(duì)theme進(jìn)行的設(shè)置.我上次在View的構(gòu)造函數(shù)分析時(shí)講到了樣式屬性的獲取,這里的也是一樣的方法.我們可以找到WindowgetWindowStyle()函數(shù)的代碼看看.

    /**
     * Return the {@link android.R.styleable#Window} attributes from this
     * window's theme.
     */
    public final TypedArray getWindowStyle() {
        synchronized (this) {
            if (mWindowStyle == null) {
                mWindowStyle = mContext.obtainStyledAttributes(
                        com.android.internal.R.styleable.Window);
            }
            return mWindowStyle;
        }
    }

果然就是獲取了com.android.internal.R.styleable.Window的資源屬性.相關(guān)的內(nèi)容可以參考Github上的Android文件platform_frameworks_base/core/res/res/values/attrs.xml.
接下來我們來看第二部分的代碼:

....
    WindowManager.LayoutParams params = getAttributes();
        if (!hasSoftInputMode()) {
            params.softInputMode = a.getInt(
                    com.android.internal.R.styleable.Window_windowSoftInputMode,
                    params.softInputMode);
        }
        if (a.getBoolean(com.android.internal.R.styleable.Window_backgroundDimEnabled,
                mIsFloating)) {
            /* All dialogs should have the window dimmed */
            if ((getForcedWindowFlags()&WindowManager.LayoutParams.FLAG_DIM_BEHIND) == 0) {
                params.flags |= WindowManager.LayoutParams.FLAG_DIM_BEHIND;
            }
            params.dimAmount = a.getFloat(
                    android.R.styleable.Window_backgroundDimAmount, 0.5f);
        }
        if (params.windowAnimations == 0) {
            params.windowAnimations = a.getResourceId(
                    com.android.internal.R.styleable.Window_windowAnimationStyle, 0);
        }
        // The rest are only done if this window is not embedded; otherwise,
        // the values are inherited from our container.
        if (getContainer() == null) {
            if (mBackgroundDrawable == null) {
                if (mBackgroundResource == 0) {
                    mBackgroundResource = a.getResourceId(
                            com.android.internal.R.styleable.Window_windowBackground, 0);
                }
                if (mFrameResource == 0) {
                    mFrameResource = a.getResourceId(com.android.internal.R.styleable.Window_windowFrame, 0);
                }
                if (false) {
                    System.out.println("Background: "
                            + Integer.toHexString(mBackgroundResource) + " Frame: "
                            + Integer.toHexString(mFrameResource));
                }
            }
            mTextColor = a.getColor(com.android.internal.R.styleable.Window_textColor, 0xFF000000);
        }
....

第二部分的代碼就是設(shè)置WindowManager.LayoutParams窗口布局參數(shù),然后對(duì)窗口的mBackgroundResourcemFrameResource進(jìn)行賦值.這部分的代碼也是對(duì)window進(jìn)行樣式的設(shè)置.我們接著看第三部分的代碼.

        // Inflate the window decor.
        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) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        com.android.internal.R.attr.dialogTitleIconsDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = com.android.internal.R.layout.screen_title_icons;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
            // System.out.println("Title Icons!");
        } else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
                && (features & (1 << FEATURE_ACTION_BAR)) == 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!");
        } else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) {
            // Special case for a window with a custom title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        com.android.internal.R.attr.dialogCustomTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else {
                layoutResource = com.android.internal.R.layout.screen_custom_title;
            }
            // XXX Remove this once action bar supports these features.
            removeFeature(FEATURE_ACTION_BAR);
        } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
            // If no other features and not embedded, only need a title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        com.android.internal.R.attr.dialogTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
                if ((features & (1 << FEATURE_ACTION_BAR_OVERLAY)) != 0) {
                    layoutResource = com.android.internal.R.layout.screen_action_bar_overlay;
                } else {
                    layoutResource = com.android.internal.R.layout.screen_action_bar;
                }
            } else {
                layoutResource = com.android.internal.R.layout.screen_title;
            }
            // System.out.println("Title!");
        } else {
            // Embedded, so no decoration is needed.
            layoutResource = com.android.internal.R.layout.screen_simple;
            // System.out.println("Simple!");
        }

這部分的代碼定義了layoutResourcefeature兩個(gè)局部變量,這兩個(gè)變量在后面會(huì)有特別的作用.第3部分的代碼就是根據(jù)feature的值來對(duì)layoutResource進(jìn)行賦值.layoutResource的值可能會(huì)是下列值的其中一個(gè):

  • com.android.internal.R.layout.screen_title_icons
  • com.android.internal.R.layout.screen_progress
  • com.android.internal.R.layout.screen_custom_title
  • com.android.internal.R.layout.screen_action_bar_overlay
  • com.android.internal.R.layout.screen_action_bar
  • com.android.internal.R.layout.screen_title
  • com.android.internal.R.layout.screen_simple
  • 或是一個(gè)dialog的布局

這些資源id所對(duì)應(yīng)的布局文件同樣可以在Android的Github網(wǎng)站上找到.可以從這些布局文件的名字看出這些都是window screen的布局.看過這些文件的同學(xué)都會(huì)發(fā)現(xiàn)它們都有一個(gè)共同點(diǎn),那就是它們大多都是一個(gè)LinearLayout的父布局下面有一個(gè)ID為action_mode_bar_stubViewStub和一個(gè)ID為contentFrameLayout,這一點(diǎn)我們后面會(huì)提到.
經(jīng)過分析我們知道了layoutResource代表的是一個(gè)布局,至于這個(gè)布局是誰的布局,我們看了第4部分的代碼就清楚了.

.....
        mDecor.startChanging();
        // 標(biāo)注開始
        View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        // 標(biāo)注結(jié)束
        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);
            }
        }
        // Remaining setup -- of background and title -- that only applies
        // to top-level windows.
        if (getContainer() == null) {
            Drawable drawable = mBackgroundDrawable;
            if (mBackgroundResource != 0) {
                drawable = getContext().getResources().getDrawable(mBackgroundResource);
            }
            mDecor.setWindowBackground(drawable);
            drawable = null;
            if (mFrameResource != 0) {
                drawable = getContext().getResources().getDrawable(mFrameResource);
            }
            mDecor.setWindowFrame(drawable);
            // System.out.println("Text=" + Integer.toHexString(mTextColor) +
            // " Sel=" + Integer.toHexString(mTextSelectedColor) +
            // " Title=" + Integer.toHexString(mTitleColor));
            if (mTitleColor == 0) {
                mTitleColor = mTextColor;
            }
            if (mTitle != null) {
                setTitle(mTitle);
            }
            setTitleColor(mTitleColor);
        }
        mDecor.finishChanging();
        return contentParent;
    }

第4部分的代碼最主要的部分就是我在代碼中用注釋標(biāo)注出來的那3句代碼了.首先第1句代碼將第3部分中得到的layoutResource布局inflate出來,第2句就將inflate出來的View添加到decor(就是傳入的函數(shù)參數(shù)mDecor)中,并設(shè)置寬高為MATCH_PARENT.最后第3句通過findViewById(ID_ANDROID_CONTENT)找到了ID為ID_ANDROID_CONTENT的View并賦值的函數(shù)的返回值contentParent.
這里有兩點(diǎn)需要注意.第一點(diǎn)就是這里的findViewById是在PhoneWindow的父類Window類下的下的方法,它調(diào)用的是getDecorView().findViewById(int),最終它調(diào)用的就是mDecor.findViewById(),也就是說contentParent是在mDecor里的child,而它也是我們的mContentParent.還記得我們的mContentParent中的注釋嗎?

    // 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;

這里我們終于知道了注釋是什么意思了.
第2點(diǎn)需要注意的就是findViewById(int)的參數(shù)ID_ANDROID_CONTENT.這是一個(gè)Window的常量.

    /**
     * The ID that the main layout in the XML layout file should have.
     */
    public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

可以看出,這個(gè)就是我們上面提到的第3部分代碼中layoutResource布局中ID為contentFrameLayout布局,也就是我們的activity_main.xml布局的父布局.generateLayout()剩下的代碼就是為了設(shè)置window的背景和標(biāo)題.
講了這么久,終于把generateLayout()的代碼講完了,我們可以往回走了.先回到installDecor()中.

private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor(); //標(biāo)注1
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
        }
        if (mContentParent == null) { //標(biāo)注2
            mContentParent = generateLayout(mDecor); 
            mTitleView = (TextView)findViewById(com.android.internal.R.id.title);
            if (mTitleView != null) {
                if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) {
                    View titleContainer = findViewById(com.android.internal.R.id.title_container);
                    if (titleContainer != null) {
                        titleContainer.setVisibility(View.GONE);
                    } else {
                        mTitleView.setVisibility(View.GONE);
                    }
                    if (mContentParent instanceof FrameLayout) {
                        ((FrameLayout)mContentParent).setForeground(null);
                    }
                } else {
                    mTitleView.setText(mTitle);
                }
            } else {
                mActionBar = (ActionBarView) findViewById(com.android.internal.R.id.action_bar);
                if (mActionBar != null) {
                    if (mActionBar.getTitle() == null) {
                        mActionBar.setWindowTitle(mTitle);
                    }
                    final int localFeatures = getLocalFeatures();
                    if ((localFeatures & (1 << FEATURE_PROGRESS)) != 0) {
                        mActionBar.initProgress();
                    }
                    if ((localFeatures & (1 << FEATURE_INDETERMINATE_PROGRESS)) != 0) {
                        mActionBar.initIndeterminateProgress();
                    }
                    // Post the panel invalidate for later; avoid application onCreateOptionsMenu
                    // being called in the middle of onCreate or similar.
                    mDecor.post(new Runnable() {
                        public void run() {
                            if (!isDestroyed()) {
                                invalidatePanelMenu(FEATURE_ACTION_BAR);
                            }
                        }
                    });
                }
            }
        }
    }

我們標(biāo)注2的代碼也分析完了,mDecormContentParent就像前面猜測的一樣,它們通過generateLayout()聯(lián)系了在一起.剩下的代碼可以簡單的看出還是一些window的設(shè)置,包括window的titleActionBar.接著我們繼續(xù)往回走,回到setContentView()的地方.

    @Override
    public void setContentView(int layoutResID) {
        if (mContentParent == null) {
            installDecor();// 標(biāo)注1
        } else {
            mContentParent.removeAllViews();
        }
        mLayoutInflater.inflate(layoutResID, mContentParent);// 標(biāo)注2
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }

現(xiàn)在再看setContentView()的代碼就感覺清晰了不少,標(biāo)注1mContentParent初始化,標(biāo)注2就將我們自己的布局文件(activity_main.xml)添加到mContentParent中.到此我們把setContentView()都分析清楚了.

總結(jié)

本篇文章篇幅有點(diǎn)長,但如果能認(rèn)真的看下來我相信一定會(huì)有所收獲的.下面我們來用一張圖來總結(jié)setContentView的關(guān)鍵的地方和基本的流程.


圖中的橫向的箭頭表示了相關(guān)的對(duì)應(yīng)關(guān)系,豎向的箭頭為基本的流程,希望能對(duì)大家有所幫助.

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末窄俏,一起剝皮案震驚了整個(gè)濱河市乖仇,隨后出現(xiàn)的幾起案子婉徘,更是在濱河造成了極大的恐慌奸披,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,378評(píng)論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件羹呵,死亡現(xiàn)場離奇詭異洒沦,居然都是意外死亡速兔,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,356評(píng)論 2 382
  • 文/潘曉璐 我一進(jìn)店門淤齐,熙熙樓的掌柜王于貴愁眉苦臉地迎上來股囊,“玉大人,你說我怎么就攤上這事更啄≈烧睿” “怎么了?”我有些...
    開封第一講書人閱讀 152,702評(píng)論 0 342
  • 文/不壞的土叔 我叫張陵祭务,是天一觀的道長内狗。 經(jīng)常有香客問我,道長待牵,這世上最難降的妖魔是什么其屏? 我笑而不...
    開封第一講書人閱讀 55,259評(píng)論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮缨该,結(jié)果婚禮上偎行,老公的妹妹穿的比我還像新娘。我一直安慰自己贰拿,他們只是感情好蛤袒,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,263評(píng)論 5 371
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著膨更,像睡著了一般妙真。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上荚守,一...
    開封第一講書人閱讀 49,036評(píng)論 1 285
  • 那天珍德,我揣著相機(jī)與錄音,去河邊找鬼矗漾。 笑死锈候,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的敞贡。 我是一名探鬼主播泵琳,決...
    沈念sama閱讀 38,349評(píng)論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了获列?” 一聲冷哼從身側(cè)響起谷市,我...
    開封第一講書人閱讀 36,979評(píng)論 0 259
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎击孩,沒想到半個(gè)月后迫悠,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,469評(píng)論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡溯壶,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,938評(píng)論 2 323
  • 正文 我和宋清朗相戀三年及皂,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片且改。...
    茶點(diǎn)故事閱讀 38,059評(píng)論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡验烧,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出又跛,到底是詐尸還是另有隱情碍拆,我是刑警寧澤,帶...
    沈念sama閱讀 33,703評(píng)論 4 323
  • 正文 年R本政府宣布慨蓝,位于F島的核電站隔嫡,受9級(jí)特大地震影響菠净,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,257評(píng)論 3 307
  • 文/蒙蒙 一咒唆、第九天 我趴在偏房一處隱蔽的房頂上張望喘垂。 院中可真熱鬧鸡挠,春花似錦譬嚣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,262評(píng)論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至阴汇,卻和暖如春数冬,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背搀庶。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評(píng)論 1 262
  • 我被黑心中介騙來泰國打工拐纱, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人哥倔。 一個(gè)月前我還...
    沈念sama閱讀 45,501評(píng)論 2 354
  • 正文 我出身青樓戳玫,卻偏偏與公主長得像,于是被迫代替她去往敵國和親未斑。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,792評(píng)論 2 345

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