视图的渲染流程

本文内代码基于API 31

视图的渲染流程

一、创建视图对象,组建视图树

在Android系统中所有的视图都对应一个view对象,其中包含了视图的尺寸、位置、如何绘制等信息,可以通过 开发人员选项--绘图--显示布局边界 可以查看布局尺寸和位置:

视图对象之间相互影响、相互依赖的关系被以树的形式记录下来,视图树的根节点一般为此window内的DecorView。

二、生成视图树对应的渲染树

每个需要被渲染的视图都要经过至少一次的测量、定位过程才能进行绘制。

2.1. 测量:

父视图调用子视图的measure方法,传入当前父视图分配给子视图的宽、高以及对应的测量模式:public final void measure(int widthMeasureSpec, int heightMeasureSpec) 。widthMeasureSpec或heightMeasureSpec的高2位表示测量模式,低位表示具体大小,调用 MeasureSpec.getMode方法获取测量模式,调用MeasureSpec.getSize方法获取具体分配大小。

测量模式一共有3种:

  1. MeasureSpec.EXACTLY:父视图已经确定了子视图的确切尺寸;当子视图的LayoutParams内的withheight设置为LayoutParams.MATCH_PARENT或者>=0的具体数字时,父视图会以此模式调用子视图测量。
  2. MeasureSpec.AT_MOST:父视图返回剩余可用空间给子视图,由子视图决定尺寸;此时子视图的LayoutParams内的withheight设置为LayoutParams.WRAP_CONTENT
  3. MeasureSpec.UNSPECIFIED:父视图对子视图没有任何约束,Api23以前子视图得到的分配大小永远为0,版本号23及以后会得到父视图内剩余可用空间;此模式一般在一些特殊情况,如ScrollView测量子视图时才会用到。

measure方法

scss 复制代码
//使用宽度值与高度值生成缓存key
long key = (long) widthMeasureSpec << 32 | (long) heightMeasureSpec & 0xffffffffL;
//创建缓存大小为2的缓存队列
if (mMeasureCache == null) mMeasureCache = new LongSparseLongArray(2);
//检查强制更新标识,此标识一般在调用requestLayout时添加
final boolean forceLayout = (mPrivateFlags & PFLAG_FORCE_LAYOUT) == PFLAG_FORCE_LAYOUT;
//测量模式是否变化
final boolean specChanged = widthMeasureSpec != mOldWidthMeasureSpec
        || heightMeasureSpec != mOldHeightMeasureSpec;
//长、宽是否都为精确模式
final boolean isSpecExactly = MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY
        && MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY;
//长、宽数值是否变化
final boolean matchesSpecSize = getMeasuredWidth() == MeasureSpec.getSize(widthMeasureSpec)
        && getMeasuredHeight() == MeasureSpec.getSize(heightMeasureSpec);
//sAlwaysRemeasureExactly = targetSdkVersion <= Build.VERSION_CODES.M;
final boolean needsLayout = specChanged
        && (sAlwaysRemeasureExactly || !isSpecExactly || !matchesSpecSize);
//检查更新标识,如果不需要更新跳过这段代码,仅更新缓存
if (forceLayout || needsLayout) {
    //清除已测量标识
    mPrivateFlags &= ~PFLAG_MEASURED_DIMENSION_SET;
    //检查并设置左、右手模式相关参数
    resolveRtlPropertiesIfNeeded();
    //强制更新不可使用缓存
    int cacheIndex = forceLayout ? -1 : mMeasureCache.indexOfKey(key);
    //sIgnoreMeasureCache = targetSdkVersion < Build.VERSION_CODES.KITKAT;
    if (cacheIndex < 0 || sIgnoreMeasureCache) {
        //无可用缓存或忽略缓存时,执行onMeasure测量自身尺寸
        onMeasure(widthMeasureSpec, heightMeasureSpec);
        //去除执行layout前进行测量的标识
        mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    } else {
        long value = mMeasureCache.valueAt(cacheIndex);
        //与缓存key的创建规则类似,高32位记录宽度,低32位记录高度
        setMeasuredDimensionRaw((int) (value >> 32), (int) value);
        //添加layout前进行测量的标识,由于使用了缓存跳过了onMeasure过程所以要在执行layout方法前执行onMeasure方法,
        //猜测这么设计是为了减少onMeasure执行,同时又避免onMeasure一次都不执行
        mPrivateFlags3 |= PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    }
    //只有调用了setMeasuredDimensionRaw方法后才会重新设置此标识
    //避免复写onMeasure后没有设定尺寸
    if ((mPrivateFlags & PFLAG_MEASURED_DIMENSION_SET) != PFLAG_MEASURED_DIMENSION_SET) {
        throw new IllegalStateException("View with id " + getId() + ": "
                + getClass().getName() + "#onMeasure() did not set the"
                + " measured dimension by calling"
                + " setMeasuredDimension()");
    }
    //标记需要重新定位
    mPrivateFlags |= PFLAG_LAYOUT_REQUIRED;
}
//记录本次测量参数
mOldWidthMeasureSpec = widthMeasureSpec;
mOldHeightMeasureSpec = heightMeasureSpec;
//更新缓存
mMeasureCache.put(key, ((long) mMeasuredWidth) << 32 |
        (long) mMeasuredHeight & 0xffffffffL);

