Android视图相关之setContentView()源码分析

日常使用Activity时都会用到setContentView(int layoutId)的方法,今天挖一下相关的源码,方便日后装逼(学会这个好像最直接的作用就是可以装逼)。
(基于Android8.1的源码)

1、setContentView(int layoutId)

public class AppCompatActivity {
    @Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }
    
    -->
    @NonNull
    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }
}


   -->AppCompatDelegate中的create方法
   private static AppCompatDelegate create(Activity activity,  AppCompatCallback callback) {
        return create(activity , activity.getWindow(), callback);
   }

    -->
    /**
    *  AppCompatDelegateImplV9是下面几个AppCompatDelegateImplX 类的父类
    *  setContentView在AppCompatDelegateImplV9中实现
    */
    private static AppCompatDelegate create(Context context, Window window,
            AppCompatCallback callback) {
        if (Build.VERSION.SDK_INT >= 24) {
            return new AppCompatDelegateImplN(context, window, callback);
        } else if (Build.VERSION.SDK_INT >= 23) {
            return new AppCompatDelegateImplV23(context, window, callback);
        } else if (Build.VERSION.SDK_INT >= 14) {
            return new AppCompatDelegateImplV14(context, window, callback);
        } else if (Build.VERSION.SDK_INT >= 11) {
            return new AppCompatDelegateImplV11(context, window, callback);
        } else {
            return new AppCompatDelegateImplV9(context, window, callback);
        }
    }
  • Activity继承了AppCompatActivity, 从AppCompatActivity开始探索;
  • AppCompatActivity中setContentView调用了AppCompatDelegate中的create()方法;
  • create()方法在不同sdk版本中返回的类,都继承自AppCompatDelegateImplV9,而且setContentView()是在AppCompatDelegateImplV9中实现的,就是调用了AppCompatDelegateImplV9类中的setcontentView()的方法。
class AppCompatDelegateImplV9{

  private ViewGroup mSubDecor;
    @Override
    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);//常见的inflate layout
        mOriginalWindowCallback.onContentChanged();//调用onContentChanged()的生命周期方法
    }

    -->
    private void ensureSubDecor() {
        if (!mSubDecorInstalled) {
            mSubDecor = createSubDecor();//初始化mSubDecor
            
            mSubDecorInstalled = true;//标记mSubDecor已经初始化

            ...
        }
    }
}

小结:

  • setContentView首先是调用ensureSubDecor,间接调用createSubDecor来初始化mSubDecor(ViewGroup);
  • 然后初始化id为android.R.id.content的contentParent,这个contentParent为mSubDecor的子ViewGroup;
  • 清空contentParent的所有子view;
  • 调用inflate(resId, contentParent),构建我们的设置的布局,并设置为contentParent的子View。

2、createSubDecor()

