Android学习笔记---深入理解View#01

上次的两篇文章,我们讨论了创建自定义View的基本流程.对View有了基本的了解后,有好奇心的同学可能会对View的基本原理充满好奇(其实我也非常好奇View在Android系统下是怎样实现的),所以我就本着好奇心看了很多关于View的实现原理和View的基本工作流程的文章,也看了一些源码,对View有了更加深入的理解.在这我就跟大家分享一下我对View的理解,希望能对大家的学习有所帮助.若有什么错误的地方还希望大家帮我指出.

setContentView开始

写过Android应用的都知道,我们最开始接触View是在布局文件activity_main.xml中.在我们的MainActivityonCreate()方法中,通过setContentView(R.layout.activity_main)设置我们的Activity布局.View的所有工作都是从这里开始的,为了一探究竟,我们就进到setContentView()中去,看看它究竟是怎样实现的.下面就是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为使用兼容库的应用所提供的一个Activity基类,这里为了研究方便,减少其他无关代码的干扰,我们使用Activity类进行讨论.

我们可以看到这里最终调用的是getWindow().setContentView(layoutID)进行布局设置.

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

可以看到这是一个抽象方法,需要由子类来实现.这就需要我们找到Window的实现类了.我们再来看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成员对象.但我们还是没找到Window的实现类啊,先别急,我们在Activity类里搜索mWindow关键字,看看它是在哪里被实例化的.经过搜索后,发现了在mWindow是在Activity类下的attach()函数里被实例化的.下面是相关的代码:

    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在这里被实例化
        mWindow = new PhoneWindow(this);
       ........
    }

由于attach()的代码比较长,我只贴出了我们关心的代码,就是上面那一句.这里很容易就看出了mWindow就是一个PhoneWindow的一个实例.这样我们就可以查看mWindow中的setContentView()的实现了.

一切都在PhoneWindow

PhoneWindow并不是Android SDK内的类,我们在Android Studio中无法看到其代码.遇到这种情况肯定要到Internet上搜索一下,果然我们可以到一些在线的Android源码网站上查看它的源代码,我们可以找到PhoneWindow的源码地址.然后经过网页内的搜索找到了setContentView(int)的代码.

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

我在代码中标注了两个地方,我们先来看标注2.这里可以清除的看到Android将我们传进来的xml布局文件进行了inflate并将它添加到mContentParent这个容器中.我们找到mContentParent的声明,发现它是一个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;

这里的注释说了,这个mContentParent是存放窗口内容的一个View,它要不就是mDecor自己,要不就是一个存放窗口内容的mDecorchild.而在这行代码的上面正好是mDecor的声明,它是一个DecorView对象,是window的顶层view,里面有window decor.
看到这里是不是有点晕?不要紧,先接着看,我马上让你的头脑清晰起来.我们先转到DecorView这个PhoneWindow的内部类的声明:

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

这里可以看到DecorView继承了FrameLayout,而我们知道FrameLayout继承了ViewGroup.所以说这里的DecorView也是一个ViewGroup对象,看到这里,上面关于mContentParentmDecor的注释也就清楚是什么意思了.mContentParent就是mDecor,或者就是mDecor里的一个子View,而且里面的内容就是顶层窗口的内容,就是我们activity_main.xml布局的父布局(可以说是Activity的根布局).
知道了mCOntentParentmDecor是什么之后,我就在想:既然mContentParentmDecor有关系,那究竟它俩是怎样联系起来的呢? 抱着这个问题先回到setContentView()的代码中,为了方便,我在这重新将代码再贴一遍.

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

我们来看标注1的地方,这里是当mContentParent为空的时候才会执行,我猜这个installDecor()函数是为了对mDecor进行初始化设置并对mContentParent进行赋值的,这里就是mContentParentmDecor产生联系的地方.为了看一下我的猜测是否正确,我们找到installDecor()的代码.

private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor(); //标注1
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
        }
        if (mContentParent == null) { //标注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);
                            }
                        }
                    });
                }
            }
        }
    }

这里的代码比较长,但这并不影响我们的阅读,因为在函数的前面几行,我看到了关键的部分,这些关键的部分我都在上面的代码标注了起来.我们先来看标注1.

if (mDecor == null) {
            mDecor = generateDecor(); //标注1
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
}

这里对mDecor使用了generateDecor()进行赋值,可以从函数名看出这是一个创建DecorView对象的函数,我们来看一下是否如此.

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

果然如此,这里直接new了一个DecorView对象并返回.但是参数列表里的-1是什么呢?我们来到DecorView的构造函数.

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

可以看到,第二个构造参数是一个featureID,而且将此赋值到了mFeatureId.这个mFeatureId是DecorView的feature ID,-1表示这个DecorView是applicationDecorView.这里的mFeatureId应该是用来标注DecorView的类型的,或者是用来设置窗口的具体样式的.我也搞不太清楚,在Internet上搜了很久也没找到答案,如果有同学知道答案希望能分享一下.虽然在这里遇到了一点问题,但这并不会对我们的后续分析有太大的影响.
我们清楚了generateDecor()是用于初始化mDecor对象,我们接着分析标注1后面的代码,这里

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

两句代码可以看得出是对mDecor进行的一些初始化的操作.然后我们来看标注2的代码.

        if (mContentParent == null) { //标注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);
                }
            }

这里同样的也调用了generateLayout(mDecor)函数为mContentParent赋值,注意这里将初始化好的mDecor作为参数传入.按照惯例,转到generateLayout()的代码.由于代码有点长,为了方便分析,我就分开几部分别贴出来.首先我们先看第一部分的代码:

    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的主题样式属性并进行相应设置,就是处理我们在AndroidManifest.xml或在style.xml对theme进行的设置.我上次在View的构造函数分析时讲到了样式属性的获取,这里的也是一样的方法.我们可以找到WindowgetWindowStyle()函数的代码看看.

    /**
     * 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的资源属性.相关的内容可以参考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);
        }
....

第二部分的代码就是设置WindowManager.LayoutParams窗口布局参数,然后对窗口的mBackgroundResourcemFrameResource进行赋值.这部分的代码也是对window进行样式的设置.我们接着看第三部分的代码.

        // 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两个局部变量,这两个变量在后面会有特别的作用.第3部分的代码就是根据feature的值来对layoutResource进行赋值.layoutResource的值可能会是下列值的其中一个:

  • 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
  • 或是一个dialog的布局

这些资源id所对应的布局文件同样可以在Android的Github网站上找到.可以从这些布局文件的名字看出这些都是window screen的布局.看过这些文件的同学都会发现它们都有一个共同点,那就是它们大多都是一个LinearLayout的父布局下面有一个ID为action_mode_bar_stubViewStub和一个ID为contentFrameLayout,这一点我们后面会提到.
经过分析我们知道了layoutResource代表的是一个布局,至于这个布局是谁的布局,我们看了第4部分的代码就清楚了.

.....
        mDecor.startChanging();
        // 标注开始
        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);
            }
        }
        // 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部分的代码最主要的部分就是我在代码中用注释标注出来的那3句代码了.首先第1句代码将第3部分中得到的layoutResource布局inflate出来,第2句就将inflate出来的View添加到decor(就是传入的函数参数mDecor)中,并设置宽高为MATCH_PARENT.最后第3句通过findViewById(ID_ANDROID_CONTENT)找到了ID为ID_ANDROID_CONTENT的View并赋值的函数的返回值contentParent.
这里有两点需要注意.第一点就是这里的findViewById是在PhoneWindow的父类Window类下的下的方法,它调用的是getDecorView().findViewById(int),最终它调用的就是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点需要注意的就是findViewById(int)的参数ID_ANDROID_CONTENT.这是一个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;

可以看出,这个就是我们上面提到的第3部分代码中layoutResource布局中ID为contentFrameLayout布局,也就是我们的activity_main.xml布局的父布局.generateLayout()剩下的代码就是为了设置window的背景和标题.
讲了这么久,终于把generateLayout()的代码讲完了,我们可以往回走了.先回到installDecor()中.

private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor(); //标注1
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
        }
        if (mContentParent == null) { //标注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);
                            }
                        }
                    });
                }
            }
        }
    }

我们标注2的代码也分析完了,mDecormContentParent就像前面猜测的一样,它们通过generateLayout()联系了在一起.剩下的代码可以简单的看出还是一些window的设置,包括window的titleActionBar.接着我们继续往回走,回到setContentView()的地方.

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

现在再看setContentView()的代码就感觉清晰了不少,标注1mContentParent初始化,标注2就将我们自己的布局文件(activity_main.xml)添加到mContentParent中.到此我们把setContentView()都分析清楚了.

总结

本篇文章篇幅有点长,但如果能认真的看下来我相信一定会有所收获的.下面我们来用一张图来总结setContentView的关键的地方和基本的流程.


图中的横向的箭头表示了相关的对应关系,竖向的箭头为基本的流程,希望能对大家有所帮助.

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,233评论 4 360
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,013评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,030评论 0 241
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,827评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,221评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,542评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,814评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,513评论 0 198
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,225评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,497评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 31,998评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,342评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,986评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,055评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,812评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,560评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,461评论 2 266

推荐阅读更多精彩内容