关于前端:有道词典-Flutter-架构与应用

52次阅读

共计 15004 个字符,预计需要花费 38 分钟才能阅读完成。

分割咱们 有道技术团队助手 :ydtech01 /  邮箱:mailto:ydtech@rd.netease.com

在 18 年 Flutter 公布正式版 1.0 版本以来,有道 Luna 团队放弃继续的关注,在不少业务上进行大量的尝试,Flutter 自身对立 Skia 引擎带来的跨平台个性和统一的体验,AOT 下高性能,JIT 下热重载带来进步开发效率等个性,都让人们放弃极大的激情和继续的投入,其生态社区也在快速增长。

从理论体现上来看,整个技术栈设计很好。下层 Flutter Framework 引入 Widget/LayerTree 等概念本人实现了界面形容框架,上层 Flutter Engine 把 LayerTree 用 OpenGL 渲染成用户界面。

长期来看,用 Flutter 来代替 Native,实现双端代码对立,节约人力开发,也是咱们继续摸索的方向。

一、前言

1.1 词典业务尝试

咱们应用 Flutter 在有道词典往年的 6、7 月份上线了单词本和听力模考业务,当初是 flutter 1.12 版本以下是业务展现

  1. 单词本:

  1. 听力模考:

咱们在较为独立的新业务上进行大胆尝试,新技术难免会有问题,然而还是要敢于尝试。

二、Flutter 根底简介

2.1 Dart

  • Dart 单线程模型

Dart 和 JavaScript 都是单线程模型,运行机制很类似,Dart 官网提供了一张 运行原理 图:

Dart 在单线程中是以音讯循环机制来运行的,其中蕴含两个工作队列,一个是“微工作队列”microtask queue,另一个叫做“事件队列”event queue。从图中能够发现,微工作队列的执行优先级高于事件队列。

其中 event queue:负责解决 I / O 事件、绘制事件、手势事件、接管其余 isolate 音讯等内部事件;microtask queue:能够本人向 isolate 外部增加事件,事件的优先级比 event queue 高。

事件队列模型过程:

  1. 先查看 MicroTask 队列是否为空,非空则先执行 MicroTask 队列中的 MicroTask
  2. 一个 MicroTask 执行完后,查看有没有下一个 MicroTask,直到 MicroTask 队列为空,才去执行 Event 队列
  3. 在 Evnet 队列取出一个事件处理完后,再次返回第一步,去查看 MicroTask 队列是否为空

在事件循环中,当某个工作产生异样并没有被捕捉时,程序并不会退出,而间接导致的后果是当前任务的后续代码就不会被执行了,也就是说一个工作中的异样是不会影响其它工作执行的。

异样捕捉上传至统计解体平台也是利用这个模型,前面会讲到

2.2 Flutter Widget

介绍完 Dart,咱们再看下 Flutter Widget。在 Flutter 中所有皆为 Widget,通过应用 Widget 能够实现页面整体布局、文本展现、图片展现、手势操作、事件响应等。

2.2.1 StatelessWidget

StatelessWidget 是一个 没有状态的 widget ——没有要治理的外部状态。它通过构建一系列其余小部件来更加具体地形容用户界面,从而形容用户界面的一部分。当咱们的页面不依赖 Widget 对象自身中的配置信息以及 BuildContext 时,就能够用到无状态组件。例如当咱们只须要显示一段文字时。实际上 Icon、Divider、Dialog、Text 等都是 StatelessWidget 的子类。

2.2.2 StatefulWidget

StatefulWidget 是 可变状态的 widget。应用 setState 办法治理 StatefulWidget 的状态的扭转。调用 setState 告诉 Flutter 框架某个状态产生了变动,Flutter 会从新运行 build 办法,应用程序变能够显示最新的状态。状态是在构建 widget 的时候,widget 能够同步读取的信息,而这些状态会发生变化。要确保在状态扭转的时候即便告诉 widget 进行动静更改,就须要用到 StatefulWidget。例如一个计数器,咱们点击按钮就要让数字加一。在 Flutter 中,Checkbox、FadeImage 等都是有状态组件。

StatefulWidget 的生命周期大抵可分为三个阶段:

  • 初始化:插入渲染树,这一阶段波及的生命周期函数次要有 createState、initState、didChangeDependencies 和 build。
  • 运行中:在渲染树中存在,这一阶段波及的生命周期函数次要有 didUpdateWidge t 和 build。
  • 销毁:从渲染树中移除,此阶段波及的生命周期函数次要有 deactivate 和 dispose。

具体的申明周期调用过程如下:

2.2.3 StatefulWidget 和 StatelessWidget 的实用场景

在 Flutter 中,组件和页面数据变动是通过 State 驱动的,对于有交互的页面或组件能够继承 StatefulWidget,动态组件或页面能够继承 StatelessWidget。

StatelessWidget 没有外部状态,Icon、IconButton 和 Text 都是无状态 widget, 他们都是 StatelessWidget 的子类。

StatefulWidget 是动静的. 用户能够和其交互或者能够随工夫扭转 (兴许是数据扭转导致的 UI 更新)。Checkbox、Radio、Slider, InkWell、Form、TextField 都是 StatefulWidget, 他们都是 StatefulWidget 的子类。

应用 StatefulWidget 还是 StatelessWidget 的 判断根据

  • 如果用户与 widget 交互,widget 会发生变化,那么它就是有状态的.
  • widget 的状态(state)是一些能够更改的值, 如一个 slider 滑动条的以后值或 checkbox 是否被选中.
  • widget 的状态保留在一个 State 对象中, 它和 widget 的布局显示拆散。
  • 当 widget 状态扭转时, 调用 setState(), 通知框架去重绘 widget.

三、混合开发 – 整体框架

开发之初咱们思考两个问题:

  • 场景 1 :a,b 两个业务线,都要在 flutter 工程外面开发业务?
  • 场景 2 :M app 有 flutter 工程,这个时候咱们 N app 里的 flutter 工程要嵌入到 M app 里咱们怎么办?

起初咱们心愿生成多个产物进行嵌入,通过 Flutter 的线下会议探讨发现这个思路是比拟前期的事件,然而也失去了另一个思路将咱们的业务进行“下沉”,下沉到同一个工程外面进行业务辨别,引入组件化的概念进行实际;

3.1 反对多团队开发 

Flutter 工程中,通常有以下 4 种工程类型,上面别离简略概述下:

1. Flutter Application:规范的 Flutter App 工程,蕴含规范的 Dart 层与 Native 平台层

2. Flutter Module:Flutter 组件工程,仅蕴含 Dart 层实现,Native 平台层子工程为通过 Flutter 主动生成的暗藏工程

3. Flutter Plugin:Flutter 平台插件工程,蕴含 Dart 层与 Native 平台层的实现

4. Flutter Package

Flutter 纯 Dart 插件工程,仅蕴含 Dart 层的实现,往往定义一些公共 Widget

Flutter 工程之间的依赖治理是通过 Pub 来治理的,依赖的产物是间接源码依赖,这种依赖形式和 IOS 中的 Pod 有点像,都能够进行依赖库版本号的区间限定与 Git 近程依赖 path 等,其中具体申明依赖是在 pubspec.yaml 文件中,其中的依赖编写是基于 YAML 语法,YAML 是一个专门用来编写文件配置的语言,上面是依赖示例:

vibration:
  git:
   url: https://github.com/YoudaoMobile/flutter_vibration.git
   ref: 'task/youdao'
flutter_jsbridge_builder:
  path: ../../Common/flutter_jsbridge_builder

所以,通过 Flutter Plugin / Flutter Package + Pub 达到解耦的目标

Flutter Plugin / Flutter Package为模块开发,原则上咱们将工程分为壳工程, 业务组件, 根底组件;依赖关系为壳工程 -> 业务组件 -> 根底组件,不能依赖倒置,同层之间不能互相援用。

 

3.2 根底组件积淀

通过以组件化模式进行开发,通过各个团队业务的一直迭代,逐渐积淀出一套 CommonUI 的根底 Widget 组件,不便其余业务和团队扩大应用。

  • button工具:YDLabelButton,YDRaisedButton 
  • 字体 工具:YDFontWeight,YDText,YDHtmlText
  • 遮罩 工具:YDMaskView
  • loading工具:YDDefaultLoadingView,YDLoadingView
  • 圆角 工具:CornerDecoration
  • 模态弹出 工具:YDCupertinoModalPopupRoute
  • 点击弹窗 工具:YDCoordinateTap
  • 帧序列动画 工具:YDSimpleFrameAniImage
  • YDRaisedButton,YDLabelButton 是咱们对立恪守有道 UI 准则自定义的一套 button 内容
    YDText 集成了英文屏幕取词,以及解决中日韩同时展现在界面字体展现异样的问题
    YDHtmlText 咱们集成了基于 html 标签展现进行深层定制,来实现富文本的成果
    YDSimpleFrameAniImage 帧动画播放组件,解决了单纯的图片第一次循环播放会闪动等问题的播放动画组件
    YDCupertinoModalPopupRoute 仿照新的 ios 模态弹出的成果,反对顺手滑动隐没的交互方式
    ….

3.3 元编程

咱们在开发过程发现咱们的 bridge 的内容大多数是雷同的,只不过是形参,函数名不同罢了,所以咱们打算引入 source_gen,来生成 bridge 层的代码,这样也带来两个益处,一是避免手误,带来的不必要的 bug,二是将代码对立

source_gen 次要提解决 dart 源码,能够通过注解生成代码。

大抵的流程是通过 source_gen 一个 _Builder,_Builder 须要生成器 Generator,之后通过 Generator 去生成代码。

总结一下,在 Flutter 中利用注解以及生成代码仅需一下几个步骤:

1. 依赖 

dev_dependencies:
 source_gen: ^0.9.0

2. 创立注解

class JSBridgeModule {
  final String moduleName;
 final List<String> enumTypeName;
 const JSBridgeModule({this.moduleName : "app", this.enumTypeName : const []});
}
 

3. 创立生成器

