ReactNative实战

一、引言

之前的文章客户端Hybrid架构设计之——WebView方案的实现,已经介绍了如何用WebView来实现Hybrid架构。当时提到了这种方案的几个缺陷:

  • H5页面的部分用户体验不如native
  • H5也需适配各平台机型
  • 原生端基础框架搭建费时费力,有时一些特殊功能需要ios、Android、H5三方联合开发,增加了沟通协调成本

所以在这套架构基本开发完善之后,项目组马不停蹄的开始研究相对更先进、更优的解决方案——ReactNative/Weex。他们相比WebView方案的区别就在于,js代码最终会被翻译成原生代码,呈现在用户面前的也是原生组件,所以性能、体验也是无限趋近于原生级(之所以只能是趋近,因为还是有js和原生通信所产生的性能损耗)。

经过近半年的调研、学习、实战,项目最新版本的主要页面已经全部由ReactNative来实现,既然有了些成果,这次就来跟大家做个简单的分享。本文为了方便,主要还是从Android的角度出发,iOS的思路也是一样的。

二、选择RN还是Weex

结果很明显,我们选择了RN,但不得不说,从一开始,烫爷一直是更倾向于Weex的,因为当时觉得Weex相比RN有以下几点优势:

  • Weex开发者自身就是RN早期使用者,他们遇到的很多RN问题,都在Weex中设法解决了
  • Weex号称“一次编写,多端运行”,比RN的“Learn Once Write Anywhere”强不少
  • Weex对新人非常友好,上手简单,这一点能得到很高的印象分
  • Weex使用的Vue相比于ReactNative使用的React,更加轻量级,对于无前端经验的客户端开发人员来说更友好
  • Weex是中国人自己的框架,支持国货,人人有责。

简单说,Weex更新更性感更中国。那么大家要问了,Weex那么棒,最后怎么还是选了RN?理由也不复杂:

  • RN比Weex成熟太多了,业界已经有不少成功案例,而Weex在当时只有几个小demo,如果项目开发中遇到框架本身绕不过去的坑,很容易两眼抓瞎
  • RN虽然不标榜“一次编写,多端运行”,但是大多数的组件,包括社区提供的第三方,还是可以在iOS、Android两个平台通用,不能通用的也可以通过js中间层去屏蔽对业务代码的影响,毕竟这些都是一劳永逸的活
  • RN的文档小烂,但是Weex几乎没有文档
  • Weex是中国人自己的框架,还他喵的是阿里的框架(大雾)

技术选型,可能还是要稳妥一点,不能一味追求新潮,毕竟支撑业务才是第一位的。

三、ReactNative实现核心模块简介

RN方案其实和WebView方案非常相似,实现起来也并没有更麻烦,主要还是以下几个模块

  • RN框架
    RN框架本身已经是一个相当成熟的框架了,前期的配置看一些官方文档已经足够,可以较方便的把RN项目跑起来。
  • 路由模块
    其实一个纯RN应用只要一个Activity就足够了,但大多数项目都有历史包袱,比如烫爷的项目,又有原生页面,又有H5页面,现在又多了RN页面,怎么在这些页面之间自由跳转,就得依赖路由模块了,这个模块的介绍同样可以参考之前的文章客户端Hybrid架构设计之——WebView方案的实现
  • 更新模块
    RN的一大好处是可以不用发客户端版本,动态更新,所以更新模块尤为关键。
  • 统计模块
    由于市面上大多数统计sdk不会统计RN数据,所以我们很有必要自己实现相应的统计模块,不然我们对RN页面性能到底如何,完全不能做到门清,那后期优化就会比较的茫然和无的放矢。

同时还有一些需要额外学习的前端知识

  • ES6语法和React
    这是基础中的基础,写RN代码必须的,建议可以看看阮一峰相关的教程。
  • Flexbox布局
    在ReactNative中我们使用flexbox规则来指定某个组件的子元素的布局,这个不难,看看官方文档就足够了。
  • Redux、saga等配合React使用的框架
    这次折腾,一大感慨就是前端社区确实比客户端社区热闹好多,各种各样各种功能的前端框架层出不穷。不过烫爷建议,一开始只要使用React已经足够了,只有当你的应用足够复杂以后,才有必要去使用Redux等第三方框架,一开始千万不要贪多嚼不烂,那样很容易由于学习曲线过于陡峭,而打击积极性。

四、原生改造

  • Bundle路径修改
    RN会把所有的RN代码打包成一个Bundle,框架读取这个Bundle就可以加载相应的RN页面了。框架默认是读取预置在本地比如Assets里的bundle文件,如果要修改路径,就需要修改相关的RN代码。

    拿Android举例,就需要写个类继承ReactNativeHost,并修改getJSBundleFile()方法,并且为了能应用这个自定义的Host,还需要修改或者覆盖对应的一系列文件,如ReactActivityDelegate、ReactActivity等。

  • Activity和原生模块关联
    有些RN无法完成的功能,需要靠自定义原生模块来实现,但是由于RN框架没考虑多Activity的情况,那样在原生和RN之间的交互就可能引发混乱。

    举个例子,RN页面需要通过接口请求结果来通过原生模块设置title,但是在结果返回之前,我们可能已经跳到一个新Activity了,这个时候就不应该由新的Activity去设置title,而应该找到之前的Activity去做这件事。所以我们要做的就是把Activity和RN页面,以及在这个页面上发出的原生模块方法做一个绑定。

    具体的做法是每个RNActivity都使用自己实例的hashcode作为自己的id,在创建RN页面时把这个id传给RN,而RN页面在调用原生模块方法时,则把这个id作为一个参数再传回来。同时我们维护一个RNActivityPool,用RNActivity的id作key,来保存RNActivity,这个时候在调用原生模块方法时,就可以通过id来找到需要对这个方法负责的RNActivity了。

  • Launch页面和预加载
    无论是RN框架的启动还是RNBundle的加载、RN页面的实际渲染,都需要一定时间,而当这些因素叠加在一起,也即启动第一个RN页面时,白屏时间会长的有点让人难以接受(5s左右甚至更长)。

    解决的办法是Launch页面完全用原生做,并且在Launch页面预加载bundle。一般我们的Launch页面都会做一些别的初始化工作,或者广告加载等,这些时间足够完成RN的预加载了,这样一来打开第一个RN页面就和后面打开其他RN页面没有明显的差距了。预加载的方法也有现成的

ReactInstanceManager reactInstanceManager =
 ((ReactApplication) getApplication()).getReactNativeHost().getReactInstanceManager();

if (!reactInstanceManager.hasStartedCreatingInitialContext()) { // 这句必须加,不然容易重复create,造成RN抛出异常
    reactInstanceManager.createReactContextInBackground();
}

五、更新模块

首先,当然可以使用Microsoft/code-push这类第三方更新组件,但是为了更好的把控这一核心功能,包括未来对多Bundle的处理优化等,有余力的同学,最好还是自己实现更新模块。

1、差分更新技术

更新模块中的一个核心技术点就是差分更新技术,差分更新技术主要依赖文件的开源二进制比较工具bsdiff。我们用apk更新的例子来简单介绍下差分更新技术:

自从 Android 4.1 开始,Google Play 引入了应用程序的增量更新功能,App使用该升级方式,可节省约2/3的流量。现在国内主流的应用市场也都支持应用的增量更新了。

增量更新的原理,就是将手机上已安装apk与服务器端最新apk进行二进制对比,得到差分包,用户更新程序时,只需要下载差分包,并在本地使用差分包与已安装apk,合成新版apk。

例如,当前手机中已安装微博V1,大小为12.8MB,现在微博发布了最新版V2,大小为15.4MB,我们对两个版本的apk文件差分比对之后,发现差异只有3M,那么用户就只需要要下载一个3M的差分包,使用旧版apk与这个差分包,合成得到一个新版本apk,提醒用户安装即可,不需要整包下载15.4M的微博V2版apk。

以上内容摘自 cundong/SmartAppUpdates

如上所述,差分更新技术可以有效减小更新包的大小,让更新更有效率。

