4

Flutter框架渲染流程与使用 - 滴水微澜

 1 year ago
source link: https://www.cnblogs.com/zhou--fei/p/17068412.html
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

Flutter框架渲染流程与使用

Flutter简述
Flutter是一个UI SDK, 可以进行移动端(iOS, Android),Web端, 桌面,它是一个跨平台解决方法。
Flutter的特点:美观,快速,高效,开放。
美观:Flutter内置了美丽的Material Design和 Cupertino widget, 方便开发出美丽的页面。
快速:Flutter的UI渲染性能好,在生产环境下,Flutter将代码编译成机器码执行,并充分利用GPU的图形加速能力,可以实现60FPS。Debug环境下则使用JIM,提升了开发效率。
Flutter引擎使用C++编写,包含了高效的Skia 2D渲染引擎,Dart运行时和文本渲染库。
高效:Hot reload, 在前端不是新鲜的东西,在移动端却并不常见。
开放:Flutter是开放的,它是一个完全开源的项目。
Flutter有一统大前端的野心,并且它正在侵蚀iOS,Andriod这些原生开发。
跨平台解决方案历史
第一个阶段:通过webView
iOS端有UIWebView, Android端WebView
最早出现的跨平台框架是基于JavaScript和WebView的,代表框架有PhoneGap, Apache Cordova, Ionic等。
主要是通过Html, Css开发页面。对于调用的一些本地服务如相机,蓝牙等。需要通过JS进行桥接调用Native功能的一些功能代码,本身的性能和体验并不理想。
0
第二阶段:React Native
RN是FaceBook早先开源的JS框架React在原生移动平台的衍生产物,目前支持iOS和安卓两大平台。
RN是使用JS语言,使用类似于HTML的JSX, 以及CSS来做移动开发。
使用原生自带的UI组件实现核心的渲染引擎,从而保证了良好的渲染性能。
但是RN的本质是通过JavaScript VM调用原生接口,通信相对比较低效,并且框架本身不负责渲染,而是间接通过原生进行渲染的。
0
第三个阶段:Flutter
拥有自渲染闭环,是理想的跨平台框架。
0
安卓原生渲染流程:
1.通过Java语言,调用Android框架提供的framework的API 写出页面布局。
2.页面布局通过Android框架中framework进行翻译,将翻译的结果提交给Skia。
3.Skia给CPU/GPU 提供数据进行渲染。
Flutter渲染流程:
1.通过Dart语言,调用Flutter框架提供的framework的API 写出页面布局。
2.页面布局通过Flutter框架中framework进行翻译,将翻译的结果提交给Skia。
3.Skia给CPU/GPU 提供数据进行渲染。
从上面可以发现,Flutter和安卓的渲染流程是一样的。
0
RN的渲染流程
1.通过JS, JSX, CSS, Redux等调用React框架提供的API编写页面布局。
2.React框架通过JavaScript VM, Bridge将JS布局转换成原生布局。
3.原生页面布局通过Android框架中framework进行翻译,将翻译的结果提交给Skia。
4.Skia给CPU/GPU 提供数据进行渲染。
RN渲染增加了1,2步骤的转换,造成了性能消耗。所以RN的性能没有Flutter的高。
Flutter不需要依赖原生控件,利用Skia绘图引擎,直接通过CPU,GPU进行绘制。和安卓的原生绘制流程一样。
而像RN框架,必须先通过桥接的方式转成原生调用,然后再进行渲染。存在性能消耗。
0
Flutter绘制原理
1.GPU将VSync绘制信号同步到UI线程。
2.UI线程用Dart将Flutter代码构建图层树Layer Tree。
3.图层树Layer Tree在GPU线程内的Compositor进行合成。
4.Compositor合成的结果交给Skia进行渲染。
5.Skia渲染的结果通过OpenGL或Vulkan交给GPU进行图像绘制。
6.GPU绘制完成后把图像放入到双缓存的Back Buffer,等下一个VSync来时,系统从Back Buffer复制到Frame Buffer并产生新一轮的CPU/GPU绘制过程。
上面是Flutter完整的渲染闭环,这是它和ReactNative的本质区别。
ReactNative是没有自己的渲染闭环的,它是通过JS VM调用系统组件,使用的iOS、安卓的原生渲染。
图像的显示原理
在屏幕上看到的任何东西都是图像,包括图片,GIF图像,视频。
当图片连续播放的频率超过16帧时,人眼就会感到非常流畅。当小于16帧时就会感到卡顿。
帧率(fps): Frames Per Second, 每秒生成多少帧图像。
刷新率:显示屏的频率,比如iPhone的屏幕每秒刷新60下,表示为60Hz。
帧率和刷新率的关系
GPU/CPU生成图像(每秒生成多少张图片是帧率)放入Buffer中,屏幕从Buffer中取图像,刷新(每秒刷新多少次是刷新率)后显示。这是一个生产者-消费者模型。
很容易产生的问题是:GPU在新的一帧图片写入一半时,屏幕从中取出图像展示。此时会取出一张上半部分和下半部分不一致的图像。
显示出来的图像出现上半部分和下半部分明显偏差的现象,我们称为“tearing”(撕裂)。
0
双重缓存和VSync
为了解决“tearing”(撕裂)问题,就出现了双重缓存和VSync。
0
两个缓存分别为Back Buffer和Frame Buffer, GPU向Back Buffer写数据,屏幕从Frame Buffer读数据。VSync信号负责从Back Buffer到Frame Buffer的复制操作。底层的复制是用“指针交换”的假复制,效率高。
工作流程:
某个时间点,一个屏幕刷新周期完成,VSync信号产生,先完成复制操作,然后通知CPU/GPU绘制一帧图像。
复制操作完成后,开始下一个刷新周期,将刚复制到Frame Buffer的数据显示到屏幕上。
在这种模型下,只有当VSync信号产生时,CPU/GPU才开始绘制。
双重缓存存在的问题
双重缓存的缺陷:当CPU/GPU绘制一帧的时间过长(比如超过16ms一个刷新周期时),会产生Jank(画面停顿,甚至空白),VSync信号是在一个刷新周期结束后产生的。
0
三重缓存
在每次VSync信号来时,多缓存一个Buffer作为备用。
渲染引擎skia
skia是Flutter向GPU提供数据的途径。
skia是一个C++编写的开源形库。
目前skia是安卓的官方图像引擎,所以Flutter的安卓应用SDK无需内嵌Skia引擎。
而对于iOS来说,Skia引擎需要嵌入到iOS SDK中,替代了iOS必源的Core Graphics / Core Animation / Core Text, 所以Flutter iOS SDK打包的APP包体积比安卓的大。
因为底层渲染能力的统一,上层开发接口和功能也就是统一的。skia保证了同一套代码调用在iOS和Android上渲染效果是完全一致的。
0
创建一个Flutter项目
命令行创建flutter项目
项目名称不支持中文,不支持大小,可以使用_进行链接
flutter create flutter_demo

