快手、孩子王、华为等专家分享大模型在电商运营、母婴消费、翻译等行业场景的实际应用 了解详情
写点什么

闲鱼基于 Flutter 的移动端跨平台应用实践

  • 2018-09-10
  • 本文字数:6858 字

    阅读完需:约 23 分钟

闲鱼为什么使用 Flutter

Flutter 作为 Google 新一代的跨平台框架,有较多的优点,但跟其他跨平台解决方案相比,最吸引我们的是它的高性能,可以轻松构建更流畅的 UI。虽然各跨平台方案都有各自的特点,但 Flutter 的出现,给闲鱼、给大家都提供了一种新的可能性。

那么,Flutter 为什么会有高性能呢?

首先,Flutter 自建了一个绘制引擎,底层是由 C++ 编写的引擎,负责渲染,文本处理,Dart VM 等;上层的 Dart Framework 直接调用引擎。避免了以往 JS 解决方案的 JS Bridge、线程跳跃等问题。

第二,引擎基于 Skia 绘制,操作 OpenGL、GPU,不需要依赖原生的组件渲染框架。

第三,Dart 的引入,是 Flutter 团队做了很多思考后的决定,Dart 有 AOT 和 JIT 两种模式,线上使用时以 AOT 的方式编译成机器代码,保证了线上运行时的效率;而在开发期,Dart 代码以 JIT 的方式运行,支持代码的即时生效(HotReload),提高开发效率。

第四,Flutter 的页面和布局是基于 Widget 树的方式,看似不习惯,但这种树状结构解析简单,布局、绘制都可以单次遍历完成计算,而原生布局往往要往复多次计算,“simple is fast”的设计效果。

下面截图是目前闲鱼已经上线的商品详情页面:

商品详情页包含混合栈、视频、动画、原生组件、多图、留言盖楼等功能,页面较复杂,有代表性,也是闲鱼最重要的页面之一。选择商品详情页做为第一个 Flutter 页面,是闲鱼能成功快速使用起 Flutter 的重要因素。

接下来介绍一下,闲鱼的实践过程和总结。

Flutter 与 Native 混合开发实践

Flutter Hybrid 工程实践(研发时)

我们把 Flutter 和闲鱼现有的 APP 做渐进式的整合,App 中会同时有 Native、Flutter 和 H5 页面。现有的 Flutter Demo 和应用,都是独立的 Flutter 应用,而当把它和 Native 混合的时候,会碰到很多的困难。

首先是研发时的问题,怎么让 Flutter 在现有的 Native 工程中开发起来。这个要从这张图说起:

闲鱼 Flutter 工程结构如图,三个蓝色背景的目录分别是安卓工程、iOS 工程和 main.dart 入口。编译产物中以 iOS 为例,APP Framework 是 Flutter 应用页面代码,Flutter Framework 是 Flutter 引擎。

这个过程,需要重点考虑几个问题:如何基于现有工程搭建混合工程?如何支持过渡期的 Flutter 开发及纯 Native 开发的双开发模式?如何让 Flutter 与现有持续集成、构建工具集成?

首先,现有的 Native 工程并不符合 Flutter 默认的规范,两者不能完全匹配,需要修改打包脚本,甚至修改 Flutter 的打包 Tool 来解决。另外,我们通过 Submodule 将现有⼯程引入到 Flutter 父工程中。

纯 Native 开发同学,不需要引入 Flutter 工程,直接在 iOS 或 Android 工程下开发,Flutter 以产物的方式集成到 Native 中运行,Flutter 的开发同学引入 Submodule。

上图是工程上的修改点。绿色虚线部分是 Flutter 默认的结构,红色虚线是闲鱼在 Flutter 基础上做的定制。Flutter 的构建工具 gen_snapshot,会把业务代码,Flutter 框架、引擎编译成中间产物,以 so 或 Framework 的方式变成 Native 的一部分。

几个主要的改动点:

第一,构建私有的仓库,用来管理阿里私有包,如 CDN、无线网关等中间件适配 Package。

第二,构建工具和引擎的优化。

第三,跟现有的构建工具打通,混合调试等。

Flutter Hybrid 栈管理

除了上述的研发时问题,接下来就是让它跑起来,解决运行时问题。其中最重要的是实现混合栈。

混合栈的定义

在混合工程中,Native 页面,Flutter 页面之间会以多种可能的顺序混合入栈,出栈。要怎么去做?先看一下 Flutter 内部栈的管理默认下是怎么做的:

