在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://github.com/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负责开发和维护,在2011年10启动项目,2017年9月发布第一个2.0-dev版本。

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

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

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

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

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

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

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

深入解析美团外卖Flutter 架构演进之路(上篇)

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

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

深入解析美团外卖Flutter 架构演进之路(上篇)

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

Flutter Framework

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

分层架构

深入解析美团外卖Flutter 架构演进之路(上篇)

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框架中完成,合成则交由引擎负责:

深入解析美团外卖Flutter 架构演进之路(上篇)

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

深入解析美团外卖Flutter 架构演进之路(上篇)

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

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

深入解析美团外卖Flutter 架构演进之路(上篇)

布局完成后,渲染对象树中的每个节点都有了明确的尺寸和位置,Flutter会把所有对象绘制到不同的图层上:

深入解析美团外卖Flutter 架构演进之路(上篇)

因为绘制节点时也是深度遍历,可以看到第二个节点在绘制它的背景和前景不得不绘制在不同的图层上,因为第四个节点切换了图层(因为“4”节点是一个需要独占一个图层的内容,比如视频),而第六个节点也一起绘制到了红色图层。这样会导致第二个节点的前景(也就是“5”)部分需要重绘时,和它在逻辑上毫不相干但是处于同一图层的第六个节点也必须重绘。为了避免这种情况,Flutter提供了另外一个“重回边界”的概念:

深入解析美团外卖Flutter 架构演进之路(上篇)

在进入和走出重绘边界时,Flutter会强制切换新的图层,这样就可以避免边界内外的互相影响。典型的应用场景就是ScrollView,当滚动内容重绘时,一般情况下其他内容是不需要重绘的。虽然重绘边界可以在任何节点手动设置,但是一般不需要我们来实现,Flutter提供的控件默认会在需要设置的地方自动设置。

控件库(Widgets)

Flutter的控件库提供了非常丰富的控件,包括最基本的文本、图片、容器、输入框和动画等等。在Flutter中“一切皆是控件”,通过组合、嵌套不同类型的控件,就可以构建出任意功能、任意复杂度的界面。它包含的最主要的几个类有:

class WidgetsFlutterBinding extends BindingBase with GestureBinding, ServicesBinding, SchedulerBinding, PaintingBinding, RendererBinding, WidgetsBinding { … }abstract class Widget extends DiagnosticableTree { … }abstract class StatelessWidget extends Widget { … }abstract class StatefulWidget extends Widget { … }abstract class RenderObjectWidget extends Widget { … }abstract class Element extends DiagnosticableTree implements BuildContext { … }class StatelessElement extends ComponentElement { … }class StatefulElement extends ComponentElement { … }abstract class RenderObjectElement extends Element { … }…

基于Flutter控件系统开发的程序都需要使用WidgetsFlutterBinding,它是Flutter的控件框架和Flutter引擎的胶水层。Widget就是所有控件的基类,它本身所有的属性都是只读的。RenderObjectWidget所有的实现类则负责提供配置信息并创建具体的RenderObjectElement。Element是Flutter用来分离控件树和真正的渲染对象的中间层,控件用来描述对应的element属性,控件重建后可能会复用同一个element。RenderObjectElement持有真正负责布局、绘制和碰撞测试(hit test)的RenderObject对象。

StatelessWidget和StatefulWidget并不会直接影响RenderObject创建,只负责创建对应的RenderObjectWidgetStatelessElement和StatefulElement也是类似的功能。

它们之间的关系如下图:

深入解析美团外卖Flutter 架构演进之路(上篇)

如果控件的属性发生了变化(因为控件的属性是只读的,所以变化也就意味着重新创建了新的控件树),但是其树上每个节点的类型没有变化时,element树和render树可以完全重用原来的对象(因为element和render object的属性都是可变的):

深入解析美团外卖Flutter 架构演进之路(上篇)

但是,如果控件树中某个节点的类型发生了变化,则element树和render树中的对应节点也需要重新创建:

深入解析美团外卖Flutter 架构演进之路(上篇)

下期文章我将继续为大家解析美团Flutter技术项目实践以及未来 App 最终组成形态。敬请期待!

Android核心知识点

面试成功其实是必然的,因为我做足了充分的准备工作,包括刷题啊,看一些Android核心的知识点,看一些面试的博客吸取大家面试的一些经验。

下面这份PDF是我翻阅了差不多3个月左右一些Android大博主的博客从他们那里取其精华去其糟泊所整理出来的一些Android的核心知识点,全部都是精华中的精华,我能面试到现在2-2资深开发人员跟我整理的这本Android核心知识点有密不可分的关系,在这里本着共赢的心态分享给各位朋友。

不管是Android基础还是Java基础以及常见的数据结构,这些是无原则地必须要熟练掌握的,尤其是非计算机专业的同学,面试官一上来肯定是问你基础,要是基础表现不好很容易被扣上基础不扎实的帽子,常见的就那些,只要你平时认真思考过基本上面试是没太大问题的。

最后为了帮助大家深刻理解Android相关知识点的原理以及面试相关知识,这里放上我搜集整理的2019-2021BAT 面试真题解析,我把大厂面试中常被问到的技术点整理成了PDF,包知识脉络 + 诸多细节。

节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!
成了PDF,包知识脉络 + 诸多细节。

节省大家在网上搜索资料的时间来学习,也可以分享给身边好友一起学习。
《Android学习笔记总结+移动架构视频+大厂面试真题+项目实战源码》点击传送门,即可获取!

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