【23】Android高级知识之Window(四) - ThreadedRenderer

一、概述

在上一篇文章中已经讲了setView整个流程中,最开始的addToDisplay和WMS跨进程通信的整个过程做了什么。继文章Android基础知识之Window(二),这算是另外一个分支了,接着讲分析在performTraversals的三个操作中,最后触发performDraw执行绘制的绘制原理。

二、SurfaceFlinger基础

SurfaceFlinger是Android操作系统中一个关键组件,负责管理和合成显示内容。你说它是显示引擎也可以,说他是Android的显示服务器也可以。

2.1 创建

它属于一个独立的进程,在系统启动过程中,会通过init进程解析init.rc,然后再去加载SurfaceFlinger。最后加载的路径在*/frameworks/native/services/surfaceflinger/main_surfaceflinger.cpp*,执行它的main函数。

//main_surfaceflinger.cpp
int main(int, char**) {signal(SIGPIPE, SIG_IGN);...// start the thread poolsp<ProcessState> ps(ProcessState::self());ps->startThreadPool();...// instantiate surfaceflinger// 实例化SurfaceFlingersp<SurfaceFlinger> flinger = surfaceflinger::createSurfaceFlinger();...

2.2 图形系统概要

这里简单的介绍一下图形系统,应用程序可以借助图形系统在屏幕上显示画面与用户完成交互。把图形系统进行划分,可以分为UI框架、渲染系统(Skia/OpenGL)、窗口系统(X11/Wayland/SurfaceFlinger)、显示系统(DRM/显示驱动等),可以看到讲的SurfaceFlinger属于系统层级中的窗口系统。

  • 显示系统:对屏幕的抽象和封装
  • 渲染系统:抽象和封装GPU提供的渲染能力
  • 窗口系统:把一块屏幕拆分为几个window使得多个应用同时使用屏幕
  • UI框架:向应用程序提供与用户交互的能力

纵向分层,从下层至上层分为
GPU -> GPU驱动 -> OpenGL -> 2D图形库(Skia等)-> UI框架(Android原生View /Flutter等)

在来说一下渲染和绘制这两个概念,很多地方经常会互用,但也没有问题,有时候我们说渲染某个画面,或者绘制某个画面也是同一个意思。但是如果需要认真区分,它们就是两个不同的概念了。

  • 绘制:View -> 2D几何图形(矩阵/圆/三角形)和文字
  • 渲染:点/直线/三角面片/ -> (光栅化/着色)像素(矢量图转变位图)
    在这里插入图片描述
    在这里插入图片描述

三、绘制

基本的概念补充了一下,就讲这次的主要内容了,performTraversals执行了测量、布局、和绘制三个操作,前面两个操作都是为最后一个绘制做的准备工作。在应用上层中,常常提到的绘制,我们知道是执行View#onDraw方法,可是怎么执行进来的,在之前文章中只是讲了一个大概,这次就详细分析一下这个流程,perfromDraw中主要的函数draw。

//ViewRootImpl.java
private boolean draw(boolean fullRedrawNeeded, boolean forceDraw) {...//DEBUG下,可以捕获当前fps值if (DEBUG_FPS) {trackFPS();}...//脏视图的集合是否为空(有没有变化的视图区域)if (!dirty.isEmpty() || mIsAnimating || accessibilityFocusDirty) {//判断是否开启了硬件加速(是否硬件支持)if (isHardwareEnabled()) {...//硬件绘制(ThreadRenderer进行绘制)mAttachInfo.mThreadedRenderer.draw(mView, mAttachInfo, this);} else {...//软件绘制if (!drawSoftware(surface, mAttachInfo, xOffset, yOffset,scalingRequired, dirty, surfaceInsets)) {return false;}}}
}

3.1 drawSoftware

先看一下软件绘制drawSoftware做了什么,一般情况没有开启硬件加速,在performDraw执行进来过后,就执行这部分逻辑。

//ViewRootImpl.java
private boolean drawSoftware(Surface surface, AttachInfo attachInfo, int xoff, int yoff,boolean scalingRequired, Rect dirty, Rect surfaceInsets) {// Draw with software renderer.final Canvas canvas;try {//拿到Surface的画布canvas = mSurface.lockCanvas(dirty);canvas.setDensity(mDensity);} catch (Surface.OutOfResourcesException e) {handleOutOfResourcesException(e);return false;} catch (IllegalArgumentException e) {Log.e(mTag, "Could not lock surface", e);mLayoutRequested = true;    // ask wm for a new surface next time.return false;}try {if (!canvas.isOpaque() || yoff != 0 || xoff != 0) {canvas.drawColor(0, PorterDuff.Mode.CLEAR);}//清空脏视图缓存dirty.setEmpty();mIsAnimating = false;mView.mPrivateFlags |= View.PFLAG_DRAWN;canvas.translate(-xoff, -yoff);if (mTranslator != null) {mTranslator.translateCanvas(canvas);}canvas.setScreenDensity(scalingRequired ? mNoncompatDensity : 0);//回调到View的onDraw方法mView.draw(canvas);drawAccessibilityFocusedDrawableIfNeeded(canvas);} finally {try {//将后缓冲区提交到前缓冲区显示surface.unlockCanvasAndPost(canvas);} catch (IllegalArgumentException e) {Log.e(mTag, "Could not unlock surface", e);mLayoutRequested = true;    // ask wm for a new surface next time.//noinspection ReturnInsideFinallyBlockreturn false;}}return true;
}    

mSurface是ViewRootImpl创建的一个Surface对象,也就说明一个windnow对应一个Surface和SurfaceControl对象,这个在之前文章有讲过。Surface涉及的双缓冲机制,分前缓冲区和后缓冲区,前缓冲区用于显示,绘制在后缓冲区,绘制完成通过unlockCanvasAndPost和前缓冲区互换,完成显示,防止闪烁的问题。这里我们看到了mView#draw方法,回调View当中的onDraw,通过Surface拿到的canvas执行绘制代码。

补充:ViewRootImpl 和 SurfaceView 可以看作是一个层级的事物,他们都持有一个 surface,ViewRootImpl 自己把 ViewTree 渲染到 surface 上,SurfaceView 的 surface 供应用自行使用,应用可以把游戏/视频/相机/3D图形库生成数据放到 surface 上

3.2 ThreadedRenderer#draw

然后继续看一下mAttachInfo.mThreadedRenderer.draw这个方法,mThreadedRenderer是我们常说的渲染线程,mAttachInfo属于View类中的一个内部类。在performTraversals中,会判断并执行enableHardwareAcceleration,然后创建renderer对象。

//ViewRootImpl.java@UnsupportedAppUsageprivate void enableHardwareAcceleration(WindowManager.LayoutParams attrs) {...if (ThreadedRenderer.sRendererEnabled || forceHwAccelerated) {if (mAttachInfo.mThreadedRenderer != null) {mAttachInfo.mThreadedRenderer.destroy();}final Rect insets = attrs.surfaceInsets;final boolean hasSurfaceInsets = insets.left != 0 || insets.right != 0|| insets.top != 0 || insets.bottom != 0;final boolean translucent = attrs.format != PixelFormat.OPAQUE || hasSurfaceInsets;final ThreadedRenderer renderer = ThreadedRenderer.create(mContext, translucent,attrs.getTitle().toString());mAttachInfo.mThreadedRenderer = renderer;renderer.setSurfaceControl(mSurfaceControl, mBlastBufferQueue);updateColorModeIfNeeded(attrs.getColorMode());updateRenderHdrSdrRatio();updateForceDarkMode();mAttachInfo.mHardwareAccelerated = true;mAttachInfo.mHardwareAccelerationRequested = true;if (mHardwareRendererObserver != null) {renderer.addObserver(mHardwareRendererObserver);}}}
}

代码我们可以看到,通过ThreadedRenderer#create的静态方法,创建renderer对象,并赋值给了mAttachInfo.mThreadedRenderer属性。继续看一下renderer#draw方法。

//ThreadedRenderer.java/*** Draws the specified view.** @param view The view to draw.* @param attachInfo AttachInfo tied to the specified view.*/void draw(View view, AttachInfo attachInfo, DrawCallbacks callbacks) {attachInfo.mViewRootImpl.mViewFrameInfo.markDrawStart();updateRootDisplayList(view, callbacks);// register animating rendernodes which started animating prior to renderer// creation, which is typical for animators started prior to first drawif (attachInfo.mPendingAnimatingRenderNodes != null) {final int count = attachInfo.mPendingAnimatingRenderNodes.size();for (int i = 0; i < count; i++) {registerAnimatingRenderNode(attachInfo.mPendingAnimatingRenderNodes.get(i));}attachInfo.mPendingAnimatingRenderNodes.clear();// We don't need this anymore as subsequent calls to// ViewRootImpl#attachRenderNodeAnimator will go directly to us.attachInfo.mPendingAnimatingRenderNodes = null;}final FrameInfo frameInfo = attachInfo.mViewRootImpl.getUpdatedFrameInfo();int syncResult = syncAndDrawFrame(frameInfo);if ((syncResult & SYNC_LOST_SURFACE_REWARD_IF_FOUND) != 0) {Log.w("OpenGLRenderer", "Surface lost, forcing relayout");// We lost our surface. For a relayout next frame which should give us a new// surface from WindowManager, which hopefully will work.attachInfo.mViewRootImpl.mForceNextWindowRelayout = true;attachInfo.mViewRootImpl.requestLayout();}if ((syncResult & SYNC_REDRAW_REQUESTED) != 0) {attachInfo.mViewRootImpl.invalidate();}}

