前言
最近看了Android中,Activity->ViewGroup->View的事件分發機制,於是順帶就看了下setContentView方法的源碼。在這裏和大家分享一下我的個人所得。
正文
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
這是Activity中setContentView的源碼,先看getWindow().setContentView(layoutResID);這裏的getWindow()(我們都知道每個Activity中都持有一個自己的Window對象,而Window類是一個abstract抽象類,它的唯一的實現類,即PhoneWindow)實際上指的是PhoneWindow對象。那麼我們就一起看一下PhoneWindow這個類中的setContentView方法:
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
...
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
....
}
這裏省略了一部分代碼,我們看主要的:
如果mContentParent這個變量爲空,那麼就執行 installDecor();而這裏的mContentParent,我們看源碼可以知道,其實是一個ViewGroup:
ViewGroup mContentParent;
先不管這個,我們看一下installDecor()方法:
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
mDecor = generateDecor(-1);
...
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
...
}
}
還是省略一些代碼,我們看主要的,這裏對mDecor變量進行了判斷,mDecor實際上是個DecorView,也就是說在這裏判斷有沒有DecorView對象,如果有就把這個DecorView的對象,通過setWindow(this)和PhoneWindow做關聯,這也解釋了爲什麼一個PhoneWindow裏面包含了一個DecorView,而DecorView是繼承於FrameLayout的,FrameLayout又是一個ViewGroup。扯遠了,言歸正傳,我們看一下generateDecor(-1):
protected DecorView generateDecor(int featureId) {
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());
}
我們發現在generateDecor中創建了DecorView的對象。
再返回上面看installDecor方法,這裏還對mContentParent變量做了判空,如果這個變量爲空,那麼就通過generateLayout方法創建,我們來看下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) {
...
} else {
layoutResource = R.layout.screen_simple;
}
mDecor.startChanging();
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
...
return contentParent;
}
代碼太長,我們只看重點部分,這裏有個screen_simple佈局,我們用這個screen_simple佈局來做剖析,我們看下screen_simple佈局是什麼樣子:
發現這個佈局的根佈局是個垂直方向的LinearLayout,裏面包含了兩個View,一個是ViewStub,另一個就是FrameLayout,注意這個FrameLayout的id:android:id="@android:id/content"。我們在這裏先管這個FrameLayout叫做內容佈局吧。
再返回來看generateLayout方法中的這行代碼:
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
我們看下DecorView中的onReSourcesLoaded方法做了哪些事情?
先看這個方法的兩個參數,一個是佈局填充器,一個就是我們的R.layout.screen_simple佈局。
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
...
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();
}
通過查看onResourcesLoaded方法可以知道,裏面調用了addView方法,把R.layout.screen_simple佈局填充的View添加到了DecorView中。
還接着看generateLayout方法,看裏面的:
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
我們看下這裏的ID_ANDROID_CONTENT:
public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
這裏通過findViewById方法,找到了id爲,R.id.content的View,也就是我們前面看到的screen_simple佈局中的那個FrameLayout。(這裏我們把findViewById方法也找到了,這個其實是和我們常用的findViewById方法都是一樣的,其歸根結底都是調用的Window類裏面的findViewById方法)
@Nullable
public <T extends View> T findViewById(@IdRes int id) {
return getDecorView().findViewById(id);
}
然後generateLayout方法返回了這個contentParent,也就是我們在上面看到了mContentParent變量賦了值。
小結:由上面的分析可知,其實Activity中的setContentView方法,最終是把佈局添加到了DecorView中一個id爲content的FrameLayout裏面。
這裏借用某大神的一張一個Activity的層次圖:
其實我們平時在Activity中使用的setContentView就是把Activity的佈局添加到了黃色部分的FrameLayout上。
瞭解上面關於Content部分的View,不要忘記,上面我們在Activity的setContentView方法裏面還有一行代碼:
initWindowDecorActionBar();
這個就是關於ActionBar的顯示,我們一起來看一下這個方法:
private void initWindowDecorActionBar() {
Window window = getWindow();
// Initializing the window decor can change window feature flags.
// Make sure that we have the correct set before performing the test below.
window.getDecorView();
if (isChild() || !window.hasFeature(Window.FEATURE_ACTION_BAR) || mActionBar != null) {
return;
}
mActionBar = new WindowDecorActionBar(this);
mActionBar.setDefaultDisplayHomeAsUpEnabled(mEnableDefaultActionBarUp);
mWindow.setDefaultIcon(mActivityInfo.getIconResource());
mWindow.setDefaultLogo(mActivityInfo.getLogoResource());
}
在這個方面裏面創建了一個WindowDecorActionBar對象,我們來看一下,WindowDecorActionBar對應的構造方法:
@RestrictTo(LIBRARY_GROUP_PREFIX)
public WindowDecorActionBar(View layout) {
assert layout.isInEditMode();
init(layout);
}
發現裏面有一個init方法:
private void init(View decor) {
mOverlayLayout = (ActionBarOverlayLayout) decor.findViewById(R.id.decor_content_parent);
if (mOverlayLayout != null) {
mOverlayLayout.setActionBarVisibilityCallback(this);
}
mDecorToolbar = getDecorToolbar(decor.findViewById(R.id.action_bar));
mContextView = (ActionBarContextView) decor.findViewById(
R.id.action_context_bar);
mContainerView = (ActionBarContainer) decor.findViewById(
R.id.action_bar_container);
if (mDecorToolbar == null || mContextView == null || mContainerView == null) {
throw new IllegalStateException(getClass().getSimpleName() + " can only be used " +
"with a compatible window decor layout");
}
mContext = mDecorToolbar.getContext();
// This was initially read from the action bar style
final int current = mDecorToolbar.getDisplayOptions();
final boolean homeAsUp = (current & DISPLAY_HOME_AS_UP) != 0;
if (homeAsUp) {
mDisplayHomeAsUpSet = true;
}
ActionBarPolicy abp = ActionBarPolicy.get(mContext);
setHomeButtonEnabled(abp.enableHomeButtonByDefault() || homeAsUp);
setHasEmbeddedTabs(abp.hasEmbeddedTabs());
final TypedArray a = mContext.obtainStyledAttributes(null,
R.styleable.ActionBar,
R.attr.actionBarStyle, 0);
if (a.getBoolean(R.styleable.ActionBar_hideOnContentScroll, false)) {
setHideOnContentScrollEnabled(true);
}
final int elevation = a.getDimensionPixelSize(R.styleable.ActionBar_elevation, 0);
if (elevation != 0) {
setElevation(elevation);
}
a.recycle();
}
這裏面有這麼幾行代碼:
mDecorToolbar = getDecorToolbar(decor.findViewById(R.id.action_bar));
mContextView = (ActionBarContextView) decor.findViewById(
R.id.action_context_bar);
mContainerView = (ActionBarContainer) decor.findViewById(
R.id.action_bar_container);
在分析Content的時候,我們用的是R.layout.screen_simple,這裏我們看下含有ActionBar對應的佈局:screen_action_bar,
以screen_action_bar佈局爲例:
<com.android.internal.widget.ActionBarOverlayLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/decor_content_parent"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:splitMotionEvents="false"
android:theme="?attr/actionBarTheme">
<FrameLayout android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent" />
<com.android.internal.widget.ActionBarContainer
android:id="@+id/action_bar_container"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignParentTop="true"
style="?attr/actionBarStyle"
android:transitionName="android:action_bar"
android:touchscreenBlocksFocus="true"
android:keyboardNavigationCluster="true"
android:gravity="top">
<com.android.internal.widget.ActionBarView
android:id="@+id/action_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/actionBarStyle" />
<com.android.internal.widget.ActionBarContextView
android:id="@+id/action_context_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
style="?attr/actionModeStyle" />
</com.android.internal.widget.ActionBarContainer>
<com.android.internal.widget.ActionBarContainer android:id="@+id/split_action_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="?attr/actionBarSplitStyle"
android:visibility="gone"
android:touchscreenBlocksFocus="true"
android:keyboardNavigationCluster="true"
android:gravity="center"/>
</com.android.internal.widget.ActionBarOverlayLayout>
可以看到這裏的id爲action_bar以及id爲action_context_bar以及id爲action_bar_container,這些都是在DecorView裏面的,所以也不難明白這些ActionBar爲什麼都存在於DecorView中了。
總結
通過上面對Activity中setContentView方法的源碼剖析,可以瞭解到一個Activity的視圖層次,以及一個Activity在setContentView的時候都做了哪些操作,以及怎樣把我們創建的佈局添加到Activity中展示出來的。