安卓应用的界面渲染

548 阅读4分钟

1.总体渲染流程

本文介绍安卓应用的渲染流程,每次需要更新界面的时候用先向SurfaceFllinger请求Vsync信号,应用进程接收到这个信号之后,开始绘制View树,然后在RenderThread线程调用skia库挥着HWUI去渲染,渲染好的Surface会交给SurfaceFlinger去合成,最终由屏幕显示出具体的画面。

2.重要类

draw.png

3.更新一帧的具体流程

3.1 触发场景

有两个触发点

1.应用启动的时候第一次渲染。 应用启动的时候执行完Activity.onResume方法之后会调用ViewRoot.setView方法去刷新第一帧,当然setview方法最终还是调用Choreographer.postCallback方法请求vsync信号,不熟悉这个过程的可以参考之前文章《安卓应用启动流程》

2.界面更新的时候 当开发者需要更新界面的时候,可以调用View.invalidate方法或者是View.requestlayput方法来触发界面更新 View.invalidate最终会调用到Choreographer.postCallback去请求vsync信号,而View.requestlayput也是最终会调用Choreographer.postCallback去请求vsync信号。

View.invalidate过程如下:

public void invalidate() {
//  最终会调用到invalidateInternal方法
    invalidate(true);
}

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache,
        boolean fullInvalidate) {
        // 这里的p指的是marent属性,其实也就是ViewRootImpl对象
    p.invalidateChild(this, damage);
    }
    

public ViewParent invalidateChildInParent(...) {
// 这个方法里头会调用scheduleTraversals方法
    invalidate();
    }

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
        // 这里取请求vsync信号
        mChoreographer.postCallback(
                Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        notifyRendererOfFramePending();
        pokeDrawLockIfNeeded();
    }
}

接下来看看requestLayout函数的内部实现

public void requestLayout() {
if (mParent != null && !mParent.isLayoutRequested()) {
// 这里的p指的是marent属性,其实也就是ViewRootImpl对象
    mParent.requestLayout();
}


public void requestLayout() {
// 接下来和View.invalidate的过程是一样的
        scheduleTraversals();
}

3.2 vsync信号的处理

Choreographer收到vsync信号之后会在会包装成一个消息丢进主线程的消息队列中,然后这个消息会由FrameHandler来处理,FrameHandler最终会调用ViewRootImpl.performTraversals方法来绘制这一阵

public void onVsync(long timestampNanos, long physicalDisplayId, int frame,
      VsyncEventData vsyncEventData) {
      // 构造一个异步消息,这个消息的callback绑定的就是FrameDisplayEventReceiver对象,这个类继承自runable
      Message msg = Message.obtain(mHandler, this);
      msg.setAsynchronous(true);
      // 放进消息队列,处理消息的之后时候会执行FrameDisplayEventReceiver.run方法
      mHandler.sendMessageAtTime(msg, timestampNanos / TimeUtils.NANOS_PER_MS);
 }

FrameDisplayEventReceiver.run方法里面最终会调用ViewRootImlp.doTraversal方法完成绘制

        public void run() {
            mHavePendingVsync = false;
            doFrame(mTimestampNanos, mFrame, mLastVsyncEventData);
        }

3.3 UI线程的绘制

ViewRootImpl.performTraversals绘制的时候会经过measure, layout, draw三个过程。 measure过程确定每个view的大小,layout过程确定每个view的位置,draw过程会绘制每个view的样子。

private void performTraversals() {
    if (windowShouldResize ) {
        // 根据需要执行messure,
        performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

    // 根据需要执行layout, invalite跟新的时候这个didLayout变量为false,不需要重新layout
    // requestLayout的时候这个变量为true,需要重新layout
    if (didLayout) {
        performLayout(lp, mWidth, mHeight);
    }

    //执行draw流程, 这个函数最终会调用draw方法
    if (!performDraw()) {
        mActiveSurfaceSyncGroup.markSyncReady();
    }
}

draw方法方法的主要职责是执行视图树的绘制操作。它会处理绘制缓存、调用视图树的draw方法,并确保所有需要绘制的视图都被正确地渲染到屏幕上

private boolean draw(boolean fullRedrawNeeded, boolean forceDraw) {
    if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {
        if (isHardwareEnabled()) {
            // 如果开启了硬件加速,会走这里,安卓4以上的版本默认会开启
            mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
        } else {
            // 如果没有开启硬件加速,那么会走软件渲染
            if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,
                    scalingRequired, dirty, surfaceInsets)) {
                return false;
            }
        }
    }
    return useAsyncReport;
}
// ThreadedRenderer.java
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
    
    updateRootDisplayList(view, callbacks);

    //  调用native层的方法RenderProxy::syncAndDrawFrame通知GPU线程完成实际的渲染
    int syncResult = syncAndDrawFrame(frameInfo);
}

private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
    updateViewTreeDisplayList(view);
}
private void updateViewTreeDisplayList(View view) {
    // 调用根view, 也就是decorview的updateDisplayListIfDirty方法,这个方法在父类View中实现的
    view.updateDisplayListIfDirty();
}

updateViewTreeDisplayList方法最总会调用到view.draw方法完成绘制,每个view都会先绘制背景,然后绘制内容,再绘制子view,最后绘制前景

View.java
private void updateViewTreeDisplayList(View view) {
    // 调用根view, 也就是decorview的updateDisplayListIfDirty方法,这个方法在父类View中实现的
    view.updateDisplayListIfDirty();
}

public RenderNode updateDisplayListIfDirty() {
    draw(canvas);
}

public void draw(@NonNull Canvas canvas) {
    //  1, 绘制背景
    drawBackground(canvas);
    // 2. 绘制内容
    onDraw(canvas);
    // 3. 绘制子view
    dispatchDraw(canvas);
    // 4. 绘制前景
    onDrawForeground(canvas);
}

3.4 GPU Thread线程的渲染

在GPU线程中,会用opengles来完成界面的最终渲染。 紧接着上面说过的RenderProxy::syncAndDrawFrame方法,看看内部实现

int RenderProxy::syncAndDrawFrame() {
    // 最终会调用到DrawFrameTask::postAndWait方法
    return mDrawFrameTask.drawFrame();
}

void DrawFrameTask::postAndWait() {
    // 这里运行run本对象的run方法
    mRenderThread->queue().post([this]() { run(); });
}

void DrawFrameTask::run() {
    context->draw(solelyTextureViewUpdates);
}

完成界面的渲染之后,接下来就是提交给SurfaceFlinger去合成,然后就显示在界面上了。

4. 一些常见疑问

Q 每次调用view.invalidate方法刷新的时候都是重新绘制整个屏幕吗

A 不是的,仅仅绘制需要更新的矩形区域

Q invalidate和requestlayput更新的区别,

A invalidate只会重新draw,而requestlayout会重新measure, layout和draw

参考

安卓14源码