先上代码:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
}
这里以 android-8.1为例子,以上代码是我们创建 Activity 的代码,只要写好页面的布局,调用 setContentView 运行后就能显示。
MainActivity 是继承 supportV7 包中的 AppCompatActivity。AppCompatActivity 最终是继承于Activity,继承关系如图:
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);
}
几个不同的 setContentView 方法都调用 getDelegate().setContentView,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 对象。而 AppCompatDelegate 类是一个抽象类。在这这个类中的 setContentView 都是抽象方法:
/**
* Should be called instead of {@link Activity#setContentView(android.view.View)}}
*/
public abstract void setContentView(View v);
/**
* Should be called instead of {@link Activity#setContentView(int)}}
*/
public abstract void setContentView(@LayoutRes int resId);
/**
* Should be called instead of
* {@link Activity#setContentView(android.view.View, android.view.ViewGroup.LayoutParams)}}
*/
public abstract void setContentView(View v, ViewGroup.LayoutParams lp);
AppCompatDelegate 的 create 方法:
public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
return new AppCompatDelegateImpl(activity, activity.getWindow(), callback);
}
AppCompatDelegateImpl 是抽象类 AppCompatDelegate 的实现类。构造方法中传入 Activity,以及 activity.getWindow()。
看看Activity 中的 getWindow 方法:
public Window getWindow() {
return mWindow;
}
从代码中可以看到返回的是一个 mWindow 对象。 而这个 Window对象是在 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,
Window window, ActivityConfigCallback activityConfigCallback) {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
...
...
}
从这个方法可以看出,在 Activity 的 attach 方法中,mWindow 对象实际是 PhoneWindow。这里就不继续去追这个 attach 方法如何被调用的。
继续返回 AppCompatDelegateImpl 这个实现类实现的 setContentView 方法:
//1
@Override
public void setContentView(View v) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
contentParent.addView(v);
mOriginalWindowCallback.onContentChanged();
}
//2
@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();
}
//3
@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();
}
这三个 setContentView 方法都类似,只是传入的参数不一致,这里就只追注释1:
@Override
public void setContentView(View v) {
//4
ensureSubDecor();
//5
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
//6
contentParent.removeAllViews();
//7
contentParent.addView(v);
mOriginalWindowCallback.onContentChanged();
}
注释4的 ensureSubDecor 方法:(AppCompatDelegateImpl.java)
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
//8
mSubDecor = createSubDecor();
...
...
//9
applyFixedSizeWindow();
//10
onSubDecorInstalled(mSubDecor);
...
...
}
}
在注释8通过方法 createSubDecor() 创建了 ViewGroup mSubDecor 对象。
createSubDecor 方法:
private ViewGroup createSubDecor() {
...
//11
mWindow.getDecorView();
final LayoutInflater inflater = LayoutInflater.from(mContext);
ViewGroup subDecor = null;
...
...
} else if (mHasActionBar) {
...
subDecor = (ViewGroup) LayoutInflater.from(themedContext)
.inflate(R.layout.abc_screen_toolbar, null);
mDecorContentParent = (DecorContentParent) subDecor
.findViewById(R.id.decor_content_parent);
mDecorContentParent.setWindowCallback(getWindowCallback());
...
...
}
} 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);
}
...
}
...
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
mWindow.setContentView(subDecor);
contentView.setAttachListener(new ContentFrameLayout.OnAttachListener() {
@Override
public void onAttachedFromWindow() {}
@Override
public void onDetachedFromWindow() {
dismissPopups();
}
});
return subDecor;
}
看看 mWindow.getDecorView 方法,由于 Window 类是抽象类,真正实现是在 PhoneWindow 类。
@Override
public final View getDecorView() {
if (mDecor == null || mForceDecorInstall) {
installDecor();
}
return mDecor;
}
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);
...
...
一开始 mDecor 肯定为空,所以会执行 generateDecor 方法。
protected DecorView generateDecor(int featureId) {
...
...
return new DecorView(context, featureId, this, getAttributes());
}
在方法 generateDecor 中创建 DecorView 对象,并赋值给 mDecor,再看看 installDecor 方法 中的 generateLayout 方法,参数为 DecorView。
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
...
...
} 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);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
...
...
return contentParent;
}
这里 getWindowStyle()的作用就是在manifest文件配置的Activity的时候有时会指定theme,getWindowStyle()就是获取我们配置的theme信息。
看看其中的一种布局 screen_simple.xml
<?xml version="1.0" encoding="utf-8"?>
<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"
android:theme="?attr/actionBarTheme" />
<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>
从上面可以可以看出 generateLayout 方法返回的是 xml中id为content 的控件。
再回头看以下代码,ensureSubDecor 创建了DecorView,且其中有个控件的id 为content。
最后将我们在创建Activity设置view 放到 content这个布局中去。
@Override
public void setContentView(View v) {
//4
ensureSubDecor();
//5
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
//6
contentParent.removeAllViews();
//7
contentParent.addView(v);
mOriginalWindowCallback.onContentChanged();
}
总结:
Activity的setContentView方法中会调用getWindow().setContentView,getWindow获取的mWindow是在attach方法中创建的PhoneWindow对象。在PhoneWindow中的setContentView方法调用installDecor()方法,去创建一个DecorView对象,这个对象就是Activity中的根View。DecorView继承FrameLayout。DecorView创建后,根据不同的条件去加载不同的layout(frameworks\base\core\res\res\layout).真正setContentView传入的layout,放在DecorView中的content组件里面。
他们的关系如图所示: