对于Android开发者来说,我们或多或少有了解过Android图像显示的知识点,刚刚学习Android开发的人会知道,在Actvity的onCreate方法中设置我们的View后,再经过onMeasure,onLayout,onDraw的流程,界面就显示出来了;对Android比较熟悉的开发者会知道,onDraw流程分为软件绘制和硬件绘制两种模式,软绘是通过调用Skia来操作,硬绘是通过调用Opengl ES来操作;对Android非常熟悉的开发者会知道绘制出来的图形数据最终都通过GraphiBuffer内共享内存传递给SurfaceFlinger去做图层混合,图层混合完成后将图形数据送到帧缓冲区,于是,图形就在我们的屏幕显示出来了。
创新互联公司专业为企业提供延安网站建设、延安做网站、延安网站设计、延安网站制作等企业网站建设、网页设计与制作、延安企业网站模板建站服务,十载延安做网站经验,不只是建网站,更提供有价值的思路和整体网络服务。
但我们所知道的Activity或者是应用App界面的显示,只属于Android图形显示的一部分。同样可以在Android系统上展示图像的WebView,Flutter,或者是通过Unity开发的3D游戏,他们的界面又是如何被绘制和显现出来的呢?他们和我们所熟悉的Acitvity的界面显示又有什么异同点呢?我们可以不借助Activity的setView或者InflateView机制来实现在屏幕上显示出我们想要的界面吗?Android系统显示界面的方式又和IOS,或者Windows等系统有什么区别呢?……
去探究这些问题,比仅仅知道Acitvity的界面是如何显示出来更加的有价值,因为想要回答这些问题,就需要我们真正的掌握Android图像显示的底层原理,当我们掌握了底层的显示原理后,我们会发现WebView,Flutter或者未来会出现的各种新的图形显示技术,原来都是大同小异。
我会花三篇文章的篇幅,去深入的讲解Android图形显示的原理,OpenGL ES和Skia的绘制图像的方式,他们如何使用,以及他们在Android中的使用场景,如开机动画,Activity界面的软件绘制和硬件绘制,以及Flutter的界面绘制。那么,我们开始对Android图像显示原理的探索吧。
在讲解Android图像的显示之前,我会先讲一下屏幕图像的显示原理,毕竟我们图像,最终都是在手机屏幕上显示出来的,了解这一块的知识会让我们更容易的理解Android在图像显示上的机制。
图像显示的完整过程,分为下面几个阶段:
图像数据→CPU→显卡驱动→显卡(GPU)→显存(帧缓冲)→显示器
我详细介绍一下这几个阶段:
实际上显卡驱动,显卡和显存,包括数模转换模块都是属于显卡的模块。但为了能能详细的讲解经历的步骤,这里做了拆分。
当显存中有数据后,显示器又是怎么根据显存里面的数据来进行界面的显示的呢?这里以LCD液晶屏为例,显卡会将显存里的数据,按照从左至右,从上到下的顺序同步到屏幕上的每一个像素晶体管,一个像素晶体管就代表了一个像素。
如果我们的屏幕分辨率是1080x1920像素,就表示有1080x1920个像素像素晶体管,每个橡素点的颜色越丰富,描述这个像素的数据就越大,比如单色,每个像素只需要1bit,16色时,只需要4bit,256色时,就需要一个字节。那么1080x1920的分辨率的屏幕下,如果要以256色显示,显卡至少需要1080x1920个字节,也就是2M的大小。
刚刚说了,屏幕上的像素数据是从左到右,从上到下进行同步的,当这个过程完成了,就表示一帧绘制完成了,于是会开始下一帧的绘制,大部分的显示屏都是以60HZ的频率在屏幕上绘制完一帧,也就是16ms,并且每次绘制新的一帧时,都会发出一个垂直同步信号(VSync)。我们已经知道,图像数据都是放在帧缓冲中的,如果帧缓冲的缓冲区只有一个,那么屏幕在绘制这一帧的时候,图像数据便没法放入帧缓冲中了,只能等待这一帧绘制完成,在这种情况下,会有很大了效率问题。所以为了解决这一问题,帧缓冲引入两个缓冲区,即 双缓冲机制 。双缓冲虽然能解决效率问题,但会引入一个新的问题。当屏幕这一帧还没绘制完成时,即屏幕内容刚显示一半时,GPU 将新的一帧内容提交到帧缓冲区并把两个缓冲区进行交换后,显卡的像素同步模块就会把新的一帧数据的下半段显示到屏幕上,造成画面撕裂现象。
为了解决撕裂问题,就需要在收到垂直同步的时候才将帧缓冲中的两个缓冲区进行交换。Android4.1黄油计划中有一个优化点,就是CPU和GPU都只有收到垂直同步的信号时,才会开始进行图像的绘制操作,以及缓冲区的交换工作。
我们已经了解了屏幕图像显示的原理了,那么接着开始对Android图像显示的学习。
从上一章已经知道,计算机渲染界面必须要有GPU和帧缓冲。对于Linux系统来说,用户进程是没法直接操作帧缓冲的,但我们想要显示图像就必须要操作帧缓冲,所以Linux系统设计了一个虚拟设备文件,来作为对帧缓冲的映射,通过对该文件的I/O读写,我们就可以实现读写屏操作。帧缓冲对应的设备文件于/dev/fb* ,*表示对多个显示设备的支持, 设备号从0到31,如/dev/fb0就表示第一块显示屏,/dev/fb1就表示第二块显示屏。对于Android系统来说,默认使用/dev/fb0这一个设帧缓冲作为主屏幕,也就是我们的手机屏幕。我们Android手机屏幕上显示的图像数据,都是存储在/dev/fb0里,早期AndroidStuio中的DDMS工具实现截屏的原理就是直接读取/dev/fb0设备文件。
我们知道了手机屏幕上的图形数据都存储在帧缓冲中,所以Android手机图像界面的原理就是将我们的图像数据写入到帧缓冲内。那么,写入到帧缓冲的图像数据是怎么生成的,又是怎样加工的呢?图形数据是怎样送到帧缓冲去的,中间经历了哪些步骤和过程呢?了解了这几个问题,我们就了解了Android图形渲染的原理,那么带着这几个疑问,接着往下看。
想要知道图像数据是怎么产生的,我们需要知道 图像生产者 有哪些,他们分别是如何生成图像的,想要知道图像数据是怎么被消费的,我们需要知道 图像消费者 有哪些,他们又分别是如何消费图像的,想要知道中间经历的步骤和过程,我们需要知道 图像缓冲区 有哪些,他们是如何被创建,如何分配存储空间,又是如何将数据从生产者传递到消费者的,图像显示是一个很经典的消费者生产者的模型,只有对这个模型各个模块的击破,了解他们之间的流动关系,我们才能找到一条更容易的路径去掌握Android图形显示原理。我们看看谷歌提供的官方的架构图是怎样描述这一模型的模块及关系的。
如图, 图像的生产者 主要有MediaPlayer,CameraPrevier,NDK,OpenGl ES。MediaPlayer和Camera Previer是通过直接读取图像源来生成图像数据,NDK(Skia),OpenGL ES是通过自身的绘制能力生产的图像数据; 图像的消费者 有SurfaceFlinger,OpenGL ES Apps,以及HAL中的Hardware Composer。OpenGl ES既可以是图像的生产者,也可以是图像的消费者,所以它也放在了图像消费模块中; 图像缓冲区 主要有Surface以及前面提到帧缓冲。
Android图像显示的原理,会仅仅围绕 图像的生产者 , 图像的消费者 , 图像缓冲区 来展开,在这一篇文章中,我们先看看Android系统中的图像消费者。
SurfaceFlinger是Android系统中最重要的一个图像消费者,Activity绘制的界面图像,都会传递到SurfaceFlinger来,SurfaceFlinger的作用主要是接收图像缓冲区数据,然后交给HWComposer或者OpenGL做合成,合成完成后,SurfaceFlinger会把最终的数据提交给帧缓冲。
那么SurfaceFlinger是如何接收图像缓冲区的数据的呢?我们需要先了解一下Layer(层)的概念,一个Layer包含了一个Surface,一个Surface对应了一块图形缓冲区,而一个界面是由多个Surface组成的,所以他们会一一对应到SurfaceFlinger的Layer中。SurfaceFlinger通过读取Layer中的缓冲数据,就相当于读取界面上Surface的图像数据。Layer本质上是 Surface和SurfaceControl的组合 ,Surface是图形生产者和图像消费之间传递数据的缓冲区,SurfaceControl是Surface的控制类。
前面在屏幕图像显示原理中讲到,为了防止图像的撕裂,Android系统会在收到VSync垂直同步时才会开始处理图像的绘制和合成工作,而Surfaceflinger作为一个图像的消费者,同样也是遵守这一规则,所以我们通过源码来看看SurfaceFlinger是如何在这一规则下,消费图像数据的。
SurfaceFlinger专门创建了一个EventThread线程用来接收VSync。EventThread通过Socket将VSync信号同步到EventQueue中,而EventQueue又通过回调的方式,将VSync信号同步到SurfaceFlinger内。我们看一下源码实现。
上面主要是SurfaceFlinger初始化接收VSYNC垂直同步信号的操作,主要有这几个过程:
经过上面几个步骤,我们接收VSync的初始化工作都准备好了,EventThread也开始运转了,接着看一下EventThread的运转函数threadLoop做的事情。
threadLoop主要是两件事情
mConditon又是怎么接收VSync的呢?我们来看一下
可以看到,mCondition的VSync信号实际是DispSyncSource通过onVSyncEvent回调传入的,但是DispSyncSource的VSync又是怎么接收的呢?在上面讲到的SurfaceFlinger的init函数,在创建EventThread的实现中,我们可以发现答案—— mPrimaryDispSync 。
DispSyncSource的构造方法传入了mPrimaryDispSync,mPrimaryDispSync实际是一个DispSyncThread线程,我们看看这个线程的threadLoop方法
DispSyncThread的threadLoop会通过mPeriod来判断是否进行阻塞或者进行VSync回调,那么mPeriod又是哪儿被设置的呢?这里又回到SurfaceFlinger了,我们可以发现在SurfaceFlinger的 resyncToHardwareVsync 函数中有对mPeriod的赋值。
可以看到,这里最终通过HWComposer,也就是硬件层拿到了period。终于追踪到了VSync的最终来源了, 它从HWCompser产生,回调至DispSync线程,然后DispSync线程回调到DispSyncSource,DispSyncSource又回调到EventThread,EventThread再通过Socket分发到MessageQueue中 。
我们已经知道了VSync信号来自于HWCompser,但SurfaceFlinger并不会一直监听VSync信号,监听VSync的线程大部分时间都是休眠状态,只有需要做合成工作时,才会监听VSync,这样即保证图像合成的操作能和VSync保持一致,也节省了性能。SurfaceFlinger提供了一些主动注册监听VSync的操作函数。
可以看到,只有当SurfaceFlinger调用 signalTransaction 或者 signalLayerUpdate 函数时,才会注册监听VSync信号。那么signalTransaction或者signalLayerUpdate什么时候被调用呢?它可以由图像的生产者通知调用,也可以由SurfaceFlinger根据自己的逻辑来判断是否调用。
现在假设App层已经生成了我们界面的图像数据,并调用了 signalTransaction 通知SurfaceFlinger注册监听VSync,于是VSync信号便会传递到了MessageQueue中了,我们接着看看MessageQueue又是怎么处理VSync的吧。
MessageQueue收到VSync信号后,最终回调到了SurfaceFlinger的 onMessageReceived 中,当SurfaceFlinger接收到VSync后,便开始以一个图像消费者的角色来处理图像数据了。我们接着看SurfaceFlinger是以什么样的方式消费图像数据的。
VSync信号最终被SurfaceFlinger的onMessageReceived函数中的INVALIDATE模块处理。
INVALIDATE的流程如下:
handleMessageTransaction的处理比较长,处理的事情也比较多,它主要做的事情有这些
handleMessageRefresh函数,便是SurfaceFlinger真正处理图层合成的地方,它主要下面五个步骤。
我会详细介绍每一个步骤的具体操作
合成前预处理会判断Layer是否发生变化,当Layer中有新的待处理的Buffer帧(mQueuedFrames0),或者mSidebandStreamChanged发生了变化, 都表示Layer发生了变化,如果变化了,就调用signalLayerUpdate,注册下一次的VSync信号。如果Layer没有发生变化,便只会做这一次的合成工作,不会注册下一次VSync了。
重建Layer栈会遍历Layer,计算和存储每个Layer的脏区, 然后和当前的显示设备进行比较,看Layer的脏区域是否在显示设备的显示区域内,如果在显示区域内的话说明该layer是需要绘制的,则更新到显示设备的VisibleLayersSortedByZ列表中,等待被合成
rebuildLayerStacks中最重要的一步是 computeVisibleRegions ,也就是对Layer的变化区域和非透明区域的计算,为什么要对变化区域做计算呢?我们先看看SurfaceFlinger对界面显示区域的分类:
还是以这张图做例子,可以看到我们的状态栏是半透明的,所以它是一个opaqueRegion区域,微信界面和虚拟按键是完全不透明的,他是一个visibleRegion,除了这三个Layer外,还有一个我们看不到的Layer——壁纸,它被上方visibleRegion遮挡了,所以是coveredRegion
对这几个区域的概念清楚了,我们就可以去了解computeVisibleRegions中做的事情了,它主要是这几步操作:
页面中的各界面元素(Widget)以树的形式组织,即控件树。Flutter通过控件树中的每个控件创建不同类型的渲染对象,组成渲染对象树。而渲染对象树在Flutter的展示过程分为三个阶段:布局、绘制、合成和渲染。
(一)布局
Flutter采用深度优先机制遍历渲染对象树,决定渲染对象树中各渲染对象在屏幕上的位置和尺寸。在布局过程中,渲染对象树中的每个渲染对象都会接收父对象的布局约束参数,决定自己的大小,然后父对象按照控件逻辑决定各个子对象的位置,完成布局过程。
为了防止因子节点发生变化而导致整个控件树重新布局,Flutter加入了一个机制——布局边界(Relayout Boundary),可以在某些节点自动或手动地设置布局边界,当边界内的任何对象发生重新布局时,不会影响边界外的对象,反之亦然。
二)绘制
布局完成后,渲染对象树中的每个节点都有了明确的尺寸和位置。Flutter会把所有的渲染对象绘制到不同的图层上。与布局过程一样,绘制过程也是深度优先遍历,而且总是先绘制自身,再绘制子节点。
以下图为例:节点1在绘制完自身后,会再绘制节点2,然后绘制它的子节点3、4和5,最后绘制节点6。
可以看到,由于一些其他原因(比如,视图手动合并)导致2的子节点5与它的兄弟节点6处于了同一层,这样会导致当节点2需要重绘的时候,与其无关的节点6也会被重绘,带来性能损耗。
为了解决这一问题,Flutter提出了与布局边界对应的机制——重绘边界(Repaint Boundary)。在重绘边界内,Flutter会强制切换新的图层,这样就可以避免边界内外的互相影响,避免无关内容置于同一图层引起不必要的重绘。
重绘边界的一个典型场景是Scrollview。ScrollView滚动的时候需要刷新视图内容,从而触发内容重绘。而当滚动内容重绘时,一般情况下其他内容是不需要重绘的,这时候重绘边界就派上用场了。
(三)合成和渲染
终端设备的页面越来越复杂,因此Flutter的渲染树层级通常很多,直接交付给渲染引擎进行多图层渲染,可能会出现大量渲染内容的重复绘制,所以还需要先进行一次图层合成,即将所有的图层根据大小、层级、透明度等规则计算出最终的显示效果,将相同的图层归类合并,简化渲染树,提高渲染效率。
合并完成后,Flutter会将几何图层数据交由Skia引擎加工成二维图像数据,最终交由GPU进行渲染,完成界面的展示。
四、总结
咱们从各种业界主流跨端方案与Flutter的对比开始,到Flutter的简要介绍以及Flutter的运行机制,并以界面渲染过程为例,从布局、绘制、合成和渲染三个阶段讲述了Flutter的实现原理。相信大家对Flutter已经有一个整体认知,赶快一起上手操作起来吧!
1.圆角对性能的影响
尽量避免用Clipxxx组件,建议用BoxDecoration的image属性实现,如果用Clipxxx组件,圆角取整后性能会提升。
2.减少重绘
根据场景合理使用RePaintBoundary,使绘制独立于父布局,避免重绘,提升性能,但过度使用增加的图层会带来Raster合成的耗时。例如scrollview是滑动过程会导致所有的节点都重绘,可以在scrollview下一层使用RePaintBoundary。
3.滚动步长插值器优化(了解)
官方的滚动差值器在出现小卡顿时,滚动步长会出现大的跳跃,导致体感上出现很明显的抖动,优化步长偏移量算法与原生效果对齐。
4.开启SurfaceView
官方推荐Flutter用SurfaceView ,因为SurfaceView与应用窗口内容分隔开,在专有硬件中合成,产生的中间副本少于TextureView,所以性能高,占用内存少,但是在混合栈遇到的问题需要突破
5.使用RepaintBoundary 提升频繁重绘控件的性能。使用RelayoutBoundary提升频繁修改大小,增删的布局中也可以提升性能。
6.build中不要去写大量的耗时逻辑,因为数据更新会触发build的多次调用,在里面做耗时逻辑会降低性能。
7.尽量使用statelessWidget代替statefulWidget,因为statefulWidget的销毁重建会引起子widget的销毁与重建。
8.解析json可以放到子线程线程中,开Isolate去解析,这样,当返回数据特别大的时候也不会阻塞界面。
9.使用不变的组件的时候可以添加const,const组件不会进行build更新
10.由于flutter通过widget.runtimeType和key来判断是否需要跟新组建,所以我们写组件的时候尽量保持key不变,或者不写key。对于一些需要频繁改变,例如新增、删除、排序的最好加上key。如果type一直,如果不写key容易导致,element无法区分新旧widget,导致无法更新。
en ,是小说飘???gone with the wind flutter 也有飘的意思 float也有 追问: 不是 米切尔 的小说,float是指流水的漂,fluttet多指鸟类,也不是drift。我要的是“飘”! 回答: unfettered 逍遥,也有你说的飘的意思吧 free应该是最简单的吧 leisurely也有这方面的意思 happy-go-lucky 现在网 上这个“ 合成词 ”也是逍遥 at large 这个学过 新概念 的都知道,逍遥,飘的意思 饿,还有啥不满意的
ModalRoute中的参数maintainState。其意义为:已经不可见(被上面的盖住完全看不到啦~)的组件,是否还需要保存状态。OverlayEntry类源码中说maintainState设置为true是昂贵的,但是如果maintainState为false,那当不可见时,对应的页面会被销掉,如果pop回去时重新创建的(State 会跑initState方法),那上个页面的状态就无法保持了。这可能也是MaterialPageRoute默认maintainState为true的原因。
这里的saveLayer()是说canvas的saveLayer()方法。一般用于合成特殊效果
可能触发 saveLayer() 并可能代价高昂的其他小部件:
避免调用 saveLayer() :
文章题目:flutter合成,flutter构造函数
文章位置:/article30/dsiiiso.html
成都网站建设公司_创新互联,为您提供动态网站、面包屑导航、静态网站、服务器托管、云服务器、响应式网站
声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联