android 14应用启动流程 - (下)显示流程分析

06-01 1378阅读

android 14显示流程分析

6. 应用UI布局与绘制

接着android应用启动流程的分析。

应用主线程中在执行Activity的Resume流程的最后,会创建ViewRootImpl对象并调用其setView函数,从此并开启了应用界面UI布局与绘制的流程。在开始讲解这个过程之前,我们先来整理一下前面代码中讲到的这些概念,如Activity、PhoneWindow、DecorView、ViewRootImpl、WindowManager它们之间的关系与职责,因为这些核心类基本构成了Android系统的GUI显示系统在应用进程侧的核心架构,其整体架构如下图所示:

android 14应用启动流程 - (下)显示流程分析

  • 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内部关键流程如下:

    1. requestLayout()通过一系列调用触发界面绘制(measure、layout、draw)动作,下文会详细展开分析;
    2. 通过Binder调用访问系统窗口管理服务WMS的addWindow接口,实现添加、注册应用窗口的操作,并传入本地创建inputChannel对象用于后续接收系统的触控事件,这一步执行完我们的View就可以显示到屏幕上了。关于WMS的内部实现流程也非常复杂,由于篇幅有限本文就不详细展开分析了。
    3. 创建WindowInputEventReceiver对象,封装实现应用窗口接收系统触控事件的逻辑;
    4. 执行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 渲染链路中承上启下的角色:

    1. 承上:负责接收和处理 App 的各种更新消息和回调,等到 Vsync 到来的时候统一处理。比如集中处理 Input(主要是 Input 事件的处理) 、Animation(动画相关)、Traversal(包括 measure、layout、draw 等操作) ,判断卡顿掉帧情况,记录 CallBack 耗时等;
    2. 启下:负责请求和接收 Vsync 信号。接收 Vsync 事件回调(通过 FrameDisplayEventReceiver.onVsync ),请求 Vsync(FrameDisplayEventReceiver.scheduleVsync) 。

    Choreographer在收到CALLBACK_TRAVERSAL类型的绘制任务后,其内部的工作流程如下图所示:

    android 14应用启动流程 - (下)显示流程分析

    从以上流程图可以看出: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中负责的整个应用界面绘制的主要流程如下:

    1. 从界面View控件树的根节点DecorView出发,递归遍历整个View控件树,完成对整个View控件树的measure测量操作,由于篇幅所限,本文就不展开分析这块的详细流程;
    2. 界面第一次执行绘制任务时,会通过Binder IPC访问系统窗口管理服务WMS的relayout接口,实现窗口尺寸的计算并向系统申请用于本地绘制渲染的Surface“画布”的操作(具体由SurfaceFlinger负责创建应用界面对应的BufferQueueLayer对象,并通过内存共享的方式通过Binder将地址引用透过WMS回传给应用进程这边),由于篇幅所限,本文就不展开分析这块的详细流程;
    3. 从界面View控件树的根节点DecorView出发,递归遍历整个View控件树,完成对整个View控件树的layout测量操作;
    4. 从界面View控件树的根节点DecorView出发,递归遍历整个View控件树,完成对整个View控件树的draw测量操作,如果开启并支持硬件绘制加速(从Android 4.X开始谷歌已经默认开启硬件加速),则走GPU硬件绘制的流程,否则走CPU软件绘制的流程;

    以上绘制过程从systrace上看如下图所示:

    android 14应用启动流程 - (下)显示流程分析

    借用一张图来总结应用UI绘制的流程,如下所示:

    android 14应用启动流程 - (下)显示流程分析

    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  
    

    从上面的代码可以看出,硬件加速绘制主要包括两个阶段:

    1. 从DecorView根节点出发,递归遍历View控件树,记录每个View节点的drawOp绘制操作命令,完成绘制操作命令树的构建;
    2. 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函数,最终完成绘制树的创建,简述流程如下:

    1. 利用View对象构造时创建的RenderNode获取一个SkiaRecordingCanvas“画布”;
    2. 利用SkiaRecordingCanvas,在每个子View控件的onDraw绘制函数中调用drawLine、drawRect等绘制操作时,创建对应的DisplayListOp绘制命令,并缓存记录到其内部的SkiaDisplayList持有的DisplayListData中;
    3. 将包含有DisplayListOp绘制命令缓存的SkiaDisplayList对象设置填充到RenderNode中;
    4. 最后将根View的缓存DisplayListOp设置到RootRenderNode中,完成构建。

    以上过程从trace分析如下:

    android 14应用启动流程 - (下)显示流程分析

    构建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被唤醒,开始渲染,大致流程如下:

    1. syncFrameState中遍历View树上每一个RenderNode,执行prepareTreeImpl函数,实现同步绘制命令树的操作;
    2. 调用OpenGL库API使用GPU,按照构建好的绘制命令完成界面的渲染(具体过程,由于本文篇幅所限,暂不展开分析);
    3. 将前面已经绘制渲染好的图形缓冲区Binder上帧给SurfaceFlinger合成和显示;

    整个过程可以用如下流程图表示:

    android 14应用启动流程 - (下)显示流程分析

    以上过程从trace分析如下:

    android 14应用启动流程 - (下)显示流程分析

    RenderThread实现界面渲染.png

    8. SurfaceFlinger合成显示

    SurfaceFlinger合成显示部分完全属于Android系统GUI中图形显示的内容,逻辑结构也比较复杂,但不属于本文介绍内容的重点。所以本小节中只是总体上介绍一下其工作原理与思想,不再详细分析源码,感兴趣的读者可以关注笔者后续的文章再来详细分析讲解。简单的说SurfaceFlinger作为系统中独立运行的一个Native进程,**借用Android官网的描述,其职责就是负责接受来自多个来源的数据缓冲区,对它们进行合成,然后发送到显示设备。**如下图所示:

    android 14应用启动流程 - (下)显示流程分析

    SurfaceFlinger工作原理.jpg

    从上图可以看出,其实SurfaceFlinger在Android系统的整个图形显示系统中是起到一个承上启下的作用:

    • 对上:通过Surface与不同的应用进程建立联系,接收它们写入Surface中的绘制缓冲数据,对它们进行统一合成。
    • 对下:通过屏幕的后缓存区与屏幕建立联系,发送合成好的数据到屏幕显示设备。

      图形的传递是通过Buffer作为载体,Surface是对Buffer的进一步封装,也就是说Surface内部具有多个Buffer供上层使用,如何管理这些Buffer呢?答案就是BufferQueue ,下面我们来看看BufferQueue的工作原理:

      8.1 BufferQueue机制

      借用一张经典的图来描述BufferQueue的工作原理:

      android 14应用启动流程 - (下)显示流程分析

      BufferQueue状态转换图.jpg

      BufferQueue是一个典型的生产者-消费者模型中的数据结构。在Android应用的渲染流程中,应用扮演的就是“生产者”的角色,而SurfaceFlinger扮演的则是“消费者”的角色,其配合工作的流程如下:

      1. 应用进程中在开始界面的绘制渲染之前,需要通过Binder调用dequeueBuffer接口从SurfaceFlinger进程中管理的BufferQueue 中申请一张处于free状态的可用Buffer,如果此时没有可用Buffer则阻塞等待;
      2. 应用进程中拿到这张可用的Buffer之后,选择使用CPU软件绘制渲染或GPU硬件加速绘制渲染,渲染完成后再通过Binder调用queueBuffer接口将缓存数据返回给应用进程对应的BufferQueue(如果是 GPU 渲染的话,这里还有个 GPU处理的过程,所以这个 Buffer 不会马上可用,需要等 GPU 渲染完成的Fence信号),并申请sf类型的Vsync以便唤醒“消费者”SurfaceFlinger进行消费;
      3. SurfaceFlinger 在收到 Vsync 信号之后,开始准备合成,使用 acquireBuffer获取应用对应的 BufferQueue 中的 Buffer 并进行合成操作;
      4. 合成结束后,SurfaceFlinger 将通过调用 releaseBuffer将 Buffer 置为可用的free状态,返回到应用对应的 BufferQueue中。

      8.2 Vsync同步机制

      Vysnc垂直同步是Android在“黄油计划”中引入的一个重要机制,本质上是为了协调BufferQueue的应用生产者生成UI数据动作和SurfaceFlinger消费者的合成消费动作,避免出现画面撕裂的Tearing现象。Vysnc信号分为两种类型:

      1. app类型的Vsync:app类型的Vysnc信号由上层应用中的Choreographer根据绘制需求进行注册和接收,用于控制应用UI绘制上帧的生产节奏。根据第6小结中的分析:应用在UI线程中调用invalidate刷新界面绘制时,需要先透过Choreographer向系统申请注册app类型的Vsync信号,待Vsync信号到来后,才能往主线程的消息队列放入待绘制任务进行真正UI的绘制动作;
      2. sf类型的Vsync:sf类型的Vsync是用于控制SurfaceFlinger的合成消费节奏。应用完成界面的绘制渲染后,通过Binder调用queueBuffer接口将缓存数据返还给应用对应的BufferQueue时,会申请sf类型的Vsync,待SurfaceFlinger 在其UI线程中收到 Vsync 信号之后,便开始进行界面的合成操作。

      Vsync信号的生成是参考屏幕硬件的刷新周期的,其架构如下图所示:

      android 14应用启动流程 - (下)显示流程分析

      vsync.png

      trace上SurfaceFlinger工作的流程如下图所示:

      android 14应用启动流程 - (下)显示流程分析

      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启动浅析 - 兴华

免责声明:我们致力于保护作者版权,注重分享,被刊用文章因无法核实真实出处,未能及时与作者取得联系,或有版权异议的,请联系管理员,我们会立即处理! 部分文章是来自自研大数据AI进行生成,内容摘自(百度百科,百度知道,头条百科,中国民法典,刑法,牛津词典,新华词典,汉语词典,国家院校,科普平台)等数据,内容仅供学习参考,不准确地方联系删除处理! 图片声明:本站部分配图来自人工智能系统AI生成,觅知网授权图片,PxHere摄影无版权图库和百度,360,搜狗等多加搜索引擎自动关键词搜索配图,如有侵权的图片,请第一时间联系我们。

目录[+]

取消
微信二维码
微信二维码
支付宝二维码