createSubDecor方法中会调用mWindow.getDecorView() ;执行了以下操作

  1. 在window对象中新建一个DecorView(FrameLayout)赋值给mDecor
  2. 根据窗口属性配置加载对应layout子布局作为DecorView的子View;
  3. 从这个子view种找到一个id为android:id/content的ViewGroup,赋值给mContentParent
    private ViewGroup createSubDecor() {
        //获取AppCompatTheme主题属性
        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);

        //根据activity设置的window属性,配置activity风格样式
        if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
            requestWindowFeature(Window.FEATURE_NO_TITLE);//无title
        } else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);//设置actionbar
        }
        if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) {
             //ActionBar的Overlay模式,让ActionBar浮动且透明
            requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
        }
        if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) {
            requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
        }
        mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);//悬浮
        a.recycle();

        // Now let's make sure that the Window has installed its decor by retrieving it
        mWindow.getDecorView();//给mWindow创建一个DecorView

        final LayoutInflater inflater = LayoutInflater.from(mContext);
        ViewGroup subDecor = null;//subDecor会作为结果返回出去

        //根据主题属性的标志来决定实现哪个layout,然后赋值给subDecor 
        if (!mWindowNoTitle) {
            if (mIsFloating) {
                // If we're floating, inflate the dialog title decor
                subDecor = (ViewGroup) inflater.inflate(
                        R.layout.abc_dialog_title_material, null);
            } else if (mHasActionBar) {
                // Now inflate the view using the themed context and set it as the content view
                subDecor = (ViewGroup) LayoutInflater.from(themedContext)
                        .inflate(R.layout.abc_screen_toolbar, null);
                ...
            }
        } else {
            if (mOverlayActionMode) {
                subDecor = (ViewGroup) inflater.inflate(
                        R.layout.abc_screen_simple_overlay_action_mode, null);
            } else {
                subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
            }
        }
        ...

        //获取内容contentView,上面的四个layout中都有id为action_bar_activity_content的子view
        final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
                R.id.action_bar_activity_content);

        // 获取PhoneWindow中的content布局对象,这里其实就是mWindow的mContentParent
        final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
        if (windowContentView != null) {
            // There might be Views already added to the Window's content view so we need to
            // migrate them to our content view
            //如果mWindow的contentview已经添加过一些view,把第一个子view放到contentView里面
            while (windowContentView.getChildCount() > 0) {
                final View child = windowContentView.getChildAt(0);
                windowContentView.removeViewAt(0);
                contentView.addView(child);
            }

            // Change our content FrameLayout to use the android.R.id.content id.
            // Useful for fragments.
            //把mWindow中的mContentParent的id修改成View.NO_ID,原来的id是android:id/content
            windowContentView.setId(View.NO_ID);
            contentView.setId(android.R.id.content);
        }

        // Now set the Window's content view with the decor
        mWindow.setContentView(subDecor);//把subDecor设置成mWindow的内容
        ...
        return subDecor;
    }

分析:

  • 首先根据主题属性,设置activity中的window属性,设置activity的样式;
  • 加载对应的布局,赋值给subDecor
  • 找到id为R.id.action_bar_activity_content的contentView,以及id为android.R.id.content的windowContentView;
  • 把windowContentView的id设置为View.NO_ID,把contentView的id设置为android.R.id.content;
  • 执行mWindow.setContentView(subDecor)。

3、mWindow.setContentView(View view)

    @Override
    public void setContentView(View view) {
        setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens. 
        if (mContentParent == null) {
            installDecor();//如果mWindow中的mContentParent 为null,执行installDecor()会初始化mContentParent
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            view.setLayoutParams(params);
            final Scene newScene = new Scene(mContentParent, view);
            transitionTo(newScene);
        } else {
            //把subDecor添加到了window中
            mContentParent.addView(view, params);
        }
        ...
    }

解析:
mWindow.setContentView(subDecor)就是把mSubDecor添加到mWindow的mContentParent布局中。

4、再回到AppCompatDelegateImplV9的setContentView

    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();//清空contentParent布局中的子控件
        LayoutInflater.from(mContext).inflate(resId, contentParent);//把我们设置的layout添加到contentParent中
        mOriginalWindowCallback.onContentChanged();//调用onContentChanged()的生命周期方法
    }

从mSubDecor中获取contentParent的布局,把我们设置的layout添加到contentParent中。

总结:
1、Activity中有一个window(PhoneWindow), 配置主题属性就是经过这个window;
2、Activity视图层级中的根布局是DecorView,这是个FrameLayout。
3、我们设置的布局到DecorView之间还存在三个或者四个布局。

Android视图相关之setContentView()源码分析_第1张图片
Activity视图层级.png

思维图

Android视图相关之setContentView()源码分析_第2张图片
setContentView.png

结论验证

写个demo,布局页面如下




    
Android视图相关之setContentView()源码分析_第3张图片
preview.png

在来看一下视图层级


Android视图相关之setContentView()源码分析_第4张图片
视图层级分析.JPG

你可能感兴趣的:(Android视图相关之setContentView()源码分析)