setContentView 方法如下:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
getWindow() , 实际上就是PhoneWindow , 继续查看PhoneWindow类中的setContentView方法
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
/// 这里初始化 decor
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
// 这里是不是很眼熟,可以猜测是加载布局文件,
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
继续查看如何初始化 decor
private void installDecor() {
if (mDecor == null) {
// 初始化decor
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
// 设置DecorView的window 为PhoneWindow对象
mDecor.setWindow(this);
}
if (mContentParent == null) {
// 生成内容根节点
mContentParent = generateLayout(mDecor);
}
// ...其他代码忽略
}
先查看 generateDecor
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());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}
创建DecorView 对象
在看看 generateLayout(decorView)
protected ViewGroup generateLayout(DecorView decor) {
// .....
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
} else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
// Don't allow an action bar if there is no title.
// 这里的代码比较眼熟,设置主体的时候会用到
requestFeature(FEATURE_ACTION_BAR);
}
/// ...
// Inflate the window decor.
int layoutResource;
// 这里获取一个features , 根据这个值获取一个布局文件
int features = getLocalFeatures();
// 那最后一个布局查看
if(...){...}
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!");
}
// 这个方法也很重要, 通过DecorView 加载上面获取到的布局文件
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// 这里是拿到哪个id 为@android:id/content 的对象
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
// ...
// 最后返回这个content,那由此我们可以知道 PhoneWindow的mContentParent 对象其实就是id为@android:id/content 的FrameLayout对象
return contentParent;
}
screen_simple布局文件
<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>
这里就是一个普通的LinearLayout , 重点是里面有个FrameLayout而且id为@android:id/content , 这个就是我们自定义开发布局都会放在这个FrameLayout 里面
现在再回过头来看看setContentView 方法里面的 mLayoutInflater.inflate(layoutResID, mContentParent);
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
// 这个根据名字可以猜出,是预加载布局文件
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
// 这个是获取xml资源解析器
XmlResourceParser parser = res.getLayout(resource);
try {
// 重要的是这个方法
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
继续查看 inflate(parser, root, attachToRoot);
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
final Context inflaterContext = mContext;
// 拿到所有的属性
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
// 这个result 就算 decorView
View result = root;
if (TAG_MERGE.equals(name)) {
// 这里判断了是否是 <merge>解点, 不能是最外层的根节点
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
// 继续通过递归调用的方式,加载出所有的子节点,并放在root节点,即decorView 下面
rInflate(parser, root, inflaterContext, attrs, false);
}
}
}
/// finishInflate 是否停止解析
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
boolean pendingRequestFocus = false;
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
pendingRequestFocus = true;
consumeChildElements(parser);
} else if (TAG_TAG.equals(name)) {
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
// 解析include 标签,不能是根节点标签
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else {
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
// 再次递归调用,解析子节点
rInflateChildren(parser, view, attrs, true);
// 把解析出来的子节点,放在上级节点view[]里面
viewGroup.addView(view, params);
}
}
if (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
// 停止解析
if (finishInflate) {
parent.onFinishInflate();
}
}
通过上面一层一层的解析,就能把我们自己定义的布局,解析出来, 并放在mContentParent里面, 这个就是@android:id/content的FrameLayout。
到这一步,好像还缺点啥,还没看到 mContentParent , 是如何放进DecorView 里面的。再回头看看PhoneWindow类里面的 generateLayout方法,刚才说的有一个加载资源的方法,我们漏看了
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
这个方法,从表面上看, 是拿到了某个主题的资源文件,并解析
void onResourcesLoaded(LayoutInflater inflater, int layoutResource) {
// ...
mDecorCaptionView = createDecorCaptionView(inflater);
// 这一步就是解析系统主题中的某个带有@android:id/content的哪个布局
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.
// 在这一步,把mContentParent 添加到 decorView 的第一个元素里面了
addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
}
mContentRoot = (ViewGroup) root;
initializeElevation();
}
到这一步,基本上都清晰了
image.png
网友评论