在View的繪制流程一中我們已經(jīng)了解了View是怎樣添加到父容器中的屡拨,而在Activity中布局是怎樣被添加進(jìn)去的呢?是不是和View的添加流程一樣呢褥实?帶著疑問我們跟著源碼來看一下布局是怎樣被添加到Activity中的吧
注:現(xiàn)如今的 Android 開發(fā)過程中大多都會繼承 AppCompatActivity 所以本文是以 AppCompatActivity 來講解的
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
}
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
@NonNull
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
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);
}
}
上面一些簡單的代碼追蹤可以看出谷歌工程師為了版本兼容花費了很多心思呀狼。針對不同的版本重寫了各種AppCompatDelegate子類,其中AppCompatDelegateImplN繼承AppCompatDelegateImplV23损离,AppCompatDelegateImplV23繼承AppCompatDelegateImplV14哥艇,AppCompatDelegateImplV14繼承AppCompatDelegateImplV11,AppCompatDelegateImplV11繼承AppCompatDelegateImplV9而AppCompatDelegateImplV9重寫了setContentView()
方法所以getDelegate().setContentView(view);
最終會調(diào)用AppCompatDelegateImplV9的setContentView()
方法
@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();
}
上面的代碼看起來很簡單調(diào)用了ensureSubDecor();
方法之后獲取一個contentParent的父容器僻澎,移除父容器中所有的子View然后將Activity中設(shè)置的資源文件添加到這個父容器之后通知一下布局發(fā)生改變貌踏。那么mSubDecor
是什么呢andorid.R.id.content
這個id又是什么呢為什么要將布局添加到contentParent
中去呢?帶著疑問我們繼續(xù)來分析ensureSubDecor()
中代碼
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
//創(chuàng)建SubDecor
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);
}
}
}
這里我們主要分析mSubDecor是怎樣創(chuàng)建的窟勃,跟蹤代碼
/**
* 該方法主要是進(jìn)行版本兼容使用自己的subDecor來替換phoneWindow中的DecorView
*/
private ViewGroup createSubDecor() {
//初始化一些style屬性根據(jù)不同的屬性兼容不同的初始布局
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.");
}
//設(shè)置狀態(tài)位祖乳。決定初始布局是否需要title、actionBar等等
//以下都是對window的狀態(tài)進(jìn)行初始化設(shè)置并設(shè)置到feature屬性
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);
}
//判斷window是否是浮窗形式的
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
//新建phoneWindow下的decorView
mWindow.getDecorView();
final LayoutInflater inflater = LayoutInflater.from(mContext);
ViewGroup subDecor = null;
//根據(jù)之前的feature狀態(tài)屬性來初始化subDecor的布局
...省略部分代碼...
//上面省略部分根據(jù)不同條件設(shè)置subDecor的代碼秉氧。這里有個abc+screen_simple我們會分析一下這個布局的代碼
//其他布局道理也是一樣的
subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
...省略部分代碼...
//下面代碼就很重要了谷歌工程師為了進(jìn)行版本適配在下面代碼進(jìn)行了偷梁換柱
//獲取contentVIew容器
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
//這里的windowContentView是decorView創(chuàng)建的眷昆。
//上文中setContent方法中出現(xiàn)的android.R.id.content這里又出現(xiàn)了,在上面方法中可以知道activity
//的資源布局就是添加到id為android.R.id.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
//這里將windowContentView中所有的子View全部轉(zhuǎn)移到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.
//這里巧妙的將android.R.id.content從windowContentView轉(zhuǎn)移到contentView中實現(xiàn)了subDecor
//到decorView的替換
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
//將subDecor添加到windowContentView中
mWindow.setContentView(subDecor);
return subDecor;
}
上述代碼是整個setContentView流程的核心亚斋,這里主要就是把phoneWindow中創(chuàng)建的DecorView的內(nèi)容轉(zhuǎn)移到自己的subDecor中去
<android.support.v7.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.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" />
<android.support.v7.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" />
</android.support.v7.widget.FitWindowsLinearLayout>
上面對應(yīng)的是abc_screen_simple的代碼很簡單就不做分析了
@Override
public final View getDecorView() {
if (mDecor == null || mForceDecorInstall) {
installDecor();
}
return mDecor;
}
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
//創(chuàng)建decorView對象mDecor
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) {
//這里初始化mDecor布局,并且通過android.R.id.content ID找到mContentParent布局
//具體初始化流程跟上面的createSubDecor方法類似就不做分析了
mContentParent = generateLayout(mDecor);
...省略部分代碼...
//剩下代碼主要做一些樣式初始化和設(shè)置就不進(jìn)行分析了
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());
}
總結(jié):
到此我們的setContentView流程就已經(jīng)分析結(jié)束了攘滩,從上面的代碼中我們可以分析出AppCompatActivity的布局嵌套為