android 14显示流程分析
6. 应用UI布局与绘制
接着android应用启动流程的分析。
应用主线程中在执行Activity
的Resume流程的最后,会创建ViewRootImpl
对象并调用其setView函数,从此并开启了应用界面UI布局与绘制的流程。在开始讲解这个过程之前,我们先来整理一下前面代码中讲到的这些概念,如Activity
、PhoneWindow
、DecorView
、ViewRootImpl
、WindowManager
它们之间的关系与职责,因为这些核心类基本构成了Android系统的GUI显示系统在应用进程侧的核心架构,其整体架构如下图所示:
Window
是一个抽象类,通过控制DecorView
提供了一些标准的UI方案,比如背景、标题、虚拟按键等
,而PhoneWindow
是Window
的唯一实现类,在Activity
创建后的attach流程中创建,应用启动显示的内容装载到其内部的mDecor
(DecorView
);DecorView
是整个界面布局View控件树的根节点,通过它可以遍历访问到整个View控件树上的任意节点;WindowManager
是一个接口,继承自ViewManager
接口,提供了View
的基本操作方法;WindowManagerImp
实现了WindowManager
接口,内部通过组合
方式持有WindowManagerGlobal
,用来操作View
;WindowManagerGlobal
是一个全局单例,内部可以通过ViewRootImpl
将View
添加至窗口
中;ViewRootImpl
是所有View
的Parent
,用来总体管理View
的绘制以及与系统WMS
窗口管理服务的IPC交互从而实现窗口
的开辟;ViewRootImpl
是应用进程运转的发动机,可以看到ViewRootImpl
内部包含mView
(就是DecorView
)、mSurface
、Choregrapher
,mView
代表整个控件树,mSurfacce
代表画布,应用的UI渲染会直接放到mSurface
中,Choregorapher
使得应用请求vsync
信号,接收信号后开始渲染流程;
我们从ViewRootImpl
的setView流程继续结合代码往下看:
ViewRootImpl
setView
// frameworks/base/core/java/android/view/ViewRootImpl.java
/**
* We have one child
*/
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView,
int userId) {
synchronized (this) {
if (mView == null) {
mView = view;
mViewLayoutDirectionInitial = mView.getRawLayoutDirection();
mFallbackEventHandler.setView(view);
mWindowAttributes.copyFrom(attrs);
if (mWindowAttributes.packageName == null) {
mWindowAttributes.packageName = mBasePackageName;
}
attrs = mWindowAttributes;
setTag();
mFpsTraceName = "FPS of " + getTitle();
mLargestViewTraceName = "Largest view percentage(per hundred) of " + getTitle();
if (DEBUG_KEEP_SCREEN_ON && (mClientWindowLayoutFlags
& WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) != 0
&& (attrs.flags&WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON) == 0) {
Slog.d(mTag, "setView: FLAG_KEEP_SCREEN_ON changed from true to false!");
}
// Keep track of the actual window flags supplied by the client.
mClientWindowLayoutFlags = attrs.flags;
setAccessibilityFocus(null, null);
if (view instanceof RootViewSurfaceTaker) {
mSurfaceHolderCallback =
((RootViewSurfaceTaker)view).willYouTakeTheSurface();
if (mSurfaceHolderCallback != null) {
mSurfaceHolder = new TakenSurfaceHolder();
mSurfaceHolder.setFormat(PixelFormat.UNKNOWN);
mSurfaceHolder.addCallback(mSurfaceHolderCallback);
}
}
// Compute surface insets required to draw at specified Z value.
// TODO: Use real shadow insets for a constant max Z.
if (!attrs.hasManualSurfaceInsets) {
attrs.setSurfaceInsets(view, false /*manual*/, true /*preservePrevious*/);
}
CompatibilityInfo compatibilityInfo =
mDisplay.getDisplayAdjustments().getCompatibilityInfo();
mTranslator = compatibilityInfo.getTranslator();
// If the application owns the surface, don't enable hardware acceleration
if (mSurfaceHolder == null) {
// While this is supposed to enable only, it can effectively disable
// the acceleration too.
// 开启绘制硬件加速,初始化RenderThread渲染线程运行环境
enableHardwareAcceleration(attrs);
final boolean useMTRenderer = MT_RENDERER_AVAILABLE
&& mAttachInfo.mThreadedRenderer != null;
if (mUseMTRenderer != useMTRenderer) {
// Shouldn't be resizing, as it's done only in window setup,
// but end just in case.
endDragResizing();
mUseMTRenderer = useMTRenderer;
}
}
boolean restore = false;
if (mTranslator != null) {
mSurface.setCompatibilityTranslator(mTranslator);
restore = true;
attrs.backup();
mTranslator.translateWindowLayout(attrs);
}
if (DEBUG_LAYOUT) Log.d(mTag, "WindowLayout in setView:" + attrs);
mSoftInputMode = attrs.softInputMode;
mWindowAttributesChanged = true;
mAttachInfo.mRootView = view;
mAttachInfo.mScalingRequired = mTranslator != null;
mAttachInfo.mApplicationScale =
mTranslator == null ? 1.0f : mTranslator.applicationScale;
if (panelParentView != null) {
mAttachInfo.mPanelParentWindowToken
= panelParentView.getApplicationWindowToken();
}
mAdded = true;
int res; /* = WindowManagerImpl.ADD_OKAY; */
// 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.
// 1.触发绘制动作
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 };
// 2.Binder调用访问系统窗口管理服务WMS接口,实现addWindow添加注册应用窗口的操作,并传入inputChannel用于接收触控事件
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();
}
}
mAttachInfo.mAlwaysConsumeSystemBars =
(res & WindowManagerGlobal.ADD_FLAG_ALWAYS_CONSUME_SYSTEM_BARS) != 0;
mPendingAlwaysConsumeSystemBars = mAttachInfo.mAlwaysConsumeSystemBars;
mInsetsController.onStateChanged(mTempInsets);
mInsetsController.onControlsChanged(mTempControls.get());
final InsetsState state = mInsetsController.getState();
final Rect displayCutoutSafe = mTempRect;
state.getDisplayCutoutSafe(displayCutoutSafe);
final WindowConfiguration winConfig = getCompatWindowConfiguration();
mWindowLayout.computeFrames(mWindowAttributes, state,
displayCutoutSafe, winConfig.getBounds(), winConfig.getWindowingMode(),
UNSPECIFIED_LENGTH, UNSPECIFIED_LENGTH,
mInsetsController.getRequestedVisibleTypes(), 1f /* compactScale */,
mTmpFrames);
setFrame(mTmpFrames.frame, true /* withinRelayout */);
registerBackCallbackOnWindow();
if (DEBUG_LAYOUT) Log.v(mTag, "Added window " + mWindow);
if (res < WindowManagerGlobal.ADD_OKAY) {
mAttachInfo.mRootView = null;
mAdded = false;
mFallbackEventHandler.setView(null);
unscheduleTraversals();
setAccessibilityFocus(null, null);
switch (res) {
case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
throw new WindowManager.BadTokenException(
"Unable to add window -- token " + attrs.token
+ " is not valid; is your activity running?");
case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
throw new WindowManager.BadTokenException(
"Unable to add window -- token " + attrs.token
+ " is not for an application");
case WindowManagerGlobal.ADD_APP_EXITING:
throw new WindowManager.BadTokenException(
"Unable to add window -- app for token " + attrs.token
+ " is exiting");
case WindowManagerGlobal.ADD_DUPLICATE_ADD:
throw new WindowManager.BadTokenException(
"Unable to add window -- window " + mWindow
+ " has already been added");
case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:
// Silently ignore -- we would have just removed it
// right away, anyway.
return;
case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:
throw new WindowManager.BadTokenException("Unable to add window "
+ mWindow + " -- another window of type "
+ mWindowAttributes.type + " already exists");
case WindowManagerGlobal.ADD_PERMISSION_DENIED:
throw new WindowManager.BadTokenException("Unable to add window "
+ mWindow + " -- permission denied for window type "
+ mWindowAttributes.type);
case WindowManagerGlobal.ADD_INVALID_DISPLAY:
throw new WindowManager.InvalidDisplayException("Unable to add window "
+ mWindow + " -- the specified display can not be found");
case WindowManagerGlobal.ADD_INVALID_TYPE:
throw new WindowManager.InvalidDisplayException("Unable to add window "
+ mWindow + " -- the specified window type "
+ mWindowAttributes.type + " is not valid");
case WindowManagerGlobal.ADD_INVALID_USER:
throw new WindowManager.BadTokenException("Unable to add Window "
+ mWindow + " -- requested userId is not valid");
}
throw new RuntimeException(
"Unable to add window -- unknown error code " + res);
}
registerListeners();
// We should update mAttachInfo.mDisplayState after registerDisplayListener
// because displayState might be changed before registerDisplayListener.
mAttachInfo.mDisplayState = mDisplay.getState();
if (mExtraDisplayListenerLogging) {
Slog.i(mTag, "(" + mBasePackageName + ") Initial DisplayState: "
+ mAttachInfo.mDisplayState, new Throwable());
}
if (view instanceof RootViewSurfaceTaker) {
mInputQueueCallback =
((RootViewSurfaceTaker)view).willYouTakeTheInputQueue();
}
if (inputChannel != null) {
if (mInputQueueCallback != null) {
mInputQueue = new InputQueue();
mInputQueueCallback.onInputQueueCreated(mInputQueue);
}
// 3.创建WindowInputEventReceiver对象,实现应用窗口接收触控事件
mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
Looper.myLooper());
if (ENABLE_INPUT_LATENCY_TRACKING && mAttachInfo.mThreadedRenderer != null) {
InputMetricsListener listener = new InputMetricsListener();
mHardwareRendererObserver = new HardwareRendererObserver(
listener, listener.data, mHandler, true /*waitForPresentTime*/);
mAttachInfo.mThreadedRenderer.addObserver(mHardwareRendererObserver);
}
// Update unbuffered request when set the root view.
mUnbufferedInputSource = mView.mUnbufferedInputSource;
}
// 4.设置DecorView的mParent为ViewRootImpl
view.assignParent(this);
mAddedTouchMode = (res & WindowManagerGlobal.ADD_FLAG_IN_TOUCH_MODE) != 0;
mAppVisible = (res & WindowManagerGlobal.ADD_FLAG_APP_VISIBLE) != 0;
if (mAccessibilityManager.isEnabled()) {
mAccessibilityInteractionConnectionManager.ensureConnection();
setAccessibilityWindowAttributesIfNeeded();
}
if (view.getImportantForAccessibility() == View.IMPORTANT_FOR_ACCESSIBILITY_AUTO) {
view.setImportantForAccessibility(View.IMPORTANT_FOR_ACCESSIBILITY_YES);
}
// Set up the input pipeline.
CharSequence counterSuffix = attrs.getTitle();
mSyntheticInputStage = new SyntheticInputStage();
InputStage viewPostImeStage = new ViewPostImeInputStage(mSyntheticInputStage);
InputStage nativePostImeStage = new NativePostImeInputStage(viewPostImeStage,
"aq:native-post-ime:" + counterSuffix);
InputStage earlyPostImeStage = new EarlyPostImeInputStage(nativePostImeStage);
InputStage imeStage = new ImeInputStage(earlyPostImeStage,
"aq:ime:" + counterSuffix);
InputStage viewPreImeStage = new ViewPreImeInputStage(imeStage);
InputStage nativePreImeStage = new NativePreImeInputStage(viewPreImeStage,
"aq:native-pre-ime:" + counterSuffix);
mFirstInputStage = nativePreImeStage;
mFirstPostImeInputStage = earlyPostImeStage;
mPendingInputEventQueueLengthCounterName = "aq:pending:" + counterSuffix;
if (!mRemoved || !mAppVisible) {
AnimationHandler.requestAnimatorsEnabled(mAppVisible, this);
} else if (LOCAL_LOGV) {
Log.v(mTag, "setView() enabling visibility when removed");
}
}
}
}
从以上代码可以看出ViewRootImpl
的setView内部关键流程如下:
- requestLayout()通过一系列调用触发界面绘制(measure、layout、draw)动作,下文会详细展开分析;
- 通过Binder调用访问系统窗口管理服务
WMS
的addWindow
接口,实现添加、注册应用窗口的操作,并传入本地创建inputChannel对象用于后续接收系统的触控事件,这一步执行完我们的View
就可以显示到屏幕上了。关于WMS
的内部实现流程也非常复杂,由于篇幅有限本文就不详细展开分析了。 - 创建WindowInputEventReceiver对象,封装实现应用窗口接收系统触控事件的逻辑;
- 执行view.assignParent(this),设置
DecorView
的mParent为ViewRootImpl
。所以,虽然ViewRootImpl
不是一个View
,但它是所有View
的顶层Parent
。
我们顺着ViewRootImpl
的requestLayout
动作继续往下看界面绘制的流程代码:
requestLayout
// frameworks/base/core/java/android/view/ViewRootImpl.java
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
// 检查当前UI绘制操作是否发生在主线程,如果发生在子线程则会抛出异常
checkThread();
mLayoutRequested = true;
// 触发绘制操作
scheduleTraversals();
}
}
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
// 注意此处会往主线程的MessageQueue消息队列中添加同步栏删,因为系统绘制消息属于异步消息,需要更高优先级的处理
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
// 通过Choreographer往主线程消息队列添加CALLBACK_TRAVERSAL绘制类型的待执行消息,用于触发后续UI线程真正实现绘制动作
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
Choreographer的作用
Choreographer
的引入,主要是配合系统Vsync
垂直同步机制(Android“黄油计划”中引入的机制之一,协调APP生成UI数据和SurfaceFlinger
合成图像,避免Tearing画面撕裂的现象),给上层 App 的渲染提供一个稳定的 Message
处理的时机,也就是 Vsync
到来的时候 ,系统通过对 Vsync
信号周期的调整,来控制每一帧绘制操作的时机。Choreographer
扮演 Android 渲染链路中承上启下的角色:
- 承上:负责接收和处理 App 的各种更新消息和回调,等到
Vsync
到来的时候统一处理。比如集中处理 Input(主要是 Input 事件的处理) 、Animation(动画相关)、Traversal(包括measure、layout、draw
等操作) ,判断卡顿掉帧情况,记录 CallBack 耗时等; - 启下:负责请求和接收 Vsync 信号。接收 Vsync 事件回调(通过
FrameDisplayEventReceiver
.onVsync
),请求Vsync
(FrameDisplayEventReceiver
.scheduleVsync
) 。
Choreographer
在收到CALLBACK_TRAVERSAL
类型的绘制任务后,其内部的工作流程如下图所示:
从以上流程图可以看出:ViewRootImpl
调用Choreographer
的postCallback
接口放入待执行的绘制消息后,Choreographer
会先向系统申请APP
类型的vsync
信号,然后等待系统vsync
信号到来后,去回调到ViewRootImpl
的doTraversal
函数中执行真正的绘制动作(measure、layout、draw)
我们接着ViewRootImpl的doTraversal函数的简化代码流程往下看:
doTraversal
// frameworks/base/core/java/android/view/ViewRootImpl.java
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
// 调用removeSyncBarrier及时移除主线程MessageQueue中的Barrier同步栏删,以避免主线程发生“假死”
mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
// 执行具体的绘制任务
performTraversals();
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
performTraversals
private void performTraversals() {
mLastPerformTraversalsSkipDrawReason = null;
// cache mView since it is used so much below...
final View host = mView;
if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals");
host.debug();
}
if (host == null || !mAdded) {
mLastPerformTraversalsSkipDrawReason = host == null ? "no_host" : "not_added";
return;
}
if (mNumPausedForSync > 0) {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.instant(Trace.TRACE_TAG_VIEW,
TextUtils.formatSimple("performTraversals#mNumPausedForSync=%d",
mNumPausedForSync));
}
if (DEBUG_BLAST) {
Log.d(mTag, "Skipping traversal due to sync " + mNumPausedForSync);
}
mLastPerformTraversalsSkipDrawReason = "paused_for_sync";
return;
}
mIsInTraversal = true;
mWillDrawSoon = true;
boolean cancelDraw = false;
String cancelReason = null;
boolean isSyncRequest = false;
boolean windowSizeMayChange = false;
WindowManager.LayoutParams lp = mWindowAttributes;
int desiredWindowWidth;
int desiredWindowHeight;
final int viewVisibility = getHostVisibility();
final boolean viewVisibilityChanged = !mFirst
&& (mViewVisibility != viewVisibility || mNewSurfaceNeeded
// Also check for possible double visibility update, which will make current
// viewVisibility value equal to mViewVisibility and we may miss it.
|| mAppVisibilityChanged);
mAppVisibilityChanged = false;
final boolean viewUserVisibilityChanged = !mFirst &&
((mViewVisibility == View.VISIBLE) != (viewVisibility == View.VISIBLE));
final boolean shouldOptimizeMeasure = shouldOptimizeMeasure(lp);
WindowManager.LayoutParams params = null;
Rect frame = mWinFrame;
if (mFirst) {
mFullRedrawNeeded = true;
mLayoutRequested = true;
final Configuration config = getConfiguration();
if (shouldUseDisplaySize(lp)) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
|| lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
// For wrap content, we have to remeasure later on anyways. Use size consistent with
// below so we get best use of the measure cache.
final Rect bounds = getWindowBoundsInsetSystemBars();
desiredWindowWidth = bounds.width();
desiredWindowHeight = bounds.height();
} else {
// After addToDisplay, the frame contains the frameHint from window manager, which
// for most windows is going to be the same size as the result of relayoutWindow.
// Using this here allows us to avoid remeasuring after relayoutWindow
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
}
// We used to use the following condition to choose 32 bits drawing caches:
// PixelFormat.hasAlpha(lp.format) || lp.format == PixelFormat.RGBX_8888
// However, windows are now always 32 bits by default, so choose 32 bits
mAttachInfo.mUse32BitDrawingCache = true;
mAttachInfo.mWindowVisibility = viewVisibility;
mAttachInfo.mRecomputeGlobalAttributes = false;
mLastConfigurationFromResources.setTo(config);
mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
// Set the layout direction if it has not been set before (inherit is the default)
if (mViewLayoutDirectionInitial == View.LAYOUT_DIRECTION_INHERIT) {
host.setLayoutDirection(config.getLayoutDirection());
}
host.dispatchAttachedToWindow(mAttachInfo, 0);
mAttachInfo.mTreeObserver.dispatchOnWindowAttachedChange(true);
dispatchApplyInsets(host);
if (!mOnBackInvokedDispatcher.isOnBackInvokedCallbackEnabled()
// Don't register compat OnBackInvokedCallback for windowless window.
// The onBackInvoked event by default should forward to host app, so the
// host app can decide the behavior.
&& mWindowlessBackKeyCallback == null) {
// For apps requesting legacy back behavior, we add a compat callback that
// dispatches {@link KeyEvent#KEYCODE_BACK} to their root views.
// This way from system point of view, these apps are providing custom
// {@link OnBackInvokedCallback}s, and will not play system back animations
// for them.
registerCompatOnBackInvokedCallback();
}
} else {
desiredWindowWidth = frame.width();
desiredWindowHeight = frame.height();
if (desiredWindowWidth != mWidth || desiredWindowHeight != mHeight) {
if (DEBUG_ORIENTATION) Log.v(mTag, "View " + host + " resized to: " + frame);
mFullRedrawNeeded = true;
mLayoutRequested = true;
windowSizeMayChange = true;
}
}
if (viewVisibilityChanged) {
mAttachInfo.mWindowVisibility = viewVisibility;
host.dispatchWindowVisibilityChanged(viewVisibility);
mAttachInfo.mTreeObserver.dispatchOnWindowVisibilityChange(viewVisibility);
if (viewUserVisibilityChanged) {
host.dispatchVisibilityAggregated(viewVisibility == View.VISIBLE);
}
if (viewVisibility != View.VISIBLE || mNewSurfaceNeeded) {
endDragResizing();
destroyHardwareResources();
}
if (shouldEnableDvrr() && viewVisibility == View.VISIBLE) {
// Boost frame rate when the viewVisibility becomes true.
// This is mainly for lanuchers that lanuch new windows.
boostFrameRate(FRAME_RATE_TOUCH_BOOST_TIME);
}
}
// Non-visible windows can't hold accessibility focus.
if (mAttachInfo.mWindowVisibility != View.VISIBLE) {
host.clearAccessibilityFocus();
}
// Execute enqueued actions on every traversal in case a detached view enqueued an action
getRunQueue().executeActions(mAttachInfo.mHandler);
if (mFirst) {
// make sure touch mode code executes by setting cached value
// to opposite of the added touch mode.
mAttachInfo.mInTouchMode = !mAddedTouchMode;
ensureTouchModeLocally(mAddedTouchMode);
}
boolean layoutRequested = mLayoutRequested && (!mStopped || mReportNextDraw);
if (layoutRequested) {
if (!mFirst) {
if (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT
|| lp.height == ViewGroup.LayoutParams.WRAP_CONTENT) {
windowSizeMayChange = true;
if (shouldUseDisplaySize(lp)) {
// NOTE -- system code, won't try to do compat mode.
Point size = new Point();
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
} else {
final Rect bounds = getWindowBoundsInsetSystemBars();
desiredWindowWidth = bounds.width();
desiredWindowHeight = bounds.height();
}
}
}
// Ask host how big it wants to be
// 1.从DecorView根节点出发,遍历整个View控件树,完成整个View控件树的measure测量操作
windowSizeMayChange |= measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight, shouldOptimizeMeasure);
}
if (collectViewAttributes()) {
params = lp;
}
if (mAttachInfo.mForceReportNewAttributes) {
mAttachInfo.mForceReportNewAttributes = false;
params = lp;
}
if (mFirst || mAttachInfo.mViewVisibilityChanged) {
mAttachInfo.mViewVisibilityChanged = false;
int resizeMode = mSoftInputMode & SOFT_INPUT_MASK_ADJUST;
// If we are in auto resize mode, then we need to determine
// what mode to use now.
if (resizeMode == WindowManager.LayoutParams.SOFT_INPUT_ADJUST_UNSPECIFIED) {
final int N = mAttachInfo.mScrollContainers.size();
for (int i=0; i<N; i++) {
if (mAttachInfo.mScrollContainers.get(i).isShown()) {
resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_RESIZE;
}
}
if (resizeMode == 0) {
resizeMode = WindowManager.LayoutParams.SOFT_INPUT_ADJUST_PAN;
}
if ((lp.softInputMode & SOFT_INPUT_MASK_ADJUST) != resizeMode) {
lp.softInputMode = (lp.softInputMode & ~SOFT_INPUT_MASK_ADJUST) | resizeMode;
params = lp;
}
}
}
if (mApplyInsetsRequested) {
dispatchApplyInsets(host);
if (mLayoutRequested) {
// Short-circuit catching a new layout request here, so
// we don't need to go through two layout passes when things
// change due to fitting system windows, which can happen a lot.
windowSizeMayChange |= measureHierarchy(host, lp,
mView.getContext().getResources(), desiredWindowWidth, desiredWindowHeight,
shouldOptimizeMeasure);
}
}
if (layoutRequested) {
// Clear this now, so that if anything requests a layout in the
// rest of this function we will catch it and re-run a full
// layout pass.
mLayoutRequested = false;
}
boolean windowShouldResize = layoutRequested && windowSizeMayChange
&& ((mWidth != host.getMeasuredWidth() || mHeight != host.getMeasuredHeight())
|| (lp.width == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.width() < desiredWindowWidth && frame.width() != mWidth)
|| (lp.height == ViewGroup.LayoutParams.WRAP_CONTENT &&
frame.height() < desiredWindowHeight && frame.height() != mHeight));
windowShouldResize |= mDragResizing && mPendingDragResizing;
// Determine whether to compute insets.
// If there are no inset listeners remaining then we may still need to compute
// insets in case the old insets were non-empty and must be reset.
final boolean computesInternalInsets =
mAttachInfo.mTreeObserver.hasComputeInternalInsetsListeners()
|| mAttachInfo.mHasNonEmptyGivenInternalInsets;
boolean insetsPending = false;
int relayoutResult = 0;
boolean updatedConfiguration = false;
final int surfaceGenerationId = mSurface.getGenerationId();
final boolean isViewVisible = viewVisibility == View.VISIBLE;
boolean surfaceSizeChanged = false;
boolean surfaceCreated = false;
boolean surfaceDestroyed = false;
// True if surface generation id changes or relayout result is RELAYOUT_RES_SURFACE_CHANGED.
boolean surfaceReplaced = false;
final boolean windowAttributesChanged = mWindowAttributesChanged;
if (windowAttributesChanged) {
mWindowAttributesChanged = false;
params = lp;
}
if (params != null) {
if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0
&& !PixelFormat.formatHasAlpha(params.format)) {
params.format = PixelFormat.TRANSLUCENT;
}
adjustLayoutParamsForCompatibility(params);
controlInsetsForCompatibility(params);
if (mDispatchedSystemBarAppearance != params.insetsFlags.appearance) {
mDispatchedSystemBarAppearance = params.insetsFlags.appearance;
mView.onSystemBarAppearanceChanged(mDispatchedSystemBarAppearance);
}
}
if (mFirst || windowShouldResize || viewVisibilityChanged || params != null
|| mForceNextWindowRelayout) {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW,
TextUtils.formatSimple("%s-relayoutWindow#"
+ "first=%b/resize=%b/vis=%b/params=%b/force=%b", mTag,
mFirst, windowShouldResize, viewVisibilityChanged, params != null,
mForceNextWindowRelayout));
}
mForceNextWindowRelayout = false;
// If this window is giving internal insets to the window manager, then we want to first
// make the provided insets unchanged during layout. This avoids it briefly causing
// other windows to resize/move based on the raw frame of the window, waiting until we
// can finish laying out this window and get back to the window manager with the
// ultimately computed insets.
insetsPending = computesInternalInsets;
if (mSurfaceHolder != null) {
mSurfaceHolder.mSurfaceLock.lock();
mDrawingAllowed = true;
}
boolean hwInitialized = false;
boolean dispatchApplyInsets = false;
boolean hadSurface = mSurface.isValid();
try {
if (DEBUG_LAYOUT) {
Log.i(mTag, "host=w:" + host.getMeasuredWidth() + ", h:" +
host.getMeasuredHeight() + ", params=" + params);
}
if (mFirst || viewVisibilityChanged) {
mViewFrameInfo.flags |= FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED;
}
// 2.第一次执行traversals绘制任务时,Binder调用访问系统窗口管理服务WMS的relayoutWindow接口,实现WMS计算应用窗口尺寸并向系统surfaceflinger正式申请Surface“画布”操作
relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
cancelDraw = (relayoutResult & RELAYOUT_RES_CANCEL_AND_REDRAW)
== RELAYOUT_RES_CANCEL_AND_REDRAW;
cancelReason = "relayout";
final boolean dragResizing = mPendingDragResizing;
if (mSyncSeqId > mLastSyncSeqId) {
mLastSyncSeqId = mSyncSeqId;
if (DEBUG_BLAST) {
Log.d(mTag, "Relayout called with blastSync");
}
reportNextDraw("relayout");
mSyncBuffer = true;
isSyncRequest = true;
if (!cancelDraw) {
mDrewOnceForSync = false;
}
}
final boolean surfaceControlChanged =
(relayoutResult & RELAYOUT_RES_SURFACE_CHANGED)
== RELAYOUT_RES_SURFACE_CHANGED;
if (mSurfaceControl.isValid()) {
updateOpacity(mWindowAttributes, dragResizing,
surfaceControlChanged /*forceUpdate */);
// No need to updateDisplayDecoration if it's a new SurfaceControl and
// mDisplayDecorationCached is false, since that's the default for a new
// SurfaceControl.
if (surfaceControlChanged && mDisplayDecorationCached) {
updateDisplayDecoration();
}
if (surfaceControlChanged
&& mWindowAttributes.type
== WindowManager.LayoutParams.TYPE_STATUS_BAR) {
mTransaction.setDefaultFrameRateCompatibility(mSurfaceControl,
Surface.FRAME_RATE_COMPATIBILITY_NO_VOTE).apply();
}
}
if (DEBUG_LAYOUT) Log.v(mTag, "relayout: frame=" + frame.toShortString()
+ " surface=" + mSurface);
// If the pending {@link MergedConfiguration} handed back from
// {@link #relayoutWindow} does not match the one last reported,
// WindowManagerService has reported back a frame from a configuration not yet
// handled by the client. In this case, we need to accept the configuration so we
// do not lay out and draw with the wrong configuration.
if (mRelayoutRequested
&& !mPendingMergedConfiguration.equals(mLastReportedMergedConfiguration)) {
if (DEBUG_CONFIGURATION) Log.v(mTag, "Visible with new config: "
+ mPendingMergedConfiguration.getMergedConfiguration());
performConfigurationChange(new MergedConfiguration(mPendingMergedConfiguration),
!mFirst, INVALID_DISPLAY /* same display */);
updatedConfiguration = true;
}
final boolean updateSurfaceNeeded = mUpdateSurfaceNeeded;
mUpdateSurfaceNeeded = false;
surfaceSizeChanged = false;
if (!mLastSurfaceSize.equals(mSurfaceSize)) {
surfaceSizeChanged = true;
mLastSurfaceSize.set(mSurfaceSize.x, mSurfaceSize.y);
}
final boolean alwaysConsumeSystemBarsChanged =
mPendingAlwaysConsumeSystemBars != mAttachInfo.mAlwaysConsumeSystemBars;
updateColorModeIfNeeded(lp.getColorMode(), lp.getDesiredHdrHeadroom());
surfaceCreated = !hadSurface && mSurface.isValid();
surfaceDestroyed = hadSurface && !mSurface.isValid();
// When using Blast, the surface generation id may not change when there's a new
// SurfaceControl. In that case, we also check relayout flag
// RELAYOUT_RES_SURFACE_CHANGED since it should indicate that WMS created a new
// SurfaceControl.
surfaceReplaced = (surfaceGenerationId != mSurface.getGenerationId()
|| surfaceControlChanged) && mSurface.isValid();
if (surfaceReplaced) {
mSurfaceSequenceId++;
}
if (alwaysConsumeSystemBarsChanged) {
mAttachInfo.mAlwaysConsumeSystemBars = mPendingAlwaysConsumeSystemBars;
dispatchApplyInsets = true;
}
if (updateCaptionInsets()) {
dispatchApplyInsets = true;
}
if (dispatchApplyInsets || mLastSystemUiVisibility !=
mAttachInfo.mSystemUiVisibility || mApplyInsetsRequested) {
mLastSystemUiVisibility = mAttachInfo.mSystemUiVisibility;
dispatchApplyInsets(host);
// We applied insets so force contentInsetsChanged to ensure the
// hierarchy is measured below.
dispatchApplyInsets = true;
}
if (surfaceCreated) {
// If we are creating a new surface, then we need to
// completely redraw it.
mFullRedrawNeeded = true;
mPreviousTransparentRegion.setEmpty();
// Only initialize up-front if transparent regions are not
// requested, otherwise defer to see if the entire window
// will be transparent
if (mAttachInfo.mThreadedRenderer != null) {
try {
hwInitialized = mAttachInfo.mThreadedRenderer.initialize(mSurface);
if (hwInitialized && (host.mPrivateFlags
& View.PFLAG_REQUEST_TRANSPARENT_REGIONS) == 0) {
// Don't pre-allocate if transparent regions
// are requested as they may not be needed
mAttachInfo.mThreadedRenderer.allocateBuffers();
}
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
mLastPerformTraversalsSkipDrawReason = "oom_initialize_renderer";
return;
}
}
} else if (surfaceDestroyed) {
// If the surface has been removed, then reset the scroll
// positions.
if (mLastScrolledFocus != null) {
mLastScrolledFocus.clear();
}
mScrollY = mCurScrollY = 0;
if (mView instanceof RootViewSurfaceTaker) {
((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
}
if (mScroller != null) {
mScroller.abortAnimation();
}
// Our surface is gone
if (isHardwareEnabled()) {
mAttachInfo.mThreadedRenderer.destroy();
}
} else if ((surfaceReplaced || surfaceSizeChanged || updateSurfaceNeeded)
&& mSurfaceHolder == null
&& mAttachInfo.mThreadedRenderer != null
&& mSurface.isValid()) {
mFullRedrawNeeded = true;
try {
// Need to do updateSurface (which leads to CanvasContext::setSurface and
// re-create the EGLSurface) if either the Surface changed (as indicated by
// generation id), or WindowManager changed the surface size. The latter is
// because on some chips, changing the consumer side's BufferQueue size may
// not take effect immediately unless we create a new EGLSurface.
// Note that frame size change doesn't always imply surface size change (eg.
// drag resizing uses fullscreen surface), need to check surfaceSizeChanged
// flag from WindowManager.
mAttachInfo.mThreadedRenderer.updateSurface(mSurface);
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
mLastPerformTraversalsSkipDrawReason = "oom_update_surface";
return;
}
}
if (mDragResizing != dragResizing) {
if (dragResizing) {
final boolean backdropSizeMatchesFrame =
mWinFrame.width() == mPendingBackDropFrame.width()
&& mWinFrame.height() == mPendingBackDropFrame.height();
// TODO: Need cutout?
startDragResizing(mPendingBackDropFrame, !backdropSizeMatchesFrame,
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets);
} else {
// We shouldn't come here, but if we come we should end the resize.
endDragResizing();
}
}
if (!mUseMTRenderer) {
if (dragResizing) {
mCanvasOffsetX = mWinFrame.left;
mCanvasOffsetY = mWinFrame.top;
} else {
mCanvasOffsetX = mCanvasOffsetY = 0;
}
}
} catch (RemoteException e) {
} finally {
if (Trace.isTagEnabled(Trace.TRACE_TAG_VIEW)) {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
if (DEBUG_ORIENTATION) Log.v(
TAG, "Relayout returned: frame=" + frame + ", surface=" + mSurface);
mAttachInfo.mWindowLeft = frame.left;
mAttachInfo.mWindowTop = frame.top;
// !!FIXME!! This next section handles the case where we did not get the
// window size we asked for. We should avoid this by getting a maximum size from
// the window session beforehand.
if (mWidth != frame.width() || mHeight != frame.height()) {
mWidth = frame.width();
mHeight = frame.height();
}
if (mSurfaceHolder != null) {
// The app owns the surface; tell it about what is going on.
if (mSurface.isValid()) {
// XXX .copyFrom() doesn't work!
//mSurfaceHolder.mSurface.copyFrom(mSurface);
mSurfaceHolder.mSurface = mSurface;
}
mSurfaceHolder.setSurfaceFrameSize(mWidth, mHeight);
mSurfaceHolder.mSurfaceLock.unlock();
if (surfaceCreated) {
mSurfaceHolder.ungetCallbacks();
mIsCreating = true;
SurfaceHolder.Callback[] callbacks = mSurfaceHolder.getCallbacks();
if (callbacks != null) {
for (SurfaceHolder.Callback c : callbacks) {
c.surfaceCreated(mSurfaceHolder);
}
}
}
if ((surfaceCreated || surfaceReplaced || surfaceSizeChanged
|| windowAttributesChanged) && mSurface.isValid()) {
SurfaceHolder.Callback[] callbacks = mSurfaceHolder.getCallbacks();
if (callbacks != null) {
for (SurfaceHolder.Callback c : callbacks) {
c.surfaceChanged(mSurfaceHolder, lp.format,
mWidth, mHeight);
}
}
mIsCreating = false;
}
if (surfaceDestroyed) {
notifyHolderSurfaceDestroyed();
mSurfaceHolder.mSurfaceLock.lock();
try {
mSurfaceHolder.mSurface = new Surface();
} finally {
mSurfaceHolder.mSurfaceLock.unlock();
}
}
}
final ThreadedRenderer threadedRenderer = mAttachInfo.mThreadedRenderer;
if (threadedRenderer != null && threadedRenderer.isEnabled()) {
if (hwInitialized
|| mWidth != threadedRenderer.getWidth()
|| mHeight != threadedRenderer.getHeight()
|| mNeedsRendererSetup) {
threadedRenderer.setup(mWidth, mHeight, mAttachInfo,
mWindowAttributes.surfaceInsets);
mNeedsRendererSetup = false;
}
}
// TODO: In the CL "ViewRootImpl: Fix issue with early draw report in
// seamless rotation". We moved processing of RELAYOUT_RES_BLAST_SYNC
// earlier in the function, potentially triggering a call to
// reportNextDraw(). That same CL changed this and the next reference
// to wasReportNextDraw, such that this logic would remain undisturbed
// (it continues to operate as if the code was never moved). This was
// done to achieve a more hermetic fix for S, but it's entirely
// possible that checking the most recent value is actually more
// correct here.
if (!mStopped || mReportNextDraw) {
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);
// Implementation of weights from WindowManager.LayoutParams
// We just grow the dimensions as needed and re-measure if
// needs be
int width = host.getMeasuredWidth();
int height = host.getMeasuredHeight();
boolean measureAgain = false;
if (lp.horizontalWeight > 0.0f) {
width += (int) ((mWidth - width) * lp.horizontalWeight);
childWidthMeasureSpec = MeasureSpec.makeMeasureSpec(width,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (lp.verticalWeight > 0.0f) {
height += (int) ((mHeight - height) * lp.verticalWeight);
childHeightMeasureSpec = MeasureSpec.makeMeasureSpec(height,
MeasureSpec.EXACTLY);
measureAgain = true;
}
if (measureAgain) {
if (DEBUG_LAYOUT) Log.v(mTag,
"And hey let's measure once more: width=" + width
+ " height=" + height);
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
}
layoutRequested = true;
}
}
} else {
// Not the first pass and no window/insets/visibility change but the window
// may have moved and we need check that and if so to update the left and right
// in the attach info. We translate only the window frame since on window move
// the window manager tells us only for the new frame but the insets are the
// same and we do not want to translate them more than once.
maybeHandleWindowMove(frame);
}
if (mViewMeasureDeferred) {
// It's time to measure the views since we are going to layout them.
performMeasure(
MeasureSpec.makeMeasureSpec(frame.width(), MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(frame.height(), MeasureSpec.EXACTLY));
}
if (!mRelayoutRequested && mCheckIfCanDraw) {
// We had a sync previously, but we didn't call IWindowSession#relayout in this
// traversal. So we don't know if the sync is complete that we can continue to draw.
// Here invokes cancelDraw to obtain the information.
try {
cancelDraw = mWindowSession.cancelDraw(mWindow);
cancelReason = "wm_sync";
if (DEBUG_BLAST) {
Log.d(mTag, "cancelDraw returned " + cancelDraw);
}
} catch (RemoteException e) {
}
}
if (surfaceSizeChanged || surfaceReplaced || surfaceCreated ||
windowAttributesChanged || mChildBoundingInsetsChanged) {
// If the surface has been replaced, there's a chance the bounds layer is not parented
// to the new layer. When updating bounds layer, also reparent to the main VRI
// SurfaceControl to ensure it's correctly placed in the hierarchy.
//
// This needs to be done on the client side since WMS won't reparent the children to the
// new surface if it thinks the app is closing. WMS gets the signal that the app is
// stopping, but on the client side it doesn't get stopped since it's restarted quick
// enough. WMS doesn't want to keep around old children since they will leak when the
// client creates new children.
prepareSurfaces();
mChildBoundingInsetsChanged = false;
}
final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
boolean triggerGlobalLayoutListener = didLayout
|| mAttachInfo.mRecomputeGlobalAttributes;
if (didLayout) {
// 3.从DecorView根节点出发,遍历整个View控件树,完成整个View控件树的layout测量操作
performLayout(lp, mWidth, mHeight);
// By this point all views have been sized and positioned
// We can compute the transparent area
if ((host.mPrivateFlags & View.PFLAG_REQUEST_TRANSPARENT_REGIONS) != 0) {
// start out transparent
// TODO: AVOID THAT CALL BY CACHING THE RESULT?
host.getLocationInWindow(mTmpLocation);
mTransparentRegion.set(mTmpLocation[0], mTmpLocation[1],
mTmpLocation[0] + host.mRight - host.mLeft,
mTmpLocation[1] + host.mBottom - host.mTop);
host.gatherTransparentRegion(mTransparentRegion);
final Rect bounds = mAttachInfo.mTmpInvalRect;
if (getAccessibilityFocusedRect(bounds)) {
host.applyDrawableToTransparentRegion(getAccessibilityFocusedDrawable(),
mTransparentRegion);
}
if (mTranslator != null) {
mTranslator.translateRegionInWindowToScreen(mTransparentRegion);
}
if (!mTransparentRegion.equals(mPreviousTransparentRegion)) {
mPreviousTransparentRegion.set(mTransparentRegion);
mFullRedrawNeeded = true;
// TODO: Ideally we would do this in prepareSurfaces,
// but prepareSurfaces is currently working under
// the assumption that we paused the render thread
// via the WM relayout code path. We probably eventually
// want to synchronize transparent region hint changes
// with draws.
SurfaceControl sc = getSurfaceControl();
if (sc.isValid()) {
mTransaction.setTransparentRegionHint(sc, mTransparentRegion).apply();
}
}
}
if (DBG) {
System.out.println("======================================");
System.out.println("performTraversals -- after setFrame");
host.debug();
}
}
boolean didUseTransaction = false;
// These callbacks will trigger SurfaceView SurfaceHolder.Callbacks and must be invoked
// after the measure pass. If its invoked before the measure pass and the app modifies
// the view hierarchy in the callbacks, we could leave the views in a broken state.
if (surfaceCreated) {
notifySurfaceCreated(mTransaction);
didUseTransaction = true;
} else if (surfaceReplaced) {
notifySurfaceReplaced(mTransaction);
didUseTransaction = true;
} else if (surfaceDestroyed) {
notifySurfaceDestroyed();
}
if (didUseTransaction) {
applyTransactionOnDraw(mTransaction);
}
if (triggerGlobalLayoutListener) {
mAttachInfo.mRecomputeGlobalAttributes = false;
mAttachInfo.mTreeObserver.dispatchOnGlobalLayout();
}
Rect contentInsets = null;
Rect visibleInsets = null;
Region touchableRegion = null;
int touchableInsetMode = TOUCHABLE_INSETS_REGION;
boolean computedInternalInsets = false;
if (computesInternalInsets) {
final ViewTreeObserver.InternalInsetsInfo insets = mAttachInfo.mGivenInternalInsets;
// Clear the original insets.
insets.reset();
// Compute new insets in place.
mAttachInfo.mTreeObserver.dispatchOnComputeInternalInsets(insets);
mAttachInfo.mHasNonEmptyGivenInternalInsets = !insets.isEmpty();
// Tell the window manager.
if (insetsPending || !mLastGivenInsets.equals(insets)) {
mLastGivenInsets.set(insets);
// Translate insets to screen coordinates if needed.
if (mTranslator != null) {
contentInsets = mTranslator.getTranslatedContentInsets(insets.contentInsets);
visibleInsets = mTranslator.getTranslatedVisibleInsets(insets.visibleInsets);
touchableRegion = mTranslator.getTranslatedTouchableArea(insets.touchableRegion);
} else {
contentInsets = insets.contentInsets;
visibleInsets = insets.visibleInsets;
touchableRegion = insets.touchableRegion;
}
computedInternalInsets = true;
}
touchableInsetMode = insets.mTouchableInsets;
}
boolean needsSetInsets = computedInternalInsets;
needsSetInsets |= !Objects.equals(mPreviousTouchableRegion, mTouchableRegion) &&
(mTouchableRegion != null);
if (needsSetInsets) {
if (mTouchableRegion != null) {
if (mPreviousTouchableRegion == null) {
mPreviousTouchableRegion = new Region();
}
mPreviousTouchableRegion.set(mTouchableRegion);
if (touchableInsetMode != TOUCHABLE_INSETS_REGION) {
Log.e(mTag, "Setting touchableInsetMode to non TOUCHABLE_INSETS_REGION" +
" from OnComputeInternalInsets, while also using setTouchableRegion" +
" causes setTouchableRegion to be ignored");
}
} else {
mPreviousTouchableRegion = null;
}
if (contentInsets == null) contentInsets = new Rect(0,0,0,0);
if (visibleInsets == null) visibleInsets = new Rect(0,0,0,0);
if (touchableRegion == null) {
touchableRegion = mTouchableRegion;
} else if (touchableRegion != null && mTouchableRegion != null) {
touchableRegion.op(touchableRegion, mTouchableRegion, Region.Op.UNION);
}
try {
mWindowSession.setInsets(mWindow, touchableInsetMode,
contentInsets, visibleInsets, touchableRegion);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
} else if (mTouchableRegion == null && mPreviousTouchableRegion != null) {
mPreviousTouchableRegion = null;
try {
mWindowSession.clearTouchableRegion(mWindow);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
if (mFirst) {
if (sAlwaysAssignFocus || !isInTouchMode()) {
// handle first focus request
if (DEBUG_INPUT_RESIZE) {
Log.v(mTag, "First: mView.hasFocus()=" + mView.hasFocus());
}
if (mView != null) {
if (!mView.hasFocus()) {
mView.restoreDefaultFocus();
if (DEBUG_INPUT_RESIZE) {
Log.v(mTag, "First: requested focused view=" + mView.findFocus());
}
} else {
if (DEBUG_INPUT_RESIZE) {
Log.v(mTag, "First: existing focused view=" + mView.findFocus());
}
}
}
} else {
// Some views (like ScrollView) won't hand focus to descendants that aren't within
// their viewport. Before layout, there's a good change these views are size 0
// which means no children can get focus. After layout, this view now has size, but
// is not guaranteed to hand-off focus to a focusable child (specifically, the edge-
// case where the child has a size prior to layout and thus won't trigger
// focusableViewAvailable).
View focused = mView.findFocus();
if (focused instanceof ViewGroup
&& ((ViewGroup) focused).getDescendantFocusability()
== ViewGroup.FOCUS_AFTER_DESCENDANTS) {
focused.restoreDefaultFocus();
}
}
if (shouldEnableDvrr()) {
// Boost the frame rate when the ViewRootImpl first becomes available.
boostFrameRate(FRAME_RATE_TOUCH_BOOST_TIME);
}
}
final boolean changedVisibility = (viewVisibilityChanged || mFirst) && isViewVisible;
if (changedVisibility) {
maybeFireAccessibilityWindowStateChangedEvent();
}
mFirst = false;
mWillDrawSoon = false;
mNewSurfaceNeeded = false;
mViewVisibility = viewVisibility;
final boolean hasWindowFocus = mAttachInfo.mHasWindowFocus && isViewVisible;
mImeFocusController.onTraversal(hasWindowFocus, mWindowAttributes);
if ((relayoutResult & WindowManagerGlobal.RELAYOUT_RES_FIRST_TIME) != 0) {
reportNextDraw("first_relayout");
}
mCheckIfCanDraw = isSyncRequest || cancelDraw;
boolean cancelDueToPreDrawListener = mAttachInfo.mTreeObserver.dispatchOnPreDraw();
boolean cancelAndRedraw = cancelDueToPreDrawListener
|| (cancelDraw && mDrewOnceForSync);
if (!cancelAndRedraw) {
// A sync was already requested before the WMS requested sync. This means we need to
// sync the buffer, regardless if WMS wants to sync the buffer.
if (mActiveSurfaceSyncGroup != null) {
mSyncBuffer = true;
}
createSyncIfNeeded();
notifyDrawStarted(isInWMSRequestedSync());
mDrewOnceForSync = true;
// If the active SSG is also requesting to sync a buffer, the following needs to happen
// 1. Ensure we keep track of the number of active syncs to know when to disable RT
// RT animations that conflict with syncing a buffer.
// 2. Add a safeguard SSG to prevent multiple SSG that sync buffers from being submitted
// out of order.
if (mActiveSurfaceSyncGroup != null && mSyncBuffer) {
updateSyncInProgressCount(mActiveSurfaceSyncGroup);
safeguardOverlappingSyncs(mActiveSurfaceSyncGroup);
}
}
if (!isViewVisible) {
if (mLastTraversalWasVisible) {
logAndTrace("Not drawing due to not visible");
}
mLastPerformTraversalsSkipDrawReason = "view_not_visible";
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).endChangingAnimations();
}
mPendingTransitions.clear();
}
handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mHasPendingTransactions,
mPendingTransaction, "view not visible");
} else if (cancelAndRedraw) {
if (!mWasLastDrawCanceled) {
logAndTrace("Canceling draw."
+ " cancelDueToPreDrawListener=" + cancelDueToPreDrawListener
+ " cancelDueToSync=" + (cancelDraw && mDrewOnceForSync));
}
mLastPerformTraversalsSkipDrawReason = cancelDueToPreDrawListener
? "predraw_" + mAttachInfo.mTreeObserver.getLastDispatchOnPreDrawCanceledReason()
: "cancel_" + cancelReason;
// Try again
scheduleTraversals();
} else {
if (mWasLastDrawCanceled) {
logAndTrace("Draw frame after cancel");
}
if (!mLastTraversalWasVisible) {
logAndTrace("Start draw after previous draw not visible");
}
if (mPendingTransitions != null && mPendingTransitions.size() > 0) {
for (int i = 0; i < mPendingTransitions.size(); ++i) {
mPendingTransitions.get(i).startChangingAnimations();
}
mPendingTransitions.clear();
}
// 4.从DecorView根节点出发,遍历整个View控件树,完成整个View控件树的draw测量操作
if (!performDraw(mActiveSurfaceSyncGroup)) {
handleSyncRequestWhenNoAsyncDraw(mActiveSurfaceSyncGroup, mHasPendingTransactions,
mPendingTransaction, mLastPerformDrawSkippedReason);
}
}
mWasLastDrawCanceled = cancelAndRedraw;
mLastTraversalWasVisible = isViewVisible;
if (mAttachInfo.mContentCaptureEvents != null) {
notifyContentCaptureEvents();
}
mIsInTraversal = false;
mRelayoutRequested = false;
if (!cancelAndRedraw) {
mReportNextDraw = false;
mLastReportNextDrawReason = null;
mActiveSurfaceSyncGroup = null;
mHasPendingTransactions = false;
mSyncBuffer = false;
if (isInWMSRequestedSync()) {
mWmsRequestSyncGroup.markSyncReady();
mWmsRequestSyncGroup = null;
mWmsRequestSyncGroupState = WMS_SYNC_NONE;
}
}
// For the variable refresh rate project.
// We set the preferred frame rate and frame rate category at the end of performTraversals
// when the values are applicable.
setPreferredFrameRate(mPreferredFrameRate);
setPreferredFrameRateCategory(mPreferredFrameRateCategory);
mFrameRateCategoryHighCount = mFrameRateCategoryHighCount > 0
? mFrameRateCategoryHighCount - 1 : mFrameRateCategoryHighCount;
mFrameRateCategoryNormalCount = mFrameRateCategoryNormalCount > 0
? mFrameRateCategoryNormalCount - 1 : mFrameRateCategoryNormalCount;
mFrameRateCategoryLowCount = mFrameRateCategoryLowCount > 0
? mFrameRateCategoryLowCount - 1 : mFrameRateCategoryLowCount;
mPreferredFrameRateCategory = FRAME_RATE_CATEGORY_NO_PREFERENCE;
mPreferredFrameRate = -1;
mIsFrameRateConflicted = false;
}
relayoutWindow
// frameworks/base/core/java/android/view/ViewRootImpl.java
private int relayoutWindow(WindowManager.LayoutParams params, int viewVisibility,
boolean insetsPending) throws RemoteException {
final WindowConfiguration winConfigFromAm = getConfiguration().windowConfiguration;
final WindowConfiguration winConfigFromWm =
mLastReportedMergedConfiguration.getGlobalConfiguration().windowConfiguration;
final WindowConfiguration winConfig = getCompatWindowConfiguration();
final int measuredWidth = mMeasuredWidth;
final int measuredHeight = mMeasuredHeight;
final boolean relayoutAsync;
if ((mViewFrameInfo.flags & FrameInfo.FLAG_WINDOW_VISIBILITY_CHANGED) == 0
&& mWindowAttributes.type != TYPE_APPLICATION_STARTING
&& mSyncSeqId <= mLastSyncSeqId
&& winConfigFromAm.diff(winConfigFromWm, false /* compareUndefined */) == 0) {
final InsetsState state = mInsetsController.getState();
final Rect displayCutoutSafe = mTempRect;
state.getDisplayCutoutSafe(displayCutoutSafe);
mWindowLayout.computeFrames(mWindowAttributes.forRotation(winConfig.getRotation()),
state, displayCutoutSafe, winConfig.getBounds(), winConfig.getWindowingMode(),
measuredWidth, measuredHeight, mInsetsController.getRequestedVisibleTypes(),
1f /* compatScale */, mTmpFrames);
mWinFrameInScreen.set(mTmpFrames.frame);
if (mTranslator != null) {
mTranslator.translateRectInAppWindowToScreen(mWinFrameInScreen);
}
// If the position and the size of the frame are both changed, it will trigger a BLAST
// sync, and we still need to call relayout to obtain the syncSeqId. Otherwise, we just
// need to send attributes via relayoutAsync.
final Rect oldFrame = mLastLayoutFrame;
final Rect newFrame = mTmpFrames.frame;
final boolean positionChanged =
newFrame.top != oldFrame.top || newFrame.left != oldFrame.left;
final boolean sizeChanged =
newFrame.width() != oldFrame.width() || newFrame.height() != oldFrame.height();
relayoutAsync = !positionChanged || !sizeChanged;
} else {
relayoutAsync = false;
}
float appScale = mAttachInfo.mApplicationScale;
boolean restore = false;
if (params != null && mTranslator != null) {
restore = true;
params.backup();
mTranslator.translateWindowLayout(params);
}
if (params != null) {
if (DBG) Log.d(mTag, "WindowLayout in layoutWindow:" + params);
if (mOrigWindowType != params.type) {
// For compatibility with old apps, don't crash here.
if (mTargetSdkVersion < Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
Slog.w(mTag, "Window type can not be changed after "
+ "the window is added; ignoring change of " + mView);
params.type = mOrigWindowType;
}
}
}
final int requestedWidth = (int) (measuredWidth * appScale + 0.5f);
final int requestedHeight = (int) (measuredHeight * appScale + 0.5f);
int relayoutResult = 0;
mRelayoutSeq++;
if (relayoutAsync) {
mWindowSession.relayoutAsync(mWindow, params,
requestedWidth, requestedHeight, viewVisibility,
insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mRelayoutSeq,
mLastSyncSeqId);
} else {
// 通过Binder IPC访问系统WMS服务的relayout接口,申请Surface“画布”操作
relayoutResult = mWindowSession.relayout(mWindow, params,
requestedWidth, requestedHeight, viewVisibility,
insetsPending ? WindowManagerGlobal.RELAYOUT_INSETS_PENDING : 0, mRelayoutSeq,
mLastSyncSeqId, mTmpFrames, mPendingMergedConfiguration, mSurfaceControl,
mTempInsets, mTempControls, mRelayoutBundle);
mRelayoutRequested = true;
final int maybeSyncSeqId = mRelayoutBundle.getInt("seqid");
if (maybeSyncSeqId > 0) {
mSyncSeqId = maybeSyncSeqId;
}
mWinFrameInScreen.set(mTmpFrames.frame);
if (mTranslator != null) {
mTranslator.translateRectInScreenToAppWindow(mTmpFrames.frame);
mTranslator.translateRectInScreenToAppWindow(mTmpFrames.displayFrame);
mTranslator.translateRectInScreenToAppWindow(mTmpFrames.attachedFrame);
mTranslator.translateInsetsStateInScreenToAppWindow(mTempInsets);
mTranslator.translateSourceControlsInScreenToAppWindow(mTempControls.get());
}
mInvCompatScale = 1f / mTmpFrames.compatScale;
CompatibilityInfo.applyOverrideScaleIfNeeded(mPendingMergedConfiguration);
mInsetsController.onStateChanged(mTempInsets);
mInsetsController.onControlsChanged(mTempControls.get());
mPendingAlwaysConsumeSystemBars =
(relayoutResult & RELAYOUT_RES_CONSUME_ALWAYS_SYSTEM_BARS) != 0;
}
final int transformHint = SurfaceControl.rotationToBufferTransform(
(mDisplay.getInstallOrientation() + mDisplay.getRotation()) % 4);
final boolean transformHintChanged = transformHint != mPreviousTransformHint;
mPreviousTransformHint = transformHint;
mSurfaceControl.setTransformHint(transformHint);
WindowLayout.computeSurfaceSize(mWindowAttributes, winConfig.getMaxBounds(), requestedWidth,
requestedHeight, mWinFrameInScreen, mPendingDragResizing, mSurfaceSize);
final boolean sizeChanged = !mLastSurfaceSize.equals(mSurfaceSize);
final boolean surfaceControlChanged =
(relayoutResult & RELAYOUT_RES_SURFACE_CHANGED) == RELAYOUT_RES_SURFACE_CHANGED;
if (mAttachInfo.mThreadedRenderer != null &&
(transformHintChanged || sizeChanged || surfaceControlChanged)) {
if (mAttachInfo.mThreadedRenderer.pause()) {
// Animations were running so we need to push a frame
// to resume them
mDirty.set(0, 0, mWidth, mHeight);
}
}
if (mSurfaceControl.isValid()) {
if (mPendingDragResizing && !mSurfaceSize.equals(
mWinFrameInScreen.width(), mWinFrameInScreen.height())) {
// During drag-resize, a single fullscreen-sized surface is reused for optimization.
// Crop to the content size instead of the surface size to avoid exposing garbage
// content that is still on the surface from previous re-layouts (e.g. when
// resizing to a larger size).
mTransaction.setWindowCrop(mSurfaceControl,
mWinFrameInScreen.width(), mWinFrameInScreen.height());
} else if (!HardwareRenderer.isDrawingEnabled()) {
// When drawing is disabled the window layer won't have a valid buffer.
// Set a window crop so input can get delivered to the window.
mTransaction.setWindowCrop(mSurfaceControl, mSurfaceSize.x, mSurfaceSize.y).apply();
}
}
if (mAttachInfo.mContentCaptureManager != null) {
ContentCaptureSession mainSession = mAttachInfo.mContentCaptureManager
.getMainContentCaptureSession();
mainSession.notifyWindowBoundsChanged(mainSession.getId(),
getConfiguration().windowConfiguration.getBounds());
}
if (mSurfaceControl.isValid()) {
updateBlastSurfaceIfNeeded();
if (mAttachInfo.mThreadedRenderer != null) {
// 本地Surface对象获取指向远端分配的Surface的引用
mAttachInfo.mThreadedRenderer.setSurfaceControl(mSurfaceControl, mBlastBufferQueue);
}
mHdrRenderState.forceUpdateHdrSdrRatio();
if (transformHintChanged) {
dispatchTransformHintChanged(transformHint);
}
} else {
if (mAttachInfo.mThreadedRenderer != null && mAttachInfo.mThreadedRenderer.pause()) {
mDirty.set(0, 0, mWidth, mHeight);
}
destroySurface();
}
if (restore) {
params.restore();
}
setFrame(mTmpFrames.frame, true /* withinRelayout */);
return relayoutResult;
}
performMeasure
// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
// 原生标识View树的measure测量过程的trace tag
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
// 从mView指向的View控件树的根节点DecorView出发,遍历访问整个View树,并完成整个布局View树的测量工作
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mMeasuredWidth = mView.getMeasuredWidth();
mMeasuredHeight = mView.getMeasuredHeight();
mViewMeasureDeferred = false;
}
performLayout
// frameworks/base/core/java/android/view/ViewRootImpl.java
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
mScrollMayChange = true;
mInLayout = true;
final View host = mView;
if (host == null) {
return;
}
if (DEBUG_ORIENTATION || DEBUG_LAYOUT) {
Log.v(mTag, "Laying out " + host + " to (" +
host.getMeasuredWidth() + ", " + host.getMeasuredHeight() + ")");
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
try {
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mInLayout = false;
int numViewsRequestingLayout = mLayoutRequesters.size();
if (numViewsRequestingLayout > 0) {
// requestLayout() was called during layout.
// If no layout-request flags are set on the requesting views, there is no problem.
// If some requests are still pending, then we need to clear those flags and do
// a full request/measure/layout pass to handle this situation.
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
false);
if (validLayoutRequesters != null) {
// Set this flag to indicate that any further requests are happening during
// the second pass, which may result in posting those requests to the next
// frame instead
mHandlingLayoutInLayoutRequest = true;
// Process fresh layout requests, then measure and layout
int numValidRequests = validLayoutRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = validLayoutRequesters.get(i);
Log.w("View", "requestLayout() improperly called by " + view +
" during layout: running second layout pass");
view.requestLayout();
}
measureHierarchy(host, lp, mView.getContext().getResources(),
desiredWindowWidth, desiredWindowHeight, false /* forRootSizeOnly */);
mInLayout = true;
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
mHandlingLayoutInLayoutRequest = false;
// Check the valid requests again, this time without checking/clearing the
// layout flags, since requests happening during the second pass get noop'd
validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters, true);
if (validLayoutRequesters != null) {
final ArrayList<View> finalRequesters = validLayoutRequesters;
// Post second-pass requests to the next frame
getRunQueue().post(new Runnable() {
@Override
public void run() {
int numValidRequests = finalRequesters.size();
for (int i = 0; i < numValidRequests; ++i) {
final View view = finalRequesters.get(i);
Log.w("View", "requestLayout() improperly called by " + view +
" during second layout pass: posting in next frame");
view.requestLayout();
}
}
});
}
}
}
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
mInLayout = false;
}
performDraw
// frameworks/base/core/java/android/view/ViewRootImpl.java
private boolean performDraw(@Nullable SurfaceSyncGroup surfaceSyncGroup) {
mLastPerformDrawSkippedReason = null;
if (mAttachInfo.mDisplayState == Display.STATE_OFF && !mReportNextDraw) {
mLastPerformDrawSkippedReason = "screen_off";
if (!mLastDrawScreenOff) {
logAndTrace("Not drawing due to screen off");
}
mLastDrawScreenOff = true;
return false;
} else if (mView == null) {
mLastPerformDrawSkippedReason = "no_root_view";
return false;
}
if (mLastDrawScreenOff) {
logAndTrace("Resumed drawing after screen turned on");
mLastDrawScreenOff = false;
}
final boolean fullRedrawNeeded = mFullRedrawNeeded || surfaceSyncGroup != null;
mFullRedrawNeeded = false;
mIsDrawing = true;
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "draw-" + mTag);
addFrameCommitCallbackIfNeeded();
boolean usingAsyncReport;
try {
usingAsyncReport = draw(fullRedrawNeeded, surfaceSyncGroup, mSyncBuffer); // 继续
if (mAttachInfo.mThreadedRenderer != null && !usingAsyncReport) {
mAttachInfo.mThreadedRenderer.setFrameCallback(null);
}
} finally {
mIsDrawing = false;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
// For whatever reason we didn't create a HardwareRenderer, end any
// hardware animations that are now dangling
if (mAttachInfo.mPendingAnimatingRenderNodes != null) {
final int count = mAttachInfo.mPendingAnimatingRenderNodes.size();
for (int i = 0; i < count; i++) {
mAttachInfo.mPendingAnimatingRenderNodes.get(i).endAllAnimators();
}
mAttachInfo.mPendingAnimatingRenderNodes.clear();
}
final Transaction pendingTransaction;
if (!usingAsyncReport && mHasPendingTransactions) {
pendingTransaction = new Transaction();
pendingTransaction.merge(mPendingTransaction);
} else {
pendingTransaction = null;
}
if (mReportNextDraw) {
// if we're using multi-thread renderer, wait for the window frame draws
if (mWindowDrawCountDown != null) {
try {
mWindowDrawCountDown.await();
} catch (InterruptedException e) {
Log.e(mTag, "Window redraw count down interrupted!");
}
mWindowDrawCountDown = null;
}
if (mAttachInfo.mThreadedRenderer != null) {
mAttachInfo.mThreadedRenderer.setStopped(mStopped);
}
if (LOCAL_LOGV) {
Log.v(mTag, "FINISHED DRAWING: " + mWindowAttributes.getTitle());
}
if (mSurfaceHolder != null && mSurface.isValid()) {
usingAsyncReport = true;
SurfaceCallbackHelper sch = new SurfaceCallbackHelper(() -> {
handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction != null,
pendingTransaction, "SurfaceHolder");
});
SurfaceHolder.Callback callbacks[] = mSurfaceHolder.getCallbacks();
sch.dispatchSurfaceRedrawNeededAsync(mSurfaceHolder, callbacks);
} else if (!usingAsyncReport) {
if (mAttachInfo.mThreadedRenderer != null) {
mAttachInfo.mThreadedRenderer.fence();
}
}
}
if (!usingAsyncReport) {
handleSyncRequestWhenNoAsyncDraw(surfaceSyncGroup, pendingTransaction != null,
pendingTransaction, "no async report");
}
if (mPerformContentCapture) {
performContentCaptureInitialReport();
}
return true;
}
draw
// frameworks/base/core/java/android/view/ViewRootImpl.java
private boolean draw(boolean fullRedrawNeeded, @Nullable SurfaceSyncGroup activeSyncGroup,
boolean syncBuffer) {
Surface surface = mSurface;
if (!surface.isValid()) {
return false;
}
if (DEBUG_FPS) {
trackFPS();
}
if (sToolkitMetricsForFrameRateDecisionFlagValue) {
collectFrameRateDecisionMetrics();
}
if (!sFirstDrawComplete) {
synchronized (sFirstDrawHandlers) {
sFirstDrawComplete = true;
final int count = sFirstDrawHandlers.size();
for (int i = 0; i< count; i++) {
mHandler.post(sFirstDrawHandlers.get(i));
}
}
}
scrollToRectOrFocus(null, false);
if (mAttachInfo.mViewScrollChanged) {
mAttachInfo.mViewScrollChanged = false;
mAttachInfo.mTreeObserver.dispatchOnScrollChanged();
}
boolean animating = mScroller != null && mScroller.computeScrollOffset();
final int curScrollY;
if (animating) {
curScrollY = mScroller.getCurrY();
} else {
curScrollY = mScrollY;
}
if (mCurScrollY != curScrollY) {
mCurScrollY = curScrollY;
fullRedrawNeeded = true;
if (mView instanceof RootViewSurfaceTaker) {
((RootViewSurfaceTaker) mView).onRootViewScrollYChanged(mCurScrollY);
}
}
final float appScale = mAttachInfo.mApplicationScale;
final boolean scalingRequired = mAttachInfo.mScalingRequired;
final Rect dirty = mDirty;
if (mSurfaceHolder != null) {
// The app owns the surface, we won't draw.
dirty.setEmpty();
if (animating && mScroller != null) {
mScroller.abortAnimation();
}
return false;
}
if (fullRedrawNeeded) {
dirty.set(0, 0, (int) (mWidth * appScale + 0.5f), (int) (mHeight * appScale + 0.5f));
}
if (DEBUG_ORIENTATION || DEBUG_DRAW) {
Log.v(mTag, "Draw " + mView + "/"
+ mWindowAttributes.getTitle()
+ ": dirty={" + dirty.left + "," + dirty.top
+ "," + dirty.right + "," + dirty.bottom + "} surface="
+ surface + " surface.isValid()=" + surface.isValid() + ", appScale:" +
appScale + ", width=" + mWidth + ", height=" + mHeight);
}
mAttachInfo.mTreeObserver.dispatchOnDraw();
int xOffset = -mCanvasOffsetX;
int yOffset = -mCanvasOffsetY + curScrollY;
final WindowManager.LayoutParams params = mWindowAttributes;
final Rect surfaceInsets = params != null ? params.surfaceInsets : null;
if (surfaceInsets != null) {
xOffset -= surfaceInsets.left;
yOffset -= surfaceInsets.top;
// Offset dirty rect for surface insets.
dirty.offset(surfaceInsets.left, surfaceInsets.top);
}
boolean accessibilityFocusDirty = isAccessibilityFocusDirty();
// Force recalculation of transparent regions
if (accessibilityFocusDirty) {
final Rect bounds = mAttachInfo.mTmpInvalRect;
if (getAccessibilityFocusedRect(bounds)) {
requestLayout();
}
}
mAttachInfo.mDrawingTime =
mChoreographer.getFrameTimeNanos() / TimeUtils.NANOS_PER_MS;
boolean useAsyncReport = false;
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
if (isHardwareEnabled()) {
// If accessibility focus moved, always invalidate the root.
boolean invalidateRoot = accessibilityFocusDirty || mInvalidateRootRequested;
mInvalidateRootRequested = false;
// Draw with hardware renderer.
mIsAnimating = false;
if (mHardwareYOffset != yOffset || mHardwareXOffset != xOffset) {
mHardwareYOffset = yOffset;
mHardwareXOffset = xOffset;
invalidateRoot = true;
}
if (invalidateRoot) {
mAttachInfo.mThreadedRenderer.invalidateRoot();
}
dirty.setEmpty();
// Stage the content drawn size now. It will be transferred to the renderer
// shortly before the draw commands get send to the renderer.
final boolean updated = updateContentDrawBounds();
if (mReportNextDraw) {
// report next draw overrides setStopped()
// This value is re-sync'd to the value of mStopped
// in the handling of mReportNextDraw post-draw.
mAttachInfo.mThreadedRenderer.setStopped(false);
}
if (updated) {
requestDrawWindow();
}
useAsyncReport = true;
if (mHdrRenderState.updateForFrame(mAttachInfo.mDrawingTime)) {
final float renderRatio = mHdrRenderState.getRenderHdrSdrRatio();
applyTransactionOnDraw(mTransaction.setExtendedRangeBrightness(
getSurfaceControl(), renderRatio,
mHdrRenderState.getDesiredHdrSdrRatio()));
mAttachInfo.mThreadedRenderer.setTargetHdrSdrRatio(renderRatio);
}
if (activeSyncGroup != null) {
registerCallbacksForSync(syncBuffer, activeSyncGroup);
if (syncBuffer) {
mAttachInfo.mThreadedRenderer.forceDrawNextFrame();
}
} else if (mHasPendingTransactions) {
// Register a callback if there's no sync involved but there were calls to
// applyTransactionOnDraw. If there is a sync involved, the sync callback will
// handle merging the pending transaction.
registerCallbackForPendingTransactions();
}
// 如果开启并支持硬件绘制加速,则走硬件绘制的流程(从Android 4.+开始,默认情况下都是支持跟开启了硬件加速的)
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
} else {
// If we get here with a disabled & requested hardware renderer, something went
// wrong (an invalidate posted right before we destroyed the hardware surface
// for instance) so we should just bail out. Locking the surface with software
// rendering at this point would lock it forever and prevent hardware renderer
// from doing its job when it comes back.
// Before we request a new frame we must however attempt to reinitiliaze the
// hardware renderer if it's in requested state. This would happen after an
// eglTerminate() for instance.
if (mAttachInfo.mThreadedRenderer != null &&
!mAttachInfo.mThreadedRenderer.isEnabled() &&
mAttachInfo.mThreadedRenderer.isRequested() &&
mSurface.isValid()) {
try {
mAttachInfo.mThreadedRenderer.initializeIfNeeded(
mWidth, mHeight, mAttachInfo, mSurface, surfaceInsets);
} catch (OutOfResourcesException e) {
handleOutOfResourcesException(e);
return false;
}
mFullRedrawNeeded = true;
scheduleTraversals();
return false;
}
// 否则走drawSoftware软件绘制的流程
if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
scalingRequired, dirty, surfaceInsets)) {
return false;
}
}
}
if (animating) {
mFullRedrawNeeded = true;
scheduleTraversals();
}
return useAsyncReport;
}
从上面的代码流程可以看出,ViewRootImpl
中负责的整个应用界面绘制的主要流程如下:
- 从界面View控件树的根节点
DecorView
出发,递归遍历整个View控件树,完成对整个View
控件树的measure
测量操作,由于篇幅所限,本文就不展开分析这块的详细流程; - 界面第一次执行绘制任务时,会通过
Binder
IPC
访问系统窗口管理服务WMS的relayout接口,实现窗口尺寸的计算并向系统申请用于本地绘制渲染的Surface“画布”的操作(具体由SurfaceFlinger
负责创建应用界面对应的BufferQueueLayer
对象,并通过内存共享的方式通过Binder
将地址引用透过WMS回传给应用进程这边),由于篇幅所限,本文就不展开分析这块的详细流程; - 从界面View控件树的根节点
DecorView
出发,递归遍历整个View控件树,完成对整个View
控件树的layout
测量操作; - 从界面View控件树的根节点
DecorView
出发,递归遍历整个View
控件树,完成对整个View
控件树的draw
测量操作,如果开启并支持硬件绘制加速(从Android 4.X开始谷歌已经默认开启硬件加速),则走GPU
硬件绘制的流程,否则走CPU
软件绘制的流程;
以上绘制过程从systrace上看如下图所示:
借用一张图来总结应用UI绘制的流程,如下所示:
UI绘制流程.png
7.RenderThread渲染
截止到目前,在ViewRootImpl
中完成了对界面的measure、layout和draw等绘制流程后,用户依然还是看不到屏幕上显示的应用界面内容,因为整个Android
系统的显示流程除了前面讲到的UI线程的绘制外,界面还需要经过RenderThread
线程的渲染处理,渲染完成后,还需要通过Binder
调用“上帧”交给surfaceflinger
进程中进行合成后送显才能最终显示到屏幕上。
我们将接上一节中ViewRootImpl
中最后draw的流程继续往下分析开启硬件加速情况下,RenderThread
渲染线程的工作流程。由于目前Android 4.X之后系统默认界面是开启硬件加速的,所以本文我们重点分析硬件加速条件下的界面渲染流程,我们先分析一下简化的代码流程:
7.1 硬件加速绘制
// frameworks/base/core/java/android/view/ViewRootImpl.java
private boolean draw(boolean fullRedrawNeeded, @Nullable SurfaceSyncGroup activeSyncGroup,boolean syncBuffer) {
...
if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
...
// 硬件加速条件下的界面渲染流程
mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
} else {
...
}
}
ThreadedRenderer**.**draw
// frameworks/base/core/java/android/view/ThreadedRenderer.java
/**
* Draws the specified view.
*
* @param view The view to draw.
* @param attachInfo AttachInfo tied to the specified view.
*/
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
attachInfo.mViewRootImpl.mViewFrameInfo.markDrawStart();
// 1.从DecorView根节点出发,递归遍历View控件树,记录每个View节点的绘制操作命令,完成绘制操作命令树的构建
updateRootDisplayList(view, callbacks);
// register animating rendernodes which started animating prior to renderer
// creation, which is typical for animators started prior to first draw
if (attachInfo.mPendingAnimatingRenderNodes != null) {
final int count = attachInfo.mPendingAnimatingRenderNodes.size();
for (int i = 0; i < count; i++) {
registerAnimatingRenderNode(
attachInfo.mPendingAnimatingRenderNodes.get(i));
}
attachInfo.mPendingAnimatingRenderNodes.clear();
// We don't need this anymore as subsequent calls to
// ViewRootImpl#attachRenderNodeAnimator will go directly to us.
attachInfo.mPendingAnimatingRenderNodes = null;
}
final FrameInfo frameInfo = attachInfo.mViewRootImpl.getUpdatedFrameInfo();
// 2.JNI调用同步Java层构建的绘制命令树到Native层的RenderThread渲染线程,并唤醒渲染线程利用OpenGL执行渲染任务;
int syncResult = syncAndDrawFrame(frameInfo);
if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {
Log.w("HWUI", "Surface lost, forcing relayout");
// We lost our surface. For a relayout next frame which should give us a new
// surface from WindowManager, which hopefully will work.
attachInfo.mViewRootImpl.mForceNextWindowRelayout = true;
attachInfo.mViewRootImpl.requestLayout();
}
if ((syncResult & SYNC_REDRAW_REQUESTED) != 0) {
attachInfo.mViewRootImpl.invalidate();
}
}
从上面的代码可以看出,硬件加速绘制主要包括两个阶段:
- 从
DecorView
根节点出发,递归遍历View
控件树,记录每个View
节点的drawOp
绘制操作命令,完成绘制操作命令树的构建; JNI
调用同步Java
层构建的绘制命令树到Native
层的RenderThread
渲染线程,并唤醒渲染线程利用OpenGL
执行渲染任务;
7.2 构建绘制命令树
我们先来看看第一阶段构建绘制命令树的代码简化流程:
updateRootDisplayList
// frameworks/base/core/java/android/view/ThreadedRenderer.java
private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
// 原生标记构建View绘制操作命令树过程的systrace tag
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Record View#draw()");
// 递归子View的updateDisplayListIfDirty实现构建DisplayListOp
updateViewTreeDisplayList(view);
// Consume and set the frame callback after we dispatch draw to the view above, but before
// onPostDraw below which may reset the callback for the next frame. This ensures that
// updates to the frame callback during scroll handling will also apply in this frame.
if (mNextRtFrameCallbacks != null) {
final ArrayList<FrameDrawingCallback> frameCallbacks = mNextRtFrameCallbacks;
mNextRtFrameCallbacks = null;
setFrameCallback(new FrameDrawingCallback() {
@Override
public void onFrameDraw(long frame) {
}
@Override
public FrameCommitCallback onFrameDraw(int syncResult, long frame) {
ArrayList<FrameCommitCallback> frameCommitCallbacks = new ArrayList<>();
for (int i = 0; i < frameCallbacks.size(); ++i) {
FrameCommitCallback frameCommitCallback = frameCallbacks.get(i)
.onFrameDraw(syncResult, frame);
if (frameCommitCallback != null) {
frameCommitCallbacks.add(frameCommitCallback);
}
}
if (frameCommitCallbacks.isEmpty()) {
return null;
}
return didProduceBuffer -> {
for (int i = 0; i < frameCommitCallbacks.size(); ++i) {
frameCommitCallbacks.get(i).onFrameCommit(didProduceBuffer);
}
};
}
});
}
if (mRootNodeNeedsUpdate || !mRootNode.hasDisplayList()) {
// 获取根View的SkiaRecordingCanvas
RecordingCanvas canvas = mRootNode.beginRecording(mSurfaceWidth, mSurfaceHeight);
try {
final int saveCount = canvas.save();
canvas.translate(mInsetLeft, mInsetTop);
callbacks.onPreDraw(canvas);
canvas.enableZ();
// 利用canvas缓存DisplayListOp绘制命令
canvas.drawRenderNode(view.updateDisplayListIfDirty());
canvas.disableZ();
callbacks.onPostDraw(canvas);
canvas.restoreToCount(saveCount);
mRootNodeNeedsUpdate = false;
} finally {
// 将所有DisplayListOp绘制命令填充到RootRenderNode中
mRootNode.endRecording();
}
}
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
private void updateViewTreeDisplayList(View view) {
view.mPrivateFlags |= View.PFLAG_DRAWN;
view.mRecreateDisplayList = (view.mPrivateFlags & View.PFLAG_INVALIDATED)
== View.PFLAG_INVALIDATED;
view.mPrivateFlags &= ~View.PFLAG_INVALIDATED;
// 从DecorView根节点出发,开始递归调用每个View树节点的updateDisplayListIfDirty函数
view.updateDisplayListIfDirty();
view.mRecreateDisplayList = false;
}
View.updateDisplayListIfDirty
// frameworks/base/core/java/android/view/View.java
/**
* Gets the RenderNode for the view, and updates its DisplayList (if needed and supported)
* @hide
*/
@NonNull
@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)
public RenderNode updateDisplayListIfDirty() {
final RenderNode renderNode = mRenderNode;
if (!canHaveDisplayList()) {
// can't populate RenderNode, don't try
return renderNode;
}
if ((mPrivateFlags & PFLAG_DRAWING_CACHE_VALID) == 0
|| !renderNode.hasDisplayList()
|| (mRecreateDisplayList)) {
// Don't need to recreate the display list, just need to tell our
// children to restore/recreate theirs
if (renderNode.hasDisplayList()
&& !mRecreateDisplayList) {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
dispatchGetDisplayList();
return renderNode; // no work needed
}
// If we got here, we're recreating it. Mark it as such to ensure that
// we copy in child display lists into ours in drawChild()
mRecreateDisplayList = true;
int width = mRight - mLeft;
int height = mBottom - mTop;
int layerType = getLayerType();
// Hacky hack: Reset any stretch effects as those are applied during the draw pass
// instead of being "stateful" like other RenderNode properties
renderNode.clearStretch();
// 1.利用`View`对象构造时创建的`RenderNode`获取一个`SkiaRecordingCanvas`“画布”;
final RecordingCanvas canvas = renderNode.beginRecording(width, height);
try {
if (layerType == LAYER_TYPE_SOFTWARE) {
buildDrawingCache(true);
Bitmap cache = getDrawingCache(true);
if (cache != null) {
canvas.drawBitmap(cache, 0, 0, mLayerPaint);
}
} else {
computeScroll();
canvas.translate(-mScrollX, -mScrollY);
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
// Fast path for layouts with no backgrounds
if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
// 如果仅仅是ViewGroup,并且自身不用绘制,直接递归子View
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().draw(canvas);
}
if (isShowingLayoutBounds()) {
debugDrawFocus(canvas);
}
} else {
// 2.利用SkiaRecordingCanvas,在每个子View控件的onDraw绘制函数中调用drawLine、drawRect等绘制操作时,创建对应的DisplayListOp绘制命令,并缓存记录到其内部的SkiaDisplayList持有的DisplayListData中;
draw(canvas);
}
}
} finally {
// 3.将包含有`DisplayListOp`绘制命令缓存的`SkiaDisplayList`对象设置填充到`RenderNode`中;
renderNode.endRecording();
setDisplayListProperties(renderNode);
}
} else {
mPrivateFlags |= PFLAG_DRAWN | PFLAG_DRAWING_CACHE_VALID;
mPrivateFlags &= ~PFLAG_DIRTY_MASK;
}
return renderNode;
}
ThreadedRenderer.draw
// frameworks/base/core/java/android/view/ThreadedRenderer.java
/**
* Manually render this view (and all of its children) to the given Canvas.
* The view must have already done a full layout before this function is
* called. When implementing a view, implement
* {@link #onDraw(android.graphics.Canvas)} instead of overriding this method.
* If you do need to override this method, call the superclass version.
*
* @param canvas The Canvas to which the View is rendered.
*/
@CallSuper
public void draw(@NonNull Canvas canvas) {
final int privateFlags = mPrivateFlags;
mPrivateFlags = (privateFlags & ~PFLAG_DIRTY_MASK) | PFLAG_DRAWN;
mFrameContentVelocity = 0;
/*
* Draw traversal performs several drawing steps which must be executed
* in the appropriate order:
*
* 1. Draw the background
* 2. If necessary, save the canvas' layers to prepare for fading
* 3. Draw view's content
* 4. Draw children
* 5. If necessary, draw the fading edges and restore layers
* 6. Draw decorations (scrollbars for instance)
* 7. If necessary, draw the default focus highlight
*/
// Step 1, draw the background, if needed
int saveCount;
drawBackground(canvas);
// skip step 2 & 5 if possible (common case)
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
// Step 3, draw the content
onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (isShowingLayoutBounds()) {
debugDrawFocus(canvas);
}
// we're done...
return;
}
/*
* Here we do the full fledged routine...
* (this is an uncommon case where speed matters less,
* this is why we repeat some of the tests that have been
* done above)
*/
boolean drawTop = false;
boolean drawBottom = false;
boolean drawLeft = false;
boolean drawRight = false;
float topFadeStrength = 0.0f;
float bottomFadeStrength = 0.0f;
float leftFadeStrength = 0.0f;
float rightFadeStrength = 0.0f;
// Step 2, save the canvas' layers
int paddingLeft = mPaddingLeft;
final boolean offsetRequired = isPaddingOffsetRequired();
if (offsetRequired) {
paddingLeft += getLeftPaddingOffset();
}
int left = mScrollX + paddingLeft;
int right = left + mRight - mLeft - mPaddingRight - paddingLeft;
int top = mScrollY + getFadeTop(offsetRequired);
int bottom = top + getFadeHeight(offsetRequired);
if (offsetRequired) {
right += getRightPaddingOffset();
bottom += getBottomPaddingOffset();
}
final ScrollabilityCache scrollabilityCache = mScrollCache;
final float fadeHeight = scrollabilityCache.fadingEdgeLength;
int length = (int) fadeHeight;
// clip the fade length if top and bottom fades overlap
// overlapping fades produce odd-looking artifacts
if (verticalEdges && (top + length > bottom - length)) {
length = (bottom - top) / 2;
}
// also clip horizontal fades if necessary
if (horizontalEdges && (left + length > right - length)) {
length = (right - left) / 2;
}
if (verticalEdges) {
topFadeStrength = Math.max(0.0f, Math.min(1.0f, getTopFadingEdgeStrength()));
drawTop = topFadeStrength * fadeHeight > 1.0f;
bottomFadeStrength = Math.max(0.0f, Math.min(1.0f, getBottomFadingEdgeStrength()));
drawBottom = bottomFadeStrength * fadeHeight > 1.0f;
}
if (horizontalEdges) {
leftFadeStrength = Math.max(0.0f, Math.min(1.0f, getLeftFadingEdgeStrength()));
drawLeft = leftFadeStrength * fadeHeight > 1.0f;
rightFadeStrength = Math.max(0.0f, Math.min(1.0f, getRightFadingEdgeStrength()));
drawRight = rightFadeStrength * fadeHeight > 1.0f;
}
saveCount = canvas.getSaveCount();
int topSaveCount = -1;
int bottomSaveCount = -1;
int leftSaveCount = -1;
int rightSaveCount = -1;
int solidColor = getSolidColor();
if (solidColor == 0) {
if (drawTop) {
topSaveCount = canvas.saveUnclippedLayer(left, top, right, top + length);
}
if (drawBottom) {
bottomSaveCount = canvas.saveUnclippedLayer(left, bottom - length, right, bottom);
}
if (drawLeft) {
leftSaveCount = canvas.saveUnclippedLayer(left, top, left + length, bottom);
}
if (drawRight) {
rightSaveCount = canvas.saveUnclippedLayer(right - length, top, right, bottom);
}
} else {
scrollabilityCache.setFadeColor(solidColor);
}
// Step 3, draw the content
// View自己实现的onDraw绘制,由应用开发者自己实现
onDraw(canvas);
// Step 4, draw the children
dispatchDraw(canvas);
// Step 5, draw the fade effect and restore layers
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
// must be restored in the reverse order that they were saved
if (drawRight) {
matrix.setScale(1, fadeHeight * rightFadeStrength);
matrix.postRotate(90);
matrix.postTranslate(right, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
if (solidColor == 0) {
canvas.restoreUnclippedLayer(rightSaveCount, p);
} else {
canvas.drawRect(right - length, top, right, bottom, p);
}
}
if (drawLeft) {
matrix.setScale(1, fadeHeight * leftFadeStrength);
matrix.postRotate(-90);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
if (solidColor == 0) {
canvas.restoreUnclippedLayer(leftSaveCount, p);
} else {
canvas.drawRect(left, top, left + length, bottom, p);
}
}
if (drawBottom) {
matrix.setScale(1, fadeHeight * bottomFadeStrength);
matrix.postRotate(180);
matrix.postTranslate(left, bottom);
fade.setLocalMatrix(matrix);
p.setShader(fade);
if (solidColor == 0) {
canvas.restoreUnclippedLayer(bottomSaveCount, p);
} else {
canvas.drawRect(left, bottom - length, right, bottom, p);
}
}
if (drawTop) {
matrix.setScale(1, fadeHeight * topFadeStrength);
matrix.postTranslate(left, top);
fade.setLocalMatrix(matrix);
p.setShader(fade);
if (solidColor == 0) {
canvas.restoreUnclippedLayer(topSaveCount, p);
} else {
canvas.drawRect(left, top, right, top + length, p);
}
}
canvas.restoreToCount(saveCount);
drawAutofilledHighlight(canvas);
// Overlay is part of the content and draws beneath Foreground
if (mOverlay != null && !mOverlay.isEmpty()) {
mOverlay.getOverlayView().dispatchDraw(canvas);
}
// Step 6, draw decorations (foreground, scrollbars)
onDrawForeground(canvas);
// Step 7, draw the default focus highlight
drawDefaultFocusHighlight(canvas);
if (isShowingLayoutBounds()) {
debugDrawFocus(canvas);
}
}
RenderNode.endRecording
// frameworks/base/graphics/java/android/graphics/RenderNode.java
/**
* `
* Ends the recording for this display list. Calling this method marks
* the display list valid and {@link #hasDisplayList()} will return true.
*
* @see #beginRecording(int, int)
* @see #hasDisplayList()
*/
public void endRecording() {
if (mCurrentRecordingCanvas == null) {
throw new IllegalStateException(
"No recording in progress, forgot to call #beginRecording()?");
}
RecordingCanvas canvas = mCurrentRecordingCanvas;
mCurrentRecordingCanvas = null;
// 从SkiaRecordingCanvas中获取SkiaDisplayList对象
canvas.finishRecording(this);
// 将SkiaDisplayList对象填充到RenderNode中
canvas.recycle();
}
从以上代码可以看出,构建绘制命令树的过程是从View
控件树的根节点DecorView
触发,递归调用每个子View
节点的updateDisplayListIfDirty
函数,最终完成绘制树的创建,简述流程如下:
- 利用
View
对象构造时创建的RenderNode
获取一个SkiaRecordingCanvas
“画布”; - 利用
SkiaRecordingCanvas
,在每个子View
控件的onDraw
绘制函数中调用drawLine
、drawRect
等绘制操作时,创建对应的DisplayListOp
绘制命令,并缓存记录到其内部的SkiaDisplayList
持有的DisplayListData
中; - 将包含有
DisplayListOp
绘制命令缓存的SkiaDisplayList
对象设置填充到RenderNode
中; - 最后将根
View
的缓存DisplayListOp
设置到RootRenderNode
中,完成构建。
以上过程从trace分析如下:
构建View绘制命令树.png
7.3执行渲染绘制任务
经过上一小节中的分析,应用在UI线程中从根节点DecorView出发,递归遍历每个子View节点,搜集其drawXXX绘制动作并转换成DisplayListOp命令,将其记录到DisplayListData并填充到RenderNode中,最终完成整个View绘制命令树的构建。从此UI线程的绘制任务就完成了。下一步UI线程将唤醒RenderThread渲染线程,触发其利用OpenGL执行界面的渲染任务,本小节中我们将重点分析这个流程。
syncAndDrawFrame
// frameworks/base/graphics/java/android/graphics/HardwareRenderer.java
/**
* Syncs the RenderNode tree to the render thread and requests a frame to be drawn.
*
* @hide
*/
@SyncAndDrawResult
public int syncAndDrawFrame(@NonNull FrameInfo frameInfo) {
// JNI调用native层的相关函数
return nSyncAndDrawFrame(mNativeProxy, frameInfo.frameInfo, frameInfo.frameInfo.length);
}
android_view_ThreadedRenderer_syncAndDrawFrame
// frameworks/base/libs/hwui/jni/android_graphics_HardwareRenderer.cpp
static int android_view_ThreadedRenderer_syncAndDrawFrame(JNIEnv* env, jobject clazz,
jlong proxyPtr, jlongArray frameInfo,
jint frameInfoSize) {
LOG_ALWAYS_FATAL_IF(frameInfoSize != UI_THREAD_FRAME_INFO_SIZE,
"Mismatched size expectations, given %d expected %zu", frameInfoSize,
UI_THREAD_FRAME_INFO_SIZE);
RenderProxy* proxy = reinterpret_cast<RenderProxy*>(proxyPtr);
env->GetLongArrayRegion(frameInfo, 0, frameInfoSize, proxy->frameInfo());
return proxy->syncAndDrawFrame();
}
syncAndDrawFrame
// frameworks/base/libs/hwui/renderthread/RenderProxy.cpp
int RenderProxy::syncAndDrawFrame() {
return mDrawFrameTask.drawFrame();
}
DrawFrameTask.drawFrame
// frameworks/base/libs/hwui/renderthread/DrawFrameTask.cpp
int DrawFrameTask::drawFrame() {
LOG_ALWAYS_FATAL_IF(!mContext, "Cannot drawFrame with no CanvasContext!");
mSyncResult = SyncResult::OK;
mSyncQueued = systemTime(SYSTEM_TIME_MONOTONIC);
postAndWait(); //继续
return mSyncResult;
}
void DrawFrameTask::postAndWait() {
ATRACE_CALL();
// 向RenderThread渲染线程的MessageQueue消息队列放入一个待执行任务,以将其唤醒执行run函数
AutoMutex _lock(mLock);
mRenderThread->queue().post([this]() { run(); });
// UI线程暂时进入wait等待状态
mSignal.wait(mLock);
}
void DrawFrameTask::run() {
const int64_t vsyncId = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameTimelineVsyncId)];
// 原生标识一帧渲染绘制任务的systrace tag
ATRACE_FORMAT("DrawFrames %" PRId64, vsyncId);
mContext->setSyncDelayDuration(systemTime(SYSTEM_TIME_MONOTONIC) - mSyncQueued);
mContext->setTargetSdrHdrRatio(mRenderSdrHdrRatio);
auto hardwareBufferParams = mHardwareBufferParams;
mContext->setHardwareBufferRenderParams(hardwareBufferParams);
IRenderPipeline* pipeline = mContext->getRenderPipeline();
bool canUnblockUiThread;
bool canDrawThisFrame;
bool solelyTextureViewUpdates;
{
TreeInfo info(TreeInfo::MODE_FULL, *mContext);
info.forceDrawFrame = mForceDrawFrame;
mForceDrawFrame = false;
//1.将UI线程构建的DisplayListOp绘制命令树同步到RenderThread渲染线程
canUnblockUiThread = syncFrameState(info);
canDrawThisFrame = !info.out.skippedFrameReason.has_value();
solelyTextureViewUpdates = info.out.solelyTextureViewUpdates;
if (mFrameCommitCallback) {
mContext->addFrameCommitListener(std::move(mFrameCommitCallback));
mFrameCommitCallback = nullptr;
}
}
// Grab a copy of everything we need
CanvasContext* context = mContext;
std::function<std::function<void(bool)>(int32_t, int64_t)> frameCallback =
std::move(mFrameCallback);
std::function<void()> frameCompleteCallback = std::move(mFrameCompleteCallback);
mFrameCallback = nullptr;
mFrameCompleteCallback = nullptr;
// 同步完成后则可以唤醒UI线程
// From this point on anything in "this" is *UNSAFE TO ACCESS*
if (canUnblockUiThread) {
unblockUiThread();
}
// Even if we aren't drawing this vsync pulse the next frame number will still be accurate
if (CC_UNLIKELY(frameCallback)) {
context->enqueueFrameWork([frameCallback, context, syncResult = mSyncResult,
frameNr = context->getFrameNumber()]() {
auto frameCommitCallback = frameCallback(syncResult, frameNr);
if (frameCommitCallback) {
context->addFrameCommitListener(std::move(frameCommitCallback));
}
});
}
if (CC_LIKELY(canDrawThisFrame)) {
// 2.执行draw渲染绘制动作
context->draw(solelyTextureViewUpdates);
} else {
// Do a flush in case syncFrameState performed any texture uploads. Since we skipped
// the draw() call, those uploads (or deletes) will end up sitting in the queue.
// Do them now
if (GrDirectContext* grContext = mRenderThread->getGrContext()) {
grContext->flushAndSubmit();
}
// wait on fences so tasks don't overlap next frame
context->waitOnFences();
}
if (CC_UNLIKELY(frameCompleteCallback)) {
std::invoke(frameCompleteCallback);
}
if (!canUnblockUiThread) {
unblockUiThread();
}
if (pipeline->hasHardwareBuffer()) {
auto fence = pipeline->flush();
hardwareBufferParams.invokeRenderCallback(std::move(fence), 0);
}
}
bool DrawFrameTask::syncFrameState(TreeInfo& info) {
ATRACE_CALL();
int64_t vsync = mFrameInfo[static_cast<int>(FrameInfoIndex::Vsync)];
int64_t intendedVsync = mFrameInfo[static_cast<int>(FrameInfoIndex::IntendedVsync)];
int64_t vsyncId = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameTimelineVsyncId)];
int64_t frameDeadline = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameDeadline)];
int64_t frameInterval = mFrameInfo[static_cast<int>(FrameInfoIndex::FrameInterval)];
mRenderThread->timeLord().vsyncReceived(vsync, intendedVsync, vsyncId, frameDeadline,
frameInterval);
bool canDraw = mContext->makeCurrent();
mContext->unpinImages();
for (size_t i = 0; i < mLayers.size(); i++) {
if (mLayers[i]) {
mLayers[i]->apply();
}
}
mLayers.clear();
mContext->setContentDrawBounds(mContentDrawBounds);
// 调用CanvasContext的prepareTree函数实现绘制命令树同步的流程
mContext->prepareTree(info, mFrameInfo, mSyncQueued, mTargetNode);
// This is after the prepareTree so that any pending operations
// (RenderNode tree state, prefetched layers, etc...) will be flushed.
bool hasTarget = mContext->hasOutputTarget();
if (CC_UNLIKELY(!hasTarget || !canDraw)) {
if (!hasTarget) {
mSyncResult |= SyncResult::LostSurfaceRewardIfFound;
info.out.skippedFrameReason = SkippedFrameReason::NoOutputTarget;
} else {
// If we have a surface but can't draw we must be stopped
mSyncResult |= SyncResult::ContextIsStopped;
info.out.skippedFrameReason = SkippedFrameReason::ContextIsStopped;
}
}
if (info.out.hasAnimations) {
if (info.out.requiresUiRedraw) {
mSyncResult |= SyncResult::UIRedrawRequired;
}
}
if (info.out.skippedFrameReason) {
mSyncResult |= SyncResult::FrameDropped;
}
// If prepareTextures is false, we ran out of texture cache space
return info.prepareTextures;
}
CanvasContext.prepareTree
// frameworks/base/libs/hwui/renderthread/CanvasContext.cpp
void CanvasContext::prepareTree(TreeInfo& info, int64_t* uiFrameInfo, int64_t syncQueued,
RenderNode* target) {
mRenderThread.removeFrameCallback(this);
// If the previous frame was dropped we don't need to hold onto it, so
// just keep using the previous frame's structure instead
if (const auto reason = wasSkipped(mCurrentFrameInfo)) {
// Use the oldest skipped frame in case we skip more than a single frame
if (!mSkippedFrameInfo) {
switch (*reason) {
case SkippedFrameReason::AlreadyDrawn:
case SkippedFrameReason::NoBuffer:
case SkippedFrameReason::NoOutputTarget:
mSkippedFrameInfo.emplace();
mSkippedFrameInfo->vsyncId =
mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId);
mSkippedFrameInfo->startTime =
mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime);
break;
case SkippedFrameReason::DrawingOff:
case SkippedFrameReason::ContextIsStopped:
case SkippedFrameReason::NothingToDraw:
// Do not report those as skipped frames as there was no frame expected to be
// drawn
break;
}
}
} else {
mCurrentFrameInfo = mJankTracker.startFrame();
mSkippedFrameInfo.reset();
}
mCurrentFrameInfo->importUiThreadInfo(uiFrameInfo);
mCurrentFrameInfo->set(FrameInfoIndex::SyncQueued) = syncQueued;
mCurrentFrameInfo->markSyncStart();
info.damageAccumulator = &mDamageAccumulator;
info.layerUpdateQueue = &mLayerUpdateQueue;
info.damageGenerationId = mDamageId++;
info.out.skippedFrameReason = std::nullopt;
mAnimationContext->startFrame(info.mode);
for (const sp<RenderNode>& node : mRenderNodes) {
// Only the primary target node will be drawn full - all other nodes would get drawn in
// real time mode. In case of a window, the primary node is the window content and the other
// node(s) are non client / filler nodes.
info.mode = (node.get() == target ? TreeInfo::MODE_FULL : TreeInfo::MODE_RT_ONLY);
// 递归调用各个子View对应的RenderNode执行prepareTree动作
node->prepareTree(info);
GL_CHECKPOINT(MODERATE);
}
mAnimationContext->runRemainingAnimations(info);
GL_CHECKPOINT(MODERATE);
freePrefetchedLayers();
GL_CHECKPOINT(MODERATE);
mIsDirty = true;
if (CC_UNLIKELY(!hasOutputTarget())) {
info.out.skippedFrameReason = SkippedFrameReason::NoOutputTarget;
mCurrentFrameInfo->setSkippedFrameReason(*info.out.skippedFrameReason);
return;
}
if (CC_LIKELY(mSwapHistory.size() && !info.forceDrawFrame)) {
nsecs_t latestVsync = mRenderThread.timeLord().latestVsync();
SwapHistory& lastSwap = mSwapHistory.back();
nsecs_t vsyncDelta = std::abs(lastSwap.vsyncTime - latestVsync);
// The slight fudge-factor is to deal with cases where
// the vsync was estimated due to being slow handling the signal.
// See the logic in TimeLord#computeFrameTimeNanos or in
// Choreographer.java for details on when this happens
if (vsyncDelta < 2_ms) {
// Already drew for this vsync pulse, UI draw request missed
// the deadline for RT animations
info.out.skippedFrameReason = SkippedFrameReason::AlreadyDrawn;
}
} else {
info.out.skippedFrameReason = std::nullopt;
}
// TODO: Do we need to abort out if the backdrop is added but not ready? Should that even
// be an allowable combination?
if (mRenderNodes.size() > 2 && !mRenderNodes[1]->isRenderable()) {
info.out.skippedFrameReason = SkippedFrameReason::NothingToDraw;
}
if (!info.out.skippedFrameReason) {
int err = mNativeSurface->reserveNext();
if (err != OK) {
info.out.skippedFrameReason = SkippedFrameReason::NoBuffer;
mCurrentFrameInfo->setSkippedFrameReason(*info.out.skippedFrameReason);
ALOGW("reserveNext failed, error = %d (%s)", err, strerror(-err));
if (err != TIMED_OUT) {
// A timed out surface can still recover, but assume others are permanently dead.
setSurface(nullptr);
return;
}
}
} else {
mCurrentFrameInfo->setSkippedFrameReason(*info.out.skippedFrameReason);
}
bool postedFrameCallback = false;
if (info.out.hasAnimations || info.out.skippedFrameReason) {
if (CC_UNLIKELY(!Properties::enableRTAnimations)) {
info.out.requiresUiRedraw = true;
}
if (!info.out.requiresUiRedraw) {
// If animationsNeedsRedraw is set don't bother posting for an RT anim
// as we will just end up fighting the UI thread.
mRenderThread.postFrameCallback(this);
postedFrameCallback = true;
}
}
if (!postedFrameCallback &&
info.out.animatedImageDelay != TreeInfo::Out::kNoAnimatedImageDelay) {
// Subtract the time of one frame so it can be displayed on time.
const nsecs_t kFrameTime = mRenderThread.timeLord().frameIntervalNanos();
if (info.out.animatedImageDelay <= kFrameTime) {
mRenderThread.postFrameCallback(this);
} else {
const auto delay = info.out.animatedImageDelay - kFrameTime;
int genId = mGenerationID;
mRenderThread.queue().postDelayed(delay, [this, genId]() {
if (mGenerationID == genId) {
mRenderThread.postFrameCallback(this);
}
});
}
}
}
prepareTree
// frameworks/base/libs/hwui/RenderNode.cpp
void RenderNode::prepareTree(TreeInfo& info) {
ATRACE_CALL();
LOG_ALWAYS_FATAL_IF(!info.damageAccumulator, "DamageAccumulator missing");
MarkAndSweepRemoved observer(&info);
const int before = info.disableForceDark;
prepareTreeImpl(observer, info, false);
LOG_ALWAYS_FATAL_IF(before != info.disableForceDark, "Mis-matched force dark");
}
/**
* Traverse down the the draw tree to prepare for a frame.
*
* MODE_FULL = UI Thread-driven (thus properties must be synced), otherwise RT driven
*
* While traversing down the tree, functorsNeedLayer flag is set to true if anything that uses the
* stencil buffer may be needed. Views that use a functor to draw will be forced onto a layer.
*/
void RenderNode::prepareTreeImpl(TreeObserver& observer, TreeInfo& info, bool functorsNeedLayer) {
if (mDamageGenerationId == info.damageGenerationId && mDamageGenerationId != 0) {
// We hit the same node a second time in the same tree. We don't know the minimal
// damage rect anymore, so just push the biggest we can onto our parent's transform
// We push directly onto parent in case we are clipped to bounds but have moved position.
info.damageAccumulator->dirty(DIRTY_MIN, DIRTY_MIN, DIRTY_MAX, DIRTY_MAX);
}
info.damageAccumulator->pushTransform(this);
if (info.mode == TreeInfo::MODE_FULL) {
// 同步绘制命令树
pushStagingPropertiesChanges(info);
}
if (!mProperties.getAllowForceDark()) {
info.disableForceDark++;
}
if (!mProperties.layerProperties().getStretchEffect().isEmpty()) {
info.stretchEffectCount++;
}
uint32_t animatorDirtyMask = 0;
if (CC_LIKELY(info.runAnimations)) {
animatorDirtyMask = mAnimatorManager.animate(info);
}
bool willHaveFunctor = false;
if (info.mode == TreeInfo::MODE_FULL && mStagingDisplayList) {
willHaveFunctor = mStagingDisplayList.hasFunctor();
} else if (mDisplayList) {
willHaveFunctor = mDisplayList.hasFunctor();
}
bool childFunctorsNeedLayer =
mProperties.prepareForFunctorPresence(willHaveFunctor, functorsNeedLayer);
if (CC_UNLIKELY(mPositionListener.get())) {
mPositionListener->onPositionUpdated(*this, info);
}
prepareLayer(info, animatorDirtyMask);
if (info.mode == TreeInfo::MODE_FULL) {
pushStagingDisplayListChanges(observer, info);
}
// always damageSelf when filtering backdrop content, or else the BackdropFilterDrawable will
// get a wrong snapshot of previous content.
if (mProperties.layerProperties().getBackdropImageFilter()) {
damageSelf(info);
}
if (mDisplayList) {
info.out.hasFunctors |= mDisplayList.hasFunctor();
mHasHolePunches = mDisplayList.hasHolePunches();
// 遍历调用各个子View对应的RenderNode的prepareTreeImpl
bool isDirty = mDisplayList.prepareListAndChildren(
observer, info, childFunctorsNeedLayer,
[this](RenderNode* child, TreeObserver& observer, TreeInfo& info,
bool functorsNeedLayer) {
child->prepareTreeImpl(observer, info, functorsNeedLayer);
mHasHolePunches |= child->hasHolePunches();
});
if (isDirty) {
damageSelf(info);
}
} else {
mHasHolePunches = false;
}
pushLayerUpdate(info);
if (!mProperties.getAllowForceDark()) {
info.disableForceDark--;
}
if (!mProperties.layerProperties().getStretchEffect().isEmpty()) {
info.stretchEffectCount--;
}
info.damageAccumulator->popTransform();
}
void RenderNode::pushStagingDisplayListChanges(TreeObserver& observer, TreeInfo& info) {
if (mNeedsDisplayListSync) {
mNeedsDisplayListSync = false;
// Damage with the old display list first then the new one to catch any
// changes in isRenderable or, in the future, bounds
damageSelf(info);
syncDisplayList(observer, &info); //继续
damageSelf(info);
}
}
void RenderNode::syncDisplayList(TreeObserver& observer, TreeInfo* info) {
// Make sure we inc first so that we don't fluctuate between 0 and 1,
// which would thrash the layer cache
if (mStagingDisplayList) {
mStagingDisplayList.updateChildren([](RenderNode* child) { child->incParentRefCount(); });
}
// 完成赋值同步DisplayList对象
deleteDisplayList(observer, info);
mDisplayList = std::move(mStagingDisplayList);
if (mDisplayList) {
WebViewSyncData syncData{.applyForceDark = shouldEnableForceDark(info)};
mDisplayList.syncContents(syncData);
handleForceDark(info);
}
}
CanvasContext.draw
void CanvasContext::draw(bool solelyTextureViewUpdates) {
if (auto grContext = getGrContext()) {
if (grContext->abandoned()) {
if (grContext->isDeviceLost()) {
LOG_ALWAYS_FATAL("Lost GPU device unexpectedly");
return;
}
LOG_ALWAYS_FATAL("GrContext is abandoned at start of CanvasContext::draw");
return;
}
}
SkRect dirty;
mDamageAccumulator.finish(&dirty);
// reset syncDelayDuration each time we draw
nsecs_t syncDelayDuration = mSyncDelayDuration;
nsecs_t idleDuration = mIdleDuration;
mSyncDelayDuration = 0;
mIdleDuration = 0;
const auto skippedFrameReason = [&]() -> std::optional<SkippedFrameReason> {
if (!Properties::isDrawingEnabled()) {
return SkippedFrameReason::DrawingOff;
}
if (dirty.isEmpty() && Properties::skipEmptyFrames && !surfaceRequiresRedraw()) {
return SkippedFrameReason::NothingToDraw;
}
return std::nullopt;
}();
if (skippedFrameReason) {
mCurrentFrameInfo->setSkippedFrameReason(*skippedFrameReason);
if (auto grContext = getGrContext()) {
// Submit to ensure that any texture uploads complete and Skia can
// free its staging buffers.
grContext->flushAndSubmit();
}
// Notify the callbacks, even if there's nothing to draw so they aren't waiting
// indefinitely
waitOnFences();
for (auto& func : mFrameCommitCallbacks) {
std::invoke(func, false /* didProduceBuffer */);
}
mFrameCommitCallbacks.clear();
return;
}
ScopedActiveContext activeContext(this);
mCurrentFrameInfo->set(FrameInfoIndex::FrameInterval) =
mRenderThread.timeLord().frameIntervalNanos();
mCurrentFrameInfo->markIssueDrawCommandsStart();
Frame frame = getFrame();
SkRect windowDirty = computeDirtyRect(frame, &dirty);
ATRACE_FORMAT("Drawing " RECT_STRING, SK_RECT_ARGS(dirty));
IRenderPipeline::DrawResult drawResult;
{
// FrameInfoVisualizer accesses the frame events, which cannot be mutated mid-draw
// or it can lead to memory corruption.
// 1.调用OpenGL库使用GPU,按照构建好的绘制命令完成界面的渲染
drawResult = mRenderPipeline->draw(
frame, windowDirty, dirty, mLightGeometry, &mLayerUpdateQueue, mContentDrawBounds,
mOpaque, mLightInfo, mRenderNodes, &(profiler()), mBufferParams, profilerLock());
}
uint64_t frameCompleteNr = getFrameNumber();
waitOnFences();
if (mNativeSurface) {
// TODO(b/165985262): measure performance impact
const auto vsyncId = mCurrentFrameInfo->get(FrameInfoIndex::FrameTimelineVsyncId);
if (vsyncId != UiFrameInfoBuilder::INVALID_VSYNC_ID) {
const auto inputEventId =
static_cast<int32_t>(mCurrentFrameInfo->get(FrameInfoIndex::InputEventId));
const ANativeWindowFrameTimelineInfo ftl = {
.frameNumber = frameCompleteNr,
.frameTimelineVsyncId = vsyncId,
.inputEventId = inputEventId,
.startTimeNanos = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime),
.useForRefreshRateSelection = solelyTextureViewUpdates,
.skippedFrameVsyncId = mSkippedFrameInfo ? mSkippedFrameInfo->vsyncId
: UiFrameInfoBuilder::INVALID_VSYNC_ID,
.skippedFrameStartTimeNanos =
mSkippedFrameInfo ? mSkippedFrameInfo->startTime : 0,
};
native_window_set_frame_timeline_info(mNativeSurface->getNativeWindow(), ftl);
}
}
bool requireSwap = false;
bool didDraw = false;
int error = OK;
// 2.将前面已经绘制渲染好的图形缓冲区Binder上帧给SurfaceFlinger合成和显示
bool didSwap = mRenderPipeline->swapBuffers(frame, drawResult, windowDirty, mCurrentFrameInfo,
&requireSwap);
mCurrentFrameInfo->set(FrameInfoIndex::CommandSubmissionCompleted) = std::max(
drawResult.commandSubmissionTime, mCurrentFrameInfo->get(FrameInfoIndex::SwapBuffers));
mIsDirty = false;
if (requireSwap) {
didDraw = true;
// Handle any swapchain errors
error = mNativeSurface->getAndClearError();
if (error == TIMED_OUT) {
// Try again
mRenderThread.postFrameCallback(this);
// But since this frame didn't happen, we need to mark full damage in the swap
// history
didDraw = false;
} else if (error != OK || !didSwap) {
// Unknown error, abandon the surface
setSurface(nullptr);
didDraw = false;
}
SwapHistory& swap = mSwapHistory.next();
if (didDraw) {
swap.damage = windowDirty;
} else {
float max = static_cast<float>(INT_MAX);
swap.damage = SkRect::MakeWH(max, max);
}
swap.swapCompletedTime = systemTime(SYSTEM_TIME_MONOTONIC);
swap.vsyncTime = mRenderThread.timeLord().latestVsync();
if (didDraw) {
nsecs_t dequeueStart =
ANativeWindow_getLastDequeueStartTime(mNativeSurface->getNativeWindow());
if (dequeueStart < mCurrentFrameInfo->get(FrameInfoIndex::SyncStart)) {
// Ignoring dequeue duration as it happened prior to frame render start
// and thus is not part of the frame.
swap.dequeueDuration = 0;
} else {
swap.dequeueDuration =
ANativeWindow_getLastDequeueDuration(mNativeSurface->getNativeWindow());
}
swap.queueDuration =
ANativeWindow_getLastQueueDuration(mNativeSurface->getNativeWindow());
} else {
swap.dequeueDuration = 0;
swap.queueDuration = 0;
}
mCurrentFrameInfo->set(FrameInfoIndex::DequeueBufferDuration) = swap.dequeueDuration;
mCurrentFrameInfo->set(FrameInfoIndex::QueueBufferDuration) = swap.queueDuration;
mHaveNewSurface = false;
mFrameNumber = 0;
} else {
mCurrentFrameInfo->set(FrameInfoIndex::DequeueBufferDuration) = 0;
mCurrentFrameInfo->set(FrameInfoIndex::QueueBufferDuration) = 0;
}
mCurrentFrameInfo->markSwapBuffersCompleted();
#if LOG_FRAMETIME_MMA
float thisFrame = mCurrentFrameInfo->duration(FrameInfoIndex::IssueDrawCommandsStart,
FrameInfoIndex::FrameCompleted) /
NANOS_PER_MILLIS_F;
if (sFrameCount) {
sBenchMma = ((9 * sBenchMma) + thisFrame) / 10;
} else {
sBenchMma = thisFrame;
}
if (++sFrameCount == 10) {
sFrameCount = 1;
ALOGD("Average frame time: %.4f", sBenchMma);
}
#endif
if (didSwap) {
for (auto& func : mFrameCommitCallbacks) {
std::invoke(func, true /* didProduceBuffer */);
}
mFrameCommitCallbacks.clear();
}
if (requireSwap) {
if (mExpectSurfaceStats) {
reportMetricsWithPresentTime();
{ // acquire lock
std::lock_guard lock(mLast4FrameMetricsInfosMutex);
FrameMetricsInfo& next = mLast4FrameMetricsInfos.next();
next.frameInfo = mCurrentFrameInfo;
next.frameNumber = frameCompleteNr;
next.surfaceId = mSurfaceControlGenerationId;
} // release lock
} else {
mCurrentFrameInfo->markFrameCompleted();
mCurrentFrameInfo->set(FrameInfoIndex::GpuCompleted)
= mCurrentFrameInfo->get(FrameInfoIndex::FrameCompleted);
std::scoped_lock lock(mFrameInfoMutex);
mJankTracker.finishFrame(*mCurrentFrameInfo, mFrameMetricsReporter, frameCompleteNr,
mSurfaceControlGenerationId);
}
}
int64_t intendedVsync = mCurrentFrameInfo->get(FrameInfoIndex::IntendedVsync);
int64_t frameDeadline = mCurrentFrameInfo->get(FrameInfoIndex::FrameDeadline);
int64_t dequeueBufferDuration = mCurrentFrameInfo->get(FrameInfoIndex::DequeueBufferDuration);
mHintSessionWrapper->updateTargetWorkDuration(frameDeadline - intendedVsync);
if (didDraw) {
int64_t frameStartTime = mCurrentFrameInfo->get(FrameInfoIndex::FrameStartTime);
int64_t frameDuration = systemTime(SYSTEM_TIME_MONOTONIC) - frameStartTime;
int64_t actualDuration = frameDuration -
(std::min(syncDelayDuration, mLastDequeueBufferDuration)) -
dequeueBufferDuration - idleDuration;
mHintSessionWrapper->reportActualWorkDuration(actualDuration);
}
mLastDequeueBufferDuration = dequeueBufferDuration;
mRenderThread.cacheManager().onFrameCompleted();
return;
}
从以上代码可以看出:UI
线程利用RenderProxy
向RenderThread
线程发送一个DrawFrameTask
任务请求,RenderThread
被唤醒,开始渲染,大致流程如下:
syncFrameState
中遍历View
树上每一个RenderNode
,执行prepareTreeImpl
函数,实现同步绘制命令树的操作;- 调用
OpenGL
库API
使用GPU
,按照构建好的绘制命令完成界面的渲染(具体过程,由于本文篇幅所限,暂不展开分析); - 将前面已经绘制渲染好的图形缓冲区
Binder
上帧给SurfaceFlinger
合成和显示;
整个过程可以用如下流程图表示:
以上过程从trace分析如下:
RenderThread实现界面渲染.png
8. SurfaceFlinger合成显示
SurfaceFlinger
合成显示部分完全属于Android
系统GUI
中图形显示的内容,逻辑结构也比较复杂,但不属于本文介绍内容的重点。所以本小节中只是总体上介绍一下其工作原理与思想,不再详细分析源码,感兴趣的读者可以关注笔者后续的文章再来详细分析讲解。简单的说SurfaceFlinger
作为系统中独立运行的一个Native
进程,**借用Android
官网的描述,其职责就是负责接受来自多个来源的数据缓冲区,对它们进行合成,然后发送到显示设备。**如下图所示:
SurfaceFlinger工作原理.jpg
从上图可以看出,其实SurfaceFlinger在Android系统的整个图形显示系统中是起到一个承上启下的作用:
- 对上:通过Surface与不同的应用进程建立联系,接收它们写入Surface中的绘制缓冲数据,对它们进行统一合成。
- 对下:通过屏幕的后缓存区与屏幕建立联系,发送合成好的数据到屏幕显示设备。
图形的传递是通过Buffer
作为载体,Surface
是对Buffer
的进一步封装,也就是说Surface
内部具有多个Buffer
供上层使用,如何管理这些Buffer
呢?答案就是BufferQueue
,下面我们来看看BufferQueue
的工作原理:
8.1 BufferQueue机制
借用一张经典的图来描述BufferQueue
的工作原理:
BufferQueue状态转换图.jpg
BufferQueue是一个典型的生产者-消费者模型中的数据结构。在Android应用的渲染流程中,应用扮演的就是“生产者”的角色,而SurfaceFlinger扮演的则是“消费者”的角色,其配合工作的流程如下:
- 应用进程中在开始界面的绘制渲染之前,需要通过
Binder
调用dequeueBuffer
接口从SurfaceFlinger
进程中管理的BufferQueue
中申请一张处于free
状态的可用Buffer
,如果此时没有可用Buffer
则阻塞等待; - 应用进程中拿到这张可用的
Buffer
之后,选择使用CPU
软件绘制渲染或GPU
硬件加速绘制渲染,渲染完成后再通过Binder
调用queueBuffer
接口将缓存数据返回给应用进程对应的BufferQueue
(如果是GPU
渲染的话,这里还有个GPU
处理的过程,所以这个Buffer
不会马上可用,需要等GPU
渲染完成的Fence
信号),并申请sf
类型的Vsync
以便唤醒“消费者”SurfaceFlinger
进行消费; SurfaceFlinger
在收到Vsync
信号之后,开始准备合成,使用acquireBuffer
获取应用对应的BufferQueue
中的Buffer
并进行合成操作;- 合成结束后,
SurfaceFlinger
将通过调用releaseBuffer
将Buffer
置为可用的free
状态,返回到应用对应的BufferQueue
中。
8.2 Vsync同步机制
Vysnc
垂直同步是Android
在“黄油计划”中引入的一个重要机制,本质上是为了协调BufferQueue
的应用生产者生成UI数据动作和SurfaceFlinger
消费者的合成消费动作,避免出现画面撕裂的Tearing
现象。Vysnc
信号分为两种类型:
app
类型的Vsync
:app
类型的Vysnc
信号由上层应用中的Choreographer
根据绘制需求进行注册和接收,用于控制应用UI绘制上帧的生产节奏。根据第6小结中的分析:应用在UI线程中调用invalidate刷新界面绘制时,需要先透过Choreographer
向系统申请注册app类型的Vsync
信号,待Vsync
信号到来后,才能往主线程的消息队列放入待绘制任务进行真正UI的绘制动作;sf
类型的Vsync
:sf
类型的Vsync
是用于控制SurfaceFlinger
的合成消费节奏。应用完成界面的绘制渲染后,通过Binder
调用queueBuffer
接口将缓存数据返还给应用对应的BufferQueue
时,会申请sf
类型的Vsync
,待SurfaceFlinger
在其UI线程中收到Vsync
信号之后,便开始进行界面的合成操作。
Vsync
信号的生成是参考屏幕硬件的刷新周期的,其架构如下图所示:
vsync.png
trace上SurfaceFlinger工作的流程如下图所示:
SurfaceFlinger处理.png
9.总结
本文结合Android 14源码和Perfetto分析了从用户手指点击桌面上的应用图标到屏幕上显示出应用主Activity界面第一帧画面的完整流程,这其中涉及了App应用、system_server框架、surfaceflinger等一系列Android系统核心模块的相互配合,有很多的细节也由于篇幅所限无法完全展开分析,感兴趣的读者可以结合AOSP源码继续深入分析。而优化应用启动打开的速度这个系统核心用户体验的指标,也是多少年来谷歌、SOC芯片厂商、ODM手机厂商以及各个应用开发者共同努力优化的方向:
- 对于
SOC
芯片厂商而言:需要不断升级CPU
和GPU
的硬件算力; - 对于
Android
系统的维护者谷歌而言:在Android系统大版本升级过程中,不断的优化应用启动过程上的各个系统流程,比如进程创建的速度优化、Art
虚拟机的引入与性能优化、View绘制流程的简化、硬件绘制加速机制的引入、系统核心AMS、WMS等核心服务的锁优化等; - 对于各个
ODM
手机厂商而言:会开发识别应用启动的场景,进行针对性的CPU主频的拉升调节、触控响应速度的优化等机制; - 对于各个应用开发者而言:会结合自己的业务对应用启动的场景进行优化,比如尽量减少或推迟在
Application
、Activity
生命周期函数中的初始化逻辑、去除界面布局的过度绘制、异步化的布局XML
文件解析等机制。
参考文档
Android14应用启动流程(源码+Trace)_systemserver oq wq-CSDN博客
Android应用启动全流程分析(源码深度剖析) - 简书 (jianshu.com)
android App启动流程一-启动APP的两种方式-CSDN博客
Android U启动浅析 - 兴华