前言
Android开发最息息相关的就是界面,平时开发总会使用各种布局和视图来组合成我们的屏幕效果,在Android的知识体系中,View扮演着很重要的角色,简单来理解,View是Android在视觉上的呈现。那Android是如何将这些View一步步绘制到屏幕上的呢,这就涉及到本文所要讲的窗口绘制工作原理。
先上一张结构图,带着这张结构图去分析源码:
Activity结构图
源码分析
我们最经常接触的往往是View这一层,在它之上有Activity,Activity一般都要在onCreate中调用setContentView,那就从setContentView进去看究竟做了啥:
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}可以看到实际上是调用了getWindow得到的对象再去setContentView,所以其实在View和Activity中间还隔着一层Window,它是Activity与View之间的桥梁,是一个抽象类,PhoneWindow是它的唯一实现子类,我们在Activity中所使用的View其实都是通过PhoneWindow来呈现的,它就类似于View的一个载体,就像PS中的画板,而View就是具体要绘制的内容。
那么PhoneWindow里面又是什么构成呢?继续顺着刚才setContentView看进去:
@Override
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) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
} if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { //忽略代码
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
} //忽略代码
}可以看到先是installDecor(),installDecor源码如下:
private void installDecor() {
if (mDecor == null) {
//new一个DecorView
mDecor = generateDecor();
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
} //忽略代码}可以看到这里首先创建了一个DecorView,然后调用generateLayout,详细看下generateLayout:
protected ViewGroup generateLayout(DecorView decor) { //忽略代码
int layoutResource;
... if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
layoutResource = R.layout.screen_swipe_dismiss;
} else if ((features & ((1 << FEATURE_LEFT_ICON) | (1 << FEATURE_RIGHT_ICON))) != 0) { if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleIconsDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_title_icons;
}
...
} else if ((features & ((1 << FEATURE_PROGRESS) | (1 << FEATURE_INDETERMINATE_PROGRESS))) != 0
&& (features & (1 << FEATURE_ACTION_BAR)) == 0) {
layoutResource = R.layout.screen_progress;
} else if ((features & (1 << FEATURE_CUSTOM_TITLE)) != 0) { if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogCustomTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else {
layoutResource = R.layout.screen_custom_title;
}
} else if ((features & (1 << FEATURE_NO_TITLE)) == 0) { if (mIsFloating) {
TypedValue res = new TypedValue();
getContext().getTheme().resolveAttribute(
R.attr.dialogTitleDecorLayout, res, true);
layoutResource = res.resourceId;
} else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
layoutResource = a.getResourceId(
R.styleable.Window_windowActionBarFullscreenDecorLayout,
R.layout.screen_action_bar);
} else {
layoutResource = R.layout.screen_title;
}
} else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
layoutResource = R.layout.screen_simple_overlay_action_mode;
} else {
layoutResource = R.layout.screen_simple;
}
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); if (contentParent == null) { throw new RuntimeException("Window couldn't find content container view");
} //忽略代码
return contentParent;
}可以看到先是根据feature类型来为layoutResource赋值,随便选择一个layout文件打开如下:
<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,里面还包含着一个id为R.id.content的FrameLayout,我们继续回到代码中,刚才赋值了layoutResource后,接着调用了DecorView的onResourcesLoaded,先看下里面做了啥:
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; //...忽略代码
} @Override
public void onDraw(Canvas c) { super.onDraw(c); // When we are resizing, we need the fallback background to cover the area where we have our
// system bar background views as the navigation bar will be hidden during resizing.
mBackgroundFallback.draw(isResizing() ? this : mContentRoot, mContentRoot, c,
mWindow.mContentParent);
}可以看到这里将我们传进来的layoutResource资源inflate成了View,然后将其addView给DecorView自身并且在onDraw中绘制了出来,换句话说就是把刚才那个layout文件添加到DecorView中并绘制。
那么刚才那个layout文件用来做什么的呢?回到上一步,onResroucesLoaded之后,通过findViewById查找id为R.id.content的View(看到没,这里就是我们刚才那个layout文件中的R.id.content,所以实际上就是先将layout文件add到DecorView中去,然后再将其content给find出来),然后将其返回,赋值给刚才分析过的installDecor方法中的mContentParent,然后现在回头看回我们一开始的setContentView:
@Override
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) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
} if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { //忽略代码
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
} //忽略代码
}我们发现了mContentParent的影子!,mLayoutInflater.inflate(layoutResID, mContentParent);,它将layoutResID的资源通过inflate添加到了我们的mContentParent(inflate的参数解读见我另一篇文章:https://www.jianshu.com/p/3f871d95489c)
所以其实我们平时setContetntView传进去的R.layout.xxx最终都会添加到R.id.content这个View上面,这也就是为什么它命名为setContentView的原因了,而R.id.content又是被包裹在一层LinearLayout里面,然后这个LinearLayout文件又会被通过addView添加到DecorView这个FrameLayout中,原来 DecorView 就是承载了我们布局文件的一个顶级大佬。
总结
现在再回过头看文章开头那个结构图,是否清晰了很多呢~这里只是从setContentView分析了从Activity到Window再到DecorView的层级和绘制流程,接下来还有关于View的绘制再慢慢总结,不得不说源码里还是有很多值得借鉴的地方,多看源码对自己的理解会更有帮助,不能知其然不知其所以然。
作者:Android小Y
链接:https://www.jianshu.com/p/a8f4b7e90063