Android 布局的加载
此次分析是基于Android sdk 28,看到网络上好多有写的不错的博客,绝大多数都没有说明是基于Android 那个版本来分析,因为随着Android版本的演进,api是有变化的
在开始之前先看一个Actiivty 页面的结构图
开始
设置Activity 的布局,是通过 ==AppCompatActivity== 的:
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
然后看看这个里面 ==setContentView== 是从哪里来的?这是 ==AppCompatDelegate== 这个的一个函数,可以看看这个类是做什么用的
This class represents a delegate which you can use to extend AppCompat's support to any
* {@link android.app.Activity}.
翻译过来就是: 此类表示可用于将AppCompat支持扩展到任何Activity的委托。比较拗口,直白点就是这个类支持actiivty 的一些扩展功能,是一个代理类,支持的功能如下:
- 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)
其实这部分功能的主要实现还是在window中,只不过是将这部分功能将window与activity 进行隔离,避免耦合
可以对比一下:
以 ==setContentView(viewId)== 为例:
- android sdk 28:
AppCompatActivity$setContentView
@Override
public void setContentView(@LayoutRes int layoutResID) {
// 拿到AppCompatDelegate的实例,调用函数
getDelegate().setContentView(layoutResID);
}
- android sdk 23
Activity$setContentView
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
可以看出,同一个方法,android的不同版本是有区别的,在具体看看 ==getDelegate().setContentView(layoutResID);==
AppCompatDelegate是抽像类,具体的实现是AppCompatDelegateImp
AppCompatDelegateImp$setContentView
@Override
public void setContentView(int resId) {
//初始化DecorView
ensureSubDecor();
//设置setContentView 要记载的父布局
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
//加载之前移除所有的布局
contentParent.removeAllViews();
//完成布局的加载
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
然后我们可以看看AppCompatDelegateImp$ensureSubDecor这个函数里面做了什么事:
private void ensureSubDecor() {
//默认是false,如果为true的表明 subDecor已经被加载过了
if (!mSubDecorInstalled) {
mSubDecor = createSubDecor();
//设置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 设置固定的大小
applyFixedSizeWindow();
//空方法
onSubDecorInstalled(mSubDecor);
//设置 subDecor 已经被加载过了
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()== 函数
private void applyFixedSizeWindow() {
//注意这个控件,它在界面的位置看下面的这张图
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();
//设置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();
}
说了半天可能还是有点晕,看着张图解明白了:
到这里就结束了!