在电影《金刚川》中,导演使用了一种特殊的拍摄手法---对同一时间维度上的事情进行不同视角的重复播放。虽然有拼凑时长的嫌疑,但实际上,这一手法在艺术体系中有名有据,叫做「复调叙事」。

看似是重复,实则是力度更深的铭记,希望观众们不要忘记惨烈的过去。

在这篇文章中介绍了 View显示到屏幕上所经历的流程。这篇文章通过「复调叙事」这一手法android触屏事件,就从不同的视角分别看一下这一流程的细节,从而加深对 View 渲染、刷新的理解。

主要分从3个不同视角分分析:源码、线程、组件

源码

负责接收Vsync信号,调用方法,从此开始绘制一帧的操作。代码如下:

void doFrame(long frameTimeNanos, int frame{
    ...
    try {
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Choreographer#doFrame");
        AnimationUtils.lockAnimationClock(frameTimeNanos / TimeUtils.NANOS_PER_MS);

        mFrameInfo.markInputHandlingStart();
        doCallbacks(Choreographer.CALLBACK_INPUT, frameTimeNanos);

        mFrameInfo.markAnimationsStart();
        doCallbacks(Choreographer.CALLBACK_ANIMATION, frameTimeNanos);
        doCallbacks(Choreographer.CALLBACK_INSETS_ANIMATION, frameTimeNanos);

        mFrameInfo.markPerformTraversalsStart();
        doCallbacks(Choreographer.CALLBACK_TRAVERSAL, frameTimeNanos);

        doCallbacks(Choreographer.CALLBACK_COMMIT, frameTimeNanos);
    } finally {
        AnimationUtils.unlockAnimationClock();
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    ...
}

上述红色字体就会执行中的操作。

在中执行、、draw操作,如下:

void doTraversal() {
     ...
     // 执行具体的绘制任务
     performTraversals();
     ...
}

private void performTraversals() {
     ...
     windowSizeMayChange |= measureHierarchy(...);
     ...
     if (mFirst...) {
         relayoutResult = relayoutWindow(params, viewVisibility, insetsPending);
     }
     ...
     performLayout(lp, mWidth, mHeight);
     ...
     performDraw();
     ...
}

需要注意的是在方法中并没有直接调用View的draw方法,而是调用了一个对象的draw方法,如下:

private void performDraw() {
     ...
     boolean canUseAsync = draw(fullRedrawNeeded);
     ...
}

private boolean draw(boolean fullRedrawNeeded) {
    ...
    mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);
    ...
}

在的draw方法内部才会去真正的递归遍历View,调用View中、等各种方法,最终构建树。

这一操作就是在draw方法中的t方法中完成,如下:

/*frameworks/base/core/java/android/view/ThreadedRenderer.java*/
void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {
    ...
    updateRootDisplayList(view, callbacks);
    ...
    int syncResult = syncAndDrawFrame(choreographer.mFrameInfo);
    ...
}

其实这些被构建操作,实质上都是对渲染命令的封装,后续将这些指令同步给GPU之后,就可以直接进行渲染操作了。

/*frameworks/base/core/java/android/view/ThreadedRenderer.java*/
private void updateRootDisplayList(View view, DrawCallbacks callbacks) {
    // 递归子View的updateDisplayListIfDirty实现构建DisplayListOp
    updateViewTreeDisplayList(view);
    ...
}

private void updateViewTreeDisplayList(View view) {
    ...
    // 从DecorView根节点出发,开始递归调用每个View树节点的updateDisplayListIfDirty函数
    view.updateDisplayListIfDirty();
    ...
}

也是个二道贩子,只是起个中间过渡作用,最终还是调用View的irty()方法。

View

代码执行到了最核心也是最本质的阶段,一个View最终是显示什么内容,终究是由View自身来决定的。

/*frameworks/base/core/java/android/view/View.java*/
public RenderNode updateDisplayListIfDirty() {
     ...
     // 1.利用`View`对象构造时创建的`RenderNode`获取一个`SkiaRecordingCanvas`“画布”;
     final RecordingCanvas canvas = renderNode.beginRecording(width, height);
     try {
         ...
         if ((mPrivateFlags & PFLAG_SKIP_DRAW) == PFLAG_SKIP_DRAW) {
              // 如果仅仅是ViewGroup,并且自身不用绘制,直接递归子View
              dispatchDraw(canvas);
              ...
         } else {
              // 2.利用SkiaRecordingCanvas,在每个子View控件的onDraw绘制函数中调用drawLine、drawRect等绘制操作时,创建对应的DisplayListOp绘制命令,并缓存记录到其内部的SkiaDisplayList持有的DisplayListData中;
              draw(canvas);
         }
     } finally {
         // 3.将包含有`DisplayListOp`绘制命令缓存的`SkiaDisplayList`对象设置填充到`RenderNode`中;
         renderNode.endRecording();
         ...
     }
     ...
}

public void draw(Canvas canvas) {
    ...
    // draw the content(View自己实现的onDraw绘制,由应用开发者自己实现)
    onDraw(canvas);
    ...
    // draw the children
    dispatchDraw(canvas);
    ...
}

View绘制结束后,代码重新回到 的draw方法中,调用方法,将之前构建的View命令树同步到线程中交给GPU进行渲染、上帧了。

在中使用 API调用GPU进行渲染后,将数据同步到进程,接收到消息之后,进行数据合成操作,如下:

/*frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp*/
void SurfaceFlinger::onMessageRefresh() {
    ATRACE_CALL();
    ......
    mCompositionEngine->present(refreshArgs);
    ......


/*frameworks/native/services/surfaceflinger/CompositionEngine/src/CompositionEngine.cpp*/
void CompositionEngine::present(CompositionRefreshArgs& args) {
    ATRACE_CALL();
    ......
    for (const auto& output : args.outputs) {
        output->present(args);
    }
    ......
}

/*frameworks/native/services/surfaceflinger/CompositionEngine/src/Output.cpp*/
void Output::present(const compositionengine::CompositionRefreshArgs& refreshArgs) {
    ATRACE_CALL();
    ......
    updateAndWriteCompositionState(refreshArgs);//告知HWC service有哪些layer要参与合成
    ......
    beginFrame();
    prepareFrame();
    ......
    finishFrame(refreshArgs);
    postFramebuffer();//这里会调用到HWC service的接口去present display合成画面
}

void Output::postFramebuffer() {
    ......
    auto frame = presentAndGetFrameFences();
    ......
}

/*frameworks/native/services/surfaceflinger/displayhardware/HWComposer.cpp*/
status_t HWComposer::presentAndGetReleaseFences(DisplayId displayId) {
    ATRACE_CALL();
    ......
    auto error = hwcDisplay->present(&displayData.lastPresentFence);//送去HWC service合成
    ......
    std::unordered_map<HWC2::Layer*, sp> releaseFences;
    error = hwcDisplay->getReleaseFences(&releaseFences);
    RETURN_IF_HWC_ERROR_FOR("getReleaseFences", error, displayId, UNKNOWN_ERROR);

    displayData.releaseFences = std::move(releaseFences);//获取releaseFence, 以便通知到各个Slot, buffer被release后会通过dequeueBuffer给到应用,应用在绘图前会等待releaseFence
    ......
}

HWC 收到的请求后android触屏事件,进行图层合成操作,提交数据到内核中的屏幕驱动,View也就显示到屏幕上了。

线程

UI 主线程

View上屏的前半部分都是在UI主线程中完成,主要包括、、draw等操作,另外中处理View 和用户触屏输入的也是在UI主线程完成。

线程

在UI主线程中完成View命令树之后,接下来线程会将命令树会使用的API完成渲染操作。

渲染完成之后,会将数据传给模块(上帧)。这部分主要是通过和函数来完成的。

线程

是系统中独立运行的一个进程,其职责就是负责接受来自多个来源的数据缓冲区,对它们进行合成,然后发送到显示设备。

进程在收到Vsync信号之后,会在主线程中开始数据合成操作,使用获取应用对应的中的并进行合成操作

组件

CPU

View 的绘制会经过 、、Draw 这 3 个阶段,而这 3 个阶段的工作都是由 CPU 来负责完成。另外 CPU 还会负责一些用户输入、View 动画等事件,这部分工作都是在 UI 线程中完成。

GPU

当 CPU 绘制完成之后,会在 线程中将这部分数据提交给 GPU进行渲染,也即是对这些数据进行栅格化()操作。渲染完成后。在方法中触发动作进行上帧,可以简单理解为将栅格化后的数据缓存到一个 中。

GPU最后的上帧动作执行后,会唤醒进程中的处理线程,其中将对应用中的标记为状态,然后注册申请sf类型的Vsync信号。待sf类型的Vsync信号到来后会唤醒的主线程执行一帧的合成任务

如果你喜欢本文


限时特惠:
本站持续每日更新海量各大内部创业课程,一年会员仅需要98元,全站资源免费下载
点击查看详情

站长微信:Jiucxh

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注