基于 React Native 的移动平台研发实践-转

下面,有请@小玻玻@普元云计算 为大家直播课程,14:30郝总准时开讲,本节课后安排郝总答疑,祝大家交流愉快。

大家下午好,我是小玻玻,我现在在上海,现在上海天气达到了30度,希望我们今天的微课保持同样的热度。

课程简介:
从 2016 年以来,Gartner 提出了一个新的移动前端技术的分类——Javascript Frameworks for Native Mobile,无论是 React Native、还是 WeeX、以及普元移动平台都属于这类技术。本次分享是普元移动产品部在基于 React Native 进行移动平台研发过程的实践,希望能够抛砖引玉。分享将会围绕以下几个方面进行阐述:

课程大纲:
1、React Native 成为趋势
2、React Native 实践的一些问题
(1)React Native 的学习成本与可替代性
(2)单 Bundle 与多 Bundle 的思考
(3)如何能够更高效的调试(首屏 VS 当前屏)
(4) 热更新与按需更新
3、基于 RN,我们的实践分享
(1)Bundle 的拆解的思考
(2)基于 RN 的扩展(Require、编译等)
(3)当前屏调试
(4)按需更新的实现

嘉宾介绍:郝振明
十多年 IT 从业经验,一直专注于企业信息化的工作,近五年间一直从事企业移动信息化、移动互联网化的咨询、产品工作,曾主持参与了 Primeton Mobile 产品研发、联通集团、广东农信、诺亚财富、中信重工、索菲亚等公司的移动信息化工作。近两年来,致力于基于 React Native 工程化能力的提升、降低实施难度,以及智能化移动平台的产品研发,在移动开发智能化的路上不断进行探索。

5月11日郝振明《基于 React Native 的移动平台研发实践》签到链接请移步→http://t.cn/RaJYok9,大家可前往注册EAII会员享受课前PPT发送,不定期抽奖等一系列权益

大家好,我是普元移动平台总负责人郝振明,很荣幸今天有机会以微课堂的方式与大家在微信群里见面,分享一下我们基于React Native的移动平台研发的一些实践。期望抛砖引玉,也欢迎大家多多指教。

今天的分享我会主要围绕着三个方向展开:

1、 React Native 已经成为了移动前端技术的趋势。
2、 基于React Native 进行移动平台研发过程中的一些思考。
3、 基于React Native 进行移动平台研发过程中的一些实践。

1、React Native已经成为了移动前端技术的趋势

从2014年年底,Facebook计划开源React Native 的时候,我就已经开始关注TA了,关注的主要原因是,我们在2012年的时候,将我们的移动平台前端开发技术确定为“DSL->Javascript->Native Mobile”这个技术流派。要知道在那个时代,绝大多数的友商要么选择Hybrid,要么选择HTML5作为移动平台的跨平台前端解决方案。

然而,这两种方案最终的UI渲染,本质上都需要依赖Webkit,通俗点说就是UI最终是通过浏览器内核渲染。我们当时在技术选型的时候实在无法容忍Webkit在Andriod上的体验,而选择了驱动原生(注:这个名字是我起的,也是为了区别于传统的Hybrid技术)的方式。

当时的这个技术抉择,在当时是冒着巨大的风险的,现在看来,我们是非常幸运的。

后来Facebook 推出React Native 后,阿里系也推出了自己的Weex,甚至Gartner针对这类技术在2016年的报告(IT Market Clock for Mobile App Development, 2016 )中首次出现并并为这个技术流派起了一个名字——Javascript Frameworks for Native Mobile。
file

Garnter将这个技术流派当如了“Advantag”中,可见Gartner对这个技术流派的认可。

Javascript Frameworks for Native Mobile这类技术的几个特点:

1、 开发期基本采用类Web语言,比如React的语法
2、 运行期并不是采用Webkit做渲染,而是采用Native的渲染方式。
3、 与Native 进行交互的通道是采用Javascript的方式。

当然,因其技术的先进性让各大互联网公司纷纷进行实践上的尝试,取得了不错的效果,包括天猫、腾讯QQ、手机百度、美团点评、携程等等。React Native 也建立了很好的生态,大家对案例如果有兴趣可以关注一下https://facebook.github.io/react-native/showcase.html

考虑到群里的很多朋友对React Native 有了很深刻的理解,我这里将跳过React Native原理上的阐述,如果大家有兴趣可以分享之后加我微信一起讨论。

下面,我来讲 第二部分:基于React Native 进行移动平台研发过程中的一些思考