具体的测量逻辑写在onMeasure中,默认的实现仅仅是将父视图传递的尺寸参数设定给自己,需要复写此方法实现其他测量逻辑。

2.2. 定位

仍然是由父视图来调用子视图的layout方法:

ini 复制代码
public void layout(int l, int t, int r, int b) {
    //根据measure时设定的标识决定是否需要先执行onMeasure
    if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
        onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
        mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    }
    //使用局部变量记录旧的边界坐标
    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;
    //设定新的边界坐标,特殊布局方式通过setOpticalFrame处理后执行setFrame,默认直接执行setFrame
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    //如果边界坐标有改变,或measure方法中设定了要求重新定位的标识PFLAG_LAYOUT_REQUIRED,执行onLayout,通知位置变化等
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        //默认为空实现,需要复写此方法实现自定义的定位处理
        onLayout(changed, l, t, r, b);
        //设置滚动条
        if (shouldDrawRoundScrollbar()) {
            if(mRoundScrollbarRenderer == null) {
                mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
            }
        } else {
            mRoundScrollbarRenderer = null;
        }
        //删除要求重新定位的标识
        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
        //分发布局定位变化的通知
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnLayoutChangeListeners != null) {
            ArrayList<OnLayoutChangeListener> listenersCopy =
                (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
            int numListeners = listenersCopy.size();
            for (int i = 0; i < numListeners; ++i) {
                listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
            }
        }
    }
    //余下的逻辑大部分是处理焦点的
    ·······

layout方法内部较measure方法简单很多,主要是通过setFrame方法设置视图的左、上、右、下4个方位坐标,然后通过onLayout方法实现其他布局定位操作。setFrame方法除了设定方位坐标,在任何一个边界坐标变化时,通知尺寸变化触发onSizeChanged,并设定重绘区域。

2.3. 绘制

一般情况下父视图会调用子视图的draw方法,在源码中的注释里可以看到绘制的大致流程:

markdown 复制代码
/*
 * 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
 *         如果有必要,绘制默认的焦点高亮样式
 */

先看常规情况,不需要步骤2和步骤5

scss 复制代码
//用于绘制渐变,这个变量在此处只做了声明,没有使用
int saveCount;
//绘制背景
drawBackground(canvas);
//判断是否需要绘制水平或竖直方向的边缘渐变
final int viewFlags = mViewFlags;
boolean horizontalEdges = (viewFlags & FADING_EDGE_HORIZONTAL) != 0;
boolean verticalEdges = (viewFlags & FADING_EDGE_VERTICAL) != 0;
if (!verticalEdges && !horizontalEdges) {
    //绘制自身内容
    onDraw(canvas);
    //遍历绘制子视图
    dispatchDraw(canvas);
    //绘制获取焦点时的高亮样式
    drawAutofilledHighlight(canvas);
    //覆盖层,位于内容与前景之间,内部保存多个drawable用于绘制;ViewGroup内的`ViewGroupOverlay`还可以添加view,但是由于不在视图树上,不能触发触摸、测量或绘制事件,需要开发者自己处理。
    if (mOverlay != null && !mOverlay.isEmpty()) {
        mOverlay.getOverlayView().dispatchDraw(canvas);
    }
    //绘制装饰部分(前景, 滚动条等)
    onDrawForeground(canvas);
    //绘制获取焦点是的高亮样式
    drawDefaultFocusHighlight(canvas);
    //开发者模式下的显示布局边界
    if (isShowingLayoutBounds()) {
        debugDrawFocus(canvas);
    }
    //结束...
    return;
}

渐变边框的作用与滚动条类似,用于提示还有内容可以滚动。

