概述
开始将ViewRootImpl了,总会担心讲不好,讲不好的点有两个,一个是自己理解不够,第二个则是自己表达不够,不能讲明白这里面的东西,不能讲到大家想要了解的东西。不过我会放下心来,慢慢讲,让大家对ViewRootImpl有个自己的了解。
ViewRootImpl
这是什么东西?如果已经看过源码的同学,亦或者有多年开发经验的人,自然不必多说。但如果没有,这里建议先看一下Android基础知识之Window(一)。
WindowManagerGlobal
从上篇文章末尾可以知道,View的添加、更新、删除操作是WindowManagerGlobal来实现的。那么我们就看看这三个方法到底做了什么吧!我们一段一段分析
// WindowManagerGlobal.java
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow, int userId) {if (view == null) {throw new IllegalArgumentException("view must not be null");}if (display == null) {throw new IllegalArgumentException("display must not be null");}if (!(params instanceof WindowManager.LayoutParams)) {throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");}final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;if (parentWindow != null) {parentWindow.adjustLayoutParamsForSubWindow(wparams);} else {// If there's no parent, then hardware acceleration for this view is// set from the application's hardware acceleration setting.final Context context = view.getContext();if (context != null&& (context.getApplicationInfo().flags& ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;}}ViewRootImpl root;View panelParentView = null;/** 省略 **/if (windowlessSession == null) {root = new ViewRootImpl(view.getContext(), display);} else {root = new ViewRootImpl(view.getContext(), display,windowlessSession, new WindowlessWindowLayout());}// (1)view设置layoutparams参数view.setLayoutParams(wparams);// (2)添加view、root、wparams到它们各自的集合中去mViews.add(view);mRoots.add(root);mParams.add(wparams);// do this last because it fires off messages to start doing thingstry {// (3)root(ViewRootImpl)调用它的setView方法root.setView(view, wparams, panelParentView, userId);} catch (RuntimeException e) {final int viewIndex = (index >= 0) ? index : (mViews.size() - 1);// BadTokenException or InvalidDisplayException, clean up.if (viewIndex >= 0) {removeViewLocked(viewIndex, true);}throw e;}}
先看添加view,平常使用ViewGroup#addView会传递View和params两个参数,在这里的addView方法中,我们也可以看到(1),把传入的layoutParams设置给传入的view。接着(2)管理了三个集合,用来存储添加的view、root(ViewRootImpl)、params。最后(3)调用创建的ViewRootImpl的setView方法,把addView接收的大部分的参数传递给它。这里我们第一次接触到了ViewRootImpl这个类。
通过上述源码分析可以看出,每次Windwo#addView(常见的是Activity#setContentView)的时候,都会创建一个ViewRootImpl对象,并且把它缓存起来。通过使用三个ArrayList集合缓存View、Root、Params,可以在其他操作的时候,从缓存中快速取出与之对应的对象进行操作。
public void updateViewLayout(View view, ViewGroup.LayoutParams params) {if (view == null) {throw new IllegalArgumentException("view must not be null");}if (!(params instanceof WindowManager.LayoutParams)) {throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");}final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams)params;view.setLayoutParams(wparams);synchronized (mLock) {int index = findViewLocked(view, true);ViewRootImpl root = mRoots.get(index);mParams.remove(index);mParams.add(index, wparams);root.setLayoutParams(wparams, false);}}@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)public void removeView(View view, boolean immediate) {if (view == null) {throw new IllegalArgumentException("view must not be null");}synchronized (mLock) {int index = findViewLocked(view, true);View curView = mRoots.get(index).getView();removeViewLocked(index, immediate);if (curView == view) {return;}throw new IllegalStateException("Calling with view " + view+ " but the ViewAncestor is attached to " + curView);}}
根据updateViewLayout和removeView两个方法,也确实证实了,从mRoots取出对应的ViewRootImpl进行的操作。
setView
总算进来了ViewRootImpl,这里只跟踪setView的流程,关于updateViewLayout和removeView可以自行分析一下。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,int userId) {/** 省略 **/// 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();InputChannel inputChannel = null;if ((mWindowAttributes.inputFeatures& WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) {inputChannel = new InputChannel();}mForceDecorViewVisibility = (mWindowAttributes.privateFlags& PRIVATE_FLAG_FORCE_DECOR_VIEW_VISIBILITY) != 0;if (mView instanceof RootViewSurfaceTaker) {PendingInsetsController pendingInsetsController =((RootViewSurfaceTaker) mView).providePendingInsetsController();if (pendingInsetsController != null) {pendingInsetsController.replayAndAttach(mInsetsController);}}try {mOrigWindowType = mWindowAttributes.type;mAttachInfo.mRecomputeGlobalAttributes = true;collectViewAttributes();adjustLayoutParamsForCompatibility(mWindowAttributes);controlInsetsForCompatibility(mWindowAttributes);Rect attachedFrame = new Rect();final float[] compatScale = { 1f };res = mWindowSession.addToDisplayAsUser(mWindow, mWindowAttributes,getHostVisibility(), mDisplay.getDisplayId(), userId,mInsetsController.getRequestedVisibleTypes(), inputChannel, mTempInsets,mTempControls, attachedFrame, compatScale);if (!attachedFrame.isValid()) {attachedFrame = null;}if (mTranslator != null) {mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls.get());mTranslator.translateRectInScreenToAppWindow(attachedFrame);}mTmpFrames.attachedFrame = attachedFrame;mTmpFrames.compatScale = compatScale[0];mInvCompatScale = 1f / compatScale[0];} catch (RemoteException | RuntimeException e) {mAdded = false;mView = null;mAttachInfo.mRootView = null;mFallbackEventHandler.setView(null);unscheduleTraversals();setAccessibilityFocus(null, null);throw new RuntimeException("Adding window failed", e);} finally {if (restore) {attrs.restore();}}/** 省略 **/
}
这里的代码篇幅有点长,所以省略了很多相对不是重要的内容。上述代码需要关心两个地方,其一是requestLayout这个方法;这其二是mWindowSession.addToDisplayAsUser这个调用。
首先说一下第二点,因为这次只讲ViewRootImpl,不会讲到WMS去,这个调用会一笔带过,重点会讲一下requestLayout这个函数。
mWindowSession.addToDisplayAsUser是一个binder通信,如果还有不了解的,可以去补一下binder相关的知识。刚刚在讲WindowManagerGlobal的时候,漏了一个东西就是它持有了WMS(WindowManagerService)的代理对象。这是一个静态方法,它不仅仅是WindowManagerGlobal自己使用,还提供给外部,比如ViewRootImpl使用,通过getWindowSession就可以拿到WMS的代理对象进行binder通信。
// WindowManagerGlobal.java@UnsupportedAppUsagepublic static IWindowSession getWindowSession() {synchronized (WindowManagerGlobal.class) {if (sWindowSession == null) {try {// Emulate the legacy behavior. The global instance of InputMethodManager// was instantiated here.// TODO(b/116157766): Remove this hack after cleaning up @UnsupportedAppUsageInputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary();IWindowManager windowManager = getWindowManagerService();sWindowSession = windowManager.openSession(new IWindowSessionCallback.Stub() {@Overridepublic void onAnimatorScaleChanged(float scale) {ValueAnimator.setDurationScale(scale);}});} catch (RemoteException e) {throw e.rethrowFromSystemServer();}}return sWindowSession;}}
显然addToDisplayAsUser就是调用WMS的addToDisplay相关的方法,传入了window,window属性,displayId,InputChannel等信息下去。相关内容在下一篇文章会讲到。这里用黑体加粗了InputChannel,也是一个很重要的概念,之后写相关事件输入的文章会好好讲讲。
接着我们来看requestLayout的源码是怎样实现的
// ViewRootImpl.java@Overridepublic void requestLayout() {if (!mHandlingLayoutInLayoutRequest) {checkThread();mLayoutRequested = true;scheduleTraversals();}}@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)void scheduleTraversals() {if (!mTraversalScheduled) {mTraversalScheduled = true;mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);notifyRendererOfFramePending();pokeDrawLockIfNeeded();}}
这两段代码乍得一看很短,但实际上看也的确很短,可是能讲的内容确实很多的。我们慢慢分析,首先看到checkThread,这个有点熟悉。
void checkThread() {Thread current = Thread.currentThread();if (mThread != current) {throw new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views."+ " Expected: " + mThread.getName()+ " Calling: " + current.getName());}}
关于这个打印,我们是不是很熟悉,获取当前线程,判断当前线程会创建View的线程是不是同一个,否则抛出异常。这个打印应该不少见到过。
接着执行scheduleTraversals(安排遍历),这里的遍历是遍历什么呢【1】?这里我们先不表。继续看它里面做了什么。设置状态我们先不管,可以看到Handler先获取了Looper,再获取了Queue,然后调用了postSyncBarrier(插入同步屏障),这个Handler是主线程创建的Handler,所以默认使用的是主线程的Looper,通过消息队列插入了一个同步屏障,也就是说拦截了所有的同步消息,接下来会优先处理异步消息。这是为什么呢【2】?(这里有对同步屏障不了解的可以去看看Handler有关的内容)
再然后通过Choreography#postCallback发送一个回调,传入mTraversalRunnable来接收这个回调。这里有个重点,Choreography#postCallback可以理解为是应用层去请求Vsync信号的过程,我们知道Vsync信号在16.67ms会上来一次,也就是说这个回调每16.67ms period会上来一次。这里的Vsync信号概念以后会详细的讲解一下,这里暂时可以这么去理解。
最后通知render thread(渲染线程)去等待处理。单单这很短一段代码,我们已经有很多疑问了。我们来一一揭开。
doTraversals
final class TraversalRunnable implements Runnable {@Overridepublic void run() {doTraversal();}}final TraversalRunnable mTraversalRunnable = new TraversalRunnable();void doTraversal() {if (mTraversalScheduled) {mTraversalScheduled = false;mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);if (mProfile) {Debug.startMethodTracing("ViewAncestor");}performTraversals();if (mProfile) {Debug.stopMethodTracing();mProfile = false;}}}
刚刚上面有提到,这个runnable每16.67ms周期上来一次,每次都会执行doTraversal(开始遍历)。在遍历执行前,会移除以前添加的同步屏障,这短短一个16.67ms周期,就先添加屏障,再移除屏障,这里就解释了之前的第二点问题,同步屏障是为了处理异步消息的,而postCallback回调的就是Vsync信号,那么由此可见Vsync信号上来的方式是异步消息的方式发送上来的。然后开始执行performTraversals(执行遍历)
private void performTraversals() {/** 省略 **/if (mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight()|| dispatchApplyInsets || updatedConfiguration) {int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width,lp.privateFlags);int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height,lp.privateFlags);if (DEBUG_LAYOUT) Log.v(mTag, "Ooops, something changed! mWidth="+ mWidth + " measuredWidth=" + host.getMeasuredWidth()+ " mHeight=" + mHeight+ " measuredHeight=" + host.getMeasuredHeight()+ " dispatchApplyInsets=" + dispatchApplyInsets);// Ask host how big it wants to beperformMeasure(childWidthMeasureSpec, childHeightMeasureSpec);}/** 省略 **/final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);boolean triggerGlobalLayoutListener = didLayout|| mAttachInfo.mRecomputeGlobalAttributes;if (didLayout) {performLayout(lp, mWidth, mHeight); } /** 省略 **/ if (!performDraw() && mActiveSurfaceSyncGroup != null) {mActiveSurfaceSyncGroup.markSyncReady();}
}
由于这个方法实在是长,因此省略了很多细节的东西。但根据源码来看,主要函数就这三个performMeasure - performLayout - performDraw。这里解开了之前说的第一个问题,为什么是安排遍历,performMeasure、performLayout 、performDraw都会根据ViewTree递归调用它们的子View,依次循环的执行它们的measure、layout、draw。
- 首先执行测量,根据官方给的注解也可以看出来,测量的是视图大小,传入高宽的MeasureSpec,这个值有32位,高两位是表示View的约束模式,低三十位是表示测量的大小数值,执行View#onMeasure的测量逻辑
- 接着执行布局,可以理解为摆放位置,根据起始坐标和高宽,确定整个view的位置,执行view#onLayout的布局逻辑
- 最后执行绘制,判断是否开启了硬件支持,决定使用硬件绘制还是软件绘制,执行view#onDraw里的逻辑开始绘制
void performDraw() {if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {if (isHardwareEnabled()) {// 硬件加速绘制mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);} else {// 软件绘制drawSoftware();}}
}
我们在分析一下这段代码,通过硬件支持的判断,执行硬件加速绘制和软件绘制。先说软件绘制,ViewRootImpl管理了一个Surface和SurfaceControl,经过软件绘制,View会绘制到Canvas上面,这个Canvas是Surface提供的,也就是说明绘制到了Surface上面。再说说硬件绘制,由ThreadedRenderer#draw进行绘制,通过view获取displayList,一个显示列表,由显示列表获取Canvas执行View#onDraw,最后将绘制命令提交给GPU,执行render开始绘制。
软件绘制
View#onDraw - Canvas - Surface - ViewRootImpl
硬件绘制
View#onDraw - Canvas - Surface - displayList - ThreadedRenderer
总结
1、WIndowManagerGlobal的添加、更新、删除由ViewRootImpl来负责
2、每个Window都对应了一个ViewRootImpl的实例对象
3、ViewRootImpl通过WMS代理对象把Window对象传递给了WMS
4、requestLayout开始周期性的请求Vsync信号并执行doTraversals
5、performTraversals开始了之后的measure、layout、draw操作
至此,我希望讲的ViewRootImpl就讲完了,这里还留了很多谜题,在一下篇文章中,会开始讲WMS和ViewRootImpl通信的流程,也就是我们addToDisplay没说完的部分。其中ViewRootImpl可能也会涉及两个重要的对象Surface和SurfaceControl。
附
AOSP14源码