前言
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
共同学习,写下你的评论
评论加载中...
作者其他优质文章