一般人比较少去研究android源码,但是阅读源码不仅能够提高个人代码水平,还能锻炼自己的逻辑思维能力,对于常见的代码,我们必须要知道原理是怎么回事,不一定要能写出来,毕竟这个是谷歌那么多人弄出来的,网上也有很多关于setContentView的源码分析,但是很多都是activity的基础上分析,现在我们android开发基本都是使用AppCompatActivity,所以今天就一起分析其中的过程,为什么setContentView就能显示页面。

          首先看下我们经常使用setContentView的代码。

protected voidonCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
 setContentView(setLayout());
 }



我们先来看下 AppCompatActivity 中 setContentView 中做了什么

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

@Override
public void setContentView(View view) {
    getDelegate().setContentView(view);
}

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

这里有三个重载的方法,明显看到了使用的是代理模式,(代理模式其实就是,实现代理的对象能够访问对象本身的资源),

getDelegate()

这个代理方法是干嘛用的呢,到底给我们返回什么东西。进去看下

/**
 * @return The {@link AppCompatDelegate} being used by this Activity.
 */
@NonNull
public AppCompatDelegate getDelegate() {
    if (mDelegate == null) {
        mDelegate = AppCompatDelegate.create(this, this);
    }
    return mDelegate;
}

字面意思,创建代理对象,并且返回代理对象。那么这个代理对象是如何创立的?

AppCompatDelegate.create(this, this);

这个方法,传入一个上下文,还有一个接口回调

public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
    return create(activity, activity.getWindow(), callback);
}

继续走。。。

private static AppCompatDelegate create(Context context, Window window,
        AppCompatCallback callback) {
    final int sdk = Build.VERSION.SDK_INT;
    if (BuildCompat.isAtLeastN()) {
        return new AppCompatDelegateImplN(context, window, callback);
    } else if (sdk >= 23) {
        return new AppCompatDelegateImplV23(context, window, callback);
    } else if (sdk >= 14) {
        return new AppCompatDelegateImplV14(context, window, callback);
    } else if (sdk >= 11) {
        return new AppCompatDelegateImplV11(context, window, callback);
    } else {
        return new AppCompatDelegateImplV9(context, window, callback);
    }
}

原来这里是根据sdk版本返回不同的代理对象。

我们知道返回的对象最后是调用了

getDelegate().setContentView(view);

那我们肯定能够确认,这些对象的类里边肯定是有setContentView方法,我们找找。

class AppCompatDelegateImplN extends AppCompatDelegateImplV23

这里面没有,继续找,最终我们在他们的父类V9里面找到了这个方法。

@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(int resId) {
    ensureSubDecor();
    ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
    contentParent.removeAllViews();
    LayoutInflater.from(mContext).inflate(resId, contentParent);
    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(),然后根据id找到contentparent,先移除里面的内容,然后再添加我们的布局进去,然后通知界面发生改变。

接下来重点看下这个,你到底确认了什么东西

ensureSubDecor(); mSubDecor  这个对象是什么?

接下来我们看他的代码

private void ensureSubDecor() {
    if (!mSubDecorInstalled) {
//原来在这里创建啊
        mSubDecor = createSubDecor();

        // If a title was set before we installed the decor, propagate it now
        CharSequence title = getTitle();
        if (!TextUtils.isEmpty(title)) {
            onTitleChanged(title);
        }

        applyFixedSizeWindow();

        onSubDecorInstalled(mSubDecor);
	//标记位,判断到底创建了没,避免重复创建
        mSubDecorInstalled = true;

        // Invalidate if the panel menu hasn't been created before this.
        // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
        // being called in the middle of onCreate or similar.
        // A pending invalidation will typically be resolved before the posted message
        // would run normally in order to satisfy instance state restoration.
        PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
        if (!isDestroyed() && (st == null || st.menu == null)) {
            invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
        }
    }
}

原来是这里创建了一个subdector,这个玩意还是个viewgroup,以为后面需要这个强转成viewgroup并且找id


private ViewGroup createSubDecor() {
    TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);

    if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
        a.recycle();
//这里有个异常,记得么,当我们使用ativity集成appcompactActivity的时候,主题不能随便用的,必须使用appCompact的主题,
否则会报错
        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);
    }
    mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);
    a.recycle();
//前面都是些设置主题相关的,到了下面又是一个非常关键的地方,我们的窗口对象获取了一个什么view
// Now let's make sure that the Window has installed its decor by retrieving it
    mWindow.getDecorView();

    final LayoutInflater inflater = LayoutInflater.from(mContext);
    ViewGroup subDecor = null;


    if (!mWindowNoTitle) {
//上面的已经说了,默认的是没有title的,所以下面的方法不会走
        if (mIsFloating) {
            // If we're floating, inflate the dialog title decor
            subDecor = (ViewGroup) inflater.inflate(
                    R.layout.abc_dialog_title_material, null);

            // Floating windows can never have an action bar, reset the flags
            mHasActionBar = mOverlayActionBar = false;
        } else if (mHasActionBar) {
            /**
             * This needs some explanation. As we can not use the android:theme attribute
             * pre-L, we emulate it by manually creating a LayoutInflater using a
             * ContextThemeWrapper pointing to actionBarTheme.
             */
            TypedValue outValue = new TypedValue();
            mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true);

            Context themedContext;
            if (outValue.resourceId != 0) {
                themedContext = new ContextThemeWrapper(mContext, outValue.resourceId);
            } else {
                themedContext = mContext;
            }

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

            mDecorContentParent = (DecorContentParent) subDecor
                    .findViewById(R.id.decor_content_parent);
            mDecorContentParent.setWindowCallback(getWindowCallback());

            /**
             * Propagate features to DecorContentParent
             */
            if (mOverlayActionBar) {
                mDecorContentParent.initFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
            }
            if (mFeatureProgress) {
                mDecorContentParent.initFeature(Window.FEATURE_PROGRESS);
            }
            if (mFeatureIndeterminateProgress) {
                mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
            }
        }
    } else {
//一般都是走这里的,原来subdector就是一个布局文件,这个布局还是个viewgroup,并且根据这个group还能找id 

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

        if (Build.VERSION.SDK_INT >= 21) {
            // If we're running on L or above, we can rely on ViewCompat's
            // setOnApplyWindowInsetsListener
            ViewCompat.setOnApplyWindowInsetsListener(subDecor,
                    new OnApplyWindowInsetsListener() {
                        @Override
                        public WindowInsetsCompat onApplyWindowInsets(View v,
                                WindowInsetsCompat insets) {
                            final int top = insets.getSystemWindowInsetTop();
                            final int newTop = updateStatusGuard(top);

                            if (top != newTop) {
                                insets = insets.replaceSystemWindowInsets(
                                        insets.getSystemWindowInsetLeft(),
                                        newTop,
                                        insets.getSystemWindowInsetRight(),
                                        insets.getSystemWindowInsetBottom());
                            }

                            // Now apply the insets on our view
                            return ViewCompat.onApplyWindowInsets(v, insets);
                        }
                    });
        } else {
            // Else, we need to use our own FitWindowsViewGroup handling
            ((FitWindowsViewGroup) subDecor).setOnFitSystemWindowsListener(
                    new FitWindowsViewGroup.OnFitSystemWindowsListener() {
                        @Override
                        public void onFitSystemWindows(Rect insets) {
                            insets.top = updateStatusGuard(insets.top);
                        }
                    });
        }
    }

    if (subDecor == null) {
        throw new IllegalArgumentException(
                "AppCompat does not support the current theme features: { "
                        + "windowActionBar: " + mHasActionBar
                        + ", windowActionBarOverlay: "+ mOverlayActionBar
                        + ", android:windowIsFloating: " + mIsFloating
                        + ", windowActionModeOverlay: " + mOverlayActionMode
                        + ", windowNoTitle: " + mWindowNoTitle
                        + " }");
    }

    if (mDecorContentParent == null) {
        mTitleView = (TextView) subDecor.findViewById(R.id.title);
    }

    // Make the decor optionally fit system windows, like the window's decor
    ViewUtils.makeOptionalFitsSystemWindows(subDecor);

    final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
            R.id.action_bar_activity_content);

    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
        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.
        windowContentView.setId(View.NO_ID);
        contentView.setId(android.R.id.content);

        // The decorContent may have a foreground drawable set (windowContentOverlay).
        // Remove this as we handle it ourselves
        if (windowContentView instanceof FrameLayout) {
            ((FrameLayout) windowContentView).setForeground(null);
        }
    }

    // Now set the Window's content view with the decor
