浅析Android-setContentView源码分析

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。
本文链接: https://blog.csdn.net/Greathfs/article/details/102595494

前言

我们基本上都是在ActivityonCreate()方法中设置setContentView(),为什么要在onCreate()中设置呢?setContentView()是如何起作用的呢?我们今天就简单来说明下,欢迎吐槽指正

源码解析

首先我们来看下基本写法

 @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }

为什么在onCreate()中写setContentView()

这里可能需要稍微了解下Activity的启动模式,看完之后,我们大概就会对整个Activity启动有所了解,整个流程基本上就是ActivityThread执行完performLaunchActivity后,Activity会回调onAttach方法,然后Instrumentation会执行callActivityOnCreate()方法.然后Activity执行performCreate()方法,然后Activity执行onCreate方法,然后执行performStart()方法.接着就是Instrumentation执行callActivityOnStart()方法,然后就是ActivityonStart()

最开始执行的是onAttach()方法,但是它不能被重写,同时布局要越早加载越好,所以最好是在onCreate()方法中处理,同时官方也有说明

  /**
     * Called when the activity is starting.  This is where most initialization
     * should go: calling {@link #setContentView(int)} to inflate the
     * activity's UI, using {@link #findViewById} to programmatically interact
     * with widgets in the UI, calling
     * 
     * 就是这一小段注释,告诉我们要在这里写setContentView(int)来加载我们自己的布局
     * ...
     */
    @MainThread
    @CallSuper
    protected void onCreate(@Nullable Bundle savedInstanceState) {
       ...
       这里略过源码
       ...
    }

setContentView()是如何起作用的

我们先点开这个方法

public void setContentView(@LayoutRes int layoutResID) {
        this.getDelegate().setContentView(layoutResID);
    }

这个getDelegate()方法返回是一个AppCompatDelegate类,AppCompatDelegateAppCompatActivity的实现,并且兼容任何Activity

继续点开发现有个它有个实现类AppCompatDelegateImpl,这个类里面重写了setContentView方法

 @Override
    public void setContentView(View v) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        contentParent.addView(v);
        mOriginalWindowCallback.onContentChanged();
    }
    @Override
    public void setContentView(View v, ViewGroup.LayoutParams lp) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        contentParent.addView(v, lp);
        mOriginalWindowCallback.onContentChanged();
    }

我们看下ensureSubDecor()

  private void ensureSubDecor() {
        if (!this.mSubDecorInstalled) {
            this.mSubDecor = this.createSubDecor();
            CharSequence title = this.getTitle();
            if (!TextUtils.isEmpty(title)) {
                if (this.mDecorContentParent != null) {
                    this.mDecorContentParent.setWindowTitle(title);
                } else if (this.peekSupportActionBar() != null) {
                    this.peekSupportActionBar().setWindowTitle(title);
                } else if (this.mTitleView != null) {
                    this.mTitleView.setText(title);
                }
            }
			//适配尺寸
            this.applyFixedSizeWindow();
            this.onSubDecorInstalled(this.mSubDecor);
            this.mSubDecorInstalled = true;
            AppCompatDelegateImpl.PanelFeatureState st = this.getPanelState(0, false);
            if (!this.mIsDestroyed && (st == null || st.menu == null)) {
                this.invalidatePanelMenu(108);
            }
        }

    }

主要就是createSubDecor()我们继续往下看,里面代码很多,这里我们就找重要的说说

        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
        if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
            a.recycle();
            throw new IllegalStateException(
                    "You need to use a Theme.AppCompat theme (or descendant) with this activity.");
        }
   if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
            requestWindowFeature(Window.FEATURE_NO_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);
        }
        if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) {
            requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
        }
        if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) {
            requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
        }

通过上面的代码我们发现了在此处进行了大量的requestFeature的调用,也就是说,我们的requestFeature设置其实是在setContentView方法当中就开始了,所以我们设置一些getWindow.requestFeature时必须在setContentView之前的原因了

然后里面会有一行代码mWindow.getDecorView();这个mWindow就是Window,而且PhoneWindowWindow的唯一子类,所以最终就会到PhoneWindowsetContentView方法中

 @Override
 public void setContentView(int layoutResID) {
 
    // 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();
 
     } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
 
         mContentParent.removeAllViews();
 
     }
 
     if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
 
         final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,getContext());
         transitionTo(newScene);
 
     } else {
 
         mLayoutInflater.inflate(layoutResID, mContentParent);
 
     }
 
     mContentParent.requestApplyInsets();
 
     final Callback cb = getCallback();
 
     if (cb != null && !isDestroyed()) {
 
         cb.onContentChanged();
 
     }
 
     mContentParentExplicitlySet = true;
 
 }

这里的mContentParent是一个ViewGroup 第一次肯定为空,执行installDecor()这个方法

 private void installDecor() {
 
     mForceDecorInstall = false;
 
     if (mDecor == null) {
 
         mDecor = generateDecor(-1);
 
         mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
 
         mDecor.setIsRootNamespace(true);
 
         if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
 
             mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
 
         }
 
     } else {
 
         mDecor.setWindow(this);
 
     }
 
     if (mContentParent == null) {
 
         mContentParent = generateLayout(mDecor);
 
        ...
 
         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);
 
                 }
 
             }
 
            ...
 
         } else {
 
             mTitleView = findViewById(R.id.title);
 
             if (mTitleView != null) {
                ...
             }
         }
         ...
     }
 
 }

这里我们只关心重要代码mDecor = generateDecor(-1);mContentParent = generateLayout(mDecor);
第一方法得到的就是顶层的DecorView, 第二个方法得到的是android.R.id.content控件FramLayout

我们来看第一个方法源码:

protected DecorView generateDecor(int featureId) {
    // System process doesn't have application context and in that case we need to directly use
    // the context we have. Otherwise we want the application context, so we don't cling to the
    // activity.
    Context context;
    if (mUseDecorContext) {
        Context applicationContext = getContext().getApplicationContext();
        if (applicationContext == null) {
            context = getContext();
        } else {
            context = new DecorContext(applicationContext, getContext());
            if (mTheme != -1) {
                context.setTheme(mTheme);
            }
        }
    } else {
        context = getContext();
    }
    return new DecorView(context, featureId, this, getAttributes());
}

这里返回的是一个DecorView,我们点开这个DecorView构造方法,发现里面有一个PhoneWindow,也就是说DecorViewPhoneWindow之间具有对应关系,即一个PhoneWindow对应一个DeocrView

接下来我们再看第二个方法generateLayout(mDecor)

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!");
    }

... 省略了部分代码

    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
   ...  省略了部分代码
   
    return contentParent;

这个方法得到的得到的就是android.R.id.content的控件FrameLayout,即mContentParent实质是一个FrameLayout
layoutResource通过DecorViewonResourcesLoaded()方法添加到DecorView自身

void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
        if (mBackdropFrameRenderer != null) {
            loadBackgroundDrawablesIfNeeded();
            mBackdropFrameRenderer.onResourcesLoaded(
                    this, mResizingBackgroundDrawable, mCaptionBackgroundDrawable,
                    mUserCaptionBackgroundDrawable, getCurrentColor(mStatusColorViewState),
                    getCurrentColor(mNavigationColorViewState));
        }

        mDecorCaptionView = createDecorCaptionView(inflater);
        final View root = inflater.inflate(layoutResource, null);
        if (mDecorCaptionView != null) {
            if (mDecorCaptionView.getParent() == null) {
                addView(mDecorCaptionView,
                        new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
            }
            mDecorCaptionView.addView(root,
                    new ViewGroup.MarginLayoutParams(MATCH_PARENT, MATCH_PARENT));
        } else {

            // Put it below the color views.
            addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
        mContentRoot = (ViewGroup) root;
        initializeElevation();
    }

这里我们发现最终都会调用addView方法,这个rootView没有任何父布局依赖,而是直接以addView的方式最终加载到DecorView本身上,DecorView作为FrameLayout成为了rootView的最终父布局。

我们再次回到PhoneWindowsetContentView()方法中


if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
 
         final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
 
                 getContext());
 
         transitionTo(newScene);
 
     } else {
 
         mLayoutInflater.inflate(layoutResID, mContentParent);
 

mLayoutInflater.inflate(layoutResID, mContentParent);这句话等价于inflate(layoutResID, mContentParent, mContentParent != null),因为此时mContentParent!= null,无需执行addView操作,inflate会自动将我们传入的Actvity布局添加在了mContentParent上了,这样setContentView的工作就完成了。

总结

一个Activity启动后,首先实例化PhoneWindow对象,调用setContentView时,首先执行installDecor(),通过generateDecor()实例化一个DecorView对象,将PhoneWindowDecorView进行了关联绑定,通过generateLayout()加载系统布局到DecorView上,并将ID为contentFrameLayout赋值给mContentParent,最后执行inflate()将我们的布局文件自动添加到mContentParent。形成一个一一对应关系,如下图:

猜你喜欢

转载自blog.csdn.net/Greathfs/article/details/102595494