VSCode环境安装插件

Flutter,
Dart,
Code Runner

Flutter项目启动方式有三种:冷启动,热启动(hotReload),热重载(hotRestart)。

冷启动:代码和Flutter框架都没有加载和运行,需要从磁盘加载到内存进行运行,过程比较久,通常需要1min-5min。
热启动 hotReload:重新运行widget中的build方法。
热重载 hotRestart:重新运行APP的main入口函数,重新运行一遍程序。
Material是什么
Material是google推出的套设计风格,或者叫设计规范。里面包含了很多设计规范,如:颜色,文字排版,响应动画与过度等。
在Flutter中高度集成Material风格的Widget。
Widget是什么
Flutter中万物皆Widget。在iOS或安卓中,我们的页面有很多种类:应用Application, 视图控制器ViewController, 视图View, Button按钮等。
但是在Flutter中,这些东西都是不同的Widget。
而在我们的APP中,所有能看到的内容几乎都是Widget, 甚至内边距设置都是使用Padding的Widget来做的。
Flutter项目的入口
Flutter项目的入口是lib/main.dart下的main函数。
在main函数内部,需要运行函数runApp(widget app);, runApp函数是Flutter提供的一个全局APP运行函数入口。
runApp(widget app);

最简单的Flutter项目Demo

import 'package:flutter/material.dart';
void main(List<String> args) {
runApp(Text('Hello Flutter'));
}

此时报错:没有找到排版方向