尽管React Native 在移动前端存在着无可比拟的优势,但每一家在工程化的过程中还是存在各自不同的思考。而作为移动平台,不是简单的解决单一的一个App的问题。

移动平台是支撑企业全面移动信息化的平台,需要解决企业面向不同场景下的各种诉求。针对移动App的使用者的场景不同,存在面向人和面向组织两种不尽相对的要求:

file

面向人:每个人对应的App功能是基本相同的。人与人是平等的,就如今天在线的各位和我一样,在支付宝里看到的功能是相同的。这种情况多出现在面向最终消费者的时候。

面向组织:是指功能因其所属的组织和职级决定了其所见和所能用的功能。当事人所处的组织机构发生了变化,功能也随之产生变化。

针对面向组织,需要举个例子来说明一下:假如我本人之前是一名普通的manager,其实对于产品线的经营报告并无权阅读(图一);当我被Promote 为产品线总经理的时候,我的App里就应该有“智能报表”的微应用(图二);当我调岗到别的团队(比如从事行政工作),我就不应该再具有“智能报表”的相关功能(图三),如下图所示:

file

随着岗位和职级的变化,功能从图一到图二再到图三,我还是我,而我App内的功能却发生了变化,这在企业中是非常常见的诉求。

实现上述的功能,从技术方案角度看,有多种方式,但是怎么更合理呢?我们可以思考一下。

首先,“智能报表”功能是否可以将UI已经打入App中,通过权限控制对应的前端“智能报表”是否显示?回答是不可以。主要原因有三:

第一、类似这种功能在企业中非常多,如果要将UI全都打入到App中,这需要所有功能的全集,没有三四百M下不来。而用户实际上只有权限使用几分之一甚至是十分之一的功能,却要每次为此多更新两三百M,这是不合理的。

第二、如果智能报表这个功能在用户安装ipa或者apk的时候,尚未开发完成,而是后续才迭代上线的,那么这个用户就无法及时使用到这个功能。

第三、如果失去了相关的功能权限,需要的是相关的功能清除,甚至包括其相关的数据,而不是简单的隐藏,这既不安全又不合理。

这就意味着,移动平台必须能够动态的方式更新应用内功能,而且必须能够结合权限提供按需的热更新能力。

其次,在企业中不得不面对的是多供应商的问题,智能报表功能跟其他功能(比如:审批)是一个开发团队开发的吗?

显然,在企业中完全有可能是不同的供应商进行的开发。不同供应商之间,不可能做到代码级的共享的,拿到所有移动项目的代码再进行打包,这是一件非常难以推动的事情。

移动平台必须保证对于多团队、跨地域的方式也能支持并行研发。这就意味着必须提供开发期的隔离。

移动平台需要支撑上述的业务场景,显然直接使用React Native 是难以满足要求的,这就引发了我们对于React Native实践的一些思考。

思考一:React Native 的学习成本和可替换性

作为移动平台,不得不考虑的是学习成本,在企业的供应商中是否能够对React Native的技术储备达到相关的要求,如何能够屏蔽对于技术实现的细节。

众所周知,React Native 发布版本非常的频繁,一个周之前已经发到0.44,对于大规模使用时如何屏蔽版本的频繁升级导致的业务代码的重构,方便进行版本的可替换性。

如果能够将React Native实现换成其他实现(比如Weex),而上层业务代码能否不需要调整,真正做到实现的可替换性。

基于这一点的思考,移动平台采用了基于传统Web语法的DSL,作为开发期语言,降低了RN的学习成本;同时DSL层可以隔离业务代码与平台实现相关性,为后续RN版本更新等提供了良好的隔离,大致的示意图如下:

file

这里需要说明的一点是,我们并没有真正考虑基于Weex作为移动平台的一种实现,而是从技术架构上成为一种可能性。

思考二:React Native 的单bundle VS 多bundle

在谈论React Native的单Bundle与多Bundle的问题之前,首先,我们先回头看一下React Native 默认的Bundle 机制。

在基于RN编写App时,无论开发期创建多少个文件,RN都会将这些文件一并打到一个bundle里去,简单说默认的RN就是一种单bundle的方式,其打包bundle的大概过程如下:

file

因React Native 默认采用的是单Bundle的模式,所以,其更新机制也就仅仅能够以替换这个Bundle的方式进行,虽然有一些通过diff的方式提供增量更新的方式,但这种方案仍然无法满足上面例子中的“智能报表”的按需获取的能力。

另外,在进行编译打包的时候,需要获取所有项目的源代码,这对于多供应商的情况下也不适用。

所以需要解决的两个问题是:

1、在打包Bundle时,必须提供以多Bundle的方式进行。
2、在开发期,必须解决多微应用每个能够独立以Project的方式存在。

思考三:React Native 的调试的首屏进入VS 当前屏刷新

对于开发工程师,很重要的工作就是调试,以RN默认的单Bundle模式,势必会带来另外一个挑战,就是当资源发生任何变化时,必须重复上述的打包Bundle的过程并进行加载,看到的UI界面永远是第一屏。

实际上,我们期望的绝大多数场景是看到当前修改的资源所在的屏的UI效果。从这个维度看,我们必须能够将Bundle控制在一个资源的粒度,并确保当前bundle的动态热更。

另外,虽然React Native 默认不承诺跨平台,但跨平台(即一套代码同时支持iOS、Andriod)是移动平台的必备特性了。如何能够支持多屏同时调试,也将是一个必须考虑的问题。

思考四:React Native 的热更新VS 按需更新

说到热更新,这里不得不提的是几个月前,一堆的App被苹果拒掉的事情,这个事情曾一度让React Native 等Javascript Frameworks for Native Mobile 技术流派背黑锅。

其实这件本质上还是因为某些热更方案调用了私有的API而引起的,后来导致的局面时一堆三方的SDK都受到牵连,最终导致了使用这些SDK的App被拒。插一句,我个人觉着第三方的SDK在没有让使用它们的App知晓的情况下就进行热更新,就是耍流氓,谁又能保证更新后的SDK不做点什么呢。

回到热更本身,我认为,基于React Native 进行热更应该是一个必须的特性,而实际上我们需要提高要求,提供按需更新的能力。

最后一个部分:基于React Native 进行移动平台研发过程中的一些实践

基于上面的一些思考,我们基于React Native进行了一些实践,这里挑出几点给各位做个简单分享。

实践一:引入DSL层

首先,我们引入了DSL层,这里的语法采用了传统Web工程师熟知的HTML、CSS、Javascript,而使用移动平台的工程师无须对React进行过多的深入。在HTML的标签定义中,从语义上尽量能够对开发人员亲切,从习惯上尽量保留原有开发人员的一些习惯,比如对state的封装以getter、setter的方式提供能力,而这些标签需要一一以React Component的方式进行了实现。我们以Label为例(后续出现的代码均为示例代码片段):

DSL语言会在开发期编译成JSX,然后再编译成可被React Native 运行的javascript(涉及到拆分Bundle和编译,这里暂不展开)。

file

DSL编译成JSX,主要的工作原理大致如下:

1、 HTML 标签的处理,主要是与RN的render进行关联
2、 CSS 的处理,主要是与RN的StyleSheet进行映射
3、 Javascript的处理,主要是嵌入到JSX中。

file

上面的代码示例左侧为基于DSL语言编写的代码,右侧是生成JSX后的代码。

实际上,在工程化过程中,并不是像上面的示例代码那么容易做好,无论标签的定义,还是从DSL转换成JSX都是一个巨大的工程,且会遇到很多的问题。

实践二:拆分Bundle

在拆分Bundle上,我们遵守两个原则:

1、将系统库作为一个bundle文件,独立存在。
2、将每个的Module作为一个独立bundle文件。

这种拆分原则将bundle拆分成小粒度的针对Module级别的bundle,这带来的好处是,可以方便的跟DSL中HTML文件进行一一映射,其加载单元的粒度可以理解为Page级别,而非整个App。

我们以require 为例,下图为默认的加载方式,如果没有对应的Module Factory,就会以异常结束,如下图:

file

扩展后,当判断没有对应的Module Factory的情况下,并不是以异常退出,而是增加了加载对应的Module级别的bundle,如下图所示;

file

当然,这就必须需要移动平台自行实现RTC_PM_JSCExcutor用于加载Module级别的Bundle。这一部分代码需要采用原生的Object-C或者Andriod Java实现,下面以iOS的示例代码

file

实践三:引入微应用

在将每个Module打成一个Bundle后,会让项目内资源的关系不易管理,这时我们引入了微应用的概念,用于完善应用内逻辑关系。

file

1、将原有的一个App对应一个Bundle的模式,改成一个App对应多个MicroApp,一个MicroApp对应多个Bundle模式。

2、将原有的一个Bundle对应多个Module的模式,裁剪成一个Bundle对应一个Module的模式