2、具体流程
  • 服务器端
    每次RN代码打包时,都自动生成当前最新版本的全量包,以及新版本和最近5个旧版本的差分包,保存在服务器端。客户端发起更新检查接口时上报自己当前的RN版本号,如果版本差小于等于5,服务器端就同时下发差分包和全量包的下载地址,反之如果版本差大于5,则只下发全量包地址。

  • 客户端
    客户端每次启动时同步做两件事:
    a. 发起更新检查接口,询问服务端是否需要更新,如需要,是差分更新还是全量更新。更新时,优先进行差分更新,具体步骤为下载差分包——与旧包合成——验证合成包的MD5值是否与服务器先前下发的一致,这些都完成后就可以把合成包保存好,等待下次重启时应用了。这一过程中如果出现任何问题,则放弃差分更新,去下载全量包进行全量更新。

    b.应用当前本地保存的最新的RN包,并删除旧包。如果既没有新包,也没有旧包,则从本地Assets中拉取打包时预置的包。

    注意,之所以下载完新包不马上应用,而是要等到下次重启时才生效,主要是担心在用户使用过程中突然重新加载RN页面有可能改变页面UI甚至是业务流程,这会导致用户体验不佳或者操作失败。

六、统计模块

高级程序员应该树立一个意识,开发并不只是开发完功能就结束了,事实上功能开发可能只占了整体工作量的一半,设计、测试、统计、监控等则占了另外一半。你开发的系统不应该仅仅满足于跑起来,还应该有一套成熟的统计系统去分析系统运转状态,有一套监控系统在出现故障时能自动采取措施或至少发出预警。由于RN是一个新框架,无论是框架本身,还是我们的应用技巧都还不够成熟,所以非常有必要去对RN的性能、错误等进行统计,并以此作为未来优化的根据。

  • 性能统计
    性能统计主要是统计RN页面的白屏时间,具体方法是把一个页面创建时各个生命周期的时间戳记录下来,再统一上传到服务器做分析。生命周期从前往后依次是onCreate(原生)、componentWillMount、componentDidMount、componentWillUpdate、componentDidUpdate

    可以简单的认为
    RN初始化时间 = componentWillMount - onCreate
    静态UI加载时间 = componentDidMount - onCreate
    服务器数据返回页面刷新时间 = componentDidUpdate - onCreate

    (ps:因为只要改变state就会触发componentWillUpdate和componentDidUpdate,所以需要做个标志位,只记录关键接口第一次返回后触发的componentWillUpdate和componentDidUpdate)

    因为绝大多数页面都需要调用服务器接口获取数据,之后再刷新页面,一般也只有在此时用户才能看到有意义的信息,所以我们更关注第三个指标,也可以直接把第三个时间当做是广义的白屏时间。

    以下是烫爷的项目收集到的数据,基本是未经过什么特别优化的RN表现,可以看出白屏时间基本在1s以内,明显还是比Hybrid的表现好不少的,而且比较让人意外的是,Android平台的性能并没有比iOS差多少,这一点比WebView方案强上很多。


    WechatIMG2.jpeg
  • 埋点统计
    RN的埋点比较简单的方法是把RN自带的几个可点击组件再封装一遍,包括TouchableHighlight、TouchableNativeFeedback、TouchableOpacity、TouchableWithoutFeedback,在其中添加统计功能,这样开发业务时只要用封装好的组件,再添加对应的统计id就可以了。

七、其他

  • 屏幕适配
    RN框架默认的长度单位是dp,但是在某些场景用dp并不能很好的适配所有屏幕大小,比如有些屏幕宽320dp,有些宽360dp,这就给我们适配造成了麻烦。

    这里推荐一个三方库vitalets/react-native-extended-stylesheet,这个库有不少功能,其中一个就是可以适配iOS和Android的不同屏幕。比如我们可以在框架中设定屏幕宽度为750px,设计图全部按750px宽来出,之后开发设置具体组件的长宽时,就可以完全按照设计图上的尺寸来,随后框架会根据设备的长宽去做等比缩放以此达到适配效果,原理和安卓原生的UI适配框架hongyangAndroid/AndroidAutoLayout很像。

其实关于ReactNative的内容还有很多,这篇文章就先总结到这里,相信把上面提到的那些做好,再结合下官网文档,已经足够做出一个靠谱的商业级app了。等未来烫爷研究的再深入些,再找些别的干货来给大家作汇报,我们到时再会!

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,324评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,303评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,192评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,555评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,569评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,566评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,927评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,583评论 0 257
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,827评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,590评论 2 320
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,669评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,365评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,941评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,928评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,159评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,880评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,399评论 2 342

推荐阅读更多精彩内容