Flutter - 深入理解Flutter应用启动

亚瑟 等级 488 0 0

基于Flutter 1.5,从源码视角来深入剖析flutter应用的启动流程,相关源码目录见文末附录

一、概述

上一篇文章深入理解Flutter引擎启动 已经介绍了FlutterApplication和FlutterActivity的onCreate()方法执行过程, 并触发Flutter引擎的启动,并最终执行到runApp(Widget app)方法,这才刚刚开始执行dart的业务代码,本文便从该方法讲起说一说flutter应用内逻辑的执行流程。

1.1 起点

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Gityuan Flutter Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: GityuanHomePage(title: 'Gityuan Flutter Demo Home Page'),
    );
  }
} 

这是一段flutter app的demo代码,可见root widget就是MyApp对象。

1.2 启动流程图

1) runApp启动流程图

Flutter - 深入理解Flutter应用启动

1.3 相关类图

1) Widget/Element/RenderObject类图

Flutter - 深入理解Flutter应用启动

Flutter中有3个比较重要的树Widget/Element/RenderObject,可以看出Widget/Element继承于共同的父类DiagnosticableTree,RenderObject继承于AbstractNode父类。 这3颗树之间是什么关系,如何相互关联,接下来会详细展开,并在文末会有更详细的类关系图。

二、应用启动流程

2.1 runApp

[-> lib/src/widgets/binding.dart]

void runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()  //[见小节2.2]
    ..attachRootWidget(app)   //[见小节2.4]
    ..scheduleWarmUpFrame();  //[见小节2.7]
} 

2.2 ensureInitialized

[-> lib/src/widgets/binding.dart]

class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, SemanticsBinding, RendererBinding, WidgetsBinding {

  static WidgetsBinding ensureInitialized() {
    if (WidgetsBinding.instance == null)
      WidgetsFlutterBinding();  //首次初始化,先初始化父类BindingBase [小节2.2.1]
    return WidgetsBinding.instance;
  }
} 

WidgetsFlutterBinding这是一个单例模式,负责创建WidgetsFlutterBinding对象,WidgetsFlutterBinding继承抽象类BindingBase,并且附带7个mixin,对于mixin语法来说顺序是很重要,相同的方法会由后面的mixin覆盖前面的mixin方法,类关系图如下:

Flutter - 深入理解Flutter应用启动

图解:

  • BindingBase:抽象基类
  • WidgetsBinding:绑定组件树
  • RendererBinding:绑定渲染树
  • SemanticsBinding:绑定语义树
  • PaintingBinding:绑定绘制操作
  • SchedulerBinding:绑定帧绘制回调函数,以及widget生命周期相关事件
  • ServicesBinding:绑定平台服务消息,注册Dart层和C++层的消息传输服务;
  • GestureBinding:绑定手势事件,用于检测应用的各种手势相关操作;

2.2.1 BindingBase初始化

[-> lib/src/foundation/binding.dart]

abstract class BindingBase {
  BindingBase() {
    developer.Timeline.startSync('Framework initialization');

    initInstances();  //初始化状态 [见小节2.3]
    initServiceExtensions(); //扩展调试相关服务

    developer.postEvent('Flutter.FrameworkInitialization', <String, String>{});
    developer.Timeline.finishSync();
  }
} 

该方法的主要工作:

  • 执行initInstances()方法会依次调用到每一下mixin来初始化一些状态;
  • 执行initServiceExtensions方法会依次调用WidgetsBinding,RendererBinding,SchedulerBinding,ServicesBinding,BindingBase这5个类,主要是根据不同的包(release/profile/debug)调用registerServiceExtension()方法来注册各种扩展服务用于debug。

2.3 initInstances

mixin对于with的顺序有关,initInstances首先执行的便是最右边的WidgetsBinding类中的initInstances()方法,由于该方法都包含super.initInstances(),故会依次调用以下这些mixin的initInstances()方法进行初始化,调用栈如下图:

Flutter - 深入理解Flutter应用启动

2.3.1 WidgetsBinding.initInstances

[-> lib/src/widgets/binding.dart]

mixin WidgetsBinding on BindingBase, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
  final BuildOwner _buildOwner = BuildOwner();
  Element _renderViewElement;

  void initInstances() {
    super.initInstances();
    _instance = this;
    buildOwner.onBuildScheduled = _handleBuildScheduled;
    ui.window.onLocaleChanged = handleLocaleChanged;
    ui.window.onAccessibilityFeaturesChanged = handleAccessibilityFeaturesChanged;
    //处理导航的方法回调
    SystemChannels.navigation.setMethodCallHandler(_handleNavigationInvocation);
    //处理系统消息,比如内存紧张
    SystemChannels.system.setMessageHandler(_handleSystemMessage);
  }
} 

WidgetsBinding初始化过程会设置window的如下回调方法:

  • onLocaleChanged
  • onAccessibilityFeaturesChanged

2.3.2 RendererBinding.initInstances

[-> lib/src/rendering/binding.dart]

mixin RendererBinding on BindingBase, ServicesBinding, SchedulerBinding, SemanticsBinding, HitTestable {
  PipelineOwner _pipelineOwner;
  RenderView get renderView => _pipelineOwner.rootNode;

  void initInstances() {
    super.initInstances();
    _instance = this;
    //创建PipelineOwner对象
    _pipelineOwner = PipelineOwner(
      onNeedVisualUpdate: ensureVisualUpdate,
      onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
      onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
    );
    ui.window
      ..onMetricsChanged = handleMetricsChanged
      ..onTextScaleFactorChanged = handleTextScaleFactorChanged
      ..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
      ..onSemanticsAction = _handleSemanticsAction;
    initRenderView();  //初始化RenderView,见下文
    _handleSemanticsEnabledChanged();
    addPersistentFrameCallback(_handlePersistentFrameCallback);
  }
} 

RendererBinding初始化过程,创建PipelineOwner、RenderView对象以及设置window的如下回调方法:

  • onMetricsChanged
  • onTextScaleFactorChanged
  • onSemanticsEnabledChanged
  • onSemanticsAction
void initRenderView() {
  renderView = RenderView(configuration: createViewConfiguration(), window: window);
  renderView.scheduleInitialFrame(); //初始化首帧
}

set renderView(RenderView value) {
  _pipelineOwner.rootNode = value;
} 

创建对象RenderView,并保持到_pipelineOwner.rootNode里面,可通过RendererBinding.renderView获取。

2.3.3 SemanticsBinding

[-> lib/src/semantics/binding.dart]

mixin SemanticsBinding on BindingBase {
  static SemanticsBinding _instance;

  void initInstances() {
    super.initInstances();
    _instance = this;
    _accessibilityFeatures = window.accessibilityFeatures;
  }
} 

2.3.4 PaintingBinding

[-> lib/src/painting/binding.dart]

mixin PaintingBinding on BindingBase, ServicesBinding {
  static ShaderWarmUp shaderWarmUp = const DefaultShaderWarmUp();

  void initInstances() {
    super.initInstances();
    _instance = this;
    _imageCache = createImageCache(); //创建image缓存
    if (shaderWarmUp != null) {
      shaderWarmUp.execute();
    }
  }
} 

该过程会创建image缓存。

2.3.5 SchedulerBinding

[-> lib/src/scheduler/binding.dart]

mixin SchedulerBinding on BindingBase, ServicesBinding {
  void initInstances() {
    super.initInstances();
    _instance = this;
    window.onBeginFrame = _handleBeginFrame;
    window.onDrawFrame = _handleDrawFrame;
    SystemChannels.lifecycle.setMessageHandler(_handleLifecycleMessage);
    readInitialLifecycleStateFromNativeWindow();
  }
} 

SchedulerBinding初始化过程,设置生命周期回调以及设置window的如下回调方法:

  • onMetricsChanged
  • onBeginFrame
  • onDrawFrame

2.3.6 ServicesBinding

[-> lib/src/services/binding.dart]

mixin ServicesBinding on BindingBase {
  void initInstances() {
    super.initInstances();
    _instance = this;
    window
      ..onPlatformMessage = BinaryMessages.handlePlatformMessage;
    initLicenses();
  }
} 

ServicesBinding初始化过程,window的如下回调方法:

  • onPlatformMessage

2.3.7 GestureBinding

[-> lib/src/gestures/binding.dart]

mixin GestureBinding on BindingBase implements HitTestable, HitTestDispatcher, HitTestTarget {
  void initInstances() {
    super.initInstances();  //这里调用到BindingBase类,该类是个空方法
    _instance = this;
    window.onPointerDataPacket = _handlePointerDataPacket;
  } 

GestureBinding初始化过程,window的如下回调方法:

  • onPointerDataPacket

创建完WidgetsBinding,接下来执行其attachRootWidget()方法

2.4 WidgetsBinding.attachRootWidget

[-> lib/src/widgets/binding.dart]

mixin WidgetsBinding on BindingBase, SchedulerBinding, GestureBinding, RendererBinding, SemanticsBinding {
    Element get renderViewElement => _renderViewElement;
    Element _renderViewElement;

    void attachRootWidget(Widget rootWidget) {
      //创建桥接对象[见小节2.4.1]
      _renderViewElement = RenderObjectToWidgetAdapter<RenderBox>(
        container: renderView,
        debugShortDescription: '[root]',
        child: rootWidget
      ).attachToRenderTree(buildOwner, renderViewElement); //[见小节2.5]
    }
} 

该方法所涉及的变量:

  • rootWidget: 是指flutter应用自定义的根Widget,由开发者自定义;
  • renderView: 指由RendererBinding初始化过程创建的RenderView(该类继承于RenderObject),见小节[2.3.2];
  • _renderViewElement: 是由attachToRenderTree()执行后的返回值,类型为Element子类;

可通过WidgetsBinding的renderViewElement来获取创建的RenderObjectToWidgetElement对象。

2.4.1 RenderObjectToWidgetAdapter

[-> lib/src/widgets/binding.dart]

class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {
  final Widget child;
  final RenderObjectWithChildMixin<T> container;
  final String debugShortDescription;   //用于调试:简短描述该widget

  RenderObjectToWidgetAdapter({
    this.child,
    this.container,
    this.debugShortDescription,
  }) : super(key: GlobalObjectKey(container));
} 

RenderObjectToWidgetAdapter是从[RenderObject]到[Element]树的一个桥接对象,该类的核心方法有:

  • createElement:返回的是RenderObjectToWidgetElement类型;
  • createRenderObject:返回的便是这个container,也就是renderView;

2.5 attachToRenderTree

[-> lib/src/widgets/binding.dart ::RenderObjectToWidgetAdapter]

class RenderObjectToWidgetAdapter<T extends RenderObject> extends RenderObjectWidget {

  RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this);

  RenderObjectToWidgetElement<T> attachToRenderTree(BuildOwner owner, [RenderObjectToWidgetElement<T> element]) {
    if (element == null) {
      owner.lockState(() {
        element = createElement(); //创建Element对象,见小节2.5.1
        element.assignOwner(owner); //将owner赋值给element成员_owner
      });
      owner.buildScope(element, () {  //执行build过程,见小节2.5.2
        element.mount(null, null);
    } else {
      element._newWidget = this;
      element.markNeedsBuild();
    }
    return element;
  }

} 

首次调用attachToRenderTree时,element为空则会创建RenderObjectToWidgetElement对象,如果再次调用则会复用已创建的element对象。

2.5.1 createElement

[-> lib/src/widgets/binding.dart ::RenderObjectToWidgetAdapter]

RenderObjectToWidgetElement<T> createElement() => RenderObjectToWidgetElement<T>(this); 

此处this便是RenderObjectToWidgetAdapter对象,该过程会将RenderObjectToWidgetAdapter保存在父类Element的widget成员变量中。

2.5.2 BuildOwner.buildScope

[-> lib/src/widgets/framework.dart]

void buildScope(Element context, [VoidCallback callback]) {
  if (callback == null && _dirtyElements.isEmpty)
    return;
  Timeline.startSync('Build', arguments: timelineWhitelistArguments);
  try {
    if (callback != null) {
      _dirtyElementsNeedsResorting = false;
      callback();  //此处回调为mount() ,见小节2.5.3
    }
    ...
    _dirtyElements.sort(Element._sort);
    int dirtyCount = _dirtyElements.length;
    while (index < dirtyCount) {
        _dirtyElements[index].rebuild(); //针对脏元素执行rebuild操作
        ...
    }
  } finally {
    ...
    Timeline.finishSync();
  }
} 

2.5.3 RenderObjectToWidgetElement.mount

[-> lib/src/widgets/binding.dart]

void mount(Element parent, dynamic newSlot) {
  super.mount(parent, newSlot);  //[见小节2.5.4]
  _rebuild(); //[见小节2.6]
} 

2.5.4 RenderObjectElement.mount

[-> lib/src/widgets/framework.dart]

abstract class RenderObjectElement extends Element {

    void mount(Element parent, dynamic newSlot) {
      super.mount(parent, newSlot);  //[见小节2.5.5]
      _renderObject = widget.createRenderObject(this);
      attachRenderObject(newSlot); //将newSlot依附到RenderObject上
      _dirty = false;
    }
} 

此处的widget是RenderObjectToWidgetAdapter对象,那么_renderObject返回的便是renderView对象;

2.5.5 Element.mount

[-> lib/src/widgets/framework.dart]

abstract class Element extends DiagnosticableTree implements BuildContext {
    void mount(Element parent, dynamic newSlot) {
      _parent = parent;
      _slot = newSlot;
      _depth = _parent != null ? _parent.depth + 1 : 1;
      _active = true;
      if (parent != null)
        _owner = parent.owner;
      if (widget.key is GlobalKey) {
        final GlobalKey key = widget.key;
        key._register(this); //注册并建立globalKey和Element的关联
      }
      _updateInheritance();
    }
} 

在前面小节[2.5.3]在执行完mount操作后,便会开始执行下面的_rebuild()方法。

2.6 RenderObjectToWidgetElement._rebuild

[-> lib/src/widgets/binding.dart]

void _rebuild() {
  try {
    //[见小节2.6.1]
    _child = updateChild(_child, widget.child, _rootChildSlot);
  } catch (exception, stack) {
    ...
  }
} 

2.6.1 Element.updateChild

[-> lib/src/widgets/framework.dart]

Element updateChild(Element child, Widget newWidget, dynamic newSlot) {
  if (newWidget == null) {
    if (child != null)
      deactivateChild(child); //移除旧的子元素
    return null;
  }
  if (child != null) {
    if (child.widget == newWidget) {
      if (child.slot != newSlot)
        updateSlotForChild(child, newSlot); //更新子元素在父级所用的slot
      return child;
    }
    if (Widget.canUpdate(child.widget, newWidget)) {
      if (child.slot != newSlot)
        updateSlotForChild(child, newSlot);
      child.update(newWidget);
      return child;
    }
    deactivateChild(child);
  }
  return inflateWidget(newWidget, newSlot); //为给定的widget创建新Element [见小节2.6.2]
} 

2.6.2 Element.inflateWidget

[-> lib/src/widgets/framework.dart]

Element inflateWidget(Widget newWidget, dynamic newSlot) {
  final Key key = newWidget.key;
  if (key is GlobalKey) {
    //从_inactiveElements中查找是否有可复用的Element
    final Element newChild = _retakeInactiveElement(key, newWidget);
    if (newChild != null) {
      newChild._activateWithParent(this, newSlot);
      //找到可复用Element,则从相应列表移除,并更新子视图
      final Element updatedChild = updateChild(newChild, newWidget, newSlot);
      return updatedChild;
    }
  }
  //否则创建新的Element
  final Element newChild = newWidget.createElement();
  newChild.mount(this, newSlot);
  return newChild;
} 

2.7 SchedulerBinding.scheduleWarmUpFrame

[-> lib/src/scheduler/binding.dart]

void scheduleWarmUpFrame() {
  // _warmUpFrame用于保证不会再没有绘制完成的情况下,再次回调绘制方法
  if (_warmUpFrame || schedulerPhase != SchedulerPhase.idle)
    return;

  _warmUpFrame = true;
  Timeline.startSync('Warm-up frame');
  final bool hadScheduledFrame = _hasScheduledFrame;
  Timer.run(() {
    //使用定时器执行帧准备绘制的方法
    handleBeginFrame(null);
  });
  Timer.run(() {
    //使用定时器执行帧绘制的方法
    handleDrawFrame();
    //这帧结束后重置时间
    resetEpoch();  
    _warmUpFrame = false;
    if (hadScheduledFrame)
      scheduleFrame();
  });

  lockEvents(() async {
    await endOfFrame;
    Timeline.finishSync();
  });
} 

预热帧结束后调用resetEpoch,以便在热重载情况下,下一帧假装在预热帧之后立刻发生。 由于预热帧的时间戳通常会在过去(最后一个真实帧的时间),如果不重置时间,则会出现从预热帧中的旧时间突然跳转到 “真实”框架中的新时间。 最大的问题就是隐式动画最终会在过去被触发,然后跳过每一帧并在新时间结束,从而出现帧跳跃的情况。

另外关于handleBeginFrame和handleDrawFrame的渲染绘制过程,见文章Flutter渲染机制—UI线程中的第四Framework层处理流程部分。

三、小结

runApp(MyApp)是flutter应用开始真正执行业务逻辑代码的起点,整个过程主要工作:

  • WidgetsFlutterBinding初始化:这是一个单例模式,负责创建WidgetsFlutterBinding对象,WidgetsFlutterBinding继承抽象类BindingBase,并且附带7个mixin,初始化渲染、语义化、绘制、平台消息以及手势等一系列操作;
  • attachRootWidget:遍历挂载整个视图树,并建立Widget、Element、RenderObject之间的连接与关系,此处Element的具体类型为RenderObjectToWidgetElement;
  • scheduleWarmUpFrame:调度预热帧,执行帧绘制方法handleBeginFrame和handleDrawFrame。

Widget/Element/RenderObject类图

Flutter - 深入理解Flutter应用启动

从WidgetsFlutterBinding是单例模式,从小节[2.4]得WidgetsBinding的renderViewElement记录着唯一的RenderObjectToWidgetElement对象,从小节[2.3.2]可知RendererBinding的renderView记录着唯一的RenderView对象;也就是说每个flutter应用创建的Root Widget跟Element、RenderObject一一对应,且单例唯一。

MyApp是用户定义的根Widget,为了建立三棵树的关系,RenderObjectToWidgetAdapter起到重要的桥接功能,该类的createElement方法创建RenderObjectToWidgetElement对象,createRenderObject()方法获取的是RenderView。


微信公众号 Gityuan | 微博 weibo.com/gityuan | 博客 留言区交流


本文转自 http://gityuan.com/2019/06/29/flutter_run_app/,如有侵权,请联系删除。

收藏
评论区

相关推荐

带你全面了解 Flutter,它好在哪里?它的坑在哪里? 应该怎么学?
回顾了这段时间解答关于 Flutter 的各种问题后,我突然发现很多刚刚接触 Flutter 的萌新,对于 Flutter 都有着不同程度的误解,而每次重复的解释又十分浪费时间,最终我还是决定写篇文章来做个总结。 内容有点长,但是相信能帮你更好地去认识 Flutter 。 Flutter 的起源 Flutter 的诞生其实比较有意思,Flutter
可以用Flutter愉快的开发web 了
简单的试了一下,完全用flutter现有的widget进行开发。github上面有说现在是preview版本,有些widget还不能用。但是最终是会支持整个的flutter现有的UI的。 跟用flutter开发原生app一样。flutter_web还有很长的一段路要走。希望年底能出个像样的版本。 不支持第三方库说明 目前pub.da
Flutter 2.0 下混合开发浅析
多余的前言 Flutter 2.0 发布时,其中最受大家关注之一的内容就是 AddtoApp 相关的更新,因为除了热更新之外,Flutter 最受大家诟病的就是混合开发体验不好。 为什么不好呢?因为 Flutter 的控件渲染直接脱离了原生平台,也就是无论页面堆栈和渲染树都独立于平台运行,这固然给 Flutter 带来了较好的跨平台体验
【Flutter实战】初识Flutter
1.2 初识Flutter 1.2.1 Flutter简介Flutter 是 Google推出并开源的移动应用开发框架,主打跨平台、高保真、高性能。开发者可以通过 Dart语言开发 App,一套代码同时运行在 iOS 和 Android平台。 Flutter提供了丰富的组件、接口,开发者可以很快地为 Flutter添加 native扩展。同时 Flu
【Flutter 实战】安装Flutter
1.3 搭建Flutter开发环境工欲善其事必先利其器,本节首先会分别介绍一下在Windows和macOS下Flutter SDK的安装,然后再介绍一下配IDE和模拟器的使用。 1.3.1 安装Flutter由于Flutter会同时构建Android和IOS两个平台的发布包,所以Flutter同时依赖Android SDK和iOS SDK,在安装Fl
【Flutter实战】调试Flutter应用
2.5 调试Flutter应用有各种各样的工具和功能来帮助调试Flutter应用程序。 Dart 分析器在运行应用程序前,请运行flutter analyze测试你的代码。这个工具是一个静态代码检查工具,它是dartanalyzer工具的一个包装,主要用于分析代码并帮助开发者发现可能的错误,比如,Dart分析器大量使用了代码中的类型注释来帮
【Flutter实战】调试Flutter应用
2.5 调试Flutter应用有各种各样的工具和功能来帮助调试Flutter应用程序。 Dart 分析器在运行应用程序前,请运行flutter analyze测试你的代码。这个工具是一个静态代码检查工具,它是dartanalyzer工具的一个包装,主要用于分析代码并帮助开发者发现可能的错误,比如,Dart分析器大量使用了代码中的类型注释来帮
【Flutter实战】Widget简介
3.1 Widget简介 3.1.1 概念在前面的介绍中,我们知道在Flutter中几乎所有的对象都是一个Widget。与原生开发中“控件”不同的是,Flutter中的Widget的概念更广泛,它不仅可以表示UI元素,也可以表示一些功能性的组件如:用于手势检测的 GestureDetector widget、用于APP主题数据传递的Theme
解读 Flutter 全平台开发的误解与偏见
Flutter 2.0 发布之后,Web 版本进入了 stable 分支,Desktop 版本也可以在 stable 分支通过 snapshot beta 镜像开始预览使用,导致最近关于 “Flutter 制霸全平台” 之类的话题又被炒作起来,所以本篇主要对这个话题做一次解答。1、Flutter 制霸全平台谷歌官方在 F
【Flutter实战】布局类组件简介
4.1 布局类组件简介布局类组件都会包含一个或多个子组件,不同的布局类组件对子组件排版(layout)方式不同。我们在前面说过Element树才是最终的绘制树,Element树是通过Widget树来创建的(通过Widget.createElement()),Widget其实就是Element的配置数据。在Flutter中,根据Widget是否
Flutter - 深入理解setState更新机制
基于Flutter 1.5的源码剖析, 分析flutter的StatefulWidget的UI更新机制,相关源码:widgets/framework.dartwidgets/binding.dartscheduler/binding.dartlib/ui/window.dartflutter/runtime/runtime_controller.c
Flutter - 深入理解Flutter应用启动
基于Flutter 1.5,从源码视角来深入剖析flutter应用的启动流程,相关源码目录见文末附录一、概述上一篇文章 已经介绍了FlutterApplication和FlutterActivity的onCreate()方法执行过程, 并触发Flutter引擎的启动,并最终执行到runApp(Widget app)方法,这才刚刚开始执行dart的
Flutter - 深入理解Flutter引擎启动
基于Flutter 1.5,从源码视角来深入剖析flutter引擎的启动流程,相关源码目录见文末附录一、Flutter引擎启动工作 1.1 Flutter启动概览Flutter作为一款跨平台的框架,可以运行在Android、iOS等平台,Android为例讲解如何从Android应用启动流程中衔接到Flutter框架,
Flutter - Flutter渲染机制—GPU线程
基于Flutter 1.5,从源码视角来深入剖析flutter渲染机制,相关源码目录见文末附录一、GPU线程渲染看Flutter的渲染绘制过程的核心过程包括在ui线程和gpu线程,上一篇文章已经详细介绍了UI线程的工作原理, 本文则介绍GPU线程的工作原理,这里需要注意的是,gpu线程是指运行着GPU Task Runner的名叫gpu
字节跳动为什么选用Flutter:并非跨平台终极之选,但它可能是不一样的未来
2018 年 12 月 ,Google 宣布 Flutter 1.0 版本正式发布。截至目前, Flutter 在 Github 上已获得 88000+ 的关注和 11000+ 的 Fork ,其发展速度相当惊人,是今年移动端最火热的开发框架之一。Flutter 大火背后的原因是什么?为什么越来越多的企业和开发者会选择使用 Flutter?Flutter 会