1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > 深入解析美团外卖Flutter-架构演进之路(上篇) Android研发必问高级面试题

深入解析美团外卖Flutter-架构演进之路(上篇) Android研发必问高级面试题

时间:2019-02-26 20:17:12

相关推荐

深入解析美团外卖Flutter-架构演进之路(上篇) Android研发必问高级面试题

全局变量和静态成员变量,这些变量

不会在热刷新时更新。修改了main函数中创建的根控件节点,Flutter在热刷新后只会根据原来的根节点重新创建控件树,不会修改根节点。某个类从普通类型转换成枚举类型,或者类型的泛型参数列表变化,都会使人刷新失败。

热刷新无法实现更新时,执行一次热重启(Hot Restart)就可以全量更新所有代码,同样不需要重启App,区别是restart会将所有Dart代码打包同步到设备上,并且所有状态都会重置。

Flutter插件

Flutter使用的Dart语言无法直接调用Android系统提供的Java接口,这时就需要使用插件来实现中转。Flutter官方提供了丰富的原生接口封装:

android_alarm_manager,访问Android系统的AlertManager。android_intent,构造Android的Intent对象。battery,获取和监听系统电量变化。connectivity,获取和监听系统网络连接状态。device info,获取设备型号等信息。image_picker,从设备中选取或者拍摄照片。package_info,获取App安装包的版本等信息。path_provider,获取常用文件路径。quick_actions,App图标添加快捷方式,iOS的eponymous concept和Android的App Shortcuts。sensors,访问设备的加速度和陀螺仪传感器。shared_preferences,App KV存储功能。url_launcher,启动URL,包括打电话、发短信和浏览网页等功能。video_player,播放视频文件或者网络流的控件。

在Flutter中,依赖包由Pub仓库管理,项目依赖配置在pubspec.yaml文件中声明即可(类似于NPM的版本声明Pub Versioning Philosophy),对于未发布在Pub仓库的插件可以使用git仓库地址或文件路径:

dependencies: url_launcher: “>=0.1.2 <0.2.0” collection: “^0.1.2” plugin1: git: url: “git:///flutter/plugin1.git” plugin2: path: …/plugin2/

以shared_preferences为例,在pubspec中添加代码:

dependencies: flutter: sdk: flutter shared_preferences: “^0.4.1”

脱字号“^”开头的版本表示和当前版本接口保持兼容的最新版,^1.2.3 等效于 >=1.2.3 <2.0.0而 ^0.1.2 等效于 >=0.1.2 <0.2.0,添加依赖后点击“Packages get”按钮即可下载插件到本地,在代码中添加import语句就可以使用插件提供的接口:

import ‘package:shared_preferences/shared_preferences.Dart’;class _MyAppState extends State { int _count = 0; static const String COUNTER_KEY = ‘counter’; _MyAppState() { init(); } init() async { var pref = await SharedPreferences.getInstance(); _count = pref.getInt(COUNTER_KEY) ?? 0; setState(() {}); } increaseCounter() async { SharedPreferences pref = await SharedPreferences.getInstance(); pref.setInt(COUNTER_KEY, ++_count); setState(() {}); }…

Dart

Dart是一种强类型、跨平台的客户端开发语言。具有专门为客户端优化、高生产力、快速高效、可移植(兼容ARM/x86)、易学的OO编程风格和原生支持响应式编程(Stream & Future)等优秀特性。Dart主要由Google负责开发和维护,在10启动项目,9月发布第一个2.0-dev版本。

Dart本身提供了三种运行方式:

使用Dart2js编译成JavaScript代码,运行在常规浏览器中(Dart Web)。使用DartVM直接在命令行中运行Dart代码(DartVM)。AOT方式编译成机器码,例如Flutter App框架(Flutter)。

Flutter在筛选了20多种语言后,最终选择Dart作为开发语言主要有几个原因:

健全的类型系统,同时支持静态类型检查和运行时类型检查。代码体积优化(Tree Shaking),编译时只保留运行时需要调用的代码(不允许反射这样的隐式引用),所以庞大的Widgets库不会造成发布体积过大。丰富的底层库,Dart自身提供了非常多的库。多生代无锁垃圾回收器,专门为UI框架中常见的大量Widgets对象创建和销毁优化。跨平台,iOS和Android共用一套代码。JIT & AOT运行模式,支持开发时的快速迭代和正式发布后最大程度发挥硬件性能。

在Dart中,有一些重要的基本概念需要了解:

所有变量的值都是对象,也就是类的实例。甚至数字、函数和null也都是对象,都继承自Object类。虽然Dart是强类型语言,但是显式变量类型声明是可选的,Dart支持类型推断。如果不想使用类型推断,可以用dynamic类型。Dart支持泛型,List表示包含int类型的列表,List则表示包含任意类型的列表。Dart支持顶层(top-level)函数和类成员函数,也支持嵌套函数和本地函数。Dart支持顶层变量和类成员变量。Dart没有public、protected和private这些关键字,使用下划线“_”开头的变量或者函数,表示只在库内可见。参考库和可见性。

DartVM的内存分配策略非常简单,创建对象时只需要在现有堆上移动指针,内存增长始终是线形的,省去了查找可用内存段的过程:

Dart中类似线程的概念叫做Isolate,每个Isolate之间是无法共享内存的,所以这种分配策略可以让Dart实现无锁的快速分配。

Dart的垃圾回收也采用了多生代算法,新生代在回收内存时采用了“半空间”算法,触发垃圾回收时Dart会将当前半空间中的“活跃”对象拷贝到备用空间,然后整体释放当前空间的所有内存:

整个过程中Dart只需要操作少量的“活跃”对象,大量的没有引用的“死亡”对象则被忽略,这种算法也非常适合Flutter框架中大量Widget重建的场景。

Flutter Framework

Flutter的框架部分完全使用Dart语言实现,并且有着清晰的分层架构。分层架构使得我们可以在调用Flutter提供的便捷开发功能(预定义的一套高质量Material控件)之外,还可以直接调用甚至修改每一层实现(因为整个框架都属于“用户空间”的代码),这给我们提供了最大程度的自定义能力。Framework底层是Flutter引擎,引擎主要负责图形绘制(Skia)、文字排版(libtxt)和提供Dart运行时,引擎全部使用C++实现,Framework层使我们可以用Dart语言调用引擎的强大能力。

分层架构

Framework的最底层叫做Foundation,其中定义的大都是非常基础的、提供给其他所有层使用的工具类和方法。绘制库(Painting)封装了Flutter Engine提供的绘制接口,主要是为了在绘制控件等固定样式的图形时提供更直观、更方便的接口,比如绘制缩放后的位图、绘制文本、插值生成阴影以及在盒子周围绘制边框等等。

Animation是动画相关的类,提供了类似Android系统的ValueAnimator的功能,并且提供了丰富的内置插值器。Gesture提供了手势识别相关的功能,包括触摸事件类定义和多种内置的手势识别器。GestureBinding类是Flutter中处理手势的抽象服务类,继承自BindingBase类。

Binding系列的类在Flutter中充当着类似于Android中的SystemService系列(ActivityManager、PackageManager)功能,每个Binding类都提供一个服务的单例对象,App最顶层的Binding会包含所有相关的Bingding抽象类。如果使用Flutter提供的控件进行开发,则需要使用WidgetsFlutterBinding,如果不使用Flutter提供的任何控件,而直接调用Render层,则需要使用RenderingFlutterBinding。

Flutter本身支持Android和iOS两个平台,除了性能和开发语言上的“native”化之外,它还提供了两套设计语言的控件实现Material & Cupertino,可以帮助App更好地在不同平台上提供原生的用户体验。

渲染库(Rendering)

Flutter的控件树在实际显示时会转换成对应的渲染对象(RenderObject)树来实现布局和绘制操作。一般情况下,我们只会在调试布局,或者需要使用自定义控件来实现某些特殊效果的时候,才需要考虑渲染对象树的细节。渲染库主要提供的功能类有:

abstract class RendererBinding extends BindingBase with ServicesBinding, SchedulerBinding, HitTestable { … }abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin implements HitTestTarget {abstract class RenderBox extends RenderObject { … }class RenderParagraph extends RenderBox { … }class RenderImage extends RenderBox { … }class RenderFlex extends RenderBox with ContainerRenderObjectMixin<RenderBox, FlexParentData>, RenderBoxContainerDefaultsMixin<RenderBox, FlexParentData>, DebugOverflowIndicatorMixin { … }

RendererBinding是渲染树和Flutter引擎的胶水层,负责管理帧重回、窗口尺寸和渲染相关参数变化的监听。RenderObject渲染树中所有节点的基类,定义了布局、绘制和合成相关的接口。RenderBox和其三个常用的子类RenderParagraph、RenderImage、RenderFlex则是具体布局和绘制逻辑的实现类。

在Flutter界面渲染过程分为三个阶段:布局、绘制、合成,布局和绘制在Flutter框架中完成,合成则交由引擎负责:

控件树中的每个控件通过实现RenderObjectWidget#createRenderObject(BuildContext context) → RenderObject方法来创建对应的不同类型的RenderObject对象,组成渲染对象树。因为Flutter极大地简化了布局的逻辑,所以整个布局过程中只需要深度遍历一次:

渲染对象树中的每个对象都会在布局过程中接受父对象的Constraints参数,决定自己的大小,然后父对象就可以按照自己的逻辑决定各个子对象的位置,完成布局过程。

子对象不存储自己在容器中的位置,所以在它的位置发生改变时并不需要重新布局或者绘制。子对象的位置信息存储在它自己的parentData字段中,但是该字段由它的父对象负责维护,自身并不关心该字段的内容。同时也因为这种简单的布局逻辑,Flutter可以在某些节点设置布局边界(Relayout boundary),即当边界内的任何对象发生重新布局时,不会影响边界外的对象,反之亦然:

但是该字段由它的父对象负责维护,自身并不关心该字段的内容。同时也因为这种简单的布局逻辑,Flutter可以在某些节点设置布局边界(Relayout boundary),即当边界内的任何对象发生重新布局时,不会影响边界外的对象,反之亦然:

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。