码上用它开始Flutter混合开发——FlutterBoost

开源地址: https://github.com/alibaba/flutter_boost

为什么需要混合方案

具有一定规模的App通常有一套成熟通用的基础库,尤其是阿里系App,一般需要依赖很多体系内的基础库。那么使用Flutter重新从头开发App的成本和风险都较高。所以在Native App进行渐进式迁移是Flutter技术在现有Native App进行应用的稳健型方式。闲鱼在实践中沉淀出一套自己的混合技术方案。在此过程中,我们跟Google Flutter团队进行着密切的沟通,听取了官方的一些建议,同时也针对我们业务具体情况进行方案的选型以及具体的实现。

官方提出的混合方案

基本原理

Flutter技术链主要由C++实现的Flutter Engine和Dart实现的Framework组成(其配套的编译和构建工具我们这里不参与讨论)。Flutter Engine负责线程管理,Dart VM状态管理和Dart代码加载等工作。而Dart代码所实现的Framework则是业务接触到的主要API,诸如Widget等概念就是在Dart层面Framework内容。

一个进程里面最多只会初始化一个Dart VM。然而一个进程可以有多个Flutter Engine,多个Engine实例共享同一个Dart VM。

我们来看具体实现,在iOS上面每初始化一个FlutterViewController就会有一个引擎随之初始化,也就意味着会有新的线程(理论上线程可以复用)去跑Dart代码。Android类似的Activity也会有类似的效果。如果你启动多个引擎实例,注意此时Dart VM依然是共享的,只是不同Engine实例加载的代码跑在各自独立的Isolate。

官方建议

引擎深度共享

在混合方案方面,我们跟Google讨论了可能的一些方案。Flutter官方给出的建议是从长期来看,我们应该支持在同一个引擎支持多窗口绘制的能力,至少在逻辑上做到FlutterViewController是共享同一个引擎的资源的。换句话说,我们希望所有绘制窗口共享同一个主Isolate。

但官方给出的长期建议目前来说没有很好的支持。

多引擎模式

我们在混合方案中解决的主要问题是如何去处理交替出现的Flutter和Native页面。Google工程师给出了一个Keep It Simple的方案:对于连续的Flutter页面(Widget)只需要在当前FlutterViewController打开即可,对于间隔的Flutter页面我们初始化新的引擎。

例如,我们进行下面一组导航操作:

Flutter Page1 -> Flutter Page2 -> Native Page1 -> Flutter Page3 

我们只需要在Flutter Page1和Flutter Page3创建不同的Flutter实例即可。

这个方案的好处就是简单易懂,逻辑清晰,但是也有潜在的问题。如果一个Native页面一个Flutter
页面一直交替进行的话,Flutter Engine的数量会线性增加,而Flutter Engine本身是一个比较重的对象。

多引擎模式的问题

  • 冗余的资源问题.多引擎模式下每个引擎之间的Isolate是相互独立的。在逻辑上这并没有什么坏处,但是引擎底层其实是维护了图片缓存等比较消耗内存的对象。想象一下,每个引擎都维护自己一份图片缓存,内存压力将会非常大。
  • 插件注册的问题。插件依赖Messenger去传递消息,而目前Messenger是由FlutterViewController(Activity)去实现的。如果你有多个FlutterViewController,插件的注册和通信将会变得混乱难以维护,消息的传递的源头和目标也变得不可控。
  • Flutter Widget和Native的页面差异化问题。Flutter的页面是Widget,Native的页面是VC。逻辑上来说我们希望消除Flutter页面与Naitve页面的差异,否则在进行页面埋点和其它一些统一操作的时候都会遇到额外的复杂度。
  • 增加页面之间通信的复杂度。如果所有Dart代码都运行在同一个引擎实例,它们共享一个Isolate,可以用统一的编程框架进行Widget之间的通信,多引擎实例也让这件事情更加复杂。

因此,综合多方面考虑,我们没有采用多引擎混合方案。

现状与思考

前面我们提到多引擎存在一些实际问题,所以闲鱼目前采用的混合方案是共享同一个引擎的方案。这个方案基于这样一个事实:任何时候我们最多只能看到一个页面,当然有些特定的场景你可以看到多个ViewController,但是这些特殊场景我们这里不讨论。

我们可以这样简单去理解这个方案:我们把共享的Flutter View当成一个画布,然后用一个Native的容器作为逻辑的页面。每次在打开一个容器的时候我们通过通信机制通知Flutter View绘制成当前的逻辑页面,然后将Flutter View放到当前容器里面。

老方案在Dart侧维护了一个Navigator栈的结构。栈数据结构特点就是每次只能从栈顶去操作页面,每一次在查找逻辑页面的时候如果发现页面不在栈顶那么需要往回Pop。这样中途Pop掉的页面状态就丢失了。这个方案无法支持同时存在多个平级逻辑页面的情况,因为你在页面切换的时候必须从栈顶去操作,无法再保持状态的同时进行平级切换。

举个例子:有两个页面A,B,当前B在栈顶。切换到A需要把B从栈顶Pop出去,此时B的状态丢失,如果想切回B,我们只能重新打开B之前页面的状态无法维持住。这也是老方案最大的一个局限。

如在pop的过程当中,可能会把Flutter 官方的Dialog进行误杀。这也是一个问题。

而且基于栈的操作我们依赖对Flutter框架的一个属性修改,这让这个方案具有了侵入性的特点。这也是我们需要解决的一个问题。

具体细节,大家可以参考老方案开源项目地址:

https://github.com/alibaba-flutter/hybrid_stack_manager

新一代混合技术方案 FlutterBoost

重构计划

在闲鱼推进Flutter化过程当中,更加复杂的页面场景逐渐暴露了老方案的局限性和一些问题。所以我们启动了代号FlutterBoost(向C++ Boost致敬)的新混合技术方案。这次新的混合方案我们的主要目标有:

  • 可复用通用型混合方案
  • 支持更加复杂的混合模式。比如支持主页Tab这种情况
  • 无侵入性方案:不再依赖修改Flutter的方案
  • 支持通用页面生命周期
  • 统一明确的设计概念

跟老方案类似,新的方案还是采用共享引擎的模式实现。主要思路是由Native容器Container通过消息驱动Flutter页面容器Container,从而达到Native Container与Flutter Container的同步目的。我们希望做到Flutter渲染的内容是由Naitve容器去驱动的。

简单的理解,我们想做到把Flutter容器做成浏览器的感觉。填写一个页面地址,然后由容器去管理页面的绘制。在Native侧我们只需要关心如果初始化容器,然后设置容器对应的页面标志即可。

主要概念

Native层概念

  • Container:Native容器,平台Controller,Activity,ViewController
  • Container Manager:容器的管理者
  • Adaptor:Flutter是适配层
  • Messaging:基于Channel的消息通信

Dart层概念

  • Container:Flutter用来容纳Widget的容器,具体实现为Navigator的派生类-
  • Container Manager:Flutter容器的管理,提供show,remove等Api
  • Coordinator: 协调器,接受Messaging消息,负责调用Container Manager的状态管理。
  • Messaging:基于Channel的消息通信

关于页面的理解

在Native和Flutter表示页面的对象和概念是不一致的。在Native,我们对于页面的概念一般是ViewController,Activity。而对于Flutter我们对于页面的概念是Widget。我们希望可统一页面的概念,或者说弱化抽象掉Flutter本身的Widget对应的页面概念。换句话说,当一个Native的页面容器存在的时候,FlutteBoost保证一定会有一个Widget作为容器的内容。所以我们在理解和进行路由操作的时候都应该以Native的容器为准,Flutter Widget依赖于Native页面容器的状态。

那么在FlutterBoost的概念里说到页面的时候,我们指的是Native容器和它所附属的Widget。所有页面路由操作,打开或者关闭页面,实际上都是对Native页面容器的直接操作。无论路由请求来自何方,最终都会转发给Native去实现路由操作。这也是接入FlutterBoost的时候需要实现Platform协议的原因。

另一方面,我们无法控制业务代码通过Flutter本身的Navigator去push新的Widget。对于业务不通过FlutterBoost而直接使用Navigator操作Widget的情况,包括Dialog这种非全屏Widget,我们建议是业务自己负责管理其状态。这种类型Widget不属于FlutterBoost所定义的页面概念。

理解这里的页面概念,对于理解和使用FlutterBoost至关重要。

与老方案主要差别

前面我们提到老方案在Dart层维护单个Navigator栈结构用于Widget的切换。而新的方案则是在Dart侧引入了Container的概念,不再用栈的结构去维护现有的页面,而是通过扁平化key-value映射的形式去维护当前所有的页面,每个页面拥有一个唯一的id。这种结构很自然的支持了页面的查找和切换,不再受制于栈顶操作的问题,之前的一些由于pop导致的问题迎刃而解。同时也不再需要依赖修改Flutter源码的形式去进行实现,除去了实现的侵入性。

那这是如何做到的呢?

多Navigator的实现

Flutter在底层提供了让你自定义Navigator的接口,我们自己实现了一个管理多个Navigator的对象。当前最多只会有一个可见的Flutter Navigator,这个Navigator所包含的页面也就是我们当前可见容器所对应的页面。

Native容器与Flutter容器(Navigator)是一一对应的,生命周期也是同步的。当一个Native容器被创建的时候,Flutter的一个容器也被创建,它们通过相同的id关联起来。当Native的容器被销毁的时候,Flutter的容器也被销毁。Flutter容器的状态是跟随Native容器,这也就是我们说的Native驱动。由Manager统一管理切换当前在屏幕上展示的容器。

我们用一个简单的例子描述一个新页面创建的过程:

  1. 创建Native容器(iOS ViewController,Android Activity or Fragment)。
  2. Native容器通过消息机制通知Flutter Coordinator新的容器被创建。
  3. Flutter Container Manager进而得到通知,负责创建出对应的Flutter容器,并且在其中装载对应的Widget页面。
  4. 当Native容器展示到屏幕上时,容器发消息给Flutter Coordinator通知要展示页面的id.
  5. Flutter Container Manager找到对应id的Flutter Container并将其设置为前台可见容器。

这就是一个新页面创建的主要逻辑,销毁和进入后台等操作也类似有Native容器事件去进行驱动。

总结

目前FlutterBoost已经在生产环境支撑着在闲鱼客户端中所有的基于Flutter开发业务,为更加负复杂的混合场景提供了支持。同时也解决了一些历史遗留问题。

我们在项目启动之初就希望FlutterBoost能够解决Native App混合模式接入Flutter这个通用问题。所以我们把它做成了一个可复用的Flutter插件,希望吸引更多感兴趣的朋友参与到Flutter社区的建设。我们的方案可能不是最好的,这个方案距离完美还有很大的距离,我们希望通过多分享交流以推动Flutter技术社区的发展与建设。我们更希望看到社区能够涌现出更加优秀的组件和方案。

在有限篇幅中,我们分享了闲鱼在Flutter混合技术方案中积累的经验和代码。欢迎兴趣的同学能够积极与我们一起交流学习。

扩展补充

性能相关

在两个Flutter页面进行切换的时候,因为我们只有一个Flutter View所以需要对上一个页面进行截图保存,如果Flutter页面多截图会占用大量内存。这里我们采用文件内存二级缓存策略,在内存中最多只保存2-3个截图,其余的写入文件按需加载。这样我们可以在保证用户体验的同时在内存方面也保持一个较为稳定的水平。