方法注解说明是一个绘制指定View的方法,AttachInfo绑定到指定View上。syncAndDrawFrame是父类HardwareRenderer的一个方法,调用的是native方法。再看一下updateRootDisplayList。

//ThreadedRenderer.javaprivate void updateRootDisplayList(View view, DrawCallbacks callbacks) {Trace.traceBegin(Trace.TRACE_TAG_VIEW, "Record View#draw()");//更新view的一些标志位updateViewTreeDisplayList(view);if (mNextRtFrameCallbacks != null) {final ArrayList<FrameDrawingCallback> frameCallbacks = mNextRtFrameCallbacks;mNextRtFrameCallbacks = null;//设置每帧的绘制回调setFrameCallback(new FrameDrawingCallback() {@Overridepublic void onFrameDraw(long frame) {}@Overridepublic FrameCommitCallback onFrameDraw(int syncResult, long frame) {ArrayList<FrameCommitCallback> frameCommitCallbacks = new ArrayList<>();for (int i = 0; i < frameCallbacks.size(); ++i) {FrameCommitCallback frameCommitCallback = frameCallbacks.get(i).onFrameDraw(syncResult, frame);if (frameCommitCallback != null) {frameCommitCallbacks.add(frameCommitCallback);}}if (frameCommitCallbacks.isEmpty()) {return null;}return didProduceBuffer -> {for (int i = 0; i < frameCommitCallbacks.size(); ++i) {frameCommitCallbacks.get(i).onFrameCommit(didProduceBuffer);}};}});}if (mRootNodeNeedsUpdate || !mRootNode.hasDisplayList()) {//拿到RecordingCanvas对象,通过mRootNode获取RecordingCanvas canvas = mRootNode.beginRecording(mSurfaceWidth, mSurfaceHeight);try {final int saveCount = canvas.save();canvas.translate(mInsetLeft, mInsetTop);callbacks.onPreDraw(canvas);canvas.enableZ();//执行canvas的drawRenderNode,来执行mRootNode绘制canvas.drawRenderNode(view.updateDisplayListIfDirty());canvas.disableZ();callbacks.onPostDraw(canvas);canvas.restoreToCount(saveCount);mRootNodeNeedsUpdate = false;} finally {mRootNode.endRecording();}}Trace.traceEnd(Trace.TRACE_TAG_VIEW);

RecordingCanvas是Canvas的一个子类,而RecordingCanvas#drawRenderNode方法,将绘制任务传递给本地层,调用了nDrawRenderNode是一个native方法。/frameworks/base/libs/hwui/jni/android_graphics_DisplayListCanvas.cpp

//SkiaRecordingCavas.app
void SkiaRecordingCanvas::drawRenderNode(uirenderer::RenderNode* renderNode) {// Record the child node. Drawable dtor will be invoked when mChildNodes deque is cleared.mDisplayList->mChildNodes.emplace_back(renderNode, asSkCanvas(), true, mCurrentBarrier);auto& renderNodeDrawable = mDisplayList->mChildNodes.back();if (Properties::getRenderPipelineType() == RenderPipelineType::SkiaVulkan) {// Put Vulkan WebViews with non-rectangular clips in a HW layerrenderNode->mutateStagingProperties().setClipMayBeComplex(mRecorder.isClipMayBeComplex());}drawDrawable(&renderNodeDrawable);// use staging property, since recording on UI threadif (renderNode->stagingProperties().isProjectionReceiver()) {mDisplayList->mProjectionReceiver = &renderNodeDrawable;}
}

SkiaRecordingCanvas是一个用于记录绘制命令的类。renderNode是一个记录了绘制命令的对象。DisplayList用来存储ViewTree中需要绘制的View,所生成的renderNode节点。
1、mDisplayList把RenderNode节点添加到它的mChildNodes列表的尾部
2、然后取出列表尾部这个元素赋值给renderNodeDrawable
3、执行drawDrawable函数,传入renderNodeDrawable地址
4、Drawable#draw会将绘制命令传递给SkCanvas
5、Skia图形库再将绘制命令转换为GPU指令,并通过OpenGL等图形API发送到GPU进行渲染

