android 14应用启动流程 - (下)显示流程分析
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
从以上代码可以看出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 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() 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 0) { for (int i = 0; i 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 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
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 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
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 { 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
从上面的代码流程可以看出,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
从上面的代码可以看出,硬件加速绘制主要包括两个阶段:
- 从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 frameCallbacks = mNextRtFrameCallbacks; mNextRtFrameCallbacks = null; setFrameCallback(new FrameDrawingCallback() { @Override public void onFrameDraw(long frame) { } @Override public FrameCommitCallback onFrameDraw(int syncResult, long frame) { ArrayList frameCommitCallbacks = new ArrayList(); for (int i = 0; i { for (int i = 0; i
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(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(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 frameCallback = std::move(mFrameCallback); std::function 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(FrameInfoIndex::Vsync)]; int64_t intendedVsync = mFrameInfo[static_cast(FrameInfoIndex::IntendedVsync)]; int64_t vsyncId = mFrameInfo[static_cast(FrameInfoIndex::FrameTimelineVsyncId)]; int64_t frameDeadline = mFrameInfo[static_cast(FrameInfoIndex::FrameDeadline)]; int64_t frameInterval = mFrameInfo[static_cast(FrameInfoIndex::FrameInterval)]; mRenderThread->timeLord().vsyncReceived(vsync, intendedVsync, vsyncId, frameDeadline, frameInterval); bool canDraw = mContext->makeCurrent(); mContext->unpinImages(); for (size_t i = 0; 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& 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 && !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 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); } }); } } } 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 { 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(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(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 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 Code Search
Android U启动浅析 - 兴华