Flutter框架渲染流程与使用

时间:2023-01-27 07:14:23
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功能的一些功能代码,本身的性能和体验并不理想。
Flutter框架渲染流程与使用
 
第二阶段:React Native
RN是FaceBook早先开源的JS框架React在原生移动平台的衍生产物,目前支持iOS和安卓两大平台。
RN是使用JS语言,使用类似于HTML的JSX, 以及CSS来做移动开发。
使用原生自带的UI组件实现核心的渲染引擎,从而保证了良好的渲染性能。
但是RN的本质是通过JavaScript VM调用原生接口,通信相对比较低效,并且框架本身不负责渲染,而是间接通过原生进行渲染的。
 
Flutter框架渲染流程与使用
第三个阶段:Flutter
拥有自渲染闭环,是理想的跨平台框架。
Flutter框架渲染流程与使用
安卓原生渲染流程:
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和安卓的渲染流程是一样的。
Flutter框架渲染流程与使用
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框架,必须先通过桥接的方式转成原生调用,然后再进行渲染。存在性能消耗。
 
Flutter框架渲染流程与使用
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”(撕裂)。
 
Flutter框架渲染流程与使用
 
双重缓存和VSync
为了解决“tearing”(撕裂)问题,就出现了双重缓存和VSync。
Flutter框架渲染流程与使用
两个缓存分别为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信号是在一个刷新周期结束后产生的。
Flutter框架渲染流程与使用
三重缓存
在每次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上渲染效果是完全一致的。
Flutter框架渲染流程与使用
 
创建一个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是面向全世界很多国家的,有的国家排版是从左往右,有的是从右往左。所以需要用户自己设置。
Flutter框架渲染流程与使用
这里需要明确指定排版方向: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,