SkCanvas是Skia图形库的核心类,用于执行具体的绘制操作。

软件绘制,通过Surface.unlockCanvasAndPost把提交绘制结果到SurfaceFlinger。硬件绘制,通过使用GPU进行绘制,并通过OpenGL等图形API与SurfaceFlinger通信。它们最后都实现了SurfaceFlinger的通信过程,并提交了结果,SurfaceFlinger负责合成各个窗口的内容,并将最终的显示结果提交到屏幕上。

这里给出了Activity一帧的绘制流程:
在这里插入图片描述

总结

1、performDraw分两个流程软件绘制和硬件绘制
2、软件绘制直接在ViewRootImpl创建的Surface进行绘制并提交给SurfaceFlinger
3、判断启动硬件加速会创建Render对象
4、硬件绘制通过RecordingCanvas提交绘制任务给本地层
5、RenderNode会记录绘制命令并将绘制命令传递给SkCanvas上
6、Skia图形库将命令转换成GPU指令交由GPU进行渲染

之后最后一篇文章,主要围绕整个图形系统,详细讲讲SurfaceFlinger的概念。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/bicheng/50222.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

基于Golang+Vue3快速搭建的博客系统

WANLI 博客系统 项目介绍 基于vue3和gin框架开发的前后端分离个人博客系统&#xff0c;包含md格式的文本编辑展示&#xff0c;点赞评论收藏&#xff0c;新闻热点&#xff0c;匿名聊天室&#xff0c;文章搜索等功能。 项目在线访问&#xff1a;http://bloggo.chat/ 或 http:/…

【Web】LitCTF 2024 题解(全)

目录 浏览器也能套娃&#xff1f; 一个....池子&#xff1f; 高亮主题(划掉)背景查看器 百万美元的诱惑 SAS - Serializing Authentication exx 浏览器也能套娃&#xff1f; 随便试一试&#xff0c;一眼ssrf file:///flag直接读本地文件 一个....池子&#xff1f; {…

OAK相机支持的图像传感器有哪些?

相机支持的传感器 在 RVC2 上&#xff0c;固件必须具有传感器配置才能支持给定的相机传感器。目前&#xff0c;我们支持下面列出的相机传感器的开箱即用&#xff08;固件中&#xff09;传感器配置。 名称 分辨率 传感器类型 尺寸 最大 帧率 IMX378 40563040 彩色 1/2.…

从0到1:理发店预约剪发小程序开发笔记(上)

背景 理发师可以在小程序上设置自己的可预约时间&#xff0c;价格&#xff0c;自我介绍&#xff0c;顾客可以根据理发师的日程安排选择合适的时间进行预约和支付。这样可以提高预约的效率&#xff0c;减少沟通成本&#xff0c;方便双方的安排。 功能规划 首页展示&#xff1…

基于多种机器学习的豆瓣电影评分预测与多维度可视化【可加系统】

有需要本项目的代码或文档以及全部资源&#xff0c;或者部署调试可以私信博主 在本研究中&#xff0c;我们采用Python编程语言&#xff0c;利用爬虫技术实时获取豆瓣电影最新数据。通过分析豆瓣网站的结构&#xff0c;我们设计了一套有效的策略来爬取电影相关的JSON格式数据。…

第1章 初识 C 语言

目录 1.1 C 语言的起源 1.2 选择 C 语言的理由 1.2.1 设计特性 1.2.2 高效性 1.2.3 可移植性 1.2.4 强大而灵活 1.2.5 面向程序员 1.2.6 缺点 1.3 C 语言的应用范围 1.4 计算机能做什么 1.5 高级计算机语言和编译器 1.6 语言标准 1.6.1 第 1 个 ANSI/ISO C 标准 …

手写模拟Spring底层原理-简易实现版

通过手写模拟Spring 了解Spring的底层源码启动过程了解BeanDefinition、BeanPostProcessor的概念了解Spring解析配置类等底层源码工作流程了解依赖注入&#xff0c;Aware回调等底层源码工作流程了解Spring AOP的底层源码工作流程 这里实现一个简化版的 Spring 框架的核心功能&a…

GraphRAG深入解析

GraphRAG深入解析 GraphRAG 深入解析概述索引查询 索引过程深入解析步骤 1&#xff1a;处理文本块步骤 2&#xff1a;图提取步骤 3&#xff1a;图增强步骤 4&#xff1a;社区总结步骤 5&#xff1a;文件处理步骤 6&#xff1a;网络可视化 查询过程深入解析本地搜索问题生成全局…

苹果安卓分发的秘密:如何选择正确的渠道(苹果安卓分发)

苹果安卓分发的重要性 随着移动互联网的普及&#xff0c;移动应用程序的开发和分发变得越来越重要。苹果安卓分发是移动应用程序开发者的首要任务之一&#xff0c;因为它直接关系到应用程序的推广和收益。 苹果安卓分发并不是一件简单的事情。开发者需要选择正确的渠道&#…

Pytorch使用教学5-视图view与reshape的区别

有同学后台留言问为什么view有时可对张量进行形变操作&#xff0c;有时就会报错&#xff1f;另外它和reshape功能好像一致&#xff0c;有什么区别呢&#xff1f;本文就带你了解PyTorch中视图的概念。 在PyTorch中对张量进行形变操作时&#xff0c;很多同学也会使用view方法&am…

监测Nginx访问日志状态码,并做相应动作

文章目录 引言I 监测 Nginx 访问日志情况,并做相应动作1.1 前提准备1.2 访问日志 502 情况,重启 bttomcat9服务1.3 其他案例:访问日志 502 情况,重启 php-fpm 服务II 将Shell 脚本check499.sh包装成systemd服务2.1 创建systemd服务2.2 配置service2.3 开机启动2.4 其他常用…

华为ICT大赛之ensp软件BGP原理与配置

BGP基础 1.用于不同自治系统AS(autonomous system)之间动态交换路由信息&#xff1b; BGP取代EGP(exterior gateway protocol)外部网关协议&#xff0c;BGP在其发布路由信息基础上可以进行路由优选&#xff0c;高效处理路由信息&#xff1b; AS:同一组织管理下&#xff0c;使…

RK3568平台(显示篇)显示系统基本概念

一.显示系统概述 linux内核中包含两类图形显示设备驱动框架&#xff1a; FB设备&#xff1a;Framebuffer图形显示框架;DRM&#xff1a;直接渲染管理器&#xff08;Direct Rendering Manager&#xff09;&#xff0c;是linux目前主流的图形显示框架&#xff1b; 在实际场景中…

打通“链上数据脉络” 欧科云链数字生态建设成果凸显

7月25日&#xff0c;据Coindesk报道&#xff0c;全球领先的区块链技术和服务提供商欧科云链宣布旗下OKLink浏览器与Polygon Labs正式达成合作&#xff0c;成为AggLayer首个区块链搜索引擎及Web3数据分析平台&#xff0c;将为开发者提供精简易用的链上数据访问和开发工具&#x…

MATLAB学习教程(一)

目录 1.常见函数基本运算 2.二维绘制: plot(​..) 3.三维绘制: plot3(​..) / mesh(​..) 4.绘图美化 标题及标签 绘图命令&#xff1a;color 绘图命令&#xff1a;Line 5.代码 1.常见函数基本运算 方根函数sqrt() 自然指数函数exp() 以10为底的对数函…

巴斯勒相机(Basler) ACE2 dart 系列说明和软件

巴斯勒相机(Basler) ACE2 dart 系列说明和软件

了解高防 IP

一、高防 IP 的基本概念 高防 IP 是指拥有强大防御能力的 IP 地址。它主要通过将攻击流量引流到高防机房进行清洗和过滤&#xff0c;再将正常的流量回注到源站&#xff0c;从而保障源站服务器的稳定运行。 二、高防 IP 的工作原理 当用户的服务器遭受 DDoS 攻击时&#xff0…

Ubuntu22.04手动安装fabric release-2.5版本

这个过程稍微有点复杂&#xff0c;但完整操作完成以后会对Fabric网络有更加深入的理解&#xff0c;方便后续自己手动搭建Fabric网络。这个过程需要手动逐个下载Fabric源代码、使用命令下载Fabric镜像和用Git下载例子程序。 Fabric源代码主要用途是用来编译cryptogen、configtx…

redis的使用场景

目录 1. 热点数据缓存 1.1 什么是缓存&#xff1f; 1.2 缓存的原理 1.3 什么样的数据适合放入缓存中 1.4 哪个组件可以作为缓存 1.5 java使用redis如何实现缓存功能 1.5.1 需要的依赖 1.5.2 配置文件 1.5.3 代码 1.5.4 发现 1.6 使用缓存注解完成缓存功能 2. 分布式锁…

【Python Web】Flask扩展开发指南

Flask是一个轻量级的Python Web框架&#xff0c;它提供了丰富的扩展库和工具&#xff0c;可以帮助开发者快速构建Web应用。本篇博客将介绍如何进行Flask扩展开发&#xff0c;包括扩展的创建、配置、使用等方面的内容。 目录 Flask扩展开发指南 一、Flask扩展简介 二、创建Fl…