美柚其实在具有热更新功能的混合应用技术运用中算比较晚了,经过一系列的调研准备,于今年的3月份,开始在美柚APP上使用RN框架(去年9月,有在杭州电商里面使用了几个RN的页面,但只是粗略应用)。因为大家都没有什么经验,基本都是摸着石头过河进行尝试。
这篇文章就是用来记录基于美柚APP的RN框架从0到1的一个开发和设计的过程。
台前幕后
痛点:
在做这个事情前,我们先要明确我们要做这个事情的原因和目的,我们为什么要这么做。
- 开发维护成本太高:要开发维护两套代码,而且原生在开发用户界面上的成本非常高,我之前写android页面你写个样式的编写维护好几个xml文件,不同的列表数据还得写不同类型的适配器。开发维护成本比web的页面开发高很多。
- 繁复的版本管理:新老版本都要做兼容,就会出现一系列的版本。
- 频繁的变更需求:在ios下,新版本发布需要通过app store的提交审核,会有个时间的延迟。不具备热更新的功能。
- 性能体验的保证:在H5下的性能和体验得不到保证。
- App size越来越大:目前美柚App已经快97MB,柚宝宝已经107MB。
定位
要清楚RN的定位,RN是一种解决方法,他是为了解决以上那些痛点而存在的。也就是在对比Native 和 H5, 要清楚RN 的优势在哪里。我们也要清楚认识到,在移动端App上,native 和 H5都是有其存在的价值的,RN可以替代他们,但是目前是不可能完全取代的。
横向对比
知道为什么做、知道哪些痛点和要做这件事件的目的。现在就要知道怎么去做,用什么方式和方法。在市面上,流行的框架有 ionic nativescript weex reactnative四种比较主流的框架。这四个框架都是可以解决上面提到的那些痛点。
对于这些技术,我们提前做了下调研。
Ionic
Ionic is the beautiful, free and open source mobile SDK for developing native and progressive web apps with ease.
就算你不懂任何移动端技术,也是可以hold的住的框架。在之前,我有落地过一个INOIC 1的项目,开发体验非常好,上手速度也非常快。INONIC1是基于ng1, IONIC2 是基于ng2。纯前端的开发模式,对前端开发人员的门槛很低,学习成本也很低。他是基于webview进行开发的APP,使用cordova API调用原生的功能。而且极光JPush之类的第三方插件都对IONIC支持,生态链也非常成熟。
但是唯一的缺陷,也是致命的缺陷就是性能和体验的问题。目前出的IONIC2听说性能有很大改进,但是肯定没办法跟原生的相比。IONIC比较适合于一下小型的APP,和能力有限并且要快速迭代的初创公司。
网上对ionic的评价:
Nativescript
cross-platform mobile applications
出现的很早,比RN还出现的更早一点。和RN与weex一样,都是最终转为native的解决方案,出自于一个保加利亞的軟件公司公司Telerik,比RN还出现的略早点,版本已经到了3.1。也是native、也是有各种组件、支持各种css style、也可以chrome调试开发,也支持集成进native的混合开发。很无解它的短板在什么地方,为什么用的人不多,网络上的相关资料也很少。看到的为数不多的文章写有偏向于整体的解决方案,网上更多的评论是NS更注重于用户体验功能之类的,而对于UI渲染这块是比较弱,不置可否。
大部分来自ionic的开发者,因为可以不用去关注native的相关技术。原生的开发应用性能得到保障。但是相关文档比较少,大部分都得通过官网学习,NS的中文网站都还没翻译全。关于NS的文章也是很少,基本都得看外文文献NS vs RN
其实我对NS没有过多实践,所有知道都是从官网或者其他的人写的文章这样的第二手资料,没什么资格去说这个事情,上面的评论大家看看就好。
WEEX:
Write once, Run anywhere!
原理:Weex 表面上是一个客户端技术,但实际上它串联起了从本地开发环境到云端部署和分发的整个链路。开发者首先可以在本地像撰写 web 页面一样撰写一个 app 的页面,然后编译成一段 JavaScript 代码,形成 Weex 的一个 JS bundle;在云端,开发者可以把生成的 JS bundle 部署上去,然后通过网络请求或预下发的方式传递到用户的移动应用客户端;在移动应用客户端里,WeexSDK 会准备好一个 JavaScript 引擎,并且在用户打开一个 Weex 页面时执行相应的 JS bundle,并在执行过程中产生各种命令发送到 native 端进行的界面渲染或数据存储、网络通信、调用设备功能、用户交互响应等移动应用的场景实践;同时,如果用户没有安装移动应用,他仍然可以在浏览器里打开一个相同的 web 页面,这个页面是使用相同的页面源代码,通过浏览器里的 JavaScript 引擎运行起来的。
其实最开始选型的第一选是weex,选择他原因有三吧
- 基于vue,我们刚做完一个vue的项目,开发体验很不错
- 可以三端统一,不只是 IOS和 Android,还有 H5也是可以,由于它的代码是基于VUE的,在IOS和android只是集成了sdk进行转换。
- 对国产的支持吧
从选用开发,到弃用,归结起来的原因有以下几点吧:
- 生态链欠成熟;pull request 没有响应,论坛的提问也是过两天自己回答。
- 文档不全;刚好也碰到weex官方支持vue,在官方文档也在升级支持vue,然后我们刚好是使用的最新支持vue开发的版本。官放说明文档很不全,得新老切换这看。
- 团队对阿里开源的畏惧;好像都吃过阿里开源项目的亏,阿里KPI项目的尿性也是众人皆知了。又值阿里将weex贡献给apache时间节点,心里怕怕的。
- 能力不够;怕自己hold不住;其实最关键还是我们能力的问题,我们没有那么多时间、精力、人力耗在这个上面。不希望这个月绩效为0分;
- 页面效果不理想;我们拿贴士的页面入手,但是出来的效果,在IOS上显示差异很大。
react native
优点 目的
- 基于 React 前端的技术栈,组件化开发思想
- 相对于 native,开发速度快,成本低
- 相对于 H5,性能更优异,体验更好
- 生态比较成熟,社区活跃,市场成熟,开源组件丰富充足
RN在市场中的使用情况
(图片中数据来源于携程2016.10统计的android市场的数据)
Learn once,Write anywhere!
RN 50K
做不到 Write once, Run anywhere! 对于一些特定的组件,还是要写两套代码。
落地狂奔
快速迭代
因为从weex半路转到RN,所以基本是从零开始。对很多东西都疑惑:
- 怎么将RN集成进美柚App
- RN 和native 怎么进行交互传值
- 页面跳转;native 怎么跳 RN页面,RN页面怎么跳RN 页面。
- 如何开发编译完的bundl文件给Native。
ReactNative是基于react,而我只是对react有理论知识上的了解,还没有落地过任何项目。但是经过一个月的小步快跑。完成了八个页面的改造,18个组件的抽象,还有和native协定了交互的规范。
人员配置
不管是RN 或者weex 都不是真的像他们的宣传语一样,学一次或者写一次就能在任何地方使用,也不可能只由一个程序员完成。你得会客户端的语言,不管你是写独立的RN APP或者是像我们集成native中的APP。对于大部分公司的程序员,基本是不可能写ios、android和前端三端代码,至少现在还没遇到过。比较多的是,android客服端的同学学习react或者vue, ios 客户端的同学学习前端的语言。js是个脚本语言,相对门槛还是比较小的。而前端同学如果之前没有基础积累,再去学习客户端,不管是android 或者ios,难度都是非常大的。
所以这需要一个团队来共同完成,以下是我们的人员配置:
其实在开发过程中,会碰到很多分歧,当谁也无法说服谁的时候,就得找江大了,江大做定夺。
在开发联调过程中,如果是涉及到协议、规范、结构之类的通用部分,我们会一起协商。但是对于可行性研究,前端不可能同时和IOS、android两端同时进行,我们会选择一端进行,如果通过了才会再在另一端开发。我选择的是IOS,xcode运行内存占比只有几百MB,ASD内存占用就达到2G左右。在mac上启动xcode或者模拟器也会很快。如果是开发,你用Android 真机或者ios 真机就都差不多了,因为不用设计底层开发造成的模拟器频繁代码更新。
成果展示
Android的数据统计
来自宏弘的统计数据
从以上数据来看,RN的整体速度比WebView快了1倍,启动速度会比WebView稍微慢点,原因在于RN的启动需要解析bundle文件,需要耗时。Eatable页面,WebView比RN要快,这块主要是启动时间慢导致,可能原因是bundle文件过大导致解析慢。
IOS效果对比
RN底下访问静态页面基本是秒开秒关
H5有时候点击了要一段时间才有响应,页面加载速度也会慢一点。
在长数据列表页,或者picker选择器等上,原生体验秒杀H5
流程图
在框架搭建设计中,得先确定整体的一个流程。当你对整流程有了清晰的认知,对你开发是有很大帮助的。
开发发布流程:
- 编译打包
- 通过RN提供的命令,先生成bundle文件
- 再将编译后的文件进行拆包,根据基础和业务进行拆包,各个依赖的资源文件进行拆封。
- 压缩;混淆压缩代码。减小代码量
- 打包:并根据不同环境不同版本进行分别打成zip包
上传CDN
将文件托管于CDN上,更好的请求加载速度
服务端配置
将上传CDN的地址,配置在服务器上。由相关的权限控制人员在后台服务上配置启停相关RN版本包。
权限管控:开发 和 生产隔离,开发环境上使用的版本由相对应的人员在服务端配置。权限隔离的好处,有助于问题排查,就是责任隔离。解析RN
目前的RN的存在,是作为一个替代H5的方案,也就是只有在使用小工具这块中使用。native不管 H5或者 RN都是通过协议去调用唤醒的,所以会先进行一个判断,当使用RN协议并且RN页面存在,就打开RN页面。否则会跳转到H5页面。
渲染原理
RN的编译过程,会将我们页面的React.createElement 来构建应用 UI,最终在render方法返回的都是元组件(view、image、picker等)。
当我们的页面要在客户端显示,会先在页面的contrller层(android底下是activity类),要new一个View对象。对应的是IOS的RCTRootView.m,android的ReactRootView.java。如果你没学过IOS 和android不要紧,可以理解为是个dom节点。一个页面可以由一个VIEW构成,也可以由多个VIEW构成。所以RN可以是一个页面,也可以是页面中的一小块。比如我们的广告项目在首页的插入就是用RN实现的。在新建view对象时,我们会调用一个引擎类,ios的RCTBridge.m 和android 的ReactInstanceManage.java类,去创建一个jscontext,去加载运行bundle文件。
交互
在初始化的过程中会传递参数
RN 和 Native之间的交互,都是通过Native端提供的MeiyouRNBridge。
通信协议/网络请求
协议的使用 或者网络请求,都是通过RN协议桥 MeiyouRNBridge来实现。
例如:MeiyouRNBridge.showToast(‘请求成功’);MeiyouRNBridge.setTitle(‘测试title’);
对与网络请求和协议的页面跳转,都在底层进行了相关封装。
比如下面的RN页面的跳转,你只需要在意模块的路径,名称,页面标题等相关参数,不用在意页面跳转的类型和这些传输的数据结构。
|
|
具体可以参考地址:
http://git.meiyou.im/iOS/iOS/wikis/work/RN.md
网络请求
使用native提供的封装的请求方法,而不使用RN提供的方法fetch,native会去请求方法做一些处理。比如拼接系统参数,比如根据所处的环境,比如测试、开发,自动切换不同的地址。
异常处理
异常有很多种,网络异常,请求异常,解析异常等等,这些都属于逻辑层面的,是我们开发人员关心的。在业务层面只展示一种结果,小柚子找不到了。
所以相关就很好处理了。
- 一种写异常组件:Exception.js
- 一种是调用native提供的组件:MeiyouRNBridge.setLoadingState(3);
第一种是可以自己控制范围的,更灵活,例如你有个搜索框和列表,当你按条件搜索时候,出现超时等网络异常,你只要在展示部分显示异常,而不用全页面。
第二种是更方便,但通用性差,只要调用提供的桥方法,就能全局覆盖。
白屏处理
在native调用RN页面的时候,如果没下载会下载,还会有解析和渲染的过程,所以会出现个loading动画。在早之前,native 在启动加载RN页面后,会隐藏加载动画,杭州的电商那边就是这么做的。但是这其中有个问题,页面的渲染中包含些异步操作,会有一小段时间,也就是在加载动作结束后,会有一小段白屏或者闪屏出现,这个体验就不好了。
为了解决体验问题,只能牺牲开发一些原则。native提供了关闭加载动画的方法,由我们自行控制结束动画。
工程化
高效 可维护性。对于环境进行配置,
一键命令式的完成编译,解析,拆包,拆分资源文件,压缩混淆,打包,清楚缓存等工作。
是否有必要使用云打包,我们只要合并代码进master分支,就会自动触发。
开发
集成矢量小图标Fontawsome
对于Android 和 IOS,都提供了相对应的集成了美柚基本组件库的开发demo, 可以提供给前端开发,调试,基本可以还原美柚经期App的开发场景。
android
android 的开发demo如下图,
开启调试模式功能
IOS
IOS 的开发demo如下图
代码调试
在模拟器或者真心打开上面的开发demo, android在进入页面后点击标题,ios在电脑模拟器上可以使用快捷键command + d,真机使用手机摇一摇,调出debug页面。有开启调试模式、热更新等等功能,如下图所示
然后再在浏览器输入 http://localhost:8081/debugger-ui
可以打断点跟踪,打印日志和平常web开发调试一样,如下图所示:
只是在页面跳转的时候,使用断点监听会红屏警告,只要点击dismiss就可以跳过去,但是会很影响调试。建议测试页面跳转之类的,在跳转到相关页面再打开调试模式。
永无止境
优化
1、如图所示,JS init+Require,这块时间也就是JSBundle的执行时间,这块耗时是最长的
2、bundle文件随业务代码的增加是越来越大,如果每次更新,那耗费的流量会很大。怎么去缩减bundle文件的大小。
拆包
拆包的原理:解析
拆包策略: 按照RN的基础部分 和 业务部分。
我们拿一个空的RN包进行打包就能看到,一个bundle包的大小是1.7MB, 打包以后的zip包是400kb左右。而RN这个部分的基础包,只有更新RN版本的时候才会进行更新,而RN版本的更新是不向上兼容的,我们在从0.41升级到0.43的时候,就花了很多时间精力。不仅是前端,android 和ios也是一样的。所以这个部分没有特别的需求或者问题,不会变动。而经过拆包后。
基础包208kb, 业务包237kb。解析前index.bundle.js是889kb, 解析后的base.bundle是779kb, 业务index.bundle 是284kb。
怎么使用:
一种是在客户端进行物理合并,将bundle文件拼接在一起,讲资源文件放在统一在同一个文件夹下。但是这样做,除了减少网络开销,不会有其他优化的好处,意义不大。所以放弃了。
另一种是让基础部分和业务部分在同一个作用域中运行。
数据缓存
对网络请求的方法增加了本地缓存的功能。减少网络请求,加快了网页的响应速度。
对网络请求的方法进行封装改造,只要配置个属性标志是否需要缓存。如果这个接口是需要缓存的,我会优先读取本地的存储数据,再异步去请求数据,刷新本地的存储数据。这对很多变更性不大的页面会非常大的作用。类似能不能吃的index页面。极大的提高了打开RN页面的时间。
图片优化
图片在页面中的比重占比非常大,图片的加载速度直接影响页面的加载渲染效果,所以对于图片的优化也是很重的。
针对图片的优化,目前从图片的尺寸 和 动态转换webp两方面进行优化。
关于webp:WebP 格式是 Google 于2010年发布的一种支持有损压缩和无损压缩的图片文件格式,派生自图像编码格式 VP8。
webp的优势:无损压缩后的 WebP 比 PNG 文件少了 45% 的文件大小,即使 PNG 文件经过其他压缩工具压缩后,WebP 还是可以减少 28% 的文件大小。此外,与 JPEG 相比,在质量相同的情况下,WebP 格式图像的体积要比 JPEG 格式图像小 40%,而 WebP 在压缩方面比 JPEG 格式更优越。
美柚客户端已经支持webp格式的图片显示,前端针对webp的这种图片格式进行了优化,针对美柚前端大多数图片都是来自于七牛,做了转码动态的支持。
并设置最大长宽来进一步限制图片大小,对图片裁剪为最大显示的两倍的尺寸。保证清晰度的前提下,优化了图片的大小。
使用说明:使用封装的MImge组件,可配置needWebp 和设置size大小。
|
|
效果:
待优化
版本控制
如果开发人员在CDN上上传了新版本1.2,在服务器上配置的新地址。客户端的更新机制是,对比版本异同进行更新,原先版本在客户端有缓存,但是针对未及时打开app更新的情况下,如果原地址文件被覆盖更改,就会出问题。
- 性能优化:可优化空间,具体等上线以后的数据统计报告
- 自动化流程
- 拆包:commonjs的加载模式,所有的加载运行到内存中,AMD的异步按需加载方式
- 文档不全;开发规范,操作指南之类的。
- CDN文件被破坏风险
- 安全性优化,对 bundle file MD5,进行 RSA 验证
- 框架抽离;可以复用多个项目,单独维护。
(感谢江大,盛华,宏弘的技术支持)