页面渲染性能方面,Flutter的AOT优势展露无遗。在页面快速切换的时候,Flutter能够很灵敏的相应页面的切换,在逻辑上创造出一种Flutter多个页面的感觉。

Release 1.0支持

项目开始的时候我们基于闲鱼目前使用的Flutter版本进行开发,而后进行了Release 1.0兼容升级测试目前没有发现问题。

接入

只要是集成了Flutter的项目都可以用官方依赖的方式非常方便的以插件形式引入FlutterBoost,只需要对工程进行少量代码接入即可完成接入。
详细接入文档,请参阅GitHub主页官方项目文档。

现已开源

目前,新一代混合栈已经在闲鱼全面应用。我们非常乐意将沉淀的技术回馈给社区。欢迎大家一起贡献,一起交流,携手共建Flutter社区。

原文链接
本文为云栖社区原创内容,未经允许不得转载。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/519205.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Linux系统常用指令总结

来源 | CSDN 博客作者 | 不撸代码闲得慌,责编 | Carol出品 | CSDN云计算(ID:CSDNcloud)系统的运行级别0:关机1:单用户模式(可以找回丢失的密码)2:多用户状态没有网络服务…

像数据科学家一样思考:12步指南(下)

第三阶段-完成 一旦产品构建完成,你仍然需要做一些事情来使项目更加成功并使你的未来生活更轻松。那么我们如何完成数据科学项目呢? 10-交付产品 完成阶段的第一步是产品交付。为了创建可以交付给客户的有效产品,首先必须了解客户的观点。其…

基于Tablestore管理海量快递轨迹数据架构实现

快递轨迹管理 对于一个快递公司,在全国范围内有着大量的快递点、快递员、运输车辆以及仓储中心。而快递自产生后,就会在这些地点、人物之间流转。因而,一套完善的快递管理追踪系统是快递公司的重要管理工具; 用户通过平台客户端…

完了!Python黄了! 80%的程序员:痛快!你怎么看?

Python真的万能语言?在我的一个朋友看来,他坚信 Python 可以做任何事情。其实我是不服的,因为我在某网站看到有条评论:Python将要黄了!事实究竟如何?这篇文章会揭开这个黑幕,让程序员看清现实&a…

趣谈预留实例券,一文搞懂云上省钱最新玩法

ECS近期推出了预留实例券(Reserved Instances),简称RI,这东西很cool,今天我们聊聊这个。 首先这篇Blog不是文档,读完后想详细了解文档的朋友请点击 一个小故事 我来给大家讲一个故事理解云上的几种付费方…

到底什么是空指针?如何避免空指针_01

文章目录1. 场景案例2. 针对于空指针的场景,有哪些方式可以避免问题1. 场景案例 package com.gblfy;import org.springframework.beans.factory.annotation.Autowired;import javax.servlet.http.HttpServletRequest;/*** 理解什么是空指针*/ public class WhatIsn…

在 IntelliJ IDEA 中部署应用到服务器(Eclipse)

在之前的文章《在 Intellij IDEA 中部署 Java 应用到 阿里云 ECS》中讲解了如何将一个本地应用部署到阿里云 ECS 上去,有些读者反馈目前还有一些测试机器是在经典网络,甚至是在本地机房中,咨询是否可以通过 Cloud Toolkit 插件将应用部署到这…

linux所有文件打包压缩,Linux基础教程:对文件打包压缩

一、须知文件数量太多, 如果需要拷来拷去是不是很麻烦? 怎么办? 打包!文件太大,通过网络下载、传输会不会很费时间? 怎么办? 压缩!在Linux环境中,打包压缩文件的…

钉钉平台助力中国一汽疫情防控 数字化迈入新阶段

面对突如其来的疫情,中国一汽紧急应对、快速响应,携旗下一汽-大众、一汽丰田捐赠资金8100万元,并成立了疫情防控专项基金。同时,通过旗下“一汽出行”公司组织 “特别爱心车队”,在抗击疫情期间,提供安全、…

