打通前后端逻辑,客户端Flutter代码一天上线

一、前沿

​ 随着闲鱼的业务快速增长,运营类的需求也越来越多,其中不乏有很多界面修改或运营坑位的需求。闲鱼的版本现在是每2周一个版本,如何快速迭代产品,跳过窗口期来满足这些需求?另外,闲鱼客户端的包体也变的很大,企业包的大小,iOS已经到了94.3M,Android也到了53.5M。Android的包体大小,相比2016年,已经增长了近1倍,怎么能将包体大小降下来?首先想到的是如何动态化的解决此类问题。

​ 对于原生的能力的动态化,Android平台各公司都有很完善的动态化方案,甚至Google还提供了Android App Bundles让开发者们更好地支持动态化。由于Apple官方担忧动态化的风险,因此并不太支持动态化。因此动态化能力就会考虑跟Web结合,从一开始基于 WebView 的 Hybrid 方案 PhoneGap、Titanium,到现在与原生相结合的 React Native 、Weex。

​ 但Native和JavaScript Context之间的通讯,频繁的交互就成了程序的性能瓶颈。于此同时随着闲鱼Flutter技术的推广,已经有10多个页面用Flutter实现,上面提到的几种方式都不适合Flutter场景,如何解决这个问题Flutter的动态化的问题?

二、动态方案

我们最初调研了Google的动态化方案CodePush。

2.1 CodePush

​ CodePush是谷歌官方推出的动态化方案,目前只有在Android上面实现了。Dart VM在执行的时候,加载isolate_snapshot_data 和isolate_snapshot_instr 2个文件,通过动态更改这些文件,就达到动态更新的目的。官方的Flutter源码当中,已经有相关的提交来做动态更新的内容,具体内容可以参考 ResourceExtractor.java。

​ 根据官方给出的Guide,我们这边也做了相关的测试,patch的包体大小会很大(939kb)。为了降低包体大小,还可以通过增量的修改snapshot文件的方式来更新。通过bsdiff生成的snapshot的差异文件,2个文件分别可以缩小到48kb和870kb。

​ 目前看来,CodePush还不能做到很好的工程化。而且如何管理patch文件,需要制定baseline和patch文件的规则。

2.2 动态模板

​ 动态模板,就是通过定义一套DSL,在端侧解析动态的创建View来实现动态化,比如LuaViewSDK、Tangram-iOS和Tangram-Android。这些方案都是创建的Native的View,如果想在Flutter里面实现,需要创建Texture来桥接;Native端渲染完成之后,再将纹理贴在Flutter的容器里面,实现成本很高,性能也有待商榷,不适合闲鱼的场景。

​ 所以我们提出了闲鱼自己的Flutter动态化方案,前面已经有同事介绍过方案的原理:《做了2个多月的设计和编码,我梳理了Flutter动态化的方案对比及最佳实现》,下面看下具体的实现细节。

三、模板编译

自定义一套DSL,维护成本较高,怎么能不自定义DSL来实现模板下发?闲鱼的方案就是直接将Dart文件转化成模板,这样模板文件也可以快速沉淀到端侧。

3.1 模板规范

​ 先来看下一个完整的模板文件,以新版我的页面为例,这个是一个列表结构,每个区块都是一个独立的Widget,现在我们期望将“卖在闲鱼”这个区块动态渲染,对这个区块拆分之后,需要3个子控件:头部、菜单栏、提示栏;因为这3部分界面有些逻辑处理,所以先把他们的逻辑内置。

内置的子控件分别是MenuTitleWidgetMenuItemWidgetHintItemWidget,编写的模板如下:

@override
Widget build(BuildContext context) {return new Container(child: new Column(children: <Widget>[new MenuTitleWidget(data),    // 头部new Column(    // 菜单栏children: <Widget>[new Row(children: <Widget>[new MenuItemWidget(data.menus[0]),new MenuItemWidget(data.menus[1]),new MenuItemWidget(data.menus[2]),],)],),new Container(    // 提示栏child: new HintItemWidget(data.hints[0])),],),);
}

中间省略了样式描述,可以看到写模板文件就跟普通的widget写法一样,但是有几点要注意:

  1. 每个Widget都需要用newconst来修饰
  2. 数据访问以data开头,数组形式以[]访问,字典形式以.访问

​ 模板写好之后,就要考虑怎么在端上渲染,早期版本是直接在端侧解析文件,但是考虑到性能和稳定性,还是放在前期先编译好,然后下发到端侧。

3.2 编译流程

​ 编译模板就要用到Dart的Analyzer库,通过parseCompilationUnit函数直接将Dart源码解析成为以CompilationUnit为Root节点的AST树中,它包含了Dart源文件的语法和语义信息。接下来的目标就是将CompilationUnit转换成为一个JSON格式。

​ 上面的模板解析出来build函数孩子节点是ReturnStatementImpl,它又包含了一个子节点InstanceCreationExpressionImpl,对应模板里面的new Container(…),它的孩子节点中,我们最关心的就是ConstructorNameImplArgumentListImpl节点。ConstructorNameImpl标识创建节点的名称,ArgumentListImpl标识创建参数,参数包含了参数列表和变量参数。

定义如下结构体,来存储这些信息:

class ConstructorNode {// 创建节点的名称String constructorName;// 参数列表List<dynamic> argumentsList = <dynamic>[];// 变量参数Map<String, dynamic> arguments = <String, dynamic>{};
}

递归遍历整棵树,就可以得到一个ConstructorNode树,以下代码是解析单个Node的参数:

ArgumentList argumentList = astNode;for (Expression exp in argumentList.arguments) {if (exp is NamedExpression) {NamedExpression namedExp = exp;final String name = ASTUtils.getNodeString(namedExp.name);if (name == 'children') {continue;}/// 是函数if (namedExp.expression is FunctionExpression) {currentNode.arguments[name] =FunctionExpressionParser.parse(namedExp.expression);} else {/// 不是函数currentNode.arguments[name] =ASTUtils.getNodeString(namedExp.expression);}} else if (exp is PropertyAccess) {PropertyAccess propertyAccess = exp;final String name = ASTUtils.getNodeString(propertyAccess);currentNode.argumentsList.add(name);} else if (exp is StringInterpolation) {StringInterpolation stringInterpolation = exp;final String name = ASTUtils.getNodeString(stringInterpolation);currentNode.argumentsList.add(name);} else if (exp is IntegerLiteral) {final IntegerLiteral integerLiteral = exp;currentNode.argumentsList.add(integerLiteral.value);} else {final String name = ASTUtils.getNodeString(exp);currentNode.argumentsList.add(name);}
}

端侧拿到这个ConstructorNode节点树之后,就可以根据Widget的名称和参数,来生成一棵Widget树。

四、渲染引擎

端侧拿到编译好的模板JSON后,就是解析模板并创建Widget。先看下,整个工程的框架和工作流:

工作流程:

  1. 开发人员编写dart文件,编译上传到CDN
  2. 端侧拿到模板列表,并在端侧存库
  3. 业务方直接下发对应的模板id和模板数据
  4. Flutter侧再通过桥接获取到模板,并创建Widget树

对于Native测,主要负责模板的管理,通过桥接输出到Flutter侧。

4.1 模板获取

模板获取分为2部分,Native部分和Flutter部分;Native主要负责模板的管理,包括下载、降级、缓存等。

程序启动的时候,会先获取模板列表,业务方需要自己实现,Native层获取到模板列表会先存储在本地数据库中。Flutter侧业务代码用到模板的时候,再通过桥接获取模板信息,就是我们前面提到的JSON格式的信息,Flutter也会有缓存,已减少Flutter和Native的交互。

4.2 Widget创建

Flutter侧当拿到JSON格式的,先解析出ConstructorNode树,然后递归创建Widget。

创建每个Widget的过程,就是解析节点中的argumentsListarguments 并做数据绑定。例如,创建HintItemWidget需要传入提示的数据内容,new HintItemWidget(data.hints[0]),在解析argumentsList时,会通过key-path的方式从原始数据中解析出特定的值。

解析出来的值都会存储在WidgetCreateParam里面,当递归遍历每个创建节点,每个widget都可以从WidgetCreateParam里面解析出需要的参数。

/// 构建widget用的参数
class WidgetCreateParam {String constructorName;    /// 构建的名称dynamic context;    /// 构建的上下文Map<String, dynamic> arguments = <String, dynamic>{}; /// 字典参数List<dynamic> argumentsList = <dynamic>[]; /// 列表参数dynamic data; /// 原始数据
}

​ 通过以上的逻辑,就可以将ConstructorNode树转换为一棵Widget树,再交给Flutter Framework去渲染。

至此,我们已经能将模板解析出来,并渲染到界面上,交互事件应该怎么处理?

4.3 事件处理

在写交互的时候,一般都会通过GestureDectorInkWell等来处理点击事件。交互事件怎么做动态化?

​ 以InkWell组件为例,定义它的onTap函数为openURL(data.hints[0].href, data.hints[0].params)。在创建InkWell时,会以OpenURL作为事件ID,查找对应的处理函数,当用户点击的时候,会解析出对应的参数列表并传递过去,代码如下:

...
final List<dynamic> tList = <dynamic>[];
// 解析出参数列表
exp.argumentsList.forEach((dynamic arg) {if (arg is String) {final dynamic value = valueFromPath(arg, param.data);if (value != null) {tList.add(value);} else {tList.add(arg);}} else {tList.add(arg);}
});// 找到对应的处理函数
final dynamic handler =TeslaEventManager.sharedInstance().eventHandler(exp.actionName);
if (handler != null) {handler(tList);
}
...

五、 效果

新版我的页面添加了动态化渲染能力之后,如果有需求新添加一种组件类型,就可以直接编译发布模板,服务端下发新的数据内容,就可以渲染出来了;动态化能力有了,大家会关心渲染性能怎么样。

5.1 帧率

在加了动态加载逻辑之后,已经开放了2个动态卡片,下图是新版本我的页面近半个月的的帧率数据:

从上图可以看到,帧率并没有降低,基本保持在55-60帧左右,后续可以多添加动态的卡片,观察下效果。

注:因为我的页面会有本地的一些业务判断,从其他页面回到我的tab,都会刷新界面,所以帧率会有损耗。

​ 从实现上分析,因为每个卡片,都需要遍历ConstructorNode树来创建,而且每个构建都需要解析出里面的参数,这块可以做一些优化,比如缓存相同的Widget,只需要映射出数据内容并做数据绑定。

5.2 失败率

现在监控了渲染的逻辑,如果本地没有对应的Widget创建函数,会主动抛Error。监控数据显示,渲染的流程中,还没有异常的情况,后续还需要对桥接层和native层加错误埋点。

六、展望

​ 基于Flutter动态模板,之前需要走发版的Flutter需求,都可以来动态化更改。而且以上逻辑都是基于Flutter原生的体系,学习和维护成本都很低,动态的代码也可以快速的沉淀到端侧。

​ 另外,闲鱼正在研究UI2Code的黑科技,不了解的老铁,可以参考闲鱼大神的这篇文章《重磅系列文章!UI2CODE智能生成Flutter代码——整体设计篇》。可以设想下,如果有个需求,需要动态的显示一个组件,UED出了视觉稿,通过UI2Code转换成Dart文件,再通过这个系统转换成动态模板,下发到端侧就可以直接渲染出来,程序员都不需要写代码了,做到自动化运营,看来以后程序员失业也不是没有可能了。

​ 基于Flutter的Widget,还可以拓展更多个性化的组件,比如内置动画组件,就可以动态化下发动画了,更多好玩的东西等待大家来一起探索。


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

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

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

相关文章

迈向电商认知智能时代的基石:阿里电商认知图谱揭秘

阿里妹导读&#xff1a;电商平台最大的挑战是从日益增长的海量商品&#xff08;数十亿&#xff09;中挑选出的一个小的子集&#xff08;几十或上百&#xff09;展示给用户&#xff0c;以满足用户的个性化的购物需求。为了解决仍存在的重复推荐、缺少新意等问题&#xff0c;我们…

我是如何用6个月,从0编程经验变成数据科学家的?

来源 | medium编译 | 武明利责编 | Carol出品 | CSDN云计算&#xff08;ID&#xff1a;CSDNcloud&#xff09;我叫Kate&#xff0c;刚从长达 8 年的学习和艰苦的工作中走出来&#xff0c;没有任何预兆。你可能想问&#xff0c;为什么有人会这么做&#xff1f;不得不说&#xff…

Node.js 应用故障排查手册 —— 综合性 GC 问题和优化

楔子 本章前面两节生产案例分别侧重于单一的 CPU 高和单一的内存问题&#xff0c;我们也给大家详细展示了问题的定位排查过程&#xff0c;那么实际上还有一类相对更复杂的场景——它本质上是 V8 引擎的 GC 引发的问题。 简单的给大家介绍下什么是 GC&#xff0c;GC 实际上是语…

“龙井”开箱评测 |Alibaba Dragonwell 新手上路指南

阿里巴巴有着最丰富的 Java 应用场景&#xff0c;覆盖电商&#xff0c;金融&#xff0c;物流等众多领域&#xff0c;是世界上最大的 Java 用户之一。 2019 年 3 月 21 日&#xff0c;阿里巴巴在北京阿里云峰会上正式宣布开源了 Alibaba Dragonwell 8 产品&#xff0c;并建立了 …

基于角色的访问控制(RBAC)

来源 | 编程新说责编 | Carol出品 | CSDN云计算&#xff08;ID&#xff1a;CSDNcloud&#xff09;很多时候&#xff0c;需要对一些事物进行控制&#xff0c;如一个房间&#xff0c;为了不让人随便进&#xff0c;通常会装一把锁&#xff0c;如果要想进入&#xff0c;你必须得有一…

win10 ie中没有java,win10没有ie浏览器怎么处理_window10找不到ie浏览器如何解决

很多用户升级到win10系统之后&#xff0c;发现默认浏览器是edge&#xff0c;想要使用ie浏览器的时候却发现没有ie浏览器&#xff0c;遇到window10找不到ie浏览器的话该怎么办呢&#xff0c;下面随小编一起来看看详细的解决步骤吧。方案一&#xff1a;1、直接搜索&#xff0c;右…

手把手教程:用Python开发一个自然语言处理模型,并用Flask进行部署

截住到目前为止&#xff0c;我们已经开发了许多机器学习模型&#xff0c;对测试数据进行了数值预测&#xff0c;并测试了结果。实际上&#xff0c;生成预测只是机器学习项目的一部分&#xff0c;尽管它是我认为最重要的部分。今天我们来创建一个用于文档分类、垃圾过滤的自然语…

干货|Spring Cloud Stream 体系及原理介绍

Spring Cloud Stream 在 Spring Cloud 体系内用于构建高度可扩展的基于事件驱动的微服务&#xff0c;其目的是为了简化消息在 Spring Cloud 应用程序中的开发。Spring Cloud Stream (后面以 SCS 代替 Spring Cloud Stream) 本身内容很多&#xff0c;而且它还有很多外部的依赖&a…

阿里小程序云应用上线了,有哪些看点?

3月21日&#xff0c;在2019阿里云峰会北京上&#xff0c;阿里巴巴旗下的阿里云、支付宝、淘宝、钉钉、高德等联合发布“阿里巴巴小程序繁星计划”&#xff1a;提供20亿元补贴&#xff0c;扶持200万小程序开发者、100万商家。凡入选“超星”的小程序&#xff0c;入驻支付宝、淘宝…

10 个实用功能告诉你,谷歌云(Google Cloud)相对亚马逊云(AWS)有哪些优势?...

来源 | itnext编译 | 武明利责编 | Carol出品 | CSDN云计算&#xff08;ID&#xff1a;CSDNcloud&#xff09;有很多文章将谷歌云提供商&#xff08;GCP&#xff09;与亚马逊云服务&#xff08;AWS&#xff09;进行比较&#xff0c;但这篇文章并不想要做比较。作者主要是一个AW…

mybatis-plus大批量数据插入缓慢问题

文章目录问题排查结果建议问题 最近项目用的mybatis-plus做的映射&#xff0c;有个批处理文件内容的需求&#xff0c;在使用mybatis-plus的批处理方法saveBatch时发现速度特别慢&#xff0c;测试从1000到10000到80000条基本上是线性增加&#xff0c;80000条时差不多要90秒。 …

世界冠军之路:菜鸟车辆路径规划求解引擎研发历程

阿里妹导读&#xff1a;车辆路径规划问题&#xff08;Vehicle Routing Problem, VRP&#xff09;是物流领域最经典的优化问题之一&#xff0c;具有极大的学术研究意义和实际应用价值。菜鸟网络高级算法专家胡浩源带领仓配智能化算法团队经过两年的研发&#xff0c;逐步沉淀出了…

原来,阿里工程师才是隐藏的“修图高手”!

阿里妹导读&#xff1a;在现实世界中&#xff0c;信息通常以不同的模态同时出现。这里提到的模态主要指信息的来源或者形式。例如在淘宝场景中&#xff0c;每个商品通常包含标题、商品短视频、主图、附图、各种商品属性&#xff08;类目&#xff0c;价格&#xff0c;销量&#…

分布式数据集训营,从入门到精通,从理论到实践,你不可错过的精品课程!...

责编 | Carol出品 | CSDN云计算&#xff08;ID&#xff1a;CSDNcloud&#xff09;随着微服务、云化架构的兴起&#xff0c;分布式数据库开始在越来越多的场景得到应用&#xff0c;从外围系统到中台业务&#xff0c;再到核心交易业务&#xff0c;分布式数据库成为企业基础架构转…

mybatis批量插入10万条数据的优化过程

在使用mybatis插入大量数据的时候,为了提高效率,放弃循环插入,改为批量插入,mapper如下: package com.lcy.service.mapper;import com.lcy.service.pojo.TestVO; import org.apache.ibatis.annotations.Insert;import java.util.List;/*** 功能描述&#xff1a;** author liuc…

java spring注解维护,从一次工程启动失败谈谈 spring 注解

原标题&#xff1a;从一次工程启动失败谈谈 spring 注解檀宝权Java 后端开发工程师&#xff0c;负责度假 App 后端和广告后端开发维护工作&#xff0c;熟悉 Tomcat&#xff0c;Spring&#xff0c;Mybatis&#xff0c;会点 Python&#xff0c;Lua。一、背景线上环境升级成 JDK8后…

探索Java日志的奥秘:底层日志系统-log4j2

前言 log4j2是apache在log4j的基础上&#xff0c;参考logback架构实现的一套新的日志系统&#xff08;我感觉是apache害怕logback了&#xff09;。 log4j2的官方文档上写着一些它的优点&#xff1a; 在拥有全部logback特性的情况下&#xff0c;还修复了一些隐藏问题API 分离&…

大地震!某大厂“硬核”抢人,放话:只要AI人才,高中毕业都行!

特斯拉创始人马斯克&#xff0c;在2019年曾许下很多承诺&#xff0c;其中一个就是&#xff1a;2019年底实现完全的自动驾驶。虽然这个承诺又成了flag&#xff0c;但是不妨碍他今年继续为这个承诺努力。这不&#xff0c;就在上周一&#xff0c;马斯克之间在twitter上放话了&…

Dart编译技术在服务端的探索和应用

前言 最近闲鱼技术团队在FlutterDart的多端一体化的基础上&#xff0c;实现了FaaS研发模式。Dart吸取了其它高级语言设计的精华&#xff0c;例如Smalltalk的Image技术、JVM的HotSpot和Dart编译技术又师出同门。由Dart实现的语言容器&#xff0c;它可以在启动速度、运行性能有不…

Python + ElasticSearch:有了这个超级武器,你也可以报名参加诗词大会了! | 博文精选...

来源 | CSDN 博客作者 | 天元浪子责编 | Carol出品 | CSDN云计算&#xff08;ID&#xff1a;CSDNcloud&#xff09;意犹未尽的诗词大会正月十六&#xff0c;中国诗词大会第五季落下帷幕。从2016年2月12日第一季于开播&#xff0c;迄今恰好四周年。在这个舞台上&#xff0c;时年…