滴滴青桔单车跨端技术方案和业务技术架构,及框架设计和性能提升实践
导读:经过将近两年的发展,小程序已经深入用户的日常生活,小程序应用数量超过了百万量级,覆盖众多细分行业,日活用户达到两个亿。青桔单车是日活相对较高的小程序,这也要求我们对小程序的性能、稳定性及安全有较高的标准,综合考虑 chameleon 的框架特性,我们选择其作为跨端的框架,并在此进行分享。
——
前言
背景
行业现状——百家争鸣
业务要求——高效稳定多入口
框架期望
实践
跨端技术方案设计
跨平台框架——chameleon
青桔单车业务技术架构
多端界面一致性
差异化(定制化)
工程化
数据 mock
CML 配置
框架设计
组件调用-父子组件通信
数据的管理-store
cml 框架设计
性能
性能提升
包大小
总结
▍前言
近些年,整个前端领域发展迅速,效率型的前端框架也层出不穷,每个团队选择的技术解决方案都不太一致,因为互联网的特性及中国自身的特色,各个产品对于多端的投放的需求是一致的。像小程序这种跨端场景和现有的开发方式也不一样,为了满足业务的需求技术人员在日常开发中会因为投放平台的不一样而进行多套代码的维护,效率较低且成本也高。跨端框架应景而生(这里就不过多介绍各个跨端的框架了)。
在我看来大前端是趋势,跨端是趋势的第一步,而对于技术人员来说在不影响功能体验的情况下能解决维护多套代码的痛点非常重要,改编一下比较流行的一句话:「lean once,write once,run anywhere」
▍背景
行业现状 — 百家争鸣
经过将近两年的发展,小程序已经深入用户的日常生活,小程序应用数量超过了百万量级,覆盖200多个细分的行业,日活用户达到两个亿。与此同时,像支付宝、百度、头条、手Q等等都开始了自家的小程序生态,百家争鸣应景而生。青桔单车作为便民的出行工具,对于用户使用方式上也是成本越低体验越友好,即用即走的小程序已然成为平台选择的趋势。
业务要求 —高效稳定多入口
高效、稳定、多入口就是业务现在的要求,青桔单车是日活相对较高的小程序(目前在阿拉丁小程序 TOP 榜前10),这也要求我们对小程序的性能(加载、渲染、响应的时间)、稳定及安全有较高的标准。
同时业务也需要在各个平台上获得更多的入口,这就直接导致我们在选择框架,业务开发时要求比较严谨。
框架期望
从用户角度出发,为了减少用户使用成本(下载安装或更新 APP ),我们选择了市场上比较符合单车特性的平台作为入口。那么这时候对于研发来说就会有很多问题,我们在选择框架时,会对以下点有较高的期望或要求:
各端开发效果要高度一致,不能开发一次后用大量时间兼容多端
针对不同的端有差异化的实现方式,能较容易去扩展,以应对产品对不同端的差异化需求
工程化要好,因为前端过程式的开发比较低效,需要使用软件工程的技术和方法进行开发、管理
框架设计,接入不用改动太多代码,具有较好的抽象度
对性能上要求很高,包大小、加载、渲染、响应都需要严格考虑
▍实践
跨端技术方案设计
☉ 跨平台框架——chameleon
引用 chameleon 官网的简介:
Chameleon 不仅仅是跨端解决方案,让开发者高效、低成本开发多端原生应用。基于优秀的前端打包工具 Webpack,吸收了业内多年来积累的最有用的工程化设计,提供了前端基础开发脚手架命令工具,帮助端开发者从开发、联调、测试、上线等全流程高效的完成业务开发。
从chameleon框架的架构设计图看:
在各个平台下chameleon基于其本身的运行池增加一层包括路由、自定义生命周期、组件、数据事件绑定及管理、样式渲染等相对完整的 DSL;
chameleon-tools 做编译(基于 webpack),提供语法检查、转译、依赖组装等;
chameleon-UI、chameleon-API 做各个平台样式的不一致、API差异化的抹平;
提供多态协议的方式解决组件及 API 的差异化;
综合来看 chameleon 的设计模式比较适合我们做跨端项目的开发,提升我们的效率,不维护多套重复的代码…
☉ 青桔单车业务技术架构
青桔单车简单的业务流程图:
青桔单车业务相对复杂,包括登录、认证、电子围栏、宣教、状态扭转等超过 30 个页面(不包括各种 H5 实现),用户主动打开/微信扫码进入小程序,完成登录后开始扫码开锁,开锁成功后 === 发单成功,用户开始骑行,骑行结束后完成支付整个流程结束(这里只提到核心流程),因为业务需要,我们需要维护微信、支付宝、高德(快应用、百度接入中)等众多小程序,对于研发来说最快的是 copy 一套代码然后针对性进行修改,当进行新功能开发的时候就痛苦了。
基于业务和多端的差异抹平方案考虑,最终CML青桔单车小程序技术方案设计图如下:
从青桔单车现在模块看,为了能真正的实现跨端开发,需要解决各平台间的差异问题:
各平台提供给小程序的 SDK 差异化,例如微信、支付宝的 getAuthCode,API 方法名不一样,返回也不一样,类似的问题如何解决?
业务中台 API 根据渠道/接入方案不同造成的问题如何解决?
页面内部-组件调用-父子组件通信、store 数据的管理如何处理?
硬件底层实现方案如何解决?例如微信、支付宝 BLE 底层实现的差异;百度小程序IOS 不支持 BLE,类似问题如何解决?
各平台规则限制导致功能无法使用,例如 webview 某些端考虑稳定性限制了 h5 — data 上行到端的问题如何解决?
其他问题, utils 组件差异化……
多端界面一致性
由于各端组件化的实现方式不一样 ( 微信 webcomponents 、百度模拟的组件化、支付宝是 react 框架),多端界面的一致性是一个比较麻烦的事情,从 cml 本身的设计及实际的体验来说,不论样式的单位换算还是组件的统一封装都做了较好的统一。
来点 gif 图,预览一下青桔单车小程序基于 cml 改版后三端的效果吧:
差异化(定制化)
我们按照业务场景拆分了几个公用的模块包括用户相关、发单、硬件/蓝牙通信、订单管理、营销等,每个模块都单独 store、action 及暴露的 commonApi,配合各个页面逻辑实现整个产品功能,针对差异化我们列举一个登录的例子通过多态方式来兼容微信、支付宝登录接口。我们在项目中 src/componets 中建立一个 API 的空间作多态管理 API,针对 login 我们按照 cml 的规范建立一个 login.interface 文件。
实现如下:
<script cml-type="interface"> // 定义入参 type RESObject = { code: string, errMsg: string } type done = (res: RESObject) => Void interface MethodsInterface { login(timeout: String, opt: done): Void }
</script>
<script cml-type="wx"> class Login implements LoginInterface { login (timeout, opt) { wx.login(timeout, code => done({code: ''12', errMsg: '成功'})); } } export default new Login();
</script>
<script cml-type="alipay"> class Login implements LoginInterface { login (timeout, opt) { my.getAuthCode(timeout, code => done({code: ''12', errMsg: '成功'})); } } export default new Login();
</script>
这里想着重提一个非常容易被忽略的问题:interface 中一定要定义输入输出,规范各端实现规范,这是为了避免在修改某个端的方法入参(增加或减少)却没有考虑其他端的实现,如果全量测试会浪费很大人力且也不能保证都能覆盖到测试,为了可维护性,建议从一开始就坚持写多态组件的 interface,在程序层面上亡羊补牢有时候真的为时已晚……
接口定义后就可以用统一的方式进行调用:
import login from '../../components/Api/login/login.interface'; export const login = function ({commit}) { return new Promise((resolve, reject) => { login.login() .then(({code}) => { commit(types.SET_USER_CODE, {code}); resolve({code}); }) .catch(e => { reject(); }); });
};
再比如,针对蓝牙通信的 API 微信需要端来做 ArrayBuffer 到 HexString 的转换而支付宝不需要,这里我们也采用多态进行接口方式抽离,微信进行转换,支付宝直接 return 原数据,整个 BLE 的过程非常复杂,为了提高连接,通信的成功率我们做了很多优化,如果直接在代码中 hack 会影响整个流程甚至造成整个蓝牙动作的不稳定,影响开锁率,做这层多态封装既不影响原有逻辑,改动也相对较少成本很低。
另一方面,PM 如果在某一端提个需求,例如针对支付宝用户在登录时候加一些特殊功能,我们能在不影响其他流程比进行改造,同时这是在物理上进行了隔离,可以发布单独 npm 包,可维护性比较高。
工程化
工程化是使用软件工程的技术和方法对项目的开发、上线和维护进行管理,因为前端过程式的开发比较低效,可以通过模块化、组件化、本地开发、上线部署自动化来提高研发效率。
☉ 常用执行命令
cml dev `编译全部`,cml wx dev `编译微信`,启动开发模式,监听文件变化动态打包
cml build `编译全部`,cml wx dev `编译微信`, 构建生产环境
☉ 数据 mock
前端开发的过程中,数据 mock 是一个比较重要的功能,在验证逻辑、研发效率上及线上线下环境环境切换都起着很重要的作用。cml 这里也提供数据 mock 的功能。
import cml from "chameleon-api";
cml.get({ url: '/api/getUserInfo'
})
.then(res => { // ...省略部分实现逻辑 cml.setStorage('user', ...res)
},
err => { cml.showToast({ message: JSON.stringify(err), duration: 2000 })
});
调用方法的参数 url 中只需要写 api 的路径。那么本地 dev 开发模式如何 mock 这个 api 请求以及 build 线上模式如何请求线上地址,就需要在配置文件中配置 apiPrefix。
// 设置api请求前缀
const testApiPrefix = 'http://test.api.com';
const apiPrefix = 'http://prod.api.com';
cml.config.merge({ wx: { dev: { apiPrefix: testApiPrefix }, build: { apiPrefix } }
})
☉ CML 配置
cml 支持本地 mock,使用方式是在 /mock/api/ 文件夹下创建 mock 数据的js文件,启动dev模式后(apiPrefix留空表示本地ip+端口),可以直接实现本地mock。
chameleon 的构建过程是配置化的,项目的根目录下提供一个 chameleon.config.js文件,在该文件中可以使用全局对象 cml 的 api 去操作配置对象,针对不同端的配置方法,添加一个端还是比较简单的,在编译层配置的扩充性也有暴露
页面路由配置,chameleon 项目内置了一套各端统一的路由管理方式,为区分小程序,用 url 表示 h5,path 表示小程序,同时提供mock服务接口,在路由的管理上相对容易,在小程序端构建时会将 router.config.json 的内容,插入到 app.json 的 pages 字段,实现小程序端的路由。
框架设计
☉ 组件调用-父子组件通信
针对首页的消息流卡片我们会有登录、认证、订单、骑行卡等等不同的形式,我们封装了一个组件来处理,静态效果如下:
相对来说这个组件比较通用,我们大致分为 2 种类型,通知型与动作型,如上图 2 就是一个纯显示的消息,其他的是带有按钮的消息,在组件设计上我们根据调用组件时传的type 不一样来区分,组件使用上用 props 进行 data 传递。
通知型:TipsCard
动作型:ActionCard
下面是动作型的实现方式:
<template> <view class="action-card"> <view class="{{'content '+(remindActive && 'bigSmall')}}"> // ... </view> </view>
</template> <script> class ActionCard { mounted () { EventBus.on(REMIND_CARD, ({index}) => { if (index === this.index) this.remind(); }); } methods = { callback (e) { EventBus.emit(ACTION_CARD, {index: this.index}); } } } export default new ActionCard()
</script>
调用上,我们动态传递 component is 中的type来指定不同的消息流类型。
在自定义事件的处理上,我们通过 c-bind:action 绑定了一个 componentAction,通过 EventBus 事件来传递执行。
<template> <view> <component is="{{item.type+'-card'}}" /> // ... </view>
</template> <script> class Home { beforeMount () { this.tipsList = [] EventBus.on(ACTION_CARD, (data) => { this.componentAction(data) }); } methods = { componentAction(data) { ...// 执行实例 } } } export default new Home();
</script>
最终实现的效果如下图
☉ 数据的管理-store
cml 框架作为数据驱动视图的框架,应用由数据驱动,数据逻辑变得很复杂,必需要一个好用高效的数据管理框架,我们可能在各个页面都需要 mapState,mapAction 来做组件的状态获取,事件分发等。
import createStore from 'chameleon-store';
import user from './user';
import location from './location';
// …………这里省略其他状态管理器 const store = createStore({ modules: { user, location, bicycle, // …………这里省略其他状态管理器 }
});
export default store;
这里就列出对 user 状态的入口操作
import mutations from './mutations';
import * as actions from './actions';
import * as getters from './getters';
const user = { state: { }, mutations: { ...mutations }, actions: { ...actions }, getters: { ...getters }
};
export default user;
cml 提供了的 chameleon-store 模块包,且用法、写法和 vuex 一样,这点非常感人,手动点赞。
☉ cml 框架设计
cml 提供了计算属性—— computed、侦听属性—— watch、类 vuex 的数据管理及 DSL 语法,从学习成本上来看较小,符合当前的开发习惯。
<script> class Home { data = { remindActive: false } watch = { lockState: function(cur, old) { return this.remindActive } } computed = { ...store.mapState(['location', 'user']) } } export default new Home();
</script>
基于青桔单车日活相对较高的特性,高效、稳定、多入口就是业务现在的要求,这要求我们对小程序的性能(加载、渲染、响应的时间)、稳定及安全有较高的标准。
性能
☉ 性能提升
性能上,cml 做了 array diff,因为小程序的主要运行性能瓶颈是 webview 和 js 虚拟机的传输性能,cml 尽可能 diff 出修改的部分进行传输,性能相对会更好,贴一下源码。
源码实现比较简单,但带来实际的用处还是比较大的,单车小程序有不少列表页面,当对于列表 data 进行重新赋值先进入 diff 函数过滤出实际改变的每一项,再进行 view-render,性能上会提高不少
☉ 包大小
包大小上,cml 相对之前开发的版本并没有大的变化,1.2mb 的编译后包大小基本保持不变,这归功于代码的压缩及其本身的 runtime 代码并不大,给个好评。
▍总结
“苦尽甘来”是青桔单车小程序接入 chameleon 跨端框架的总体感受。
苦:这里的”苦“我们到现在看其实是因为一种为未来做铺垫而需要做更多工作的“苦”,原本只需要考虑一端的 CASE 但现在要自己抽象出来一系列例如接口参数,通用组件,以及需要掌握更多的平台技术方案的“苦”。
甘:当我们通过 chameleon 将青桔单车小程序上线后,应对业务抛出来的新需求,我们再也不用维护多套代码了,也把因为维护多套代码导致可能某一端不一致性的风险给彻底排除了。不仅仅是 RD 同学受益,QA 同学验收的时候因为是一套代码逻辑所以可以节省一半的时间。本来3天要完成的需求现在1.5天就可以完成也极大的满足了业务方的需求,我们认为这就是技术驱动业务发展的一个例子,技术不能只是一个工具而是要从业务角度出发去实现才有真正的价值。
cml 做了 DSL 编译转化从根本上实现了各端的统一,引入组件多态、方法多态并抽象各端的配置可以更灵活抹平差异,方便配置、扩展。通过深入了解 chameleon 框架及项目实践,我们基于 cml 可以再抽象出一些公共层的组件、接口,当然这里是指的跨平台的组件、接口,因为只有这样才能更大化提升开发的效率,给业务带来更多的可能。
我们在选择框架的时候要考虑不仅要考虑其本身的性能、稳定、安全,包大小等,还需要看一下复用性及发展,比如是否能基于框架本身扩展出适合业务甚至公司级别的通用组件、API,是否方便随着业务的变化而能做到及时响应式的扩展、变动。
到这里青桔单车 chameleon 跨端实践的介绍结束啦,有表述不好的希望多多包含并提出建议我们及时改进,谢谢。
骆 彬 彬
滴滴 | 资深研发工程师
一个不像程序员的程序员,在分布式架构、前后端分离模式上有一定研究,喜欢折腾适用不同业务的框架,爱好踢足球,Coding。
架构文摘
ID:ArchDigest
互联网应用架构丨架构技术丨大型网站丨大数据
更多精彩文章,请点击下方:阅读原文
滴滴青桔单车跨端技术方案和业务技术架构,及框架设计和性能提升实践相关推荐
- 哈罗单车、美团单车、和滴滴青桔的新三国杀
近日,美团旗下摩拜单车发布的数据显示,截至今年12月,共有近18.92万用户通过App等渠道举报私占.破坏共享单车超27.87万次,涉及车辆超20.56万. 仅举报涉及共享单车就高达20.65万辆,未 ...
- 微博展开对违规内容整治;聚美优品CFO辞职;传滴滴拟5亿美元投入青桔单车丨价值早报...
第[921]期早报由[周日]赞助播出 01 今日头条 微博展开集中整治,清查耽美CP.暴力游戏等图文视频 近日,微博分发布公告,称为了进一步营造清朗和谐的社区环境,将根据<网络安全法>等法 ...
- 青桔单车 chameleon 跨平台实践
前言 近些年,整个前端领域发展迅速,效率型的前端框架也层出不穷,每个团队选择的技术解决方案都不太一致,因为互联网的特性及中国自身的特色,各个产品对于多端的投放的需求是一致的.像小程序这种跨端场景和现有 ...
- 青桔单车 chameleon 跨平台实践分享
今天Chameleon社区公众号收到了来自不懂小彬@青桔单车的投稿,让我们一起看一下青桔单车关于使用Chameleon的经验分享. ▍目录 前言 背景 行业现状--百家争鸣 业务要求--高效稳定多入口 ...
- 流言终结者- Flutter和RN谁才是更好的跨端开发方案?
背景 论坛上很多小伙伴关心为什么闲鱼选择了Flutter而不选择其他跨端方案?站在质量的角度,高性能是一个很重的因素,我们使用Flutter重写了宝贝详情页之后,对比了Flutter和Native详情 ...
- 闲鱼的统一跨端 API 方案 —— Uni API
本文将介绍跨端 API 解决方案应该解决什么问题,提供什么能力. 背景 随着 C 端流量红利的逐渐消失,端外投放已成为业务寻求增长的重要抓手之一.而不同 App 上存在不同应用场景.不同时期诞生的前端 ...
- 大量违规投放,青桔单车被紧急约谈
10月1日消息,北京交通公众号发布信息,9月30日下午,北京市交通委员会紧急约谈青桔单车运营企业,责令其于10月底前将违规投放青桔单车运出北京. 北京交通称,今年以来,青桔单车就因为车辆报备率低.数据 ...
- taro 引入js_Taro跨端开发之多业务模块管理 React Native篇(终篇)
React Native 热更新方案 rn的业务越来越庞大,同时协同的团队越来越多. rn的动态化就必须提上日程了. 对于rn热更新,首当其冲的问题就是分包. rn的基础库很大,再加上我们依赖了很多的 ...
- 滴滴业务中台架构之术:来自技术专家的实践
按:业务中台是什么?阿里打个嘴炮,每家公司理解的都不一样.从业务技术的角度来看,意味着什么?没有答案,答案在每个实践者的心里.本文来自滴滴何修峰的分享,算是术方面的知识.还有一篇在道方面的,可以配合着 ...
最新文章
- Cannot complete the install because one or more required items could not be found.
- OC__part11.2
- Product Overview page data loss handling
- php json追加500错误,在composer.json中添加了一个git地址;composer update 报错
- HDU1233——还是通常工程(最小生成树,并查集)
- macOS Catalina下RX580/Vega系列显卡启动后DP输出黑屏和ALC1220声卡无法驱动的解决方案
- 自己动手编译android ffmpeg
- 计算机技术和通信技术的关系,计算机技术与通信技术的关系
- Canal.deployer 启动报错说CHARACTER SET 'utf8' COLLATE 'utf8_unico', expect null,rkdown编辑器
- 搜狗输入法 linux 怎么打开,ubuntu 15.10安装搜狗输入法不能打开
- mysql编写1到n的奇数和_编写程序。输入任意整数n,计算1到n的奇数和
- 曲率发动机和反重力装置原理
- 谷歌浏览器怎么设置默认隐身模式启动
- C# winfrom NPOI导出Excel 添加视频、音频文件
- Human-Level Control Through Deep Reinforcement Learning论文解读
- 计算机学后感作文400,毕业感想作文400字(4篇)
- 类型转换异常:java.lang.ClassCastException: java.util.LinkedHashMap cannot be cast to com.
- 商标被抢注的坑,自媒体人如何主动避开?
- STM32F030_LED详细配置总结
- C10:Unity3D制作智能家居设计软件——三步实现家具生长动画
热门文章
- 十部门发促消费“24条”:提高相对低收入群体待遇
- arcpy实现LUCC土地利用变化转移矩阵,2期影像或者矢量数据土地利用变化统计,生成统计表。使用“面积制表TabulateArea”轻松实现变化统计。
- 教育界「拼多多」卖课套路解析
- 作为默认网络指标的额-颞顶连接的发展轨迹:一项纵向fNIRS研究
- mysql 创建外键语句_sql创建外键语句
- 等保测评证书是由什么部门发的?申请需要满足什么条件?
- oracle物理读优化,oracle 性能优化 06_sql优化
- 怎么区分开关电源的PFM与PWM模式?
- U盘安装Win7系统时,安装界面提示 “缺少所需的CD/DVD驱动器设备驱动程序” 解决办法
- 【数据分析师---数据可视化】第二章:plotly绘图基础篇