ini 复制代码
/*
 * 这里我们完整的流程...
 * (这是一种不常见的情况,这也是为什么我们要重复做一些之前做过的测试)
 */
 //省略声明变量...
 //计算上下左右4个方位的渐变狂位置
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();
}
//ScrollabilityCache内保存了视图滚动时使用的各种字段,避免了在视图对象内保存过多无用的字段,大多数情况下保留太多未使用的字段
final ScrollabilityCache scrollabilityCache = mScrollCache;
final float fadeHeight = scrollabilityCache.fadingEdgeLength;
int length = (int) fadeHeight;
//如果顶部和顶部的渐变边框有重叠,裁剪至合适的长度
//重叠的渐变会产生奇怪的效果
if (verticalEdges && (top + length > bottom - length)) {
    length = (bottom - top) / 2;
}
//同样的,如果有需要也对水平渐变的长度进行裁剪
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;
//获取渐变边框的颜色,如果solidColor为0,采用PorterDuff.Mode.DST_OUT模式绘制,否则使用drawRect绘制
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,绘制内容
onDraw(canvas);
// Step 4,绘制子视图
dispatchDraw(canvas);
// Step 5,绘制渐变边框,恢复画布状态
final Paint p = scrollabilityCache.paint;
final Matrix matrix = scrollabilityCache.matrix;
final Shader fade = scrollabilityCache.shader;
//必须按与保存顺序相反的顺序恢复
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);

需要注意的是,ViewGroup是通过protected boolean drawChild(Canvas canvas, View child, long drawingTime)方法调用到子时图内的boolean draw(Canvas canvas, ViewGroup parent, long drawingTime)方法,最后再由此方法内部调用上面的draw方法。

scss 复制代码
......
if (!drawingWithDrawingCache) {
    if (drawingWithRenderNode) {
        mPrivateFlags &= ~PFLAG_DIRTY_MASK;
        ((RecordingCanvas) canvas).drawRenderNode(renderNode);
    } else {
        //无背景等绘制时,忽略自身绘制,快速绘制子视图
        if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
            mPrivateFlags &= ~PFLAG_DIRTY_MASK;
            dispatchDraw(canvas);
        } else {
            draw(canvas);
        }
    }
} else if (cache != null) {
   //使用缓存绘制
}
......

所以public void draw(Canvas canvas)不一定会执行。

三、GPU将视图树中需要渲染的内容解码成Graphic Buffer;

3.1. 硬件渲染与软件渲染

两者的核心差别在于软件渲染由CPU将Layer(图层)内容转换为Graphic Buffer,硬件渲染由GPU将Layer内容转换为Graphic Buffer。

3.1.1. 软件渲染:
java 复制代码
//定义在ViewRootImpl.class中
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,
        boolean scalingRequired, Rect dirty, Rect surfaceInsets) {
    // 使用软件渲染绘制.
    final Canvas canvas;
    // 此时我们可以获取到surfaceInsets在x轴、y轴方向上的偏移量,以及需要重绘制的脏区
    // 在移动绘制脏区之前,我们需要先加上偏移量
    int dirtyXOffset = xoff;
    int dirtyYOffset = yoff;
    if (surfaceInsets != null) {
        dirtyXOffset += surfaceInsets.left;
        dirtyYOffset += surfaceInsets.top;
    }
    try {
        dirty.offset(-dirtyXOffset, -dirtyYOffset);
        final int left = dirty.left;
        final int top = dirty.top;
        final int right = dirty.right;
        final int bottom = dirty.bottom;
        canvas = mSurface.lockCanvas(dirty);
        // TODO: Do this in native
        canvas.setDensity(mDensity);
    } catch (Surface.OutOfResourcesException e) {
        handleOutOfResourcesException(e);
        return false;
    } catch (IllegalArgumentException e) {
        Log.e(mTag, "Could not lock surface", e);
        // 不要认为这一定是内存溢出造成的,它也可能是因为一些其它原因,如果是别的原因,那么我们可以毫无理由地杀死东西(或我们自己)。
        mLayoutRequested = true;    // 要求wm下次给一块新的surface。
        return false;
    } finally {
        dirty.offset(dirtyXOffset, dirtyYOffset);  // 恢复原始偏移
    }
    try {
        if (DEBUG_ORIENTATION || DEBUG_DRAW) {
            Log.v(mTag, "Surface " + surface + " drawing to bitmap w="
                    + canvas.getWidth() + ", h=" + canvas.getHeight());
            //canvas.drawARGB(255, 255, 0, 0);
        }
        // 如果位图格式中包含透明度通道,我们需要在绘制之前将其清除,
        // 以便子控件将其内容恰当地组合到一块透明背景上。 这将自动地对剪辑/脏区域关联。
        // 或
        // 如果我们设置了偏移,我们要清空偏移后不可见的区域,以免在空白区域留下垃圾
        if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {
            canvas.drawColor(0, PorterDuff.Mode.CLEAR);
        }
        dirty.setEmpty();
        mIsAnimating = false;
        mView.mPrivateFlags |= View.PFLAG_DRAWN;
        if (DEBUG_DRAW) {
            Context cxt = mView.getContext();
            Log.i(mTag, "Drawing: package:" + cxt.getPackageName() +
                    ", metrics=" + cxt.getResources().getDisplayMetrics() +
                    ", compatibilityInfo=" + cxt.getResources().getCompatibilityInfo());
        }
        canvas.translate(-xoff, -yoff);
        if (mTranslator != null) {
            mTranslator.translateCanvas(canvas);
        }
        canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);
        mView.draw(canvas); // 传递canvas,完成view绘制
        drawAccessibilityFocusedDrawableIfNeeded(canvas);
    } finally {
        try {
            surface.unlockCanvasAndPost(canvas); //完成绘制合成,提交结果
        } catch (IllegalArgumentException e) {
            Log.e(mTag, "Could not unlock surface", e);
            mLayoutRequested = true;    // 要求wm下次给一块新的surface。
            //noinspection ReturnInsideFinallyBlock
            return false;
        }
        if (LOCAL_LOGV) {
            Log.v(mTag, "Surface " + surface + " unlockCanvasAndPost");
        }
    }
    return true;
}
3.1.2. 硬件渲染:
ini 复制代码
// ThreadedRenderer.calss
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
    attachInfo.mViewRootImpl.mViewFrameInfo.markDrawStart();
    updateRootDisplayList(view, callbacks); // 更新重绘区域,构建根RenderNode内的DisplayList
    // 注册动画绘制节点,并在获得渲染产物前开始动画,动画执行器通常在第一次绘制前启动
    if (attachInfo.mPendingAnimatingRenderNodes != null) {
        final int count = attachInfo.mPendingAnimatingRenderNodes.size();
        for (int i = 0; i < count; i++) {
            registerAnimatingRenderNode(
                    attachInfo.mPendingAnimatingRenderNodes.get(i));
        }
        attachInfo.mPendingAnimatingRenderNodes.clear();
        // 我们不再需要这个对象,稍后调用ViewRootImpl#attachRenderNodeAnimator方法会直接传递给我们
        attachInfo.mPendingAnimatingRenderNodes = null;
    }

    final FrameInfo frameInfo = attachInfo.mViewRootImpl.getUpdatedFrameInfo();
    //将RenderNode树同步到渲染线程,并请求绘制帧。
    int syncResult = syncAndDrawFrame(frameInfo);
    if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {
        Log.w("OpenGLRenderer", "Surface lost, forcing relayout");
        // 我们没有得到surface。向窗口请求得到新的surface,但愿它能正确工作。
        attachInfo.mViewRootImpl.mForceNextWindowRelayout = true;
        attachInfo.mViewRootImpl.requestLayout();
    }
    if ((syncResult & SYNC_REDRAW_REQUESTED) != 0) {
        attachInfo.mViewRootImpl.invalidate();
    }
}
3.1.3. 主要差别:
  1. 兼容性:软件渲染的兼容性和稳定性较好,但是复杂图形的渲染速度(或者计算速度)不如硬件渲染。(Android4. 0从开始默认使用硬件渲染)
  2. 渲染引擎:软件渲染使用Skia渲染;在8.0以前,开启硬件渲染将直接调用OpenGL/Vulkan引擎,在8.0以后,开启硬件渲染将通过Skia间接调用OpenGL/Vulkan引擎;
  3. 渲染线程:Android5.0以前软件渲染、硬件渲染都在在UI线程;Android5.0以后软件渲染在UI线程,硬件渲染在单独线程;
  4. 画布对象:软件渲染使用Canvns.class,硬件渲染使用DisplayListCanvas.class;
  5. RenderNode:Android5.0以后为硬件渲染引入RenderNode(通常一个RenderNode对应一个View,包含自身及其子View的所有DisplayList)和DisplayList(画布上的绘制操作记录)。
    • 在重绘脏区时,软件渲染需要重绘与之重叠的所有视图及对应的全部父视图。硬件渲染时只需要重绘脏区对应的视图及其全部父视图(重建DispalyList),其余重叠的视图仍使用之前的DispalyList。
    • 当修改透明度、平移等属性时,硬件渲染只修改对应RenderNode的属性,软件需要重绘。

四、合并后提交渲染;

请求垂直同步信号流程:

arduino 复制代码
View.requestLayout\View.invalidate//视图显示变化
ViewRootImpl.requestLayout\ViewRootImpl.invalidate//传递到ViewRootImpl内对应方法
ViewRootImpl.scheduleTraversals
Choreographer.postCallback//
Choreographer.scheduleFrameLocked//
Choreographer.scheduleVsyncLocked//
DisplayEventReceiver.scheduleVsync//请求垂直同步信号

app处理垂直同步信号流程:

php 复制代码
Vsync-app//发起app渲染的垂直同步信号
DisplayEventReceiver.onVsync//垂直同步信号监听回调
Choreographer.doFrame//开始渲染
Choreographer.doCallbacks//执行多个callback,执行多个渲染流程
ViewRootImpl.doTraversal//对应CALLBACK_TRAVERSAL类型callback
ViewRootImpl.performTraversals
如果需要设置或修改对应Surface内容,则执行画布创建或修改流程:
* ViewRootImpl.relayoutWindow//重新测量Surface尺寸
* mWindowSession.relayout
* WindowManagerService.relayoutwindow
* WindowManagerService.createSurfaceControl
* WindowStateAnimator.createSurfaceLocked
* new WindowSurfaceController()
* SurfaceControl.Builder.build//创建java端的SurfaceControl
* SurfaceControl.nativeCreate//创建native端的SurfaceControl,两端SurfaceControl联通
* SurfaceComposeClient::createSurfaceChecked//创建native端的Surface
* SurfaceComposeClient::createSurface
* SurfaceComposeClient::createLayer
* SurfaceFlinger::createLayer//创建native端Surface所需GraphicBuffer
* SurfaceFlingercreateBufferLayer\SurfaceFlingercreateColorLayer//双种创建方式
* BufferQueue::createBufferQueue//向BufferQueue请求GraphicBuffer
* ThreadedRenderer.setSurfaceControl//将SurfaceControl设置给渲染线程
performMeasure//开始遍历视图测量
performLayout//开始遍历视图定位
performDraw//开始遍历视图绘制
ViewRootImpl.reportDrawFinished//汇报绘制完成,修改GraphicBuffer状态
mWindowSession.finishDrawing
WindowManagerService.finishDrawingWindow
WindowState.finishDrawing
WindowStateAnimator.finishDrawingLocked
SurfaceControl.Transaction.merge//提交状态变化
SurfaceControl.nativeMergeTransaction

垂直同步信号完成渲染流程:

arduino 复制代码
Vsync-sf//发起合并信号
SurfaceFlinger::onMessageReceived//接收消息
SurfaceFlinger::handleMessageRefresh
SurfaceFlinger::preComposition//合成之前检查是否有新的变化,如果有,执行请求下一次VSync信号 
SurfaceFlinger::rebuildLayerStacks//检查是否需要重新计算合成区域和先后顺序 
SurfaceFlinger::setUpHWComposer//询问Hardware Composer HAL(HWC)当前`GraphicBuffer`内每个图层的合成方式,标记为overla(HWC在线合成)或GLES composition(离线合成)
SurfaceFlinger::doDebugFlashRegions//开发者选项中的"显示Surface刷新"功能
SurfaceFlinger::doComposition//SurfaceFlinger将不支持硬件合成的图层进行GPU合成
SurfaceFlinger::postFramebuffer//将GPU合成后的结果和需要HWC合成结果写入FrameBuffer,交给HWC
HWC最终会调用DRM框架进行送显,当下一次硬件VSync信号发生时交换Framebuffer
postComposition(refreshStartTime);//主要用于调试
相关推荐
找藉口是失败者的习惯15 分钟前
从传统到未来:Android XML布局 与 Jetpack Compose的全面对比
android·xml
Jinkey1 小时前
FlutterBasic - GetBuilder、Obx、GetX<Controller>、GetxController 有啥区别
android·flutter·ios
大白要努力!3 小时前
Android opencv使用Core.hconcat 进行图像拼接
android·opencv
天空中的野鸟4 小时前
Android音频采集
android·音视频
小白也想学C5 小时前
Android 功耗分析(底层篇)
android·功耗
曙曙学编程5 小时前
初级数据结构——树
android·java·数据结构
闲暇部落7 小时前
‌Kotlin中的?.和!!主要区别
android·开发语言·kotlin
诸神黄昏EX9 小时前
Android 分区相关介绍
android
大白要努力!10 小时前
android 使用SQLiteOpenHelper 如何优化数据库的性能
android·数据库·oracle
Estar.Lee11 小时前
时间操作[取当前北京时间]免费API接口教程
android·网络·后端·网络协议·tcp/ip