Flutter framework源码学习(一)

2021-09-19

Flutter framework源码学习(一)

以下内容只是我在团队技术分享里的keynote的摘录,实际上在分享会上会补充很多细节,这里就略过了。

Flutter架构

Flutter主要分为engine层,framework层和embeder层,engine层主要用c++写的,framework层主要是Dart语言写的,embedder层主要是平台相关的native语言写的。这里framework的源码最容易获得,也最方便容易学习。当你安装好Flutter环境之后,在Android studio打开任意Flutter项目,就可以从你的项目代码跳转到framework代码进行研究,这边跟Android是差不都的。

flutter架构

Flutter framework 源码目录说明

  • 基于stable分支,Flutter 2.2.3版本,Dart 2.12.3
  • flutter/packages/flutter/lib目录下:522个文件,378733行代码(大量注释)
  • 重要源码目录说明
目录 说明
flutter/packages/flutter/lib framework源码
flutter/example/layers/raw raw目录下的例子有助于理解framework的底层layer
flutter/bin/cache/artifacts/engine 编译后的engine二进制文件
flutter/bin/cache/dart-sdk dark sdk源码
flutter/bin/cache/pkg/sky_engine/lib framework底层源码

源码特点

  • 大量使用断言:普通的断言语句和断言代码块
  • 大量使用mixin来复用代码
  • 类属性使用getter和setter访问器
  • 异常捕获:try-catch-finally 增强程序健壮性
  • 用native关键字来定义在c++实现的方法
  • @pragma(‘vm:entry-point')表明类或属性在c++中使用
  • 特别的语法:级联操作符 .. ,is! (当对象不是相应类型时返回 true)
  • 特别表达式:result = expr1 ?? expr2(若expr1为null, 返回expr2的值,否则返回expr1的值);expr1 ??= expr2(若expr1为null, 则把expr2的值赋值给expr1)

framework启动流程

  • 默认找main.dart下的main方法
  • 启动流程:main—>runApp->WidgetsFlutterBinding.ensureInitialized—>BindingBase构造函数—>XXBinding.initInstances

关键代码

oid runApp(Widget app) {
  WidgetsFlutterBinding.ensureInitialized()
    ..scheduleAttachRootWidget(app)
    ..scheduleWarmUpFrame();
}
static WidgetsBinding ensureInitialized() {
    if (WidgetsBinding.instance == null)
      WidgetsFlutterBinding();
    return WidgetsBinding.instance!;
  }
BindingBase() {
     ....
    initInstances();
    .....
  }

mixin: 代码复用的利器

  • 用mixin关键字定义,用on实现mixin的继承机制(类似),用with来使用mixin 一个类A使用了一个mixin B,可以简单理解为 A继承于B
  • Class C extends B with A, 如果A,B,C中有同名方法,C的不会被覆盖。如果只有A, B有同名方法,A会覆盖B的方法。本类最优先,mixin比父类优先
  • 如果一个mixin A继承了(on)多个其他的mixin,那么一个类在使用(with)A时,必须也要使用其他的mixin。mixin使用通过super调用父mixin的方法。
  • 一个类使用(with)了多个mixin,那么出现同名冲突时最右的优先级最高(可以认为从右往左单向继承,越往左父层级越高)
  • mixin本质是一个没有构造函数的类。不能直接实例化,专门用来代码复用,实现多继承的机制,但是没有c++多继承那么复杂。
  • dart run test_main.dart 可直接运行dart源码,这很方便写测试代码

把上面5点都理解了,就理解了mixin的本质。我为了理解BindingXXX的启动流程,自己写了个类似的测试代码:test_mixin

initInstances方法调用链

initInstances方法调用链: WidgetsBinding—>RendererBinding—>SemanticsBinding—>PaintingBinding—>ServicesBinding—>SchedulerBinding—>GestureBinding—>BindingBase

Binding类 说明
GestureBinding 提供了 window.onPointerDataPacket 回调,绑定 Framework 手势子系统,是 Framework 事件模型与底层事件的绑定入口
ServicesBinding 提供了 window.onPlatformMessage 回调, 用于绑定平台消息通道(message channel),主要处理原生和 Flutter 通信
SchedulerBinding 提供了 window.onBeginFrame 和 window.onDrawFrame 回调,监听刷新事件,绑定 Framework 绘制调度子系统
PaintingBinding 绑定绘制库,主要用于处理图片缓存
SemanticsBinding 语义化层与 Flutter engine 的桥梁,主要是辅助功能的底层支持
RendererBinding 提供了 window.onMetricsChanged 、window.onTextScaleFactorChanged 等回调。持有PipelineOwner和RenderView(Root RenderObject), 它是渲染树与 Flutter engine 的桥梁
WidgetsBinding 提供了 window.onLocaleChanged、onBuildScheduled 等回调。持有BuildOwner和Root Element, 它是 Flutter widget 层与 engine 的桥梁
BindingBase 是其他Binding的基类,主要定义initInstances方法和公有属性window

三个树的创建

  • 通过attachRootWidget方法先后创建了RenderObjectToWidgetAdapter和RenderObjectToWidgetElement
  • RenderObjectToWidgetAdapter是root widget,它的child是APP的顶层widget。RenderObjectToWidgetAdapter的key是GlobalObjectKey
  • RenderObjectToWidgetElement是root element, 持有root widget和root RenderObject。它被创建之后调用了mount方法。
  • RenderView是 root RenderObject,由RenderBinding创建,它持有window
  • WidgetBinding持有root element,而root element持有root widget和render object。

window:Framework和Engine的桥梁

  • ui.window是SingletonFlutterWindow,是Framework和Engine的桥梁,封装了原生跟flutter dart之间的各种回调。最后的UI渲染就是通过它传给engine的
  • UI渲染数据是通过render方法传给engine层的
  • window持有PlatformDispatcher,几乎所有的回调都是通过PlatformDispatcher来跟平台交互的
  • onBeginFrame和onDrawFrame由engine主动调用,这两个callback都在SchedulerBinding里设置了。onBeginFrame最终会调用到一些动画相关的东西,onDrawFrame会调用到drawFrame,并将渲染数据通过window的render传给engine
  • scheduleFrame:由flutter层调用,告知engine适时(VSync信号到来)回调onBeginFrame和onDrawFrame
  • scheduleFrame和onDrawFrame配合就可以做到不断的更新界面

window这个类很关键,不同的framework版本还不太一样,但是渲染更新的接口都在这。

VSync信号

不管是手机还是电脑,为了更新显示画面,显示器是以固定的频率刷新的。当一帧图像绘制完毕后准备绘制下一帧时,显示器会发出一个垂直同步信号(VSync) 下面两种图可以帮助理解VSync信号原理 VSync信号 VSync信号和渲染过程

Flutter帧渲染的整体流程

通过Widget Tree和Element Tree生成绘制树RenderObject Tree。RenderObject Tree会再进行Layout布局和Paint渲染等步骤生成Layer Tree,组装成scene,通过window传给engine层。在Flutter Engine层中,主要对Layer Tree进行光栅化、合成和上屏的操作。flutter中的layer,scene实际上只是c++层中的layer,scene的封装.

渲染过程

扩展:直接使用layer和window渲染界面

  • 直接绕过三个树,采用底层layer来画控件(又回到命令式编程),连XXBinding的初始化都省了
  • flutter/example/layers/raw的例子

raw目录下的例子真的挺有意思,一定要去看看,有意想不到的收获。

参考资料


如果你觉得这篇文章有用,请打赏小钱喝杯咖啡^_^ 打赏

Category: 技术 Tagged: Flutter framework

comments


HashMap与红黑树学习总结

2021-06-11

HashMap与红黑树学习总结

HashMap解读(JDK1.8)

带着问题去看代码:

  • 里面是怎么存储数据的(使用到的数据结构)?
  • 怎么计算哈希值,怎么解决哈希冲突?
  • 初始化容量是多少?不断加入数据时,如何进行扩容? 扩容后数据的存储位置是怎么样的
  • 查找数据的时间复杂度
  • 为什么要用红黑树?这里的红黑树实现有什么特点

HashMap特性

  • hash算法效率好,高低位异或
  • 数组⻓度是2的n次幂,采用&运算来代替模运算
  • 采用modCount来实现failFast
  • 为LinkHashMap预留方法实现
  • 效率高,用链接来解决哈希冲突。插入数据时链接过长转为红黑树,删除数据时红黑树高度变低了转化为链表。
  • 数组扩容时链表会一分为二,红黑树也一样,红黑树甚至会转化为链表
  • 线程不安全。
  • 源代码里语句很简洁,经常一行代码包括N多赋值与判断,变量命名过 于简单

Hashtable是线程安全的,它是经典的哈希表实现(数组+ 链表),没有红黑树。同样是哈希表,HashMap跟Hashtable的实现天差地别,可以看出HashMap追求极致的性能,而Hashtable是线程安全的,有加锁操作,性能不会好,所以就采用了最简单的实现

红黑树 ...

Category: 技术 Tagged: HashMap 红黑树

comments

Read More

Flutter APP加入信鸽推送通知方案

2021-02-03

Flutter APP加入信鸽推送通知方案

最近我们团队又采用Flutter开发一款新APP。而且最近由于信鸽(现在又叫腾讯云移动推送)修改按量收费,我们决定在新APP里加入信鸽SDK来做推送通知功能。

我们之前主要主APP里已经使用了信鸽SDK来做推送通知功能,但这次是第一次在Flutter开发的APP上加入推送通知功能。当我们完全加入该功能之后,我觉得可以将这个技术方案分享一下。

其实加入信鸽SDK的方式跟原生APP是一样的,只是在处理推送通知页面跳转方式不一样而已。

先说说信鸽SDK的优势和注意事项。信鸽SDK以前是免费的,在去年才改成收费的,并入了腾讯云。所以他们才改名叫腾讯云移动推送。信鸽SDK还算比较稳定,毕竟是大厂开发的。收费后新增了很多统计功能,有一个重要的数据是通知开启率,还有就是通知点击率,还是很有用的数据。改成收费的的新版SDK有个问题,会跟我们的采用的iOS版的growingIO统计SDK冲突,导致growingIO活跃用户丢失。最后跟growingIO开发人员反馈后,修改growingIO SDK采用的统计方式才正常。

安卓版的信鸽SDK集成了几大厂商的通道,比较容易集成到自己项目里,这也是他的优势。然后为了推送通知能尽量在两端保持一致,这里要专门提一下推送配置。

推送配置

我觉得这一点对于推送通知很重要,尽量将两端的配置保持一致,服务器或者运营编辑在推送一条消息的时候,可以较简单的完成,不容易出错。在安卓端,我们采用客户端自定义方式,即自定义intent-filter里的data字段里的scheme,host,path字段。这里scheme,host一般是固定的,每个页面的path不同。而iOS里采用开启附加参数方式 ...

Category: 技术 Tagged: Flutter 推送通知 信鸽

comments

Read More

移动端文档边缘检测AI方案

2020-11-27

移动端文档边缘检测AI方案

需求

先说一下我们的需求: 我们需要用户拿手机扫描自己的体检单,然后我们识别体检单的内容,结构化数据后存起来用。 这里面解决方案一般都是手机客户端拍一张照片上传到服务器识别。而这张照片的好处直接影响了服务器的识别准确率。如果照片里有掺杂这别的内容就更不好了,最好照片里只有文档内容。这里就使用到了边缘检测技术。

传统的边缘检测技术

在传统的边缘检测方案中,大部分都是采用OpenCV里的边缘检测算法。OpenCV库在图像处理和识别方面真是鼎鼎大名,运用十分广泛。OpenCV库里的用到的叫cany和findContours算法,而且cany还有好几个版本。但是这个算法也不完美,很容易识别错,因为现实的场景也很复杂。

HED算法方案

现在哪里都流行用AI算法来优化。我在网上找到了fengjian大神的文章。原来我很早的时候就看到他写的这篇文章。然后就按照他的方案来开干。我运行了他的demo,大致能得到满意的效果。但是他的文章很旧了,用到的TensorFlow还是很老版本,而且只开源了iOS版本。

经过我们的摸索,还有参考了别的一些demo,我终于把它移植到了TensorFlowLite,而且我还做了安卓的版本。这样两端的解决方案一致,可以运用到正式项目中。

当然,我们自己也做了很多优化,还有自己重新收集了一些我们场景的照片来训练新的模型。最终达到了不错的效果。

我把最初移植做的demo放到github上,给有需要的人参考。这里最大优势就是安卓和iOS的方案都有。

安卓版本

iOS版本


如果你觉得这篇文章有用,请打赏小钱喝杯咖啡 ...

Category: 技术 Tagged: 边缘检测 HED OpenCV TensorFlowLite

comments

Read More

APP推送和APP换起技术方案探讨

2020-08-04

APP推送和APP换起技术方案探讨

目标:

尽量将APP两端设计成统一的方案,减少服务器适配工作量,最好可扩展,能复用。

方案说明:

我们知道APP推送目的是将对用户有用的信息,主动推给APP,作为通知信息展示,用户通过点击通知进入APP相关页面。

这里只要涉及服务器端和APP端两个技术点:服务器推送什么样的信息过来,APP这边如何解析接收到的信息,打开相应的界面和解析数据。

服务器只有一个,所以尽量让服务器发送给两端的数据较为一致,这需要一开始就应该想好整个系统的设计方案。

安卓和iOS是两个完全不同的平台,相关技术栈也不一样。不过基本都要依赖厂商的通讯通道来个APP发通知:苹果有自己的APN通道,Google也有自己的通道,当然在中国,各个厂商有自己的通道。有很多第三方SDK都整合多家厂商的通道。我们使用了信鸽(腾讯云移动推送)的SDK。之所以选他家,主要是他们集成了主流的厂商,文档也还可以(以前他们有免费版)。

为了能拉起APP不同的界面,我们需要定义一套规则。iOS的方案比较固定,只能通过json参数,这是它系统框架决定的。而安卓就不太一样,不同的厂商支持不同的方式,APP存活状态和未启动状态也不太一样。不过有一个方案是所有情形都适用的:就是通过定义Activity的scheme方式。

scheme方式是一种URI,我们经常用的URL地址也是一种URI。URI在iOS或者安卓都经常使用来拉起页面。例如我们的APP主页可以定义URI为: wegene://com.wegene ...

Category: 技术 Tagged: 推送 唤起 拉起

comments

Read More

《深度学习入门》读后感

2020-05-05

《深度学习入门:基于Python的理论与实现》读后感

最近因为做一个功能要用到深度学习相关知识和TensorFlowLite,我决定认真的学一下深度学习的知识。以前总觉得AI太高大上,怕自己看不懂。但是这次做这个功能让我意识到,如果自己再不学习,过几年可能就太晚了。我估计三五年之后,AI会渗透到各行各业,连APP开发也需要掌握一些深度学习的知识。

先说一下我们APP做的这个功能:用相机扫描检验单(体检单),将检验单截图上传到服务器,服务器进行OCR识别,并将数据结构化,用于生成用户的基因报告。这里涉及到APP开发的部分是将相机中的检验单检测并裁剪出来,上传到服务器识别。这里识别和裁剪部分很重要。将检验单准确的裁剪出来,可以提升OCR准确率。如果随意拍了一张图片,除了检验单部分,可能还包含了其他杂乱的背景,甚至其他不相关的文字内容。所以只将检验单部分裁剪出来是很重要的。

如果采用传统的OpenCV边缘检测方法来在图片中来找检验单,效果不尽人意。但是如果采用深度学习的方式来找检验单,那就可以得到很好的效果。当然这也是我们从fengjian大神博客学到的。我们采用他的方法,自己准备了一批数据训练,得到一个模型。而我们APP的任务就是在手机上采用TensorFlowLite运行我们的模型来推断得到相关结果。

因为做这个功能,我了解了下TensorFlowlite,但是我们深度学习却是了解不多。所以我决定找本书来学习一下。网上查了下,《深度学习入门:基于Python的理论与实现》这本书挺受好评,问了下我同事,他也看过这书,说写得不错 ...

Category: 技术 Tagged: 深度学习 人工智能 读后感

comments

Read More

探讨从H5页面拉起APP的技术方案和问题

2020-03-07

探讨从H5页面拉起APP的技术方案和问题

从H5页面里拉起或叫唤起APP(APP需已经安装在手机上),有两种技术方案,一种通过HTTP链接(iOS叫 Universal Link,安卓叫APP Links技术)拉起技术,一种是通过自定义scheme拉起技术(也有叫深度链接deeplink的)。这两个技术适用不同的场景,也有不同的局限性。

一、通过HTTP链接拉起

要想通过HTTP(s)链接直接拉起APP,需要APP本身做一些配置和写一些代码实现,还需要在你网站服务器配置相关的json文件。例如配置服务器host,能打开页面的path等。有些了这些配置,苹果服务器或者谷歌的服务器验证了这些配置,就可以通过相关的HTTP链接来打开APP了

这里不管是iOS叫 Universal Link,还是安卓的APP Links,他们的技术方案是基本一样的,只是不同的平台稍微有些不一样的而已。基本规则是:

  • 先按照要求生成一个配置文件,json格式的,里面包括了一些必要的信息,例如安卓的包名,apk签名等,iOS的appID,匹配URL的path
  • 将配置文件放到你的网站根目录下的.wellknown目录下,让苹果或谷歌的服务器能从这里下载这个配置文件。这个文件是关键,因为苹果或谷歌要从你网站里拉取到这个配置文件。由于你的网站只有你自己有权限上传配置文件,从而保证了安全性。
  • 在你的APP里配置相关网站host ...

Category: 技术 Tagged: APP

comments

Read More

AOP在移动开发中的应用

2020-01-21

AOP在移动开发中的应用

下面是我在我们团队上的技术分享内容,从PPT里摘抄出来的,所有很多都是个提纲摘要

AOP简介

  • AOP,Aspect Oriented Programming,面向切面编程
  • 面向切面编程是一种通过横切关注点(Cross-cutting Concerns)分离来增强代码模块性的方法,它能够在不修改业务主体代码的情况下,对它添加额外的行为。
  • 是对OOP的一种补充,是一种解耦的重要手段

AOP常用概念

  • Join point:程序执行期间的一个点,表示方法的执行
  • Pointcut: 切入点实际上也是从所有的连接点(Join point)挑选自己感兴趣的连接点的过程
  • Aspect: 程序横向切割成若干的面,即Aspect.每个面被称为切面
  • Advice: 某个特定连接点的某个方面采取的行动。不同类型的建议包括“周围(Around)”,“之前(Before)”和“之后(After)”建议

AOP实现原理

  • 编译期间的静态织入,又称为编译时增强

  • 运行期间的动态代理,又称为运行时增强

运行时AOP

  • 程序运行时 ...

Category: 技术 Tagged: AOP 移动开发

comments

Read More

记一次WebView填坑过程--由换行符引发的血案

2019-07-27

记一次WebView填坑过程--由换行符引发的血案

最近使用WebView掉坑了,然后艰难爬坑经历感触很深,写出来大家借鉴一下。

需求

我们有个网页需要用到很多js库,这些库比较大,而且基本上是不变的。为了提高性能,将这些网页和JS库放到本地,进行加载。变的数据从服务器获取,然后跟本地的HTML组装后显示。这种需求还是挺普遍的。

实现方式

由web的同事调试好HTML文件,他们把所有的HTML,js,css,image和资源一起打包给我们 我们把这些资源放到assets目录下(也可以专门在assets再建一个子目录来放)

采用Android自带的WebView来加载和显示这些网页。有两个方法loadUrlloadDataWithBaseUrl方法。

1.loadUrl("file:///android_asset/xxxxxx.html")这种方式适合HTML不需要改变的情况,直接加载展示,省时省力

2.loadDataWithBaseUrl方式需要先把HTML内容现在到内存,然后再展示。这种方式可以随意修改HTML里的内容,修改好再交由WebView展示,灵活性强。

遇到的坑

测试的时候,我们遇到同一个HTML文件,通过loadUrl方式加载展示,没有任何问题。但是通过loadDataWithBaseUrl加载展示,HTML的内容死活展示不出来 ...

Category: 技术 Tagged: Android开发 WebView JavaScript

comments

Read More

如何写出不太坏的代码

2019-02-28

如何写出不太坏的代码

本来想把标题定为“如何写出好代码”的,但是转念一想,觉得自己没那么牛逼,就改成了不太坏的代码。只要不要写出太糟糕的代码,不就是好代码了吗?

以下只是个人的一些总结,如有异议,请出门右转。

1.注重命名

包括文件名,变量名,方法名。一般变量名以功能来命名,看它的名字就能知道它的用途是什么 遵循一些命名规范。Java中成员变量名一般以m开头,而OC中的成员变量一般以_开头

2.遵循低耦合,高内聚的原则编写代码

具体表现遵循分层分模块原则,底层模块不能依赖上层模块,逻辑层不依赖界面,绝不要把逻辑层代码和界面代码混在一起

3.针对接口编程,而不是实现编程

当一个类要暴露一些方法给外部调用或者通信时,先定义好接口。接口定义应该简单明了,不容易引起歧义,接口尽量少。最后才考虑内部实现,而且尽量不暴露给外部,尽量不需要外部知道内部是如何实现的

4.一个类的代码量尽量不要太大,一个方法只做一件事情

一个类(或者一个方法)的代码越多,出错的概率越大。一个类三到5百行代码最佳。如果一个类的代码太多,就要考虑这个类是不是做的东西太多了 ...

Category: 技术 Tagged: iOS开发 Android开发

comments

Read More
Page 1 of 3

Next »