在电影《金刚川》中,导演使用了一种特殊的拍摄手法---对同一时间维度上的事情进行不同视角的重复播放。虽然有拼凑时长的嫌疑,但实际上,这一手法在艺术体系中有名有据,叫做「复调叙事」。
看似是重复,实则是力度更深的铭记,希望观众们不要忘记惨烈的过去。
在这篇文章中介绍了 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