整个 Flutter 运行在一个单例的 Activity 容器里(用安卓举例),Flutter 内部的所有页面都在这个容器中管理。 对安卓来说,怎样把这样容器里面的栈与 Native 栈混合起来,直接的一个想法就要把栈自己托管起来,把这个容器在 Android 的栈中来回移动。但 Android 里想这样操作非常难。

所以解决这个事情,就主要有两个问题要考虑,首先就是混合栈要在哪里管理?是在 Hybrid 栈管理,还是在 Flutter 管理,第二个就是关于实例剥离的问题,既然移动单例很复杂,那就把单例剥离出来,在上面 Wrap 出多个实例,这样就方便管理了。 下面是两个对比方案。

这两种方案都是可选的,方案一就是把 Flutter 直接变成多例,每个 Flutter 页面重新启动一个 Flutter 的容器,每个 Flutter 页面就像通常使用 WebView 一样,这个方便我们做了实测,发现它的启动速度有影响,能感觉到一些卡顿,另外,还有一个问题,当我想在两个页面之间去复用数据的时候,那两个引擎之间是完全隔离的,最后数据不好复用。 这个方案的好处是很简单,如果喜欢隔离性,也可以变成优点。

第二种方案,就是做浅层的单例剥离,尽量多的遵守 Flutter 的标准运行方式,以最小的影响把单例剥离出来,Wrap 成多例。

这种方案是在 Flutter View 这一层剥离,关于 Flutter View 的概念看一下源码很容易理解。

这种解决方案的好处是可以实现多页面复用,因为不用每次都取一个新的实例,加载速度会更快,因为对闲鱼来讲,我们追求的就是性能,最后我们的选择就是方案二。

这个是具体的实现方式:

把下面的 View 复用,在多个 Activity 之间移动,切换到下一个页面的时候,把这个可复用的 View 从前一个 Activity 移走,放到下一个 Activity,这是它的主要的思路。

在这个思路下也会遇到一些需要解决的问题:

  • 两个页面转场动画由于 View 在 Activity 间移动,会有一个短暂的白色闪屏,体验不好,解决闪屏的办法,就是做一个截图,从 A 页面到 B 页面的时候,对 A 页面做个截图,同时把 Flutter 自带栈的转场动画禁止掉,有这个截图,转场时就不会有闪屏的感觉了。
  • 考虑对统一 OpenUL 支持,把 Flutter 和 Native 的 URL 统一。
  • 由于 Flutter 容器内部有个栈管理,对这个栈需要与 Native 做同步的跟随。

到此,混合栈的方案就简单介绍完了。

基于 Texture 的自定义视频播放器

接下来,如果 Flutter 页面中想复用已有的 Native 组件,怎么办?

一种情况是视频播放器,Native 中我们做过很多优化的播放器,希望能复用到 Flutter 页面中。

首先,还是先看原理:

Flutter 内部的渲染,与通常的做法一样,有 layer。其中一种 Layer 叫 Texture Layer,可以把任何其他地方计算出来的纹理直接贴到 Flutter 的 Texture Layer 上。不管是视频,还是图片,如果有需要,都可以用 Texture Layer。

在这个实现的方式中,Flutter 侧负责展示这个播放器 UI,接收对播放器做控制交互,而 Native 侧负责视频的渲染,通过 TextureLayer 展示到 Flutter 侧。而控制协议,通过 Flutter 特有的 MethodChannel 来控制。

除了视频,还有没有其他类型的 Native 组件能复用到 Flutter 中?像下图这样,把 Native 控件放在 View/Window 中与 Flutter 混合,是可以的。但截止演讲时,Flutter 还无法做到在 Flutter 中挖个小天窗嵌入 Native 组件。不过这个方式 Google Flutter 团队已经在做尝试,未来可能做有办法支持,大家可以关注。

Flutter 通用问题实践

接下来,介绍一下 Flutter 商品详情页的页面的开发框架。

页面框架

右边边绿色的这一部分,就是整个页面的结构,整个详情页面是一个大列表,由商品的描述、图片,评论,个性化推荐等组成。这里简单概括几个特点:

  • 通过 Server 端返回的数据驱动 UI 界面,可以一定程度上获得页面内容的动态能力。Flutter 本身不支持动态更新,无法像 JS 那样,所以这种设计方式可以一定程度上弥补这方面的短板。
  • Widget 树结点间(或者说页面的不同组件间)的数据如何共享?这里大家知道 InheritedWidget 这个类就好了,这是解决数据共享的很有用的类。
  • 如果页面再复杂些,有很多交互,希望将视频、交互、数据等分离怎么办?也可以考虑引入 Redux 框架。

统一协议

Flutter 不支持 Dart 的反射(mirror),所以在开发 Flutter 页面时,解析服务端返回的数据,生成 Flutter 对象时,可能会很不习惯,需要有较多的硬编码。 Flutter 不支持反射,请大家理解,这样可以获得 tree shaking 能力,减少 Flutter 包的大小。

既然不支持反射,怎么去解决刚才说的数据转换问题?我们实现了一个统一协议层,把 Serve 端和客户端的请求接口和数据模型,都通过协议统一生成代码,避免了手工编码。

图片缓存方案

闲鱼的页面中有大量图片,但 Flutter 默认的图片缓存策略比较简单,截止演讲时,如上图所示,默认图片缓存策略是按照图片数量,以 1000 为上限,LRU 的方式置换。当大图片较多时,这会占用过多的内存,容易造成 Crash 或 Abort。

在我们只有详情页一种页面时,解决这个问题可以用简单粗暴的方式,首先把 1000 这个数量调小。一种修改方式如图所示,通过 WidgetsFlutterBinding 来修改(WidgetsFlutterBinding 是 Flutter 中很重要的一个机制,有兴趣可以深入了解)。

此外,还要注意图片尺寸自适应剪裁,支持 WebP 等,这些对节省图片内存和网络流量都很关键。

第二种解决方案,是官方正在做的优化,按照整个空间的大小来做缓存策略,具体可以关注图中的链接。

第三种方案,更加完善,加一层持久层的缓存,以实际的经验来看,闲鱼的场景下,持久层缓存时,通常可以提高缓存命中率 10% 到 30% 。

上线效果

线上 Crash 率

大家可能会关心 Flutter 在生产环境的稳定性,兼容性等表现。闲鱼使用 Flutter 的前期阶段,这方面确实有很大的问题。前期在真实环境中发现了很多问题,第一次灰度测试时 Crash 率有百分之一的量级,主要的 Crash 问题包括内存、GPU、icu data、视频播放、截图接口、armv7、字体缺失等。

我们和 Google 团队一起,通过几个版本的灰度迭代,用了一个半月的时间,把问题逐步解决了,目前 Crash 率收敛稳定,达到万分之一的量级,已经达到了生产标准。

Flutter 与 Native 详情页性能对比

我们对 Flutter 与 Native 的详情页做了简单的性能对比,并不严谨,仅供参考。

测试场景:进入宝贝详情页后快速浏览到页面底部,从猜你喜欢进入第二个宝贝,重复进行访问 10 个不同宝贝详情。对比 Native 版详情页和 Flutter 版详情页。

测试机型,以低端机型为主(高端机型区分不明显):

Android 4.x, 5.x...

iPhone 5c, 6s...

安卓的对比:

下面两行是体现流畅度的,LPS 或者 MS 是腾讯提出的一种流畅度的表达方式,在流畅度是 OK 的,比 Native 详情页做的好,在技术的指标上也还不错。

iOS 的结果:

iOS 上,也是 Flutter 会更流畅一些,测下来,发现在 GPU 的使用率上,Flutter 会更高一些,Flutter 在这上有更进一步的优化空间。

说到这里,可能大家也会疑惑,这个对比结果,是不是因为以前 Native 写的详情页太复杂了? 确实有这种可能。但主要分享的是,两种页面是相同团队成员开发的,并且没有针对 Flutter 做专门的性能优化,这个性能测试可以确定的结论是,使用 Flutter 还是比较容易就能开发出与 Native 性能相近的页面。

最后,说一下大家可能会关于的成本问题。对于混合开发,初期接入成本是有的。如果是全新的 Flutter 独立应用,接入成本会很低。首次接入完成后,后面开始会顺利很多,可以享受跨端统一编程,一套代码带来的效率快感。另外,关于学习成本,还好,因为 Dart 语言跟 Java 很像,跟 JS 也很像,另外 Flutter 的 UI 框架遵循响应式,声明式设计原则,个人感觉,较容易上手。谢谢大家,由于水平有限,可能会有错误,请大家指正。篇幅有限本文无法对每个细节深入探讨,关于细节的深入分享,欢迎大家关注“闲鱼技术”的公众号。

作者简介

王树彬,阿里巴巴闲鱼无线技术专家,毕业于浙江大学,2009 年加入阿里巴巴,现任阿里巴巴闲鱼架构负责人,负责闲鱼从端到云的整体架构升级。有十余年互联网研发经验。曾负责移动端 LBS 技术,是淘宝位置归一、地理围栏等技术的开拓者,为个性化、O2O 等业务提供基础能力。也曾负责淘宝的商家系统,建立商家十亿级大数据下的实时在线查询、挖掘服务。

感谢覃云对本文的审校。

2018-09-10 18:0725668

评论 1 条评论

发布
用户头像
学习,mark
2019-03-06 10:23
回复
没有更多了
发现更多内容

洞见科技姚明:隐私计算行业将会发展为多层级多领域的数据智能流通网络

洞见科技

HUAWEI DevEco Studio 3.1版本发布,配套ArkTS声明式开发全面升级

HarmonyOS开发者

HarmonyOS

有奖报名|StarRocks 获开源热力值增速第一,有你的贡献

StarRocks

数据库

打开时空隧道,重演云栖72小时云世界

阿里云视频云

阿里云 云栖大会

分布式锁

急需上岸的小谢

11月月更

阿里P8出,入职阿里必会199道SpringCloud面试题,你能掌握多少?

钟奕礼

Java java程序员 java面试 java编程

张文歆:思维需碰撞,才有更大的“火花”|对话 Doris

SelectDB

开源 职场 成长 学习路线 开源治理

EventBridge 生态实践:融合 SLS 构建一体化日志服务

阿里巴巴云原生

阿里云 云原生 EventBridge

首次!阿里巴巴团队共同携手编写“大厂面试参考指南”v1.0版本

钟奕礼

Java 面试 java程序员 java 编程 #java Java 面试题

HMS Core手语服务荣获2022中国互联网大会“特别推荐案例”:助力建设数字社会

HarmonyOS SDK

手语 HMS Core

精彩议程更新,从云原生到 Serverless 的思考和收获,邀你共同见证丨PingCAP DevCon 2022

PingCAP

TiDB

「Go实战」在 Go 项目中基于本地内存缓存的实现及应用

Go学堂

golang 缓存 开源 程序员 性能

千万级学生管理系统设计试卷存储方案

Geek_92ba6f

IM通讯协议专题学习(二):快速理解Protobuf的背景、原理、使用、优缺点

JackJiang

node.js的path路径模块和http模块

急需上岸的小谢

11月月更

战略合作再升级!合合信息与腾讯云联合推出海外智能风控方案

科技热闻

学历不是问题!社招大专老哥阿里 腾讯Java面试,上岸入职京东

钟奕礼

java程序员 java面试 java编程 #java

Dive into TensorFlow系列(3)- 揭开Tensor的神秘面纱

京东科技开发者

Python 人工智能 深度学习 tensorflow

软件测试校招面试题 | 实习生和应届生有什么区别?

测试人

面试 软件测试 自动化测试 测试开发 实习

软件测试 | 测试开发 | 校招面试真题 | 实习生和应届生有什么区别?

测吧(北京)科技有限公司

软件测试 软件测试工程师

探知数字化研发4 - 底座篇

薛飞

数字化研发 数字化底座

加密算法是什么?有哪几种类型?有什么用?

行云管家

加密算法

DTSE Tech Talk | 第11期:深入浅出畅谈华为云低时延直播技术

华为云开发者联盟

云计算 后端 华为云

软件测试 | 接口自动化你不懂?听HttpRunner的作者怎么说

测试人

软件测试 自动化测试 接口测试 接口自动化 HttpRunner

StarRocks 与 DataPipeline 完成兼容性互认证,携手共建数据基础设施生态

StarRocks

数据库

云服务器的四大作用讲解-行云管家

行云管家

云计算 服务器 云服务 云服务器

node.js的模块化与npm

急需上岸的小谢

11月月更

应用程序现代化指南

世开 Coding

应用现代化 软件升级

数据库精选 60 道面试题

钟奕礼

Java Java 面试 java程序员 java编程

一文带你回顾操作系统的内存知识点

华为云开发者联盟

操作系统 开发 内存 华为云

计算机网络:IEEE 802.11无线局域网

timerring

计算机网络 11月月更

闲鱼基于Flutter的移动端跨平台应用实践_阿里巴巴_王树彬_InfoQ精选文章