有道词典Flutter架构与应用

发表于 2年以前  | 总阅读数:2598 次

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

从实际表现上来看,整个技术栈设计很好。上层 Flutter Framework 引入 Widget/LayerTree 等概念自己实现了界面描述框架,下层 Flutter Engine 把 LayerTree 用 OpenGL 渲染成用户界面。

长期来看,用 Flutter 来替代 Native,实现双端代码统一,节约人力开发,也是我们持续探索的方向。

一、前言

ydtech

1.1 词典业务尝试

我们使用 Flutter 在有道词典去年的3月份、7月份分别上线了单词本和听力模考业务,现在是 Flutter 1.12 版本。以下是业务展示

单词本

听力模考

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

二、Flutter基础简介

ydtech

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的生命周期大致可分为三个阶段

  1. 初始化:插入渲染树,这一阶段涉及的生命周期函数主要有createState、initState、didChangeDependencies 和 build。
  2. 运行中:在渲染树中存在,这一阶段涉及的生命周期函数主要有didUpdateWidget和build。
  3. 销毁:从渲染树中移除,此阶段涉及的生命周期函数主要有deactivate和dispose。

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

2.2.3 两者的实用场景

在 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

三、混合开发 - 整体框架

ydtech

开发之初我们考虑两个问题

  • 场景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 . 依赖 source_gen

  dev_dependencies:
   source_gen: ^0.9.

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/flr-as-plugin/tree/d56e4b989c1de4b493d8e27b3f8ce4100af1f6df

3.5 异常捕获上传

在 flutter 简介里面我们介绍了 dart 的线程模型:

事件队列模型过程:

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

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

Flutter 框架为我们在很多关键的方法进行了异常捕获。在发生异常时,错误是通过 FlutterError.reportError 方法上报的,其中 onError 是 FlutterError 的一个静态属性,我们重写 onError 就可以捕获异常了;但是还有一些异步异常是需要我们通过 Zone 方法来捕获的,整理代码如下:

四、产物介绍

ydtech

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 来得到环境。

五、遇到的问题

ydtech

5.1 ios端存在的问题

5.1.1 混合栈boost出现的问题

首先感谢咸鱼团队,提供了混合栈的一种方案,我们从 flutter1.9 升级到 1.12过程中,遇到不少的问题和麻烦。

  • 生命周期多次回调

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

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

解决办法:去掉其中一个。

  • 升级 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/flutter/issues/36527。解决方式有三种

1 . 增加字体 ttf ,全局指定改字体显示。

2 . TextStyle 属性指定:可以封装成一个 widget

 fontFamilyFallback: ["PingFang SC", "Heiti SC"]

3 . 修改主题下所有 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
实验次数 1 2 3 4 5
传输时间(s) 0.112 0.003 0.006 0.005 0.004
  • JSONMessageCodec
实验次数 1 2 3 4 5
传输时间(s) 0.162 0.121 0.149 0.162 0.163
  • StringCodec
实验次数 1 2 3 4 5
传输时间(s) 0.021 0.064 0.035 0.033 0.037
  • StandardMessageCodec
实验次数 1 2 3 4 5
传输时间(s) 0.075 0.034 0.052 0.061 0.053

从实验结果上看,传输效率最优的是 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.3 如何获取当前展示列表索引

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 的接口。

六、后续计划

ydtech

我们后续计划升级到flutter2.0,但是目前来看2.0还存在问题,如果2.0真的彻底的解决多引擎复用的问题,我们也会尝试去除boost的管理机制,根据https://flutter.cn/posts/flutter-engage-china-event-recap,在多个引擎复用的视频章节,于潇分析了多引擎复用的内存增长的问题,主要在:

  • 线程
  • 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/samples/tree/master/add_to_app,但是对于 flutter 团队来说 2.0 版本只是解决了内存问题,还存在其他的问题,主要是以下几方面:

  • 只支持 AOT,不支持 debug
  • ios IOSurface 卸载,fluttervc 没有解除但是又被覆盖了的话,它的 metal layer 没有释放 IOSurface,会影响混合栈的场景,目前的办法是覆盖的时候需要手动把 flutterview 去掉
  • 不支持数据共享
  • 不支持内存共享
  • platform View 不支持
  • 只支持一个 snapshot

七、结束语

ydtech

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

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

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

本文由哈喽比特于2年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/snnRWt_LOfNw3svDk-HxhQ

 相关推荐

刘强东夫妇:“移民美国”传言被驳斥

京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。

发布于:6月以前  |  808次阅读  |  详细内容 »

博主曝三大运营商,将集体采购百万台华为Mate60系列

日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。

发布于:6月以前  |  770次阅读  |  详细内容 »

ASML CEO警告:出口管制不是可行做法,不要“逼迫中国大陆创新”

据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。

发布于:6月以前  |  756次阅读  |  详细内容 »

抖音中长视频App青桃更名抖音精选,字节再发力对抗B站

今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。

发布于:6月以前  |  648次阅读  |  详细内容 »

威马CDO:中国每百户家庭仅17户有车

日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。

发布于:6月以前  |  589次阅读  |  详细内容 »

研究发现维生素 C 等抗氧化剂会刺激癌症生长和转移

近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。

发布于:6月以前  |  449次阅读  |  详细内容 »

苹果据称正引入3D打印技术,用以生产智能手表的钢质底盘

据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。

发布于:6月以前  |  446次阅读  |  详细内容 »

千万级抖音网红秀才账号被封禁

9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...

发布于:6月以前  |  445次阅读  |  详细内容 »

亚马逊股东起诉公司和贝索斯,称其在购买卫星发射服务时忽视了 SpaceX

9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。

发布于:6月以前  |  444次阅读  |  详细内容 »

苹果上线AppsbyApple网站,以推广自家应用程序

据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。

发布于:6月以前  |  442次阅读  |  详细内容 »

特斯拉美国降价引发投资者不满:“这是短期麻醉剂”

特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。

发布于:6月以前  |  441次阅读  |  详细内容 »

光刻机巨头阿斯麦:拿到许可,继续对华出口

据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。

发布于:6月以前  |  437次阅读  |  详细内容 »

马斯克与库克首次隔空合作:为苹果提供卫星服务

近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。

发布于:6月以前  |  430次阅读  |  详细内容 »

𝕏(推特)调整隐私政策,可拿用户发布的信息训练 AI 模型

据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。

发布于:6月以前  |  428次阅读  |  详细内容 »

荣耀CEO谈华为手机回归:替老同事们高兴,对行业也是好事

9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。

发布于:6月以前  |  423次阅读  |  详细内容 »

AI操控无人机能力超越人类冠军

《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。

发布于:6月以前  |  423次阅读  |  详细内容 »

AI生成的蘑菇科普书存在可致命错误

近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。

发布于:6月以前  |  420次阅读  |  详细内容 »

社交媒体平台𝕏计划收集用户生物识别数据与工作教育经历

社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”

发布于:6月以前  |  411次阅读  |  详细内容 »

国产扫地机器人热销欧洲,国产割草机器人抢占欧洲草坪

2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。

发布于:6月以前  |  406次阅读  |  详细内容 »

罗永浩吐槽iPhone15和14不会有区别,除了序列号变了

罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。

发布于:6月以前  |  398次阅读  |  详细内容 »
 相关文章
如何有效定位Flutter内存问题? 3年以前  |  14691次阅读
Flutter的手势GestureDetector分析详解 4年以前  |  10800次阅读
Flutter插件详解及其发布插件 4年以前  |  10578次阅读
在Flutter中添加资源和图片 5年以前  |  7837次阅读
 目录