上一篇分析了四大组件之ContentProvider,这也是四大组件最后一个。因此,从这篇开始我们分析新的篇章–View绘制流程,View绘制流程在Android开发中占有非常重要的位置,只要有视图的显示,都离不开View的绘制,所以了解View绘制原理对于应用开发以及系统的学习至关重要。由于View绘制流程比较复杂,并且涉及的知识非常多,所以后面我会按照下面几方面来介绍View的绘制流程。每篇不是很长,但是尽量的详细,让每个人都看懂。
- Android系统源码分析–View绘制流程之-setContentView
- Android系统源码分析–View绘制流程之-inflate
- Android系统源码分析–View绘制流程之-onMeasure
- Android系统源码分析–View绘制流程之-onLayout
- Android系统源码分析–View绘制流程之-onDraw
- Android系统源码分析–View绘制流程之-硬件加速
- Android系统源码分析–View绘制流程之-addView
- Android系统源码分析–View绘制流程之-弹性效果
所以这篇我们先分析View绘制流程的setContentView方法,按照惯例,先贴一下流程图:
1.PhoneWindow.setContentView
调用setContentView最开始的地方是在我们继承Activity的子类中的onCreate方法中,这个方法其实是调用的Activity中的setContentView方法:
1 2 3 4 5
| public void setContentView(@LayoutRes int layoutResID) { getWindow().setContentView(layoutResID); initWindowDecorActionBar(); }
|
其实这个getWindow获取的是继承Window的PhoneWindow,所以这里getWindow.setContentView是调用的PhoneWindow.setContentView方法,具体的自己可以看看代码哪里赋值的就知道了。另外这个方法还有两个类似的方法:
1 2 3 4 5 6 7 8
| public void setContentView(View view) { getWindow().setContentView(view); initWindowDecorActionBar(); } public void setContentView(View view, ViewGroup.LayoutParams params) { getWindow().setContentView(view, params); initWindowDecorActionBar(); }
|
这三个方法差不多,只不过下面的两个直接传递了view对象,而第一个是传递了view的id。我们接着看PhoneWindow.setContentView方法。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
| public void setContentView(int layoutResID) { if (mContentParent == null) { installDecor(); } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) { mContentParent.removeAllViews(); }
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) { ... } else { mLayoutInflater.inflate(layoutResID, mContentParent); } mContentParent.requestApplyInsets(); ... mContentParentExplicitlySet = true; }
|
上面注释很详细,但是还是需要解释一下mContentParent,这个mContentParent是一个FrameLayout,这里的Content是指你setContentView传递进来的id指向的视图,所以mContentParent也就是指放置传递进来的视图的父视图。看下面的图:
上面的ActionBarContextView是标题,不过有些设置是不会显示整个标题的,所以这里只是一种情况,下面的id为content的FrameLayout就是这个mContentParent,你通过setContentView方法传递的视图会放到这个id为content的FrameLayout上面,这样你的Activity就显示了你写的布局视图了,这里先解释一下,我们下面看看是不是真的这样。由于第一次创建Activity时mContentParent是空的,所以会走PhoneWindow.installDecor方法。
2.PhoneWindow.installDecor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59
| private void installDecor() { mForceDecorInstall = false; if (mDecor == null) { mDecor = generateDecor(-1); ... } else { mDecor.setWindow(this); } if (mContentParent == null) { mContentParent = generateLayout(mDecor);
...
final DecorContentParent decorContentParent = (DecorContentParent) mDecor.findViewById( R.id.decor_content_parent);
if (decorContentParent != null) { ... if (mDecorContentParent.getTitle() == null) { mDecorContentParent.setWindowTitle(mTitle); }
... } else { mTitleView = (TextView) findViewById(R.id.title); if (mTitleView != null) { if ((getLocalFeatures() & (1 << FEATURE_NO_TITLE)) != 0) { final View titleContainer = findViewById(R.id.title_container); if (titleContainer != null) { titleContainer.setVisibility(View.GONE); } else { mTitleView.setVisibility(View.GONE); } mContentParent.setForeground(null); } else { mTitleView.setText(mTitle); } } }
if (mDecor.getBackground() == null && mBackgroundFallbackResource != 0) { mDecor.setBackgroundFallback(mBackgroundFallbackResource); }
... } }
|
这里出现了一个mDecor,这个mDecor是DecorView,继承FrameLayout,是窗口顶级视图,也就是Activity显示View的根View,包含一个TitleView和一个ContentView,也就是上面图形中的最外层蓝色的边框所指代的视图,当然,这里第一次加载时也是空的,那么会调用generateDecor函数来创建mDecor,然后通过generateLayout方法创建mContentParent视图,创建完成后会设置标题,设置标题的就不分析了,比较简单,下面先看创建mDecor的方法。
3.PhoneWindow.generateDecor
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| protected DecorView generateDecor(int featureId) { ... Context context; if (mUseDecorContext) { Context applicationContext = getContext().getApplicationContext(); if (applicationContext == null) { context = getContext(); } else { ... } } else { context = getContext(); } return new DecorView(context, featureId, this, getAttributes()); }
|
这里判断了一个applicationContext是否存在,主要是区分这个是系统调用还是应用,系统是没有applicationContext的,最后通过new关键字创建对象DecorView,这里就获取到了DecorView。
5.PhoneWindow.generateLayout
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64
| protected ViewGroup generateLayout(DecorView decor) { ... ... ... int layoutResource; int features = getLocalFeatures(); 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) { ... } else { layoutResource = R.layout.screen_title_icons; } removeFeature(FEATURE_ACTION_BAR); } 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) { ... } else { layoutResource = R.layout.screen_custom_title; } ... } else if ((features & (1 << FEATURE_NO_TITLE)) == 0) { if (mIsFloating) { ... } 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.startChanging(); mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
ViewGroup contentParent = (ViewGroup) findViewById(ID_ANDROID_CONTENT); if (contentParent == null) { throw new RuntimeException("Window couldn't find content container view"); }
... ... mDecor.finishChanging(); return contentParent; }
|
前面一大段if-else语句是根据属性值获取系统中的layout的id,主要有下面几种:
* R.layout.screen_swipe_dismiss
* R.layout.screen_title_icons
* R.layout.screen_progress
* R.layout.screen_custom_title
* R.layout.screen_title
* R.layout.screen_simple_overlay_action_mode
* R.layout.screen_simple
这些布局文件都在系统frameworks\base\core\res\res\layout\目录下,我们看其中一个screen_simple.xml布局代码:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| <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>
|
其中我们需要获取的contentParent就是xml布局中id为content的FrameLayout,为什么是这个,我们通过上面代码分析,上面我们看到了mDecor.onResourcesLoaded方法,这里的第二个参数layoutResource就是上面的xml布局,所以这里就是加载这个布局的我们看看是不是
6.DecorView.onResourcesLoaded
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24
| void onResourcesLoaded(LayoutInflater inflater, int layoutResource) { ... mDecorCaptionView = createDecorCaptionView(inflater); 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 { addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); } mContentRoot = (ViewGroup) root; initializeElevation(); }
|
这里首先创建了标题视图,然后通过LayoutInflater.inflate加载了id为layoutResource的布局文件并赋值给root引用,最终返回的也是这个root,所以上面方法5中加载了这个布局文件,加载完成后,如果标题视图文件存在,则将root添加到标题视图中,再将标题视图添加到DecorView上,如果没有标题视图,则直接将root布局添加到DecorView上面,宽高是MATCH_PARENT。再回到5中,在调用完mDecor.onResourcesLoaded方法后通过id为ID_ANDROID_CONTENT获取了一个ViewGroup,那么这个ID_ANDROID_CONTENT是什么,通过查找我们发现是:
1
| public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;
|
这里就可以知道获取的contentParent就是上面xml布局中的id为content的FrameLayout布局,所以到这里整体结构基本明白了。
另外上面调用了一个createDecorCaptionView方法并且传入了LayoutInflater,那么看看这个方法做了哪些操作。
7.DecorView.createDecorCaptionView
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| private DecorCaptionView createDecorCaptionView(LayoutInflater inflater) { ... if (!mWindow.isFloating() && isApplication && StackId.hasWindowDecor(mStackId)) { if (decorCaptionView == null) { decorCaptionView = inflateDecorCaptionView(inflater); } decorCaptionView.setPhoneWindow(mWindow, true ); } else { decorCaptionView = null; }
... return decorCaptionView; }
|
这里其实就一个方法需要再看看那就是inflateDecorCaptionView方法。
8.DecorView.inflateDecorCaptionView
1 2 3 4 5 6 7 8 9
| private DecorCaptionView inflateDecorCaptionView(LayoutInflater inflater) { final Context context = getContext(); inflater = inflater.from(context); final DecorCaptionView view = (DecorCaptionView) inflater.inflate(R.layout.decor_caption, null); ... return view; }
|
这里其实就是通过LayoutInflater.inflate方法加载frameworks\base\core\res\res\layout\下的decor_caption.xml布局,这个LayoutInflater.inflate由于比较重要,所以我们放到下一章单独讲解。
10.LayoutInflater.inflate
我们在第二步初始化完DecorView和mContentParent视图后开始调用mLayoutInflater.inflate(layoutResID, mContentParent)方法,加载我们setContentView方法传递进来的视图,也就是我们自己写的Activity布局,之前的都是系统的布局。我们知道mContentParent是放置我们自己写的Activity视图的容器,所以后面就简单了。
1 2 3
| public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) { return inflate(resource, root, root != null); }
|
上面我们说了这个方法具体分析我们下一章单独分析。所以我们接着前面分析。
11.View.requestApplyInsets
1 2 3
| public void requestApplyInsets() { requestFitSystemWindows(); }
|
12.View.requestFitSystemWindows
1 2 3 4 5
| public void requestFitSystemWindows() { if (mParent != null) { mParent.requestFitSystemWindows(); } }
|
这里的mParent是ViewParent的具体实现ViewRootImpl,所以调用的是ViewRootImpl里的requestFitSystemWindows方法。
13.ViewRootImpl.requestFitSystemWindows
1 2 3 4 5
| public void requestFitSystemWindows() { checkThread(); mApplyInsetsRequested = true; scheduleTraversals(); }
|
checkThread这个是检测线程的方法,也就是检测当前线程是不是主线程,也就是setContentView方法要在UI线程调用。然后调用scheduleTraversals方法开始绘制视图。
15.ViewRootImpl.scheduleTraversals
1 2 3 4 5 6 7 8 9 10 11 12 13
| void scheduleTraversals() { if (!mTraversalScheduled) { mTraversalScheduled = true; mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier(); mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); ... } }
|
mTraversalScheduled只有调用这个方法后才设置为true,所以在开始调用这个方法的时候是false,后面会将mTraversalRunnable放到消息处理器中,这个mTraversalRunnable是一个实现了Runnable接口的对象,所以从这里调用了TraversalRunnable中的run方法。
16.TraversalRunnable.run
1 2 3
| public void run() { doTraversal(); }
|
这里很简单就是调用了doTraversal方法。
17.ViewRootImpl.doTraversal
1 2 3 4 5 6 7 8 9 10 11
| void doTraversal() { if (mTraversalScheduled) { mTraversalScheduled = false; ...
performTraversals();
... } }
|
这里主要是调用performTraversals方法,开始View的真正绘制。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54
| private void performTraversals() { ...
if (mFirst || windowShouldResize || insetsChanged || viewVisibilityChanged || params != null || mForceNextWindowRelayout) { ...
if (!mStopped || mReportNextDraw) { ... if (focusChangedDueToTouchMode || mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight() || contentInsetsChanged || updatedConfiguration) { ...
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
... } } } else { ... }
... if (didLayout) { performLayout(lp, mWidth, mHeight); ... } ... if (!cancelDraw && !newSurface) { ...
performDraw(); } else { ... }
mIsInTraversal = false; }
|
这里开始进入测量,布局,绘制的过程,里面通过各个条件来判断需要执行哪一步或者哪几部,因为这一段主要是设计测量、布局、绘制,所以这章就不分析了,这个方法放到《Android系统源码分析–View绘制流程之-onMeasure》一章讲解。
我们下一章开始分析《Android系统源码分析–View绘制流程之-inflate》。
参考文章:
代码地址:
直接拉取导入开发工具(Intellij idea或者Android studio)
Android_Framework_Source
注
Android开发群:192508518
微信公众账号:Code-MX
注:本文原创,转载请注明出处,多谢。