这是什么东西?如果已经看过源码的同学,亦或者有多年开发经验的人,自然不必多说。但如果没有,这里建议先看一下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 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进行的操作。
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
@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(渲染线程)去等待处理。单单这很短一段代码,我们已经有很多疑问了。我们来一一揭开。
doTraversals
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。
- 首先执行测量,根据官方给的注解也可以看出来,测量的是视图大小,传入高宽的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