赞
踩
开始将ViewRootImpl了,总会担心讲不好,讲不好的点有两个,一个是自己理解不够,第二个则是自己表达不够,不能讲明白这里面的东西,不能讲到大家想要了解的东西。不过我会放下心来,慢慢讲,让大家对ViewRootImpl有个自己的了解。
这是什么东西?如果已经看过源码的同学,亦或者有多年开发经验的人,自然不必多说。但如果没有,这里建议先看一下Android基础知识之Window(一)。
从上篇文章末尾可以知道,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 things try { // (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进行的操作。
总算进来了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 @UnsupportedAppUsage public 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 @UnsupportedAppUsage InputMethodManager.ensureDefaultInstanceForDefaultDisplayIfNecessary(); IWindowManager windowManager = getWindowManagerService(); sWindowSession = windowManager.openSession( new IWindowSessionCallback.Stub() { @Override public 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 @Override public 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(渲染线程)去等待处理。单单这很短一段代码,我们已经有很多疑问了。我们来一一揭开。
final class TraversalRunnable implements Runnable { @Override public 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 be performMeasure(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。
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源码
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。