原因是Flutter是面向全世界很多国家的,有的国家排版是从左往右,有的是从右往左。所以需要用户自己设置。
0
这里需要明确指定排版方向:TextDirection.ltr。
在Flutter中万物都是widget,所以如果要设置center, padding ,margin等都是写对应的widget。
修改报错后,如下:
void main(List<String> args) {
runApp(
Center(
child:Text('Hello Flutter',
textDirection: TextDirection.ltr,
style: TextStyle(
color: Colors.red,
fontSize: 30,
),
)
)
);
}
MaterialApp:采用了Google的Material设计设计规范的Widget,里面默认设置了文字排版方向等设置。
Scaffold:脚手架Widget, 用于快速搭建页面机构,提供了不同位置的命名可选参数。
import 'package:flutter/material.dart';
/*
MaterialApp:采用了Google的Material设计设计规范的Widget,里面默认设置了文字排版方向等设置。
Scaffold:脚手架Widget, 用于快速搭建页面机构,提供了不同位置的命名可选参数。
debugShowCheckedModeBanner: 去掉右上角的debug条
*/
void main(List<String> args) {
runApp(
MaterialApp(
debugShowCheckedModeBanner: false,
home:Scaffold(
appBar: AppBar(title: Text('第一个Flutter程序'),),
body: Center(
child:Text('Hello Flutter',
style: TextStyle(
color: Colors.blue,
fontSize: 30,
),
)
),
)
)
);
}
StatelessWidget
StatelessWidget:在运行过程中,组件内容是固定的,没有状态修改的。
Widget中没有数据改动,只使用固定的数据展示或者只使用从父Widget继承过来的Widget时,使用StatelessWidget。
继承StatelessWidget产生的子Widget需要重新build方法,并在build方法里返回要展示的widget。
build方法是无法主动调用的。只能在数据改动时系统来调用。
build方法调用时机:
1.当StatelessWidget第一次插入到Widget树时(第一次被创建时)
2.当父Widget发生改变时,子Widget需要被重新构建
3.当依赖的InheritedWidget的数据发送改变时,被重新调用。
StatefulWidget
在运行过程中,状态(data)会产生改变,导致页面展示内容发生改变。
如果想使用StatefulWidget创建有状态变化的Widget, 需要一个State对象一起来实现。
StatefulWidget内部无法写var属性, 因为它继承自Widget,Widget是被@immutable修饰,不可改变。所以它的状态改变要在别的类(State)中实现。
State:在创建的State子类中添加var属性,并将其与Widget状态绑定,当有新的状态改变时,需要调用setState((){})进行更新状态
Flutter的状态更新和React的机制一样,需要调用setState通知框架进行页面更新。
与Vue不同的是Vue实例使用的是双向绑定,内部对属性做了监听,无需手动调用setState进行通知更新。
/*
StatefulWidget内部无法写var属性, 因为它继承自Widget,Widget是被@immutable修饰,不可改变。所以它的状态改变要在别的类(State)中实现。
State:在创建的State子类中添加var属性,并将其与Widget状态绑定,当有新的状态改变时,需要调用setState((){})进行更新状态
Flutter的状态更新和React的机制一样,需要调用setState通知框架进行页面更新。
与Vue不同的是Vue实例使用的是双向绑定,内部对属性做了监听,无需手动调用setState进行通知更新。
*/
class PageContent extends StatefulWidget {
@override
State<StatefulWidget> createState() {
return PageContentState();
}
}
class PageContentState extends State<PageContent> {
var flag = true;
@override
Widget build(BuildContext context) {
return Center(
child:Row(
mainAxisAlignment: MainAxisAlignment.center,
children:[
Checkbox(
value: flag,
onChanged: (value) {
setState(() {
flag = value;
});
},),
Text('Hello World')
]
)
);
}
}
声明式编程和命令式编程
iOS,安卓使用的是命令式编程,平时涉及到的是属性,成员变量。
vue, react, flutter是声明式编程,平时涉及到的是State状态,平时开发是只需要管好状态,显示内容有框架自动帮忙更新上去。
Flutter修饰符
@immutable修饰符表示Widget类是不可改变的,里面的属性都是final类型的。
required可选变量 必传修饰符, 用于表示命名可选变量为必传参数。
@immutable
abstract class Widget extends DiagnosticableTree
const Checkbox({
Key? key,
required this.value,
this.tristate = false,
required this.onChanged,

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK