热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

ReactNative拆包及热更新方案·Solartisan

作者:solart版权声明:本文图文为博主原创,转载请注明出处。随着ReactNative的不断发展完善,越来越多的公司选择使用ReactNative替代iOSAndroid进行部

作者:solart


版权声明:本文图文为博主原创,转载请注明出处。


随着 React Native 的不断发展完善,越来越多的公司选择使用 React Native 替代 iOS/Android 进行部分业务线的开发,也有不少使用 Hybrid 技术的公司转向了 React Native 。要说 React Native 最能吸引开发者的地方那就是其拥有前端的开发速度以及原生的体验。


1、序言

今天要跟大家探讨的是 React Native 的拆包及热更新方案,官方并没有很好的支持这一企业十分看中的热更新能力,因此也催生了第三方的热更新方案,如 CodePush 、 react-native-pushy 。由于公司内部有不同的业务线,所以在采用第三方的热更新方案灵活度不够,在调研的初期,我们参考了携程的提到的 jsbundle 拆分和加载优化方案,但这个方案需要改变 React Native 的打包代码及 Runtime 代码,实施难度上非常大,暂无精力深入研究,但这个方案对加载速度提升也是显而易见的。我们暂时放弃了携程的方案,我们前期需要一套相对简单稳定且可行度高的方案,在经过调研及讨论后定下了这样一套热更方案,今天我们就来聊聊这个方案。


2、流程梳理

由于存在多条业务线开发,又希望业务线之间互相独立,但是全量的 bundle 文件又过于庞大,基于这样的考量,我们决定采用业务代码独立热更的方案。

整体流程其实非常简单,不过内部一些细节规则需要仔细推敲。

技术分享图片


3、热更新模块的实现方案

当下选择使用 React Native 的项目大都是基于原有项目的基础上进行接入,所以要达到上线的项目的状态自然要各方面都准备就绪,热更新就作为基建工程之一。以下主要以 Android 端为例进行阐述,iOS 端方案基本一致。


3.1 jsbundle 的拆分

React Native 的代码打包编译后会生成一个 bundle 文件,这里要说明一下,jsbundle 的拆分是基于生成的 bundle 文件可以看成两部分构成(如下图):一是 React Native 包含的的基础类库,一是开发的业务代码。

技术分享图片

基于 bundle 文件的这一特点,我们就可以将完整的 bundle 文件拆分为两部分:

首先需要做的就是生成 common.bundle ,新建一个 blank.js 文件,在文件中仅引入 reactreact native :


1
2

import React from 'react';
import {} from 'react-native';

通过打包命令编译成 common.bundle :


1

react-native bundle --entry-file blank.js --bundle-output ~/Desktop/common.bundle --platform android --dev false

其次,打包完整的 jsbundle ,这将会包含所有的基础类库及业务代码。提醒一句保持 import 的公共模块一致:


1
2
3
4

import React from 'react';
import { AppRegistry } from 'react-native';

...

最后根据 diff 算法将两个文件进行 diff 拆分,由此会生成一个 *.diff 的二进制文件,例如 index.diff

技术分享图片

到这里,大家会得到一个 common.bundle 的文件,一个或多个 *.diff 文件。


关于 diff 算法的使用,记得有几篇文章中推荐 google-diff-match-patch ,虽然 Google 这个开源版本包含多种语言的实现,但由于是基于纯文本的 diff 所以在当下这个场景下并不十分合适,我还是推荐大家使用基于二进制的 diff ,在此也推荐另一种 java 版本的 bsdiff 的实现:jbdiff 。



3.2 bundle 文件的拷贝及合成

在完成拆分以后,我们需要将 common.bundle 及拆分的 *.diff 文件进行 zip 压缩,放入 assets 目录下,为了方便版本管理,我们将其文件名中写入版本号 jsbundle_<版本号>.zip ,例如:jsbundle_1.zip ,每次改 zip 文件包跟随发版时更新,并自动升级版本号。

接下来我们要做的就是将内置于 assets 目录下的 jsbundle_*.zip 拷贝至内部存储,这里不推荐使用外部存储,尽量避免用户删除这些文件。

在拷贝过程中根据历史记录的版本号,进行判断是否需要执行拷贝,拷贝完成后将 common.bundle*.diff 文件进行 patch 合并,合并后的文件即为一个完整的 bundle 文件,文件名规定为 *.diff.bundle ,例如:index.diff.bundle ,在加载时根据模块名进行加载即可。


3.3 diff 文件的更新

说到热更新,反而在关于 *.diff 文件的更新本身并没有什么复杂度,简单来说就是下载替换 *.diff 文件,并合成新的完整 bundle 文件,其他需要注意的则是关于 diff 文件版本的控制。

其他主要工作量在于 diff 文件的生成及上传,这部分工作量最好是不依赖于人工,可以考虑编写 shell 脚本自动完成,以下摘录部分 packer.sh 的打包代码供参考。


1

if [ $platform == "android" ]; then
react-native bundle
--entry-file $commonFile.js
--bundle-output $androidModuleDir/common.bundle
--platform android
--dev false
echo "common.bundle packed!!!"
react-native bundle
--entry-file $module.js
--bundle-output $androidModuleDir/$module.android.bundle
--platform android
--dev false
echo "$module.android.bundle packed!!!"
# 对 jbdiff 打成的 jar 执行文件
chmod +x dmp.jar
echo "diff start =========>>>"
java -jar ./dmp.jar $androidModuleDir/common.bundle
$androidModuleDir/$module.android.bundle $androidModuleDir/$module.diff
# 进行二次 zip 压缩
zip -j $androidModuleDir/$module.diff.zip $androidModuleDir/$module.diff
elfi ...


3.4 对于容器 Activity 的改造

由于对于 React Native 的 bundle 文件加载做了更改,我们就不能直接使用 sdk 提供的 ReactActivity 了,对此我们需要对容器 Activity 进行改造。

而改造的最终落脚点其实是 ReactInstanceManager 的构建,由于我们需要按业务模块加载,所以最终将其进行了部分改造:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24

public class extends ReactNativeHost{
...
protected (Application application, String moduleName) {
super(application);
mApplication = application;
mModuleName = moduleName;
}
...
@Override
protected ReactInstanceManager createReactInstanceManager() {
if(getUseDeveloperSupport()){ //为了保留 debug 的能力
return super.createReactInstanceManager();
}
String path = JSBundleManager.getJSBundleDirPath(mApplication)
.concat(mModuleName).concat(".diff.bundle");
ReactInstanceManager.Builder builder = ReactInstanceManager.builder()
.setApplication(mApplication)
.setJSBundleLoader(JSBundleLoader.createFileLoader(path))
...
...
return builder.build();
}
...
}

将改造后的 Activity 容器也要接入原有项目的路由框架(如果项目本身有的话),至此,整个更新加载就可以串起来了。由于 React Native 本身没有提供 reload 机制,如果需要在更新 bundle 文件后强制刷新页面,需要通过反射处理 ReactInstanceManagerImpl::recreateReactContextInBackgroundFromBundleLoader 达到重新加载刷新页面的效果,比较简单,这里就不再赘述。


4、热更新改造的后遗症

由于采用加载文件系统下的 bundle 文件的形式,在测试过程中发现通过此形式加载的 bundle 文件,图片加载时不能读取到 res 目录下的资源文件,带着这个问题看了相关的 js 源码,发现了一个有意思的地方:


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32

...
class AssetSourceResolver {
isLoadedFromFileSystem(): boolean {
return !!this.bundlePath;
}

defaultAsset(): ResolvedAssetSource {
if (this.isLoadedFromServer()) { //如果是从服务器下发的bundle,资源从服务器读取,对应debug模式
return this.assetServerURL();
}

if (Platform.OS === &#39;android&#39;) { //在android平台
return this.isLoadedFromFileSystem() ?
this.drawableFolderInBundle() ://如果是从文件系统读取的bundle则从文件系统取资源
this.resourceIdentifierWithoutScale();//否则从res读取资源
} else {
return this.scaledAssetPathInBundle();
}
}
...
resourceIdentifierWithoutScale(): ResolvedAssetSource {
invariant(Platform.OS === &#39;android&#39;, &#39;resource identifiers work on Android&#39;);
return this.fromSource(assetPathUtils.getAndroidResourceIdentifier(this.asset));
}

drawableFolderInBundle(): ResolvedAssetSource {
const path = this.bundlePath || &#39;&#39;;
return this.fromSource(
&#39;file://&#39; + path + getAssetPathInDrawableFolder(this.asset)
);
}
}

看到这里就明白了,源码中对资源的加载保持了跟 bundle 文件同源。要解决这个问题有两个方案:1、将 js 源码中的逻辑进行修改,都从 res 中读取资源;2、将 React Native 使用到的资源打包到本地,跟随 jsbundle_*.zip 发布。我个人比较倾向于第二个方案,我主要考虑两点:一是后续 React Native 版本升级的成本,一是可以对于 React Native 的资源单独管理,同时也意外的获得了一个 React Native 资源热更的能力。

整个更新方案到这里基本阐述完毕,方案简单且可行度高,唯一的遗憾是在加载速度上并没有什么提升。我个人感觉 React Native 在 Android 端的加载速度还是个硬伤,机型众多,性能差异极大,在 iOS 端的表现就好上很多 。

最后,吐槽下 React Native 的一个坑,目前最新的 0.41.0 版的 Android 端通过loadingIndicatorSource 属性来指定占位图依然无效,15 年的一个 issues #5017 到现在没有被修复,实在匪夷所思,感觉我是用了假的 RN !!


推荐阅读
  • 本文内容为asp.net微信公众平台开发的目录汇总,包括数据库设计、多层架构框架搭建和入口实现、微信消息封装及反射赋值、关注事件、用户记录、回复文本消息、图文消息、服务搭建(接入)、自定义菜单等。同时提供了示例代码和相关的后台管理功能。内容涵盖了多个方面,适合综合运用。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • Mac OS 升级到11.2.2 Eclipse打不开了,报错Failed to create the Java Virtual Machine
    本文介绍了在Mac OS升级到11.2.2版本后,使用Eclipse打开时出现报错Failed to create the Java Virtual Machine的问题,并提供了解决方法。 ... [详细]
  • 在说Hibernate映射前,我们先来了解下对象关系映射ORM。ORM的实现思想就是将关系数据库中表的数据映射成对象,以对象的形式展现。这样开发人员就可以把对数据库的操作转化为对 ... [详细]
  • 本文介绍了在SpringBoot中集成thymeleaf前端模版的配置步骤,包括在application.properties配置文件中添加thymeleaf的配置信息,引入thymeleaf的jar包,以及创建PageController并添加index方法。 ... [详细]
  • 本文详细介绍了Linux中进程控制块PCBtask_struct结构体的结构和作用,包括进程状态、进程号、待处理信号、进程地址空间、调度标志、锁深度、基本时间片、调度策略以及内存管理信息等方面的内容。阅读本文可以更加深入地了解Linux进程管理的原理和机制。 ... [详细]
  • 1,关于死锁的理解死锁,我们可以简单的理解为是两个线程同时使用同一资源,两个线程又得不到相应的资源而造成永无相互等待的情况。 2,模拟死锁背景介绍:我们创建一个朋友 ... [详细]
  • 后台获取视图对应的字符串
    1.帮助类后台获取视图对应的字符串publicclassViewHelper{将View输出为字符串(注:不会执行对应的ac ... [详细]
  • 《数据结构》学习笔记3——串匹配算法性能评估
    本文主要讨论串匹配算法的性能评估,包括模式匹配、字符种类数量、算法复杂度等内容。通过借助C++中的头文件和库,可以实现对串的匹配操作。其中蛮力算法的复杂度为O(m*n),通过随机取出长度为m的子串作为模式P,在文本T中进行匹配,统计平均复杂度。对于成功和失败的匹配分别进行测试,分析其平均复杂度。详情请参考相关学习资源。 ... [详细]
  • 本文介绍了通过ABAP开发往外网发邮件的需求,并提供了配置和代码整理的资料。其中包括了配置SAP邮件服务器的步骤和ABAP写发送邮件代码的过程。通过RZ10配置参数和icm/server_port_1的设定,可以实现向Sap User和外部邮件发送邮件的功能。希望对需要的开发人员有帮助。摘要长度:184字。 ... [详细]
  • 在project.properties添加#Projecttarget.targetandroid-19android.library.reference.1..Sliding ... [详细]
  • CentOS 7部署KVM虚拟化环境之一架构介绍
    本文介绍了CentOS 7部署KVM虚拟化环境的架构,详细解释了虚拟化技术的概念和原理,包括全虚拟化和半虚拟化。同时介绍了虚拟机的概念和虚拟化软件的作用。 ... [详细]
  • PDF内容编辑的两种小方法,你知道怎么操作吗?
    本文介绍了两种PDF内容编辑的方法:迅捷PDF编辑器和Adobe Acrobat DC。使用迅捷PDF编辑器,用户可以通过选择需要更改的文字内容并设置字体形式、大小和颜色来编辑PDF文件。而使用Adobe Acrobat DC,则可以通过在软件中点击编辑来编辑PDF文件。PDF文件的编辑可以帮助办公人员进行文件内容的修改和定制。 ... [详细]
  • Webpack5内置处理图片资源的配置方法
    本文介绍了在Webpack5中处理图片资源的配置方法。在Webpack4中,我们需要使用file-loader和url-loader来处理图片资源,但是在Webpack5中,这两个Loader的功能已经被内置到Webpack中,我们只需要简单配置即可实现图片资源的处理。本文还介绍了一些常用的配置方法,如匹配不同类型的图片文件、设置输出路径等。通过本文的学习,读者可以快速掌握Webpack5处理图片资源的方法。 ... [详细]
author-avatar
一枝红杏出墙来2001
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有