视图的渲染流程

本文内代码基于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);//主要用于调试
相关推荐
运维Z叔1 分钟前
云安全 | AWS S3存储桶安全设计缺陷分析
android·网络·网络协议·tcp/ip·安全·云计算·aws
Reese_Cool2 小时前
【C语言二级考试】循环结构设计
android·java·c语言·开发语言
平凡シンプル2 小时前
安卓 uniapp跨端开发
android·uni-app
elina80132 小时前
安卓实现导入Excel文件
android·excel
严文文-Chris2 小时前
【设计模式-享元】
android·java·设计模式
趋势大仙2 小时前
SQLiteDatabase insert or replace数据不生效
android·数据库
DS小龙哥2 小时前
QT For Android开发-打开PPT文件
android·qt·powerpoint
试行3 小时前
Android实现自定义下拉列表绑定数据
android·java
Dingdangr8 小时前
Android中的Intent的作用
android
技术无疆8 小时前
快速开发与维护:探索 AndroidAnnotations
android·java·android studio·android-studio·androidx·代码注入