class JSBridgeImplGenerator
    extends GeneratorForAnnotation<JSBridgeModule> {JSBridgeImplGenerator() {}
  @override
 Iterable<String> generateForAnnotatedElement (Element element, ConstantReader annotation, BuildStep buildStep) {if (element is! ClassElement) {
      final name = element.name;
 throw InvalidGenerationSourceError('Generator cannot target `$name`.',
 return _generate(classElement, moduleName, enumTypeName: checkEnumTypeName);
 }
}

4. 创立 Builder

Builder getJSBridgeImpGeneratorBuilder(BuilderOptions options) {return SharedPartBuilder();}

5. 编写配置文件

在我的项目根目录创立 build.yaml 文件,配置各项参数

builders:
  JSBridgeImpGeneratorBuilder:
    import: "package:flutter_jsbridge_builder/builder.dart"
 builder_factories: ["getJSBridgeImpGeneratorBuilder"]
    build_extensions: {".dart": ["flutter_jsbridge_builder.g.part"]}
    auto_apply: dependents
    build_to: cache
    applies_builders: ["source_gen|combining_builder"]

这样就为咱们输入一份模板代码提供了实现的可能

3.4 资源管理

家喻户晓,flutter 图片等资源管理方面,还是处在手动治理阶段,费时费力,所以举荐一款网易严选团队开发的 flr 插件,flr 配合通过 AndroidStudio 插件,将用于帮忙 Flutter 开发者在批改我的项目资源后,能够主动为资源增加申明到 pubspec.yaml 以及生成集中在一起的资源门路文件,Flutter 开发者能够在代码中通过资源 ID 函数的形式利用资源。

通过建设起一个自动化的服务来监听和治理资源变动,之后将变动的资源同步到 pubspec.yaml 和对应的资源文件当中,也反对文本,字体资源,后续咱们也和 flr 的团队反对光明模式的打算。

地址:https://github.com/Fly-Mix/fl…

3.5 异样捕捉上传

在 flutter 简介外面咱们介绍了 dart 的线程模型

事件队列模型过程:

  1. 先查看 MicroTask 队列是否为空,非空则先执行 MicroTask 队列中的 MicroTask
  2. 一个 MicroTask 执行完后,查看有没有下一个 MicroTask,直到 MicroTask 队列为空,才去执行 Event 队列
  3. 在 Evnet  队列取出一个事件处理完后,再次返回第一步,去查看 MicroTask 队列是否为空

在事件循环中,当某个工作产生异样并没有被捕捉时,程序并不会退出,而间接导致的后果是当前任务的后续代码就不会被执行了,也就是说一个工作中的异样是不会影响其它工作执行的。

Flutter 框架为咱们在很多要害的办法进行了异样捕捉。在产生异样时,谬误是通过 FlutterError.reportError 办法上报的,其中 onError 是 FlutterError 的一个动态属性,咱们重写 onError 就能够捕捉异样了;然而还有一些异步异样是须要咱们通过 Zone 办法来捕捉的,整顿代码如下:

四、产物介绍

4.1 介绍

1.12 是个分水岭,在这之前安卓打包形式有所不同,并且 iOS 官网也提供一些命令也来反对打不同的包

  • iOS 产物组成

通过 flutter build ios –release 来失去产物,而后 flutter-plugins 外面记录各种 plugin 的地位 copy 过去,放在.symlinks 文件夹下

app.framework: 代码数据段 + 图片
flutter.framework:engine+channel+…
FlutterPluginRegistrant: 源码,一些 flutter 本身的 bridge
podhelper.rd: 通过 flutter-plugins 里的 bridge 列表循环的将 bridge 填到 pod 中,在宿主工程通过 pod 引入

  • android 产物组成 flutter1.0

进入 flutter 工程的 .android 文件夹执行 ./gradlew assembleRelease 就会打出一个 flutter-release.aar 的包,然而还有 path_provider,share_preference,audioplayer 等官网插件咱们也须要 copy 进去,这里咱们发现 flutter 工程目录上面有.flutter-plugins 这个文件,这个文件记录着你以后 flutter 所应用的官网插件的文件地位,咱们通过 shell 读取文件地位,找到对应的 aar 集中到一起。

  • android flutter 1.12 当前

flutter 1.12 打包执行 flutter build  aar –no-debug –-no-profile 来失去.

 

4.2 打包问题汇总

在 flutter1.0 版本进入 android 文件夹执行./gradlew assembleRelease 会失去 aar 产物,然而此时的 aar 嵌入进去 run 起来会报错,错误信息是短少一个.dat 文件,依据官网的 issue 和对源码的思考,探讨后果是里 assets 上面少一个 flutter_assets 文件内容,事实上在 io/flutter.jar 能够看到,然而 flutter 还是会去 assets 文件夹上来找,导致嵌入 Android 失败。解决办法,从 apk 里 copy 一份 flutter_asset 放到 aar 里

在 flutter 1.12 版本官网提供 aar 产物命令,然而工程中引入官网库(shared_preferences)的时候会执行命令失败,起因是第三方会带上 macos 和 web 的 package,然而这个 package 不带 android 文件的内容,解决办法:通过批改官网 sdk 对其 android 文件夹进行兼容。

 

4.3 打包机问题汇总

在打包机配置完 flutter 环境,须要在 Jenkins 的节点配置将 flutter path 增加到 PATH 当中,否则 flutter 命令执行失败,以及 ios 打包 flutter build ios –release 会因为 code sign 没有权限的问题失败,尽量用 flutter build ios –release –no-codesign 来失去环境

 

五、遇到的问题

5. 1ios 端存在的问题

5.1.1 混合栈 boost 呈现的问题

首先感激咸鱼团队,提供了混合栈的一种计划,咱们从 flutter1.9 降级到 1.12 过程中,遇到不少的问题和麻烦。

1. 生命周期屡次回调

在 1.9 的版本中,ContainerLifeCycle.Appear 办法会回调两次,导致依赖生命周期操作反复,在 ios 这边是在 viewdidappear 的时候会发通过 channel 发 didShowPageContainer 的音讯,调用 nativeContainerDidShow,而后在 TransitionBuilder 的办法再去调用一次.

onPageStart 而后再去调用 nativeContainerDidShow,就会导致两次触发,android 也是在 onAppear 的办法上反复上述的操作。

解决办法 就是去掉其中一个。

2. 降级 1.12 之后,切前后台的 crash 问题

这个问题版本有很多,得思考业务场景,咱们这里是先模态出一个 NavigationViewController,而后在这个 NavigationViewController 根底上进行 push 和 pop 操作,而后咱们在全局提供一个回到模态之前 ViewController 的操作。在全局回退的过程中,咱们清掉了 native 的栈,而后在 native 的任意 vc,切前后台后 crash。然而在 1.9 版本时并没有发现此类问题。crash 的起因是在 1.12 的版本中 FlutterEngine 本身加了 surfaceUpdated 的操作,当你整个退出后没有正确的解决,导致 FlutterEngine 认为你的页面上还存在着 Flutter 页面,进行刷新创立工作,就 crash 了。当然这个是咱们这个业务场景总结出的 crash 的起因,据说还有其余版本 crash 问题,欢送其余敌人补充。

解决办法是在全局回退的过程中循环调用 close 办法将栈里的 vc 退出。

5.1.2 多语言显示异样

当界面同时显示在韩语 / 日语 与中文时,界面展现异样

官网 issue:https://github.com/flutter/fl…

解决形式有三种:

  1. 减少字体 ttf,全局指定改字体显示。
  2. TextStyle 属性指定
fontFamilyFallback: ["PingFang SC", "Heiti SC"]

能够封装成一个 widget

  1. 批改主题下所有 TextTheme 的 fontFamilyFallback
getThemeData() {
var themeData = ThemeData(primarySwatch: primarySwatch);

var result = themeData.copyWith(textTheme: confirmTextTheme(themeData.textTheme),
accentTextTheme: confirmTextTheme(themeData.accentTextTheme),
primaryTextTheme: confirmTextTheme(themeData.primaryTextTheme),
);
return result;
}
/// 解决 ios 上,同页面呈现韩文和简体中文,导致的显示字体异样
confirmTextTheme(TextTheme textTheme) {getCopyTextStyle(TextStyle textStyle) {return textStyle.copyWith(fontFamilyFallback: ["PingFang SC", "Heiti SC"]);
}

return textTheme.copyWith(display4: getCopyTextStyle(textTheme.display4),
display3: getCopyTextStyle(textTheme.display3),
display2: getCopyTextStyle(textTheme.display2),
display1: getCopyTextStyle(textTheme.display1),
headline: getCopyTextStyle(textTheme.headline),
title: getCopyTextStyle(textTheme.title),
subhead: getCopyTextStyle(textTheme.subhead),
body2: getCopyTextStyle(textTheme.body2),
body1: getCopyTextStyle(textTheme.body1),
caption: getCopyTextStyle(textTheme.caption),
button: getCopyTextStyle(textTheme.button),
subtitle: getCopyTextStyle(textTheme.subtitle),
overline: getCopyTextStyle(textTheme.overline),
);
}

5.2 双端存在的问题

flutter pub get 失败

Flutter 我的项目在援用第三库时,在 pub 会抉择应用 git 援用,如:

flutter_boost:
  git: 
 url: 'https://github.com/YoudaoMobile/flutter_boost.git'
 ref: 'youdao_0.0.8'

会报 pub get fail 的问题

在下载包的过程中呈现问题,下次再拉包的时候,在 .pub_cache 内的 git 可能是空目录,导致 flutter packages get 的时候异样。

所以你须要革除掉 .pub_cache 内的 git 的异样目录或者执行 flutter cache repair,之后从新执行 flutter packages get

5.3 channel 通信

Flutter 定义了三种不同类型的 Channel,它们别离是

  • BasicMessageChannel:用于传递字符串和半结构化的信息。
  • MethodChannel:用于传递办法调用(method invocation)。
  • EventChannel: 用于数据流(event streams)的通信。

其中 channel 有个很重要的变量 codec;Codec 官网定义了两种 Codec:MessageCodec 和 MethodCodec

其中 MessageCodec 有 4 种不同的品种:

BinaryCodec;StringCodec;JSONMessageCodec;StandardMessageCodec

起初咱们应用 MethodChannel 来建设通信,然而应用过程中遇到大内存的传递耗时很长的问题,咱们通过一系列的试验和官网文档的指引,当须要传递大内存数据块时,应用 BasicMessageChannel 以及 BinaryCodec 能够解决问题。

以下是试验内容:

试验机型:iphone 7 ios 13.7 零碎

试验数据:开发者能够自行模仿 1M-2M 左右的数据进行测试,因为波及到实在数据 这里就不放进去了。

试验后果:

从试验后果上看,传输效率最优的是 BinaryCodec,当然抉择什么样的 code 依据我的项目的需要来定才是最正当的

BinaryCodec>StringCodec>StandardMessageCodec>JSONMessageCodec

5.4 长列表优化

5.4.1 列表内容项长短不一

当咱们实现相似下面的页面,item 高度不一的思路必定是相似以下的代码,然而当咱们达到肯定的数量级的状况下,发现内存占用的非常重大,导致有些需要就实现不了,比如说反对滚动条疾速定位; 究其因 CustomScrollView 初始化就加载了很多的 widget

List<Widget> list = [];
for (int i = 0; i < 10000; i ++) {list.add(SliverToBoxAdapter(child: Container(height:30,color: Colors.red,),));
 list.add(SliverFixedExtentList(delegate: SliverChildBuilderDelegate((context,index){return Container(child: Text(index.toString()),);
 },childCount: 50), itemExtent: 50)
  );
}
CustomScrollView(slivers: list,);

然而如果都是同样的 itemExtent,滚动效率,内存体现都是良好的;因为当时通知好高度,而不是依赖 widget 本身的 layout 计算效率就高了很多,比如说以下的代码:

List<Widget> list = [];

 list.add(SliverFixedExtentList(delegate: SliverChildBuilderDelegate((context,index){return Container(child: Text(index.toString()),);
 },childCount: 20000), itemExtent: 50)
  );
CustomScrollView(slivers: list,);

如何 两者兼得 呢?

咱们决定自定义 SliverFixedExtentList, SliverFixedExtentList 返回的 RenderObject 是 RenderSliverFixedExtentBoxAdaptor,咱们将 RenderSliverFixedExtentBoxAdaptor 从新设计下。

RenderSliverFixedExtentBoxAdaptor 原先设计的思路是通过 scrolloffset 除以 itemExtent 计算出以后的 index(itemExtent 是写死的所以间接除),SliverConstraints 能够拿到他的 remainingCacheExtent 也就是 cacheExtent 加上滚动可见区域,也就能够拿到 lastIndex,在滚动的过程中一直的开释和创立。咱们改写的思路如下:

1.SliverFixedExtentList 在 createRenderObject 和 updateRenderObject 的时候将每个元素的地位从新计算缓存。

2. 而后重写 performLayout 办法,通过全局的 first 和 last 索引拿到元素的地位和 scrolloffset 进行比拟,失去新的 first 和 last 索引,一直的调整。

3. 将计算好的束缚布局传入子布局。

大抵的思路就是这样,接口层面咱们设计成这个样子,以下是调用示例:

YDSliverFixedExtentList(delegate: SliverChildBuilderDelegate((context, int index) {if (index > wordList.length - 1){return Container(color: Colors.transparent,);
 }
      YDWBListBaseModel model = wordList[index];
 if (model is YDWBListHeaderModel) {return buildSusWidget(model.title);
 } else if (model is YDWBListItemModel){return buildListSingleItem(index, wordList[index], onMoveTap, onDeleteTap);
 } else{return Container();
 }
    },
 childCount: wordList.length + 1,
 ),
 itemHeightDelegate: (index){if (index > wordList.length - 1){return 60;}
    var model = wordList[index];
 if (model is YDWBListHeaderModel) {return kItemHeaderHeight;} else if (model is YDWBListItemModel) {return kItemHeight;} else {return 60;}
  },
 itemIndexDelegate: (startIndex, endIndex){
    firstIndex = startIndex;
 lastIndex = endIndex;
 },

5.4.2 如何获取以后展现列表索引

ios 开发都晓得,咱们的 TableView 是有代理来晓得咱们以后页面展现的 Cell 的索引,然而在 Flutter 里咱们怎么办呢?

  • 思路 1 :给每个 item 加上 GlobalKey,而后放在 model 中,而后滚动的过程中利用去找循环遍历 model 的 GlobalKey,通过 GlobalKey 找到对应的 RenderObject,RenderObject 存在着地位坐标等信息,通过此信息能够比拟计算出 key 对应的 RenderObject 是否展现界面

代码如下:

double y=model.key.currentContext.findRenderObject().getTransformTo(null).getTranslation().y;
double height=model.key.currentContext.findRenderObject().paintBounds.size.height;

而后找到对应绑定的 model

  • 思路 2 :如果是实时获取展现的索引可能上述思路不太适合,可能每次都须要在寄存 model 里的数组去找,当然也能够在思路 1 的根底上进行算法优化,暂存以后展现的 index,来做下次起始寻找的 index,缩小循环次数。

不过,接下来介绍的是,另一种方法,改写 SliverChildBuilderDelegate,在 SliverChildBuilderDelegate 外面的 didFinishLayout 里会返回它的 firstIndex 和 lastIndex,然而要留神此时返回的是加了 cacheExtent 的 firstIndex,所以可能比理论展现的要小,所以能够联合思路一进行精确定位

class MySliverChildBuilderDelegate extends SliverChildBuilderDelegate {
  final int markIndex;
 MySliverChildBuilderDelegate(
      this.markIndex,
 Widget Function(BuildContext, int) builder, {
        int childCount,
 bool addAutomaticKeepAlives = true,
 bool addRepaintBoundaries = true,
 }) : super(builder,
 childCount: childCount,
 addAutomaticKeepAlives: addAutomaticKeepAlives,
 addRepaintBoundaries: addRepaintBoundaries,
 );

 @override
 void didFinishLayout(int firstIndex, int lastIndex) {debugPrint('pre' + 'didFinishLayout firstIndex: $firstIndex, lastIndex: $lastIndex' + "markIndex" + markIndex.toString());
 if (firstIndex == 0 && lastIndex == 0) {return;}
    YDBaseListEvent.notifyIndexChange({"firstIndex": markIndex + firstIndex, "lastIndex": markIndex + lastIndex});
 debugPrint(mark + 'didFinishLayout firstIndex: $firstIndex, lastIndex: $lastIndex' + "markIndex" + markIndex.toString());
 }
}

思路 3:能够参考长列表优化,将 SliverFixedExtentList 改写裸露对应返回 index 的接口

 

六、后续打算  

咱们后续打算降级到 flutter2.0,然而目前来看 2.0 还存在问题,如果 2.0 真的彻底的解决多引擎复用的问题,咱们也会尝试去除 boost 的管理机制,依据 https://flutter.cn/posts/flut…,在多个引擎复用的视频章节,于潇剖析了多引擎复用的内存增长的问题,次要在

  • 线程
  • GPU 资源
  • Skia Context
  • 字形
  • Dart Isolate

这五局部,每起一个 engine 之后就会起 3 个新的操作系统的线程,每个线程都是有老本的尤其是在 ios 上,在 2.0 版本上都合并在一起了;另一部分 GPU 资源,Skia Context 就蕴含了 opengl 的 context,metal context,metal buffer,shader program,skia program,为了进步应用启动将 GPU 资源和 Skia Context 的内容做共享;字形的大小都会有缓冲的,如果不加以利用的话也造成肯定的节约;dart Isolate 事实上每次创立 engine 都会从新创立一个,2.0 版本也做了一个共享。

后果也是比拟主观,优化的成果比拟显著,10 次的启动不升反降,40M 变成了 35M。有趣味的能够试下,https://github.com/flutter/sa…,然而对于 flutter 团队来说 2.0 版本只是解决了内存问题,还存在其余的问题,次要是以下几方面:

  • 只反对 AOT,不反对 debug
  • ios IOSurface 卸载,fluttervc 没有解除然而又被笼罩了的话,它的 metal layer 没有开释 IOSurface,会影响混合栈的场景,目前的方法是笼罩的时候须要手动把 flutterview 去掉
  • 不反对数据共享
  • 不反对内存共享
  • platform View 不反对
  • 只反对一个 snapshot

七、结束语

咱们在单词本和听力等模块进行 flutter 落地的摸索,在后期实际过程中,碰到了很多问题,但总体来说还处于可控的状态;后期把各种艰难都解决后,前面业务再此基础上进行开发会顺畅很多,效率会晋升很多,这个也是 flutter 冀望带给咱们的一次开发,多端运行。然而另一方面心愿开发者们在落地过程中,更为谨慎些,多多实际,提前发现提前解决,毕竟存在解决不好的状况,还须要推动官网或者生态提供更好的解决办法。

将来冀望 flutter 以及社区在平台一致性以及混合栈,内存,键盘,音视频等具体问题上继续发力,咱们也会进一步的摸索 flutter 在业务上更多实现的可能。感激观看。

以上内容仅代表个人观点,如果内容或者试验数据存在疑难和问题,欢送大家批评指正,一起学习,一起成长。

  • 本内容仅代表个人观点,不代表网易,仅供外部分享流传,不容许以任何模式外泄,否则查究法律责任。

正文完
 0