作者:Raina链接:https://juejin.cn/post/6844904086265937934已授权,著作权归作者所有。
我们本文要回答的问题:
-
setContentView原理是什么?
-
Activity在onResume之后才会显示的原因是什么?
-
ViewRoot是干嘛的,是ViewTree的rootview吗?
一、整体流程图
兄弟们,先拿上这份“地图”,毕竟“源码”这个地方吧,九曲十八弯的,看着地图走,才不会走丢。

大概说一下流程(本文最后也会再重复一次):
1、创建PhoneWindow对象,往PhoneWindow对象里面添加了一个DecorView2、为DecorView绑定一个ViewRootImpl,由这个ViewRootImpl负责View的绘制和刷新3、ViewRootImpl通过IWindowSession向WMS发起Binder调用,而WMS也会通过IWindow向应用端发起调用 4、ViewRootImpl会在WMS里面注册一个窗口,然后由WMS统一的管理所有窗口的大小,位置和层级5、在第一次绘制时,ViewRootImpl还会向WMS申请一块Surface(WMS向Surface申请),有了Surface之后,应用端就可以进行绘制了6、应用端绘制完之后,SurfaceFlinger就会按照WMS里面提供的层级等信息进行合成,最终显示
为了让大家在跟踪源码的时候不会迷路,先跟大家科普路下路上的这几个“门卫大哥”:
-
PhoneWindow:Window在手机端的唯一实现类,WMS管理的就是一个个Window,而不是View
-
DecorView:顶层的View,我们平时setContentView设置的View对应的就是蓝色的ContentView,
-
ViewRootImpl:这个哥们是View和WMS通信的桥梁,每次View想跟WMS通信要通过它;每次WMS想让View更新也要通过它;一个DecorView对应一个ViewRootImpl
-
SurfaceFlinger:负责Surface的合成,一块Surface就是一块画布,应用端其实都是在Surface上绘制图形
-
WindowManagerService:我们口中经常说的WMS,主要负责管理窗口,,并不负责view的绘制。
以下是WMS的主要作用:

对了,我采用的源码是Android 28的。
下面正式开始~ Action, go ~
二、源码分析
2.1 setContentView
// 类:---> View.javapublic void setContentView(int layoutResId){ getWindow().setContentView(layoutResId); ...}//类:---->PhoneWindow.javapublic void setContentView(int layoutResID) { if (mContentParent == null) { installDecor(); } ... mLayoutInflater.inflate(layoutResID, mContentParent); ...}
我们可以看到,通过mLayoutInflater.inflate(layoutResID, mContentParent)进行加载布局,这里有两个参数,一个是layoutId,另外一个是mContentParent,这个mContentParent又是什么?
可以看到mContentParent是通过installDecor()初始化的,继续跟吧。
2.2 installDecor
---> 类:PhoneWindow.java private void installDecor() { if (mDecor == null) { //1、new 一个 DecorView mDecor = generateDecor(-1); .... } if (mContentParent == null) { //2、inflate 布局 mContentParent = generateLayout(mDecor); .... } }//1、new 一个 DecorView protected DecorView generateDecor(int featureId) { .... return new DecorView(context, featureId, this, getAttributes()); } //2、inflate 布局 protected ViewGroup generateLayout(DecorView decor) { .... // Inflate the window decor. //3、inflate 布局,并添加到decorView中 mDecor.onResourcesLoaded(mLayoutInflater, layoutResource); //4、找到contentView,返回 ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT); ... return contentParent; } ///----->DecorView.java void onResourcesLoaded(LayoutInflater inflater, int layoutResource){ //3、 final View root = inflater.inflate(layoutResource, null); ... // Put it below the color views. addView(root, 0, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)); }
我们理一下流程,以上代码主要做了:
-
创建一个DecorView,这个DecorView本质是一个FrameLayout
-
inflate一个布局root
-
把root 添加到decorView里面
-
通过findViewById找到contentParent,其实就是id为R.id.content的View
提个小问题提提神,大家知道为什么在实现沉浸式布局的时候,通过getWindow().requestFeature(Window.FEATURE_NO_TITLE);等配置为什么要在setContentView()之前吗?看看installDecor()方法你就知道了~

到这一步,整个界面就加载完成了吗?too naive,这一步只是初始化了View Tree,整个界面还没有显示出来,界面真正要显示出来还要看onResume()这个生命周期里做了啥。继续跟吧~
2.3 handleResumeActivity(IBinder token)
---->类:ActivityThread.java@Override public void handleResumeActivity(IBinder token,..){ // TODO Push resumeArgs into the activity for consideration //1、 final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason); final Activity a = r.activity; if (r.window == null && !a.mFinished && willBeVisible) { r.window = r.activity.getWindow(); View decor = r.window.getDecorView(); decor.setVisibility(View.INVISIBLE); ViewManager wm = a.getWindowManager(); WindowManager.LayoutParams l = r.window.getAttributes(); a.mDecor = decor; if (a.mVisibleFromClient) { if (!a.mWindowAdded) { a.mWindowAdded = true; //2、 wm.addView(decor, l); } else { ... } } if (!r.activity.mFinished && willBeVisible && r.activity.mDecor != null && !r.hideForNow) { ... if (r.activity.mVisibleFromClient) { //3、 r.activity.makeVisible(); } } }
整个流程分为三步:
-
回调Activity的onResume()方法
-
调用WindowManager的addView()方法
-
设置Activity为visible
我们接下来要重点看的是addView()这个方法。为什么呢?因为第一步就是回调,第三步是让它可见,那就说明触发界面绘制的流程是在第二步。所以,我们接下来要跟踪第二步。
2.4 wm.addView(decor, l)
---->WindowManagerImpl.java @Override public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) { applyDefaultToken(params); mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow); }----> WindowManagerGlobal.java public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) { .... ViewRootImpl root; synchronized (mLock) { root = new ViewRootImpl(view.getContext(), display); ... // do this last because it fires off messages to start doing things //注释大意:最后才用调用这个方法,因为它会发送消息开始干活 try { root.setView(view, wparams, panelParentView); } .... } }
这个方法就做了两个操作:
1、创建了ViewRootImpl对象(所以,ViewRootImpl和Window的关系是一对一,第二次说明了)2、调用ViewRootImpl的setView方法
2.5 viewRootImpl.setView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) { synchronized (this) { if (mView == null) { mView = view; // Schedule the first layout -before- adding to the window // manager, to make sure we do the relayout before receiving // any other events from the system. requestLayout(); try{ ... res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mWinFrame, mAttachInfo.mContentInsets, mAttachInfo.mStableInsets, mAttachInfo.mOutsets, mAttachInfo.mDisplayCutout, mInputChannel); ... } ... } }
setContentView这个方法,重点就做了这么几件事:
1、给mView赋值2、调用requestlayout3、调用windowSession的addToDisplay
第一个分叉路口来了,我们要分开看requestlayout()方法和addToDisplay()方法。
分岔路 2.5.1 requestlayout()
----> ViewRootImpl.java @Override public void requestLayout() { if (!mHandlingLayoutInLayoutRequest) { //检查是否是主线程。下次面试官问你ui不能在子线程更新 //的异常在哪里抛出的,勇敢的告诉面试官,在ViewRootImpl //的requestLayout() checkThread(); mLayoutRequested = true; //1 scheduleTraversals(); } } //1 void scheduleTraversals() { if (!mTraversalScheduled) { ... mChoreographer.postCallback( Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null); .... } }
在这一步里面会post一个callback,这个callback在下一次Vsync信号到来的时候会被调用(关于下一次Vsync信号来了怎么回调到Java层,请看这篇文章)。Anyway,当Vsync信号到来后,最终会执行mTraversalRunnable的run方法,我们具体来看一下:
----> ViewRootImpl.java final class TraversalRunnable implements Runnable { @Override public void run() { doTraversal(); } }void doTraversal() {... performTraversals();...}private void performTraversals() { ... relayoutResult = relayoutWindow(params, viewVisibility, insetsPending); ... //绘制三部曲:measure、layout、draw performMeasure(childWidthMeasureSpec, childHeightMeasureSpec); ... performLayout(lp, mWidth, mHeight); ... performDraw(); ...}
measure、layout、draw这几个方法大家都很熟悉了,所以我们重点看relayoutWindow这个方法做了什么事情?为什么它是放在measure、layout、draw之前的?
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility, boolean insetsPending) throws RemoteException { //看到RemoteException,掐指一算,估摸有ipc通信 ... int relayoutResult = mWindowSession.relayout(..., mSurface); ... }
其实这一步的作用就一个————向WMS申请一块Surface。
这里不是有一个Surface了吗?不就是那个mSurface??(黑人问号脸❓)
其实,此时这个mSurface是一块空的Surface,它只是被创建了出来,但是还没有分配内存空间,还是空白的。
public final Surface mSurface = new Surface();
应用端需要调用WMS的relayoutWindow给Surface进行赋值,关于Surface的传输和赋值过程我就不分析了,大家可以看文章。
https://juejin.cn/post/6844904084307181581
Surface赋值初始化成功后,客户端就可以进行绘制了。当绘制完成后,会调用unlockAndPostCanvas()来通知SurfaceFlinger进行合成。现在,requestLayout()这个方法就算看完了,接下来去另一个分岔路口。
分岔路 2.5.2 windowSession.addToDisplay()
这个WindowSession是一个AIDL,它的作用是用来给应用和WMS通信的,它真正的实现类是Session,具体找寻的流程如下:



---> Session.java@Override public int addToDisplay(IWindow window, int seq, WindowManager.LayoutParams attrs,...) { //这里要注意这个IWindow,IWindow是WMS向应用端发起Binder调用的 //Session把它传给WMS是为了以后WMS可以主动向应用端发起调用 return mService.addWindow(this, window, seq, attrs, ...); }
我们可以看到,最终调用的是WMS的addView方法,调用这个方法的作用是向WMS注册窗口对象,然后就会WMS统一去协调这些Window的层级、大小和位置。
对WMS来说,它并不关心我们应用本身的Window对象或者View对象,对它来说,它一个重要的功能就是给应用端的Window分配Surface,并且,控制这些Surface的显示顺序,位置和尺寸。
当应用端在Surface绘制完了之后,SurfaceFlinger就会按照这些图像数据,按照WMS提供的尺寸、层级和位置等等进行合成,最后写到屏幕缓冲区里面,绘制出来。
2.6 小结

handleResumeActivity()这个方法整体流程大概可以概括为以上几步。
整个绘制流程比较长,等源码看的差不多了,再考虑写一个系列吧~
三、总结
到这里其实整个流程就算完成了,来总结一下:


1、创建了PhoneWindow对象,往PhoneWindow对象里面添加了一个DecorView2、为DecorView绑定一个ViewRootImpl,由这个ViewRootImpl负责View的绘制和刷新3、ViewRootImpl通过IWindowSession向WMS发起Binder调用,而WMS也会通过IWindow向应用端发起调用 4、ViewRootImpl会在WMS里面注册一个窗口,然后由WMS统一的管理所有窗口的大小,位置和层级5、在第一次绘制时,ViewRootImpl还会向WMS申请一块Surface,有了Surface之后,应用端就可以进行绘制了6、绘制完之后,SurfaceFlinger就会按照WMS里面提供的层级等信息进行合成,最终显示
四、问题解答
1、setContentView的原理是什么?
setContentView()的原理是主要是:(1)创建DecorView和ViewRootImpl,并将它们两者进行绑定(2)通过inflate方法创建ViewTree,此时还没有显示
2、Activity在onResume之后才会显示的原因是什么?
在onResume方法中才会调用ViewRootImpl的performTraversal()方法进行界面绘制以及makeVisible方法进行界面显示;
3、ViewRoot是干嘛的,是ViewTree的RootView吗?
ViewRoot,或者说它的实现类ViewRootImpl,跟View没有任何关系。ViewRoot只是ViewTree的管理者,而不是ViewTree的根节点。真正的根节点是DecorView
推荐阅读
(点击标题可跳转阅读)
Handler相关面试题你答对多少?子线程和主线程是如何切换的?
觉得本文对你有帮助?请分享给更多人
wx号:gulinhai531
顾林海公众号
不定期推出优质文
章,喜欢的朋友们
给我个好看。
好文章,我在看❤️