阿里靠什么支撑 EB 级计算力?

阿里妹导读:MaxCompute 是阿里EB级计算平台,经过十年磨砺,它成为阿里巴巴集团数据中台的计算核心和阿里云大数据的基础服务。去年MaxCompute 做了哪些工作,这些工作背后的原因是什么?大数据市场进入普惠红海的新阶段&a…

为什么说流处理即未来?

本文整理自 Flink 创始公司 Ververica 联合创始人兼 CTO - Stephan Ewen 在 Flink Forward China 2018 上的演讲《Stream Processing takes on Everything》。 这个演讲主题看似比较激进:流处理解决所有问题。很多人对于 Flink 可能还停留在最初的认知,…

别再用那些已经淘汰的技术了!2020 年 9 大顶级 Java 框架出炉!!

来源 | Patricia Neil责编 | Carol出品 | CSDN云计算(ID:CSDNcloud)顶级Java框架#1:Spring顶级Java框架#2:Hibernate顶级Java框架#3:Struts顶级Java框架#4:Play顶级Java框架#5:Googl…

字符串、数组、集合在使用时出现空指针怎么办?_03

文章目录1. 寻找代码案例中出错的场景2. 案例1. 寻找代码案例中出错的场景 字符串使用equals时报空指针错误 对象数组最燃new出来了,但是如果没有初始化,一样会报空指针错误 List对象add null不报错,但是addAll 不能添加null,否则…

云原生时代来袭 下一代云数据库技术将走向何方?

全面云化的时代已经到来,面对一系列的新技术和挑战,数据库市场将面临怎样的变革?作为云服务提供商,如何帮助更多的企业级用户把握“云”潮,提供最高效、最具价值的数据库解决方案? 日前,在阿里…

直播连麦贾扬清,谈谈他所理解的四大 AI 落地问题 | 攻“疫”技术公开课

从机器学习模型的层次来看,大致经历了两次发展浪潮:浅层学习(Shallow Learning)和深度学习(Deep Learning)。1980年代,人工神经网络的反向传播算法(也叫Back Propagation算法或者BP算…

linux区分用户的权限级别可用,Linux用户及权限管理

基本操作首选我们梳理一下 Linux 下的用户、用户组、文件权限等基本知识,然后后面通过一个案例来实际演示一下权限设置的一些操作。首先 Linux 系统中,是有用户和用户组的概念的,用户就是身份的象征,我们必须以某一个用户身份来操…

别人家的工程师:阿里巴巴工程师有了新帮手,AI可帮助修Bug

尽管工程师用代码创造了AI,但AI又可以对这些代码点评一番、甚至修复Bug,工程师和AI的关系正在变得微妙。 AI评委引热议,阿里巴巴表示:AI不会取代工程师 4月18日,2019阿里巴巴研发效能峰会——“83行代码挑战赛”决赛…

31岁,年薪33万:“谢谢今天裁掉我” !有底气的人生无需解释!

最近脉脉一则帖子炸锅了:某HR发帖称公司以按时下班为由裁员。这种情况下很多人都慌了,大家纷纷把“副业救国”奉为神律。可是你有没有认真的想过,为什么现在大家都需要副业:意外裁员后,房贷能够按时还上不至于“回收”…

linux脚本语言求累加和,Linux Shell脚本语言与数学表达式

当你理解了Shell脚本,每当需要时都能流畅编写时,那种感觉很爽的。本章中,我们将教你用脚本语言进行比较复杂的数学运算。让我们从斐波那契数列开始吧。斐波那契数列,又称黄金分割数列,指的是这样一个数列:0…

你应该知道的 HBase 基础,都在这儿了

阿里妹导读:2006 年10 月Google 发布三架马车之一的《Bigtable:A Distributed Storage System for Strctured Data》论文之后,Powerset 公司就宣布 HBase 在 Hadoop 项目中成立,作为子项目存在。后来,在2010 年左右逐渐…