实践四:多屏调试

多屏调试与当前屏刷新,在移动平台IDE端的产品的定义中还是占有很重要的地位,因其直接影响了开发期的效率。

针对React Native 默认的编译核心框架,我们简单的可以总结为四件事情:

1、 node-haste:主要是监听Module变化 ,把变化的Module从Module缓存中移除。
2、 ModuleCache:Module编译缓存,把编译好的Module缓存起来,Module没有发生变化的情况下,直接使用缓存组装成bundle
3、 Resolver:实现全局系统级库,语法级兼容实现,包括:ES5,ES6实现 兼容实现的引入 。实现Module factory的包装
4、 JSTransformer:调用Babel编译JSX文件到JS。

其中1和2有很大原因是因为单bundle导致,当每个HTML文件对应一个Module,每个Module 对应一个bundle后,移动平台需要的就是监听HTML等文件的资源变化即可。如下图:

file

而这里的编译引擎基本上做的事情是:
1、 DSL->JSX
2、 JSX->js

其中后者主要的工作如下所示:

file

而为了能够更好的调试,需要对相关两种更新机制:

1、 批量更新
a) 包括初次批量更新部署,下载所有文件
b) 使用过程中检查文件更新部署,判断需要更新的文件列表

2、 单页更新
单页更新是确保其可以当前页保存,当前页刷新调试的主要机制

file

通过上述的方式,结合移动平台的IDE,可以提供

1、同时支持多手机终端的多屏调试(可以同时iOS和Andriod)
2、提供了当前屏动态刷新动态调试

实践五:按需热更

当上述的实践完成后,按需更新就成了一个相对较容易做到的事情。所以移动平台提供了两级打包编译机制,在无需调整代码的情况下,可以选择以微应用的方式出现其他的App内,还是以独立的ipa/apk的方式存在以移动设备中。其基本原理如下图所示:

file

小结:

基于React Native进行移动平台研发是一个系统性的工程,上述的工作仅仅是其中的一小部分,期间的坑还有很多,今天的分享也仅是从大粒度的方面进行了分享,欢迎加我微信或者关注我们的公众号(EAWorld),一起探讨。

实字群提问:rn现在版本号还很低。而且根据某个版本做出来的东西升级rn版本后总是出错。对于这种情况,您能不能给些建议?

讲师回答:这个确实是一个必须要考虑的事情。在我们的考虑中,是使用DSL隔离业务代码和RN框架的强依赖关系,简单点说,业务的移动端代码,是基于DSL层编程。这一点在实践一里面提了一下。

实字群提问:请问能不能详细说说dsl的实践情况?有没有什么参考资料?对于小团队加上dsl层是不是非常困难?

讲师回答:DSL的实践,对于小团队确实有一定的门槛,DSL可以参考移动平台的API手册。其实最近的微信小程序,也可以理解为一种DSL语言,你也可以参考。另外,RN的升级确是比较棘手,建议不要每个小的版本都进行升级。

提问:两级编译最终生成跨平台包的过程中,需要对中间结果手工调整吗?

提问:rn不像android或iOS平台有自己完整的系统,他很多功能的实现都依赖于第三方库或工具。这就造成了选择第三方工具时非常困难。有时候我好不容易决定用某个库,但是发现这个库的一些依赖不支持我现用的rn版本。对于选择第三方工具和库您能不能给些意见?

讲师回答:对于第三方的依赖,我们是将RN的体系拨开了看,比如在实践四中提到的RN编译的四件事情,第一个node-haste,我们就没有依赖,而是通过IDE
的资源监听来替换,所以就无须依赖相关的三方工具了

鹤字群提问:微应用可以做到按权限显示吗?

讲师回答:对的,是按照权限显示的,由于时间原因,今天的第五点没有展开。在我们的产品中,更新服务器是结合权限认证的。App的更新策略是与更新服务器进行确认的

享字群提问:怎么可以多屏调试

讲师回答:多屏调试这个章节中提到了IDE,在开始调试的之前,IDE和手机端(可以理解为一个调试的APP)已经预先建立的通讯,这个通讯是支持1(IDE)对多(手机终端)。当IDE发现有资源变更的情况下,产生changlist,IDE的网络服务会跟调试客户端产品通讯。调试客户端按需更新(参加分享中提到的两种更新机制)js文件,每一个js都是独立的Bundle,每一个调试客户端会单独重新load这个文件。这需要IDE和多调试客户端配合起来工作的