| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 移动开发 -> 有道词典Flutter架构与应用 -> 正文阅读 |
|
[移动开发]有道词典Flutter架构与应用 |
联系我们:有道技术团队助手:ydtech01 / 邮箱: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 版本以下是业务展示: 单词本 听力模考 我们在较为独立的新业务上进行大胆尝试,新技术难免会有问题,但是还是要勇于尝试。 二、Flutter基础简介2.1 Dart2.1.1 Dart单线程模型Dart 和 JavaScript 都是单线程模型,运行机制很相似,Dart 官方提供了一张运行原理图: Dart 在单线程中是以消息循环机制来运行的,其中包含两个任务队列,一个是“微任务队列” microtask queue,另一个叫做“事件队列” event queue。从图中可以发现,微任务队列的执行优先级高于事件队列。 其中event queue:负责处理I/O事件、绘制事件、手势事件、接收其他isolate消息等外部事件;microtask queue:可以自己向isolate内部添加事件,事件的优先级比event queue高。 事件队列模型过程:
在事件循环中,当某个任务发生异常并没有被捕获时,程序并不会退出,而直接导致的结果是当前任务的后续代码就不会被执行了,也就是说一个任务中的异常是不会影响其它任务执行的。 异常捕获上传至统计崩溃平台也是应用这个模型,后面会讲到 2.2 Flutter Widget介绍完 Dart,我们再看下 Flutter Widget。在 Flutter 中一切皆为 Widget,通过使用 Widget 可以实现页面整体布局、文本展示、图片展示、手势操作、事件响应等。 2.2.1 StatelessWidgetStatelessWidget 是一个没有状态的 widget ——没有要管理的内部状态。它通过构建一系列其他小部件来更加具体地描述用户界面,从而描述用户界面的一部分。当我们的页面不依赖 Widget 对象本身中的配置信息以及BuildContext 时,就可以用到无状态组件。例如当我们只需要显示一段文字时。实际上 Icon、Divider、Dialog、Text 等都是 StatelessWidget 的子类。 2.2.2 StatefulWidgetStatefulWidget是可变状态的 widget。使用 setState 方法管理 StatefulWidget 的状态的改变。调用setState 通知 Flutter 框架某个状态发生了变化,Flutter 会重新运行 build 方法,应用程序变可以显示最新的状态。 状态是在构建 widget 的时候,widget 可以同步读取的信息,而这些状态会发生变化。要确保在状态改变的时候即使通知 widget 进行动态更改,就需要用到 StatefulWidget。例如一个计数器,我们点击按钮就要让数字加一。在 Flutter 中,Checkbox、FadeImage 等都是有状态组件。 StatefulWidget的生命周期大致可分为三个阶段:
具体的声明周期调用过程如下: 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 的判断依据:
三、混合开发 - 整体框架开发之初我们考虑两个问题:
起初我们希望生成多个产物进行嵌入,通过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 是一个专门用来编写文件配置的语言,下面是依赖示例:
所以,通过 Flutter Plugin / Flutter Package + Pub 达到解耦的目的 以 Flutter Plugin / Flutter Package为模块开发, 原则上我们将工程分为壳工程,业务组件,基础组件;依赖关系为壳工程->业务组件->基础组件,不能依赖倒置,同层之间不能相互引用。 3.2 基础组件沉淀通过以组件化形式进行开发,通过各个团队业务的不断迭代,逐步沉淀出一套CommonUI的基础Widget组件,方便其他业务和团队扩展使用。
3.3 元编程我们在开发过程发现我们的bridge的内容大多数是相同的,只不过是形参,函数名不同罢了,所以我们打算引入source_gen,来生成bridge层的代码,这样也带来两个好处,一是防止手误,带来的不必要的bug,二是将代码统一 source_gen主要提处理dart源码,可以通过注解生成代码。 大致的流程是通过 source_gen 一个 _Builder ,_Builder 需要生成器 Generator ,之后通过 Generator 去生成代码。 总结一下,在 Flutter 中应用注解以及生成代码仅需一下几个步骤: **1.依赖 **
2.创建注解
3.创建生成器
4.创建Builder
5.编写配置文件 在项目根目录创建 build.yaml 文件,配置各项参数
这样就为我们输出一份模板代码提供了实现的可能 5.4 资源管理众所周知,flutter图片等资源管理方面,还是处在手动管理阶段,费时费力,所以推荐一款网易严选团队开发的flr插件,flr配合通过AndroidStudio插件,将用于帮助Flutter开发者在修改项目资源后,可以自动为资源添加声明到 通过建立起一个自动化的服务来监听和管理资源变化,之后将变化的资源同步到 地址:https://github.com/Fly-Mix/flr-as-plugin/tree/d56e4b989c1de4b493d8e27b3f8ce4100af1f6df 3.5 异常捕获上传在flutter简介里面我们介绍了dart的线程模型 事件队列模型过程:
在事件循环中,当某个任务发生异常并没有被捕获时,程序并不会退出,而直接导致的结果是当前任务的后续代码就不会被执行了,也就是说一个任务中的异常是不会影响其它任务执行的。 Flutter 框架为我们在很多关键的方法进行了异常捕获。在发生异常时,错误是通过 FlutterError.reportError 方法上报的,其中 onError 是 FlutterError 的一个静态属性,我们重写onError就可以捕获异常了;但是还有一些异步异常是需要我们通过Zone方法来捕获的,整理代码如下: 四、产物介绍4.1 介绍1.12是个分水岭,在这之前安卓打包方式有所不同,并且iOS官方也提供一些命令也来支持打不同的包
通过 flutter build ios --release 来得到产物,然后 flutter-plugins 里面记录各种 plugin 的位置 copy 过来,放在.symlinks文件夹下 app.framework:代码数据段+图片
进入 flutter 工程的 .android 文件夹执行 ./gradlew assembleRelease 就会打出一个 flutter-release.aar 的包,但是还有 path_provider,share_preference,audioplayer 等官方插件我们也需要 copy出来,这里我们发现 flutter 工程目录下面有.flutter-plugins 这个文件,这个文件记录着你当前 flutter所使用的官方插件的文件位置,我们通过 shell 读取文件位置,找到对应的 aar 集中到一起。
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/flutter/issues/36527 解决方式有三种:
可以封装成一个widget
5.2 双端存在的问题flutter pub get失败 Flutter 项目在引用第三库时,在pub会选择使用 git 引用,如:
会报pub get fail的问题 在下载包的过程中出现问题,下次再拉包的时候,在 所以你需要清除掉 5.3 channel 通信Flutter定义了三种不同类型的Channel,它们分别是
其中channel有个很重要的变量codec;Codec官方定义了两种Codec:MessageCodec和MethodCodec 其中MessageCodec有4种不同的种类: BinaryCodec;StringCodec;JSONMessageCodec;StandardMessageCodec 起初我们使用MethodChannel来建立通信,但是使用过程中遇到大内存的传递耗时很长的问题,我们通过一系列的实验和官方文档的指引,当需要传递大内存数据块时,使用BasicMessageChannel以及BinaryCodec可以解决问题。 以下是实验内容:
5.4 长列表优化5.4.1 列表内容项长短不一当我们实现类似上面的页面,item 高度不一的思路肯定是类似以下的代码,但是当我们达到一定的数量级的情况下,发现内存占用的十分严重,导致有些需求就实现不了,比如说支持滚动条快速定位;究其因CustomScrollView 初始化就加载了很多的 widget
但是假如都是同样的 itemExtent,滚动效率,内存表现都是良好的;因为事先告诉好高度,而不是依赖 widget自身的 layout 计算效率就高了很多, 比如说以下的代码:
如何两者兼得呢? 我们决定自定义SliverFixedExtentList, SliverFixedExtentList返回的RenderObject是RenderSliverFixedExtentBoxAdaptor,我们将RenderSliverFixedExtentBoxAdaptor重新设计下。 RenderSliverFixedExtentBoxAdaptor原先设计的思路是通过scrolloffset除以itemExtent计算出当前的index(itemExtent是写死的所以直接除),SliverConstraints可以拿到他的remainingCacheExtent也就是cacheExtent加上滚动可见区域,也就可以拿到lastIndex,在滚动的过程中不断的释放和创建。我们改写的思路如下:
大致的思路就是这样,接口层面我们设计成这个样子,以下是调用示例:
5.4.2 如何获取当前展示列表索引ios开发都知道,我们的TableView是有代理来知道我们当前页面展示的Cell的索引,但是在Flutter里我们怎么办呢?
代码如下:
然后找到对应绑定的model
不过,接下来介绍的是,另一种办法,改写 SliverChildBuilderDelegate,在SliverChildBuilderDelegate里面的didFinishLayout里会返回它的firstIndex和lastIndex,但是要注意此时返回的是加了cacheExtent的firstIndex,所以可能比实际展示的要小,所以可以结合思路一进行精确定位
思路3:可以参考长列表优化,将SliverFixedExtentList改写暴露对应返回index的接口 六、后续计划我们后续计划升级到flutter2.0,但是目前来看2.0还存在问题,如果2.0真的彻底的解决多引擎复用的问题,我们也会尝试去除boost的管理机制,根据https://flutter.cn/posts/flutter-engage-china-event-recap,在多个引擎复用的视频章节,于潇分析了多引擎复用的内存增长的问题,主要在
这五部分,每起一个 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 版本只是解决了内存问题,还存在其他的问题,主要是以下几方面:
七、结束语我们在单词本和听力等模块进行 flutter 落地的探索,在前期实践过程中,碰到了很多问题,但总体来说还处于可控的状态;前期把各种困难都解决后,后面业务再此基础上进行开发会顺畅很多,效率会提升很多,这个也是 flutter 期望带给我们的一次开发,多端运行。但是另一方面希望开发者们在落地过程中,更为慎重些,多多实践,提前发现提前解决,毕竟存在处理不好的情况,还需要推动官方或者生态提供更好的解决办法。 未来期望 flutter 以及社区在平台一致性以及混合栈,内存,键盘,音视频等具体问题上持续发力,我们也会进一步的探索 flutter 在业务上更多实现的可能。感谢观看。 以上内容仅代表个人观点,如果内容或者实验数据存在疑问和问题,欢迎大家批评指正,一起学习,一起成长。 本内容仅代表个人观点,不代表网易,仅供内部分享传播,不允许以任何形式外泄,否则追究法律责任。 |
|
移动开发 最新文章 |
Vue3装载axios和element-ui |
android adb cmd |
【xcode】Xcode常用快捷键与技巧 |
Android开发中的线程池使用 |
Java 和 Android 的 Base64 |
Android 测试文字编码格式 |
微信小程序支付 |
安卓权限记录 |
知乎之自动养号 |
【Android Jetpack】DataStore |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2025年1日历 | -2025/1/31 5:42:45- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |