大橙子网站建设,新征程启航
为企业提供网站建设、域名注册、服务器等服务
flutter很强,目前一套代码可以供Android,iOS, Web 使用,妥妥的一套代码,多端使用,在跨平台开发中,有着巨大的影响。
成都创新互联公司专业为企业提供陵水黎族网站建设、陵水黎族做网站、陵水黎族网站设计、陵水黎族网站制作等企业网站建设、网页设计与制作、陵水黎族企业网站模板建站服务,十年陵水黎族做网站经验,不只是建网站,更提供有价值的思路和整体网络服务。
1、常用布局的对比
使用下来其他组件大致还算方便,但是相对布局而言使用便利程度上Android原生完胜,ConstraintLayout内部的所有子View可以设置互相之间的位置依赖关系。
而Flutter的Stack组件内部的Children只能通过外层包裹 Align后 固定位置,比如 Alignment.topLeft、Alignment.bottomRight 等。遇到复杂的堆叠布局需要通过外层包裹 Positioned 组件后设置固定的 top 和 left 距离以达到效果,内部子组件之间无法设置位置关联关系。
2、一些常用属性设置上的差异:
Margin外边距
Android:直接在布局文件对View设置android:layout_marginStart、android:layout_marginTop
Flutter:需嵌套 Container 组件并在内部设置具体的 margin 值
Padding内边距
Android:TextView、ImageView、各种Layout都可以直接在属性上设置android:paddingStart
Flutter:需嵌套 Padding 组件并在内部设置具体的值
组件的可见性
Android:每个view都可以通过setVisibility来设置可见、隐藏或者隐藏但占位
Flutter:没有单独设置组件是否显示的api,只能通过 bool 值控制是否添加该组件
事件监听
Android:常规的setOnClickListener和setOnLongClickListener设置单击和长按事件
Flutter:在需要添加事件监听的组件外层嵌套 InkWell 或 GestureDetector 并设置 onTap 等
3、生命周期
Android:
Activity和Fragment各自有完整的生命周期链路onCreate、onStart、onResume、onPause、onDestroy等
Flutter:
万物皆组件,组件继承 WidgetsBindingObserver 并重写 didChangeAppLifecycleState 函数进行监听
退回桌面依次执行inactive 》= paused,此时界面不可见用户不可操作,从桌面重新进入app执行resumed,状态较少如需在某些条件下触发特定操作可能要找别的方案,比如发通知之类的
文/陈炉军
整理/LiveVideoStack
大家好,我是阿里巴巴闲鱼事业部的陈炉军,本次分享的主题是Flutter浪潮下的音视频研发探索,主要内容是针对闲鱼APP在当下流行的跨平台框架Flutter的大规模实践,介绍其在音视频领域碰到的一些困难以及解决方案。
分享内容主要分为四个方面,首先会对Flutter有一个简单介绍以及选择Flutter作为跨平台框架的原因,其次会介绍Flutter中与音视频关系非常大的外接纹理概念,以及对它做出的一些优化。之后会对闲鱼在音视频实践过程中碰到的一些Flutter问题提出了一些解决方案——TPM音视频框架。最后是闲鱼Flutter多媒体开源组件的介绍。
Flutter
Flutter是一个跨平台框架,以往的做法是将音频、视频和网络这些模块都下沉到C++层或者ARM层,在其上封装成一个音视频的SDK,供UI层的PC、iOS和Android调用。
而Flutter做为一个UI层的跨平台框架,顾名思义就是在UI层也实现了一个跨平台开发。可以预想的是未Flutter发展的好的话,会逐渐变为一个从底层到UI层的一个全链路的跨平台开发,技术人员分别负责SDK和UI层的开发。
在Flutter之前已经有很多跨平台UI解决方案,那为什么选择Flutter呢?
我们主要考虑性能和跨平台的能力。
以往的跨平台方案比如Weex,ReactNative,Cordova等等因为架构的原因无法满足性能要求,尤其是在音视频这种性能要求几乎苛刻的场景。
而诸如Xamarin等,虽然性能可以和原生App一致,但是大部分逻辑还是需要分平台实现。
我们可以看一下,为什么Flutter可以实现高性能:
原生的native组件渲染以IOS为例,苹果的UIKit通过调用平台自己的绘制框架QuaztCore来实现UI的绘制,图形绘制也是调用底层的API,比如OpenGL、Metal等。
而Flutter也是和原生API逻辑一致,也是通过调用底层的绘制框架层SKIA实现UI层。这样相当于Flutter他自己实现了一套UI框架,提供了一种性能超越原生API的跨平台可能性。
但是我们说一个框架最终性能怎样,其实取决于设计者和开发者。至于现在到底是一个什么状况:
在闲鱼的实践中,我们发现在正常的开发没有特意的去优化UI代码的情况下,在一些低端机上,Flutter界面的流畅性是比Native界面要好的。
虽然现在闲鱼某些场景下会有卡顿闪退等情况,但是这是一个新事物发展过程中的必然问题,我们相信未来性能肯定不会成为限制Flutter发展的瓶颈的。
在闲鱼实践Flutter的过程中,混合栈和音视频是其中比较难解决的两个问题,混合栈是指一个APP在Flutter过程中不可能一口气将所有业务全部重写为Flutter,所以这是一个逐步迭代的过程,这期间原生native界面与Flutter界面共存的状态就称之为混合栈。闲鱼在混合栈上也有一些比较好的输出,例如FlutterBoost。
外接纹理
在讲音视频之前需要简要介绍一下外接纹理的概念,我们将它称之为是Flutter和Frame之间的桥梁。
Flutter渲染一帧屏幕数据首先要做的是,GPU发出的VC信号在Flutter的UI线程,通过AOT编译的机器码结合当前Dart Runtime,生成Layer Tree UI树,Layer Tree上每一个叶子节点都代表了当前屏幕上所需要渲染的每一个元素,包含了这些元素渲染所需要的内容。将Layer Tree抛给GPU线程,在GPU线程内调用Skia去完成整个UI的渲染过程。Layer Tree中有PictureLayer和TextureLayer两个比较重要的节点。PictureLayer主要负责屏幕图片的渲染,Flutter内部实现了一套图片解码逻辑,在IO线程将图片读取或者从网络上拉取之后,通过解码能够在IO线程上加载出纹理,交给GPU线程将图片渲染到屏幕上。但是由于音视频场景下系统API太过繁多,业务场景过于复杂。Flutter没有一套逻辑去实现跨平台的音视频组件,所以说Flutter提出了一种让第三方开发者来实现音视频组件的方式,而这些音视频组件的视频渲染出口,就是TextureLayer。
在整个Layer Tree渲染的过程中,TextureLayer的数据纹理需要由外部第三方开发者来指定,可以把视频数据和播放器数据送到TextureLayer里,由Flutter将这些数据渲染出来。
TextureLayer渲染过程:首先判断Layer是否已经初始化,如果没有就创建一个Texture,然后将Texture Attach到一个SufaceTexture上。
这个SufaceTexture是音视频的native代码可以获取到的对象,通过这个对象创建的Suface,我们可以将视频数据、摄像头数据解码放到Suface中,然后Flutter端通过监听SufaceTexture的数据更新就可以顺利把刚才创建的数据更新到它的纹理中,然后再将纹理交给SKIA渲染到屏幕上。
然而我们如果需要用Flutter实现美颜,滤镜,人脸贴图等等功能,就需要将视频数据读取出来,更新到纹理中,再将GPU纹理经过美颜滤镜处理后生成一个处理后的纹理。按Flutter提供的现有能力,必须先将纹理中的数据从GPU读出到CPU中,生成Bitmap后再写入Surface中,这样在Flutter中才能顺利的更新到视频数据,这样做对系统性能的消耗很大。
通过对Flutter渲染过程分析,我们知道Flutter底层需要渲染的数据就是GPU纹理,而我们经过美颜滤镜处理完成以后的结果也是GPU纹理,如果可以将它直接交给Flutter渲染,那就可以避免GPU-CPU-GPU这样的无用循环。这样的方法是可行的,但是需要一个条件,就是OpenGL上下文共享。
OpenGL
在说上下文之前,得提到一个和上线文息息相关的概念:线程。
Flutter引擎启动后会启动四个线程:
第一个线程是UI线程,这是Flutter自己定义的UI线程,主要负责GPU发出的VSync信号时候用当前Dart编译的机器码和当前运行环境创建出Layer Tree。
还有就是IO线程和GPU线程。和大部分OpenGL处理解决方案中一样,Flutter也采取一个线程责资源加载,一部分负责资源渲染这种思路。
两个线程之间纹理共享有两种方式。一种是EGLImage(IOS是 CVOpenGLESTextureCache)。一种是OpenGL Share Context。Flutter通过Share Context来实现纹理共享,将IO线程的Context和GPU线程的Context进行Share,放到同一个Share Group下面,这样两个线程下资源是互相可见可以共享的。
Platform线程是主线程,Flutter中有一个很奇怪的设定,GPU线程和主线程共用一个Context。并且在主线程也有很多OpenGL 操作。
这样的设计会给音视频开发带来很多问题,后面会详细说。
音视频端美颜处理完成的OpenGL纹理能够让Flutter直接使用的条件就是Flutter的上下文需要和平台音视频相关的OpenGL上下文处在一个Share Group下面。
由于Flutter主线程的Context就是GPU的Context,所以在音视频端主线程中有一些OpenGL操作的话,很有可能使Flutter整个OpenGL被破坏掉。所以需要将所有的OpenGL操作都限制在子线程中。
通过上述这两个条件的处理,我们就可以在没有增加GPU消耗的前提下实现美颜和滤镜等等功能。
TPM
在经过demo验证之后,我们将这个方案应用到闲鱼音视频组件中,但改造过程中发现了一些问题。
上图是摄像头采集数据转换为纹理的一段代码,其中有两个操作:首先是切进程,将后面的OpenGL操作都切到cameraQueue中。然后是设置一次上下文。然后这种限制条件或者说是潜规则往往在开发过程中容易被忽略的。而这个条件一旦忽略后果就是出现一些莫名其妙的诡异问题极难排查。因此我们就希望能抽象出一套框架,由框架本身实现线程的切换、上下文和模块生命周期等的管理,开发者接入框架以后只需要安心实现自己的算法,而不需要关心这些潜规则还有其他一些重复的逻辑操作。
在引入Flutter之前闲鱼的音视频架构与大部分音视频逻辑一样采用分层架构:
1:底层是一些独立模块
2:SDK层是对底层模块的封装
3:最上层是UI层。
引入Flutter之后,通过分析各个模块的使用场景,我们可以得出一个假设或者说是抽象:音视频应用在终端上可以归纳为视频帧解码之后视频数据帧在各个模块之间流动的过程,基于这种假设去做Flutter音视频框架的抽象。
咸鱼Flutter多媒体开源组件
整个Flutter音视频框架抽象分为管线和数据的抽象、模块的抽象、线程统一管理和上下文同一管理四部分。
管线,其实就是视频帧流动的管道。数据,音视频中涉及到的数据包括纹理、Bit Map以及时间戳等。结合现有的应用场景我们定义了管线流通数据以Texture为主数据,同时可以选择性的添加Bit Map等作为辅助数据。这样的数据定义方式,避免重复的创建和销毁纹理带来的性能开销以及多线程访问纹理带来的一些问题。也满足一些特殊模块对特殊数据的需求。同时也设计了纹理池来管理管线中的纹理数据。
模块:如果把管线和数据比喻成血管和血液,那框架音视频的场景就可以比喻成器官,我们根据模块所在管线的位置抽象出采集、处理和输出三个基类。这三个基类里实现了刚才说的线程切换,上下文切换,格式转换等等共同逻辑,各个功能模块通过集成自这些基类,可以避免很多重复劳动。
线程:每一个模块初始化的时候,初始化函数就会去线程管理的模块去获取自己的线程,线程管理模块可以决定给初始化函数分配新的线程或者已经分配过其他模块的线程。
这样有三个好处:
一是可以根据需要去决定一个线程可以挂载多少模块,做到线程间的负载均衡。第二,多线程并发式能够保证模块内的OpenGL操作是在当前线程内而不会跑到主线程去,彻底避免Flutter的OpenGL 环境被破坏。第三,多线程并行可以充分利用CPU多核架构,提升处理速度。
从Flutter端修改Flutter引擎将Context取出后,根据Context创建上下文的统一管理模块,每一个模块在初始化的时候会获取它的线程,获取之后会调用上下文管理模块获取自己的上下文。这样可以保证每一个模块的上下文都是与Flutter的上下文进行Share的,每个模块之间资源都是共享可见的,Flutter和音视频native之间也是互相共享可见的。
基于上述框架如果要实现一个简单的场景,比如画面实时预览和滤镜处理功能,
1:需要选择功能模块,功能模块包括摄像头模块、滤镜处理模块和Flutter画面渲染模块,
2:需要配置模块参数,比如采集分辨率、滤镜参数和前后摄像头设置等,
3:在创建视频管线后使用已配置的参数创建模块
4:最后管线搭载模块,开启管线就可以实现这样简单的功能。
上图为整个功能实现的代码和结构图。
结合上述音视频框架,闲鱼实现了Flutter多媒体开源组件。
组要包含四个基本组件分别是:
1:视频图像拍摄组件
2:播放器组件
3:视频图像编辑组件
4:相册选择组件
现在这些组件正在走内部开源流程。预计9月份,相册和播放器会实现开源。
后续展望和规划
1:实现开头所说的从底层SDK到UI的全链路的跨端开发。目前底层框架层和模块层都是各个平台各自实现,反而是Flutter的UI端进行了跨平台的统一,所以后续会将底层也按照音视频常用做法把逻辑下沉到C++层,尽可能的实现全链路跨平台。
2:第二部分内容为开源共建,闲鱼开源的内容不仅包括拍摄、编辑组件,还包括了很多底层模块,希望有开发者在基于Flutter开发音视频应用时可以充分利用闲鱼开源出的音视频模块能力,搭建APP框架,开发者只要去负责实现特殊需求模块就可以,尽可能的减少重复劳动。
Flutter是谷歌公司推出的跨终端的开发框架,支持Android、iOS和WEB终端。1.0版在2018年12月5日发布,目前的最新版本是1.5,它采用的开发语言是Dart,Dart也是谷歌开发的计算机编程语言,语法类似C,是编译型语言:
hello world例子,打印字符串“Hello World!”:
1、没有桥接层
React Native、Weex等技术都是跨终端的框架,然而性能跟原生App存在很大差距。这是由于它们的工作原理决定的:
React Native、Weex等技术多了一个桥接层,所以界面渲染会慢一些,由于UI渲染非常频繁,想要不卡顿,基本上比较难,性能和用户体验跟原生代码有差距。而这恰恰是Flutter的优势所在:
Dart可以被编译成不同平台的本地代码,让Flutter不通过桥接层直接跟平台通信,自然性能会快一些。
2、编译执行
JavaScript是解释执行的,Dart是编译执行的,性能谁好一目了然。
3、Flutter Engine虚拟机
Flutter是依靠Flutter Engine虚拟机在iOS和Android上运行的,Flutter Engine使用C/C++编写,开发人员通过Flutter框架直接和API在内部进行交互,所以具有输入低延迟和UI渲染高帧速率的特点。除了这特点之外,Flutter还提供了自己的小部件,Flutter小部件是使用从React获取灵感的现代框架构建的。 中心思想是您使用小部件构建UI。
窗口小部件根据其当前配置和状态描述了它们的视图。 当窗口小部件的状态发生更改时,窗口小部件会重建其描述,框架将根据前面的描述进行区分,以确定底层呈现树从一个状态转换到下一个状态所需的最小更改。可以直接在OS平台提供的画布上进行描绘,也就是一些核心类库直接放到虚拟机里面,调用起来更快。
从它的系统结构可以看出,类似安卓的ART(Android Run Time)虚拟机,同样采用AOT(Ahead of TIme)技术,会在APP安装时就编译成机器语言,不再解释执行,从而优化了APP运行的性能。
4、自带渲染引擎
Flutter使用谷歌自己的Skia渲染引擎,而Android系统自带Skia引擎,iOS平台上Flutter也会把Skia引擎打包到APP中,从而实现了高效渲染。而React Native通过桥接层访问原生UI,操作频繁就容易出性能问题。
综合所述,Flutter 是性能最接近原生代码 的一种开发框架,未来也会是构建谷歌Fuchsia应用的主要方式,前途不可限量,唯一的问题就是需要学习一门新的语言:Dart,而有Java或者C#语言基础的程序员会比较容易学习。
在编写几个 Flutter 项目后,发现 Flutter 的强大之处在于业务中所有用到的控件以及场景都有对应的处理方案;而 Dart 语言也与 Java 、 Kotlin 类似,所以对 Android 开发者来说门槛非常低;特意记录一下常用的控件及其使用:
StatelessWidget 不需要额外的创建 State
StatefulWidget 创建 State 类,并可以在其中保存一些状态
only 可以单独设置每个方向的内边距
类似于 LinearLayout 中的 orientation 设置为 vertical , mainAxisAlignment 表示竖向的一个对齐方式, crossAxisAlignment 表示横向的对齐方式
与 Column 相反,主轴是横向,对齐方式类似, crossAxisAlignment 表示竖向的对齐方式
类似 SizedBox ,一个容器,但是主要功能是有一个 decoration —— 装饰器,作用是绘制背景,或者使用 item 中的阴影
栈,先入后出,类似于 Android 上的 FrameLayout
通常配合 Stack 使用,固定显示在某一个位置
配合多 child 使用,会填充剩余的空间
Image 功能强大,使用不同的方法可以加载不同来源的图片
看到这些方法,突然觉得 Flutter 太香了,而且 Image 可以配置 clip 等裁剪出不同形状的图片,无论是圆形还是五角星都不在话下,然而 Android 要实现不规则的形状,可是要下不少功夫的。
名字和 Android 的一模一样,但是用法却比 Android 的简单很多:
主要就是 itemCount 与 itemBuilder ,其余就是配置样式, itemBuilder 需要返回一个 widget ,当然了,每个 ListView 都有其对应的 item ,在里面的方法中编写 widget 即可
与 ListView 类似,但是需要有一个 delegate 类,作用是设置有多少列,每一列之间的间距是多少
GridView 没有 build , children 表示所有的子 view
最常用的控件之一,有非常多的样式, Flutter 中通常是使用装饰器来处理控件的,如背景使用 BoxDecoration , TextFiled 使用 InputDecoration ; 使用如下
Flutter支持稳定的桌面设备开发已经一段时间了,不得不说,Flutter多平台支持的特性真的很香。我本人并没有任何桌面开发的经验,但仍然使用Flutter开发出了一个桌面版小程序,功能很简单,就是对输入的json做格式化处理和转模型。
话不多说,先来看看实际效果。 项目源码地址
开发环境如下:
Flutter : 2.8.1
Dart : 2.15.1
IDE : VSCode
JSON作为我们日常开发工作中经常要打交道的一种数据格式,它共有6种数据类型: null , num , string , object , array , bool 。我们势必对它又爱又恨。爱他因为他作为数据处理的一种格式确实非常方便简洁。但是在我们做Flutter开发中,又需要接触到json解析时,就会感觉非常棘手,因为flutter没有反射,导致json转模型这块需要手写那繁杂的映射关系。就像下面这样子。
数据量少还能接受,一旦量大,那么光手写这个解析方法都能让你怀疑人生。更何况手写还有出错的可能。好在官方有个工具**json_serializable**可以自动生成这块转换代码,也解决了flutter界json转模型的空缺。当然,业界也有专门解析json的网站,可以自动生成dart代码,使用者在生成后复制进项目中即可,也是非常方便的。
本项目以json解析为切入点,和大家一起来看下flutter是如何开发桌面应用的。
要让我们的flutter项目支持桌面设备。我们首先需要修改下flutter的设置。如下,让我们的项目支持 windows 和 macos 系统。
接下来使用 flutter create 命令创建我们的模版工程。
创建完项目后,我们就可以 run 起来了。
先来看下整体界面,界面四块,分别为功能模块、文件选择模块、输入模块、输出模块。
我们在新建一个桌面应用时,默认的模版又一个Appbar,此时应用可以用鼠标拖拽移动,放大缩小,还可以缩到很小。但是,我们一旦去掉这个导航栏,那么窗口就不能用鼠标拖动了,并且我们往往不希望用户将我们的窗口缩放的很小,这会导致页面异常,一些重要信息都展示不全。因此这里需要借助第三方组件 bitsdojo_window 。通过 bitsdojo_window ,我们可以实现窗口的定制化,拖动,最小尺寸,最大尺寸,窗口边框,窗口顶部放大、缩小、关闭的按钮等。
通过 InkWell 组件,可以捕捉到手势、鼠标、触控笔的移动和停留位置
这个功能是鼠标移动后的UI交互界面。要在窗口上显示一个提示框,可以使用 Overlay 。需要注意的是,由于在 Overlay 上的 text 的根结点不是 Material 风格的组件,因此会出现黄色的下划线。因此一定要用 Material 包一下 text 。并且你必须给创建的 OverlayEntry 一个位置,否则它将全屏显示。
读取说表拖拽的文件一开始想尝试使用 InkWell 组件,但是这个组件无法识别拖拽中的鼠标,并且也无法从中拿到文件信息。因此放弃。后来从文章《Flutter-2天写个桌面端APP》中发现一个可读取拖拽文件的组件 desktop_drop ,能满足要求。
使用开源组件 file_picker ,选完图片后的操作和拖拽选择图片后的操作一致。
Textfield 如果要显示富文本,那么需要自定义 TextEditingController 。并重写 buildTextSpan 方法。
在做导出功能时遇到下列报错,保存提示为没有权限访问对应目录下的文件。
通过Apple的开发文档找到有关权限问题的说明。其中有个授权私钥的key为 com.apple.security.files.downloads.read-write ,表示 对用户的下载文件夹的读/写访问权限 。那么,使用Xcode打开Flutter项目中的mac应用,修改工程目录下的 DebugProfile.entitlements 文件,向 entitlements 文件中添加 com.apple.security.files.downloads.read-write ,并将值设置为YES,保存后重启Flutter项目。发现已经可以向下载目录中读写文件了。
当然,这是正常操作。还有个骚操作就是关闭系统的沙盒机制。将 entitlements 文件的 App Sandbox 设置为NO。这样我们就可以访问任意路径了。当然关闭应用的沙盒也就相当于关闭了应用的防护机制,因此这个选项慎用。
原文地址: