Android 布局的加載
此次分析是基于Android sdk 28,看到網(wǎng)絡(luò)上好多有寫的不錯(cuò)的博客,絕大多數(shù)都沒有說明是基于Android 那個(gè)版本來分析,因?yàn)殡S著Android版本的演進(jìn),api是有變化的
在開始之前先看一個(gè)Actiivty 頁面的結(jié)構(gòu)圖
開始
設(shè)置Activity 的布局,是通過 ==AppCompatActivity== 的:
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
然后看看這個(gè)里面 ==setContentView== 是從哪里來的?這是 ==AppCompatDelegate== 這個(gè)的一個(gè)函數(shù),可以看看這個(gè)類是做什么用的
This class represents a delegate which you can use to extend AppCompat's support to any
* {@link android.app.Activity}.
翻譯過來就是: 此類表示可用于將AppCompat支持?jǐn)U展到任何Activity的委托茎毁。比較拗口,直白點(diǎn)就是這個(gè)類支持actiivty 的一些擴(kuò)展功能,是一個(gè)代理類,支持的功能如下:
- addContentView(android.view.View, android.view.ViewGroup.LayoutParams)
- setContentView(int)
- setContentView(android.view.View)
- setContentView(android.view.View, android.view.ViewGroup.LayoutParams)
- requestWindowFeature(int)
- hasWindowFeature(int)
- invalidateOptionsMenu()
- startSupportActionMode(android.support.v7.view.ActionMode.Callback)
- setSupportActionBar(android.support.v7.widget.Toolbar)
- getSupportActionBar()
- getMenuInflater()
- findViewById(int)
其實(shí)這部分功能的主要實(shí)現(xiàn)還是在window中,只不過是將這部分功能將window與activity 進(jìn)行隔離,避免耦合
可以對(duì)比一下:
以 ==setContentView(viewId)== 為例:
- android sdk 28:
AppCompatActivity$setContentView
@Override
public void setContentView(@LayoutRes int layoutResID) {
// 拿到AppCompatDelegate的實(shí)例,調(diào)用函數(shù)
getDelegate().setContentView(layoutResID);
}
- android sdk 23
Activity$setContentView
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
可以看出,同一個(gè)方法,android的不同版本是有區(qū)別的,在具體看看 ==getDelegate().setContentView(layoutResID);==
AppCompatDelegate是抽像類,具體的實(shí)現(xiàn)是AppCompatDelegateImp
AppCompatDelegateImp$setContentView
@Override
public void setContentView(int resId) {
//初始化DecorView
ensureSubDecor();
//設(shè)置setContentView 要記載的父布局
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
//加載之前移除所有的布局
contentParent.removeAllViews();
//完成布局的加載
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
然后我們可以看看AppCompatDelegateImp$ensureSubDecor這個(gè)函數(shù)里面做了什么事:
private void ensureSubDecor() {
//默認(rèn)是false,如果為true的表明 subDecor已經(jīng)被加載過了
if (!mSubDecorInstalled) {
mSubDecor = createSubDecor();
//設(shè)置actiivty 的title
// If a title was set before we installed the decor, propagate it now
CharSequence title = getTitle();
if (!TextUtils.isEmpty(title)) {
if (mDecorContentParent != null) {
mDecorContentParent.setWindowTitle(title);
} else if (peekSupportActionBar() != null) {
peekSupportActionBar().setWindowTitle(title);
} else if (mTitleView != null) {
mTitleView.setText(title);
}
}
//為subDecor 設(shè)置固定的大小
applyFixedSizeWindow();
//空方法
onSubDecorInstalled(mSubDecor);
//設(shè)置 subDecor 已經(jīng)被加載過了
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 (!mIsDestroyed && (st == null || st.menu == null)) {
invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
}
}
}
然后在看看 ==AppCompatDelegateImp$applyFixedSizeWindow()== 函數(shù)
private void applyFixedSizeWindow() {
//注意這個(gè)控件,它在界面的位置看下面的這張圖
ContentFrameLayout cfl = (ContentFrameLayout) mSubDecor.findViewById(android.R.id.content);
// This is a bit weird. In the framework, the window sizing attributes control
// the decor view's size, meaning that any padding is inset for the min/max widths below.
// We don't control measurement at that level, so we need to workaround it by making sure
// that the decor view's padding is taken into account.
//獲取 DecorView
final View windowDecor = mWindow.getDecorView();
//設(shè)置ContentFrameLayout 布局的間距
cfl.setDecorPadding(windowDecor.getPaddingLeft(),
windowDecor.getPaddingTop(), windowDecor.getPaddingRight(),
windowDecor.getPaddingBottom());
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
a.getValue(R.styleable.AppCompatTheme_windowMinWidthMajor, cfl.getMinWidthMajor());
a.getValue(R.styleable.AppCompatTheme_windowMinWidthMinor, cfl.getMinWidthMinor());
if (a.hasValue(R.styleable.AppCompatTheme_windowFixedWidthMajor)) {
a.getValue(R.styleable.AppCompatTheme_windowFixedWidthMajor,
cfl.getFixedWidthMajor());
}
if (a.hasValue(R.styleable.AppCompatTheme_windowFixedWidthMinor)) {
a.getValue(R.styleable.AppCompatTheme_windowFixedWidthMinor,
cfl.getFixedWidthMinor());
}
if (a.hasValue(R.styleable.AppCompatTheme_windowFixedHeightMajor)) {
a.getValue(R.styleable.AppCompatTheme_windowFixedHeightMajor,
cfl.getFixedHeightMajor());
}
if (a.hasValue(R.styleable.AppCompatTheme_windowFixedHeightMinor)) {
a.getValue(R.styleable.AppCompatTheme_windowFixedHeightMinor,
cfl.getFixedHeightMinor());
}
a.recycle();
cfl.requestLayout();
}
說了半天可能還是有點(diǎn)暈,看著張圖解明白了:
到這里就結(jié)束了!