//这里将subdector放入window里面
    mWindow.setContentView(subDecor);

    contentView.setAttachListener(new ContentFrameLayout.OnAttachListener() {
        @Override
        public void onAttachedFromWindow() {}

        @Override
        public void onDetachedFromWindow() {
            dismissPopups();
        }
    });

    return subDecor;
}



这段代码的核心思想就是加载布局,获取subDector,然后把这个view放入到window中

大家还记得下面这段代码么,这是设置去掉标题栏,全屏的方法,而且这个还必须设置到setcontengview前面,就是在这里,只有设置了没有标题,在这里才会判断有没有,否则的话,就是默认有标题栏的,就会加载布局,后设置了肯定是无效的

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    requestWindowFeature(Window.FEATURE_NO_TITLE);
    getWindow().setFlags(WindowManager.LayoutParams.FILL_PARENT
            , WindowManager.LayoutParams.FILL_PARENT);
    setContentView(R.layout.activity_test);
}



接下来看下我们得到subDector的布局到底是什么样子的

<android.support.v7.internal.widget.FitWindowsLinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/action_bar_root"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
android:fitsSystemWindows="true">

<android.support.v7.internal.widget.ViewStubCompat
    android:id="@+id/action_mode_bar_stub"
    android:inflatedId="@+id/action_mode_bar"
    android:layout="@layout/abc_action_mode_bar"
    android:layout_width="match_parent"
    android:layout_height="wrap_content" />

<include layout="@layout/abc_screen_content_include" />

</android.support.v7.internal.widget.FitWindowsLinearLayout>




abc_screen_content_include.xml


<merge xmlns:android="http://schemas.android.com/apk/res/android">

<android.support.v7.internal.widget.ContentFrameLayout
    android:id="@id/action_bar_activity_content"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:foregroundGravity="fill_horizontal|top"
    android:foreground="?android:attr/windowContentOverlay" />

</merge>



原来,这个布局就是个FitWindowsLinearLayout,用一张网上的图片,整个布局是这样子的

android cron解析 android setcontentview 解析_setContentView

接下来我们继续看

mWindow.getDecorView();


mWindow.setContentView(subDecor);

这两个方法是干啥的?

点进去发现


getDecorView()

是一个抽象方法,这个方法是在Window这个类里边,因此我们必须找到这个类的实现类,到了这里,我也不是很明白怎么回事,因为找了半天没找到他的实现类,万能的百度出场了,百度下发现这个类的实现类是phonewindow,好了,其他的不追了,我们看下phonewindow这个方法都干了些啥


public class PhoneWindow extends Window
        implements MenuBuilder.Callback {
    @Override
    public final View getDecorView() {
        if (mDecor == null || mForceDecorInstall) {
            installDecor();
        }
        return mDecor;
    }
    private void installDecor() {
        //mDecor是DecorView,第一次mDecor=null,所以调用generateDecor
        if (mDecor == null) {
            mDecor = generateDecor();
           ...
        }
        //第一次mContentParent也等于null
        if (mContentParent == null) {
            //可以看到把DecorView传入进去了
            mContentParent = generateLayout(mDecor);
        }

    }

}



在generateDecor()做了什么?其实返回了一个DecorView对象。


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().getResources());
            if (mTheme != -1) {
                context.setTheme(mTheme);
            }
        }
    } else {
        context = getContext();
    }
    return new DecorView(context, featureId, this, getAttributes());
}


DecorView是啥呢?


publicclassDecorView extends FrameLayoutimplements RootViewSurfaceTaker,WindowCallbacks

原来继承FrameLayout

protected ViewGroup generateLayout(DecorView decor) {
    TypedArray a = getWindowStyle();
    //设置一堆标志位...
    ...
    if (!mForcedStatusBarColor) {
        //获取主题状态栏的颜色
        mStatusBarColor = a.getColor(R.styleable.Window_statusBarColor, 0xFF000000);
    }
    if (!mForcedNavigationBarColor) {
        //获取底部NavigationBar颜色
        mNavigationBarColor = a.getColor(R.styleable.Window_navigationBarColor, 0xFF000000);
    }

    //获取主题一些资源
   ...

    // Inflate the window decor.

    int layoutResource;
    int features = getLocalFeatures();
    // System.out.println("Features: 0x" + Integer.toHexString(features));
    if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
        ...我们设置不同的主题以及样式,会采用不同的布局文件...
    } else {
        //记住这个布局,之后我们会来验证下布局的结构
        layoutResource = R.layout.screen_simple;
        // System.out.println("Simple!");
    }
    //要开始更改mDecor啦~
    mDecor.startChanging();
    //注意,此时把screen_simple放到了DecorView里面
    mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
    //这里的ID_ANDROID_CONTENT就是R.id.content;
    ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
    if (contentParent == null) {
        throw new RuntimeException("Window couldn't find content container view");
    }

    ...


    //这里的getContainer()返回的是个Window类,也就是父Window,一般为空
    if (getContainer() == null) {
        final Drawable background;
        if (mBackgroundResource != 0) {
            background = getContext().getDrawable(mBackgroundResource);
        } else {
            background = mBackgroundDrawable;
        }
        //设置背景
        mDecor.setWindowBackground(background);

        final Drawable frame;
        if (mFrameResource != 0) {
            frame = getContext().getDrawable(mFrameResource);
        } else {
            frame = null;
        }
        mDecor.setWindowFrame(frame);

        mDecor.setElevation(mElevation);
        mDecor.setClipToOutline(mClipToOutline);

        if (mTitle != null) {
            setTitle(mTitle);
        }

        if (mTitleColor == 0) {
            mTitleColor = mTextColor;
        }
        setTitleColor(mTitleColor);
    }

    mDecor.finishChanging();

    return contentParent;
}

可以看到根据不同主题属性使用的不同的布局,然后返回了这个布局 contentParent 。

我们来看看这个screen_simple.xml布局是什么样子的

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:layout_width="match_parent"
              android:layout_height="match_parent"
              android:fitsSystemWindows="true"
              android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
          android:inflatedId="@+id/action_mode_bar"
          android:layout="@layout/action_mode_bar"
          android:layout_width="match_parent"
          android:layout_height="wrap_content" />
<FrameLayout
    android:id="@android:id/content"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:foregroundInsidePadding="false"
    android:foregroundGravity="fill_horizontal|top"
    android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

咦,这个布局结构跟 subDecor 好相似啊。。

好了,到目前为止我们知道了,当我们调用 mWindow.getDecorView(); 的时候里面创建DecorView,然后又根据不同主题属性添加不同布局放到DecorView下,然后找到这个布局的 R.id.content ,也就是 mContentParent 。ok,搞清楚 mWindow.getDecorView(); 之后,我们在来看看 mWindow.setContentView(subDecor); (注意:此时把subDecor传入进去)

@Override
public void setContentView(View view, ViewGroup.LayoutParams params) {
    //在mWindow.getDecorView()已经创建了mContentParent
    if (mContentParent == null) {
        installDecor();
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }
    //是否有transitions动画。没有,进入else
    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        view.setLayoutParams(params);
        final Scene newScene = new Scene(mContentParent, view);
        transitionTo(newScene);
    } else {
        //重要!!将这个subDecor也就是FitWindowsLinearLayout添加到这个mContentParent里面了
        //mContentParent是FrameLayout,在之前设置的View.NO_ID
        mContentParent.addView(view, params);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

当调用了 mWindow.getDecorView(); 创建了DecorView以及 mContentParent ,并且把 subDecor 放到了 mContentParent 里面。我们再来回头看看 AppCompatDelegateImplV9 ,还记得它吗?当我们在 AppCompatActivity 的 setContentView 的时候会去调用 AppCompatDelegateImplV9 的 setContentView

AppCompatDelegateImplV9.java

@Override
public void setContentView(View v) {
    //此时DecorView和subDecor都创建好了
    ensureSubDecor();
    //还记得调用createSubDecor的时候把原本是R.id.content的windowContentView设置成了NO_ID
    //并且将contentView也就是ContentFrameLayout设置成了R.id.content吗?
    //也就是说此时的contentParent就是ContentFrameLayout
    ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
    contentParent.removeAllViews();
    //将我的布局放到contentParent里面
    contentParent.addView(v);
    mOriginalWindowCallback.onContentChanged();
}

@Override
public void setContentView(int resId) {
    ensureSubDecor();
    ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
    contentParent.removeAllViews();
    //将我们的布局id映射成View并且放到contentParent下
    LayoutInflater.from(mContext).inflate(resId, contentParent);
    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();
}


android cron解析 android setcontentview 解析_android cron解析_02


最后,网络的资源非常充足,感谢给我提供资源的小伙伴们