美文网首页
Android setContentView()源码分析

Android setContentView()源码分析

作者: 徘徊0_ | 来源:发表于2018-10-15 16:27 被阅读0次

    在Activity中,我们都会使用该方法设置自己需要的xml布局,比如:

    @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(getLayoutId());
        }
    

    下面分析一下,自己写的布局,是怎么通过setContentView()来生效的。

    首先放一张Android布局图:


    Android窗口布局.png

    其实我们平常写的layout布局,是加载到上图红色的框(ContentView)中。

    • Window是一个抽象类,定义了一些规范(API),其中就有setContentView方法
    public abstract class Window{
      ...
     /**
         * Convenience for
         * {@link #setContentView(View, android.view.ViewGroup.LayoutParams)}
         * to set the screen content from a layout resource.  The resource will be
         * inflated, adding all top-level views to the screen.
         *
         * @param layoutResID Resource ID to be inflated.
         * @see #setContentView(View, android.view.ViewGroup.LayoutParams)
         */
        public abstract void setContentView(@LayoutRes int layoutResID);
    
      ...
    }
    
    • PhoneWindow 继承自 Window,属于window的具体实现类。
    • DecorView 继承自 FrameLayout ,是所有应用窗口的根View。其中包含一个显示标题的TitleView,和一个包含内容的ContentView
    public class DecorView extends FrameLayout
    
    源码分析

    1,首先从我们经常设置布局的setContentView()分析:

    @Override
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(getLayoutId());
        }
    

    这里的setContentView最终调用的是ActivitysetContentView
    2,Activity.java 中的setContentView

    public void setContentView(@LayoutRes int layoutResID) {
            //这里的getWindow() , 就是拿到了mWindow 对象
            //并调用了window的setContentView方法
            getWindow().setContentView(layoutResID);
            initWindowDecorActionBar();
        }
    

    在Activity中,mWindow 是由下面的的代码创建:

    final void attach(Context context, ActivityThread aThread,
            ...
            //在这里创建了PhoneWindow,
            mWindow = new PhoneWindow(this, window, activityConfigCallback);
            mWindow.setWindowControllerCallback(this);
            mWindow.setCallback(this);
            mWindow.setOnWindowDismissedCallback(this);
            mWindow.getLayoutInflater().setPrivateFactory(this);
            ...
    

    因为PhoneView是Window的具体实现类,所以其实调用的是PhoneView中的setContentView

    3,PhoneWindow.java 中的 setContentView方法:

    @Override
        public void setContentView(int layoutResID) {
            // mContentParent,也就是我们设置Layout的父布局
            if (mContentParent == null) {
                installDecor();//注意该方法
            } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
                mContentParent.removeAllViews();
            }
    
            if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
                final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                        getContext());
                transitionTo(newScene);
            } else {
                //在这里进行布局初始化,mContentParent 是我们传入布局的父布局!
                mLayoutInflater.inflate(layoutResID, mContentParent);
            }
            mContentParent.requestApplyInsets();
            final Callback cb = getCallback();
            if (cb != null && !isDestroyed()) {
                cb.onContentChanged();
            }
            mContentParentExplicitlySet = true;
        }
    

    跟到 installDecor()方法(注意:该方法其实就是在mContentParent == null的时候,调用,也就是为了创建mContentParent):
    注意: generateLayout(mDecor);

    private void installDecor() {
            .......
            if (mContentParent == null) {
                //注意这个方法
                mContentParent = generateLayout(mDecor);
    
                mDecor.makeOptionalFitsSystemWindows();
    
                final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById(
                        R.id.decor_content_parent);
    
                if (decorContentParent != null) {
                    mDecorContentParent = decorContentParent;
                    mDecorContentParent.setWindowCallback(getCallback());
                    if (mDecorContentParent.getTitle() == null) {
                        mDecorContentParent.setWindowTitle(mTitle);
                    }
    
                    final int localFeatures = getLocalFeatures();
                    for (int i = 0; i < FEATURE_MAX; i++) {
                        if ((localFeatures & (1 << i)) != 0) {
                            mDecorContentParent.initFeature(i);
                        }
                    }
    
                    mDecorContentParent.setUiOptions(mUiOptions);
            
           .............         
        }
    

    跟到generateLayout()方法中:

    protected ViewGroup generateLayout(DecorView decor) {
        ....
      int layoutResource;
            int features = getLocalFeatures();
            // System.out.println("Features: 0x" + Integer.toHexString(features));
            if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
                layoutResource = R.layout.screen_swipe_dismiss;
                setCloseOnSwipeEnabled(true);
            } else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) {
                if (mIsFloating) {
                    TypedValue res = new TypedValue();
                    getContext().getTheme().resolveAttribute(
                            R.attr.dialogTitleIconsDecorLayout, res, true);
                    layoutResource = res.resourceId;
                } else {
                    layoutResource = 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 = 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(
                            R.attr.dialogCustomTitleDecorLayout, res, true);
                    layoutResource = res.resourceId;
                } else {
                    layoutResource = 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(
                            R.attr.dialogTitleDecorLayout, res, true);
                    layoutResource = res.resourceId;
                } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
                    layoutResource = a.getResourceId(
                            R.styleable.Window_windowActionBarFullscreenDecorLayout,
                            R.layout.screen_action_bar);
                } else {
                    layoutResource = R.layout.screen_title;
                }
                // System.out.println("Title!");
            } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
                layoutResource = R.layout.screen_simple_overlay_action_mode;
            } else {
                // Embedded, so no decoration is needed.
                layoutResource = R.layout.screen_simple;
                // System.out.println("Simple!");
            }
    
            mDecor.startChanging();
            mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    
            //ID_ANDROID_CONTENT 为:com.android.internal.R.id.content;
            ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    ......
    }
    
    

    在这个方法中,生成了contentParent

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

    这里的R.id.content也就是上面generateLayoutlayoutResource布局文件中的id,例如打开R.layout.screen_simple可以看到:

    R.layout.screen_simple.png
    所以,其实我们写的布局,最后是添加到了id为contentFrameLayout中。

    总结:

    • Activity 包含了一个PhoneWindow
    • PhoneWindow 继承自 Window
    • Activity 通过 setContentView 将布局文件设置到了PhoneWindow上
    • PhoneWindow 中又包含了 DecorView ,也就是布局最终添加到了 DecorView 上。

    相关文章

      网友评论

          本文标题:Android setContentView()源码分析

          本文链接:https://www.haomeiwen.com/subject/iwpfzftx.html