React Native JSBundle拆包之原理篇
概述
RN作为一款非常优秀的移动端跨平台开发框架,在近几年得到众多开发者的认可。纵观现在接入RN的大厂,如qq音乐、菜鸟、去哪儿,无疑不是将RN作为重点技术栈进行研发。
不过,熟悉RN的开发者也知道,早期的RN版本中打出来的包都只有一个jsbundle,而这个jsbundle里面包含了所有代码(RN源码、第三方库代码和自己的业务代码)。如果是纯RN代码倒没什么关系,但大部分的大厂都是在原生应用内接入RN的,而且一个RN中又包含许多不同的业务,这些不同的业务很可能是不同部门开发的,这样一个库中就有许许多多的重复的RN代码和第三方库代码。
所以,一般做法都是将重复的RN代码和第三方库打包成一个基础包,然后各个业务在基础包的基础上进行开发,这样做的好处是可以降低对内存的占用,减少加载时间,减少热更新时流量带宽等,在优化方面起到了非常大的作用。
拆包流派
moles-packer
moles-packer 是由携程框架团队研发的,与携程moles框架配套使用的React Native 打包和拆包工具,同时支持原生的 React Native 项目。
特点:重写了react native自带的打包工具,适合RN0.4.0版本之前的分包。维护少,现在基本没有多少人使用,兼容性差。
diff patch
diff patch大致的做法就是先打个正常的完整的jsbundle,然后再打个只包含了基础引用的基础包,比对一下patch,得出业务包,这样基础包和业务包都有了,更新时更新业务包即可。差分包的工具可以google-diff-match-patch
metro bundle
目前,最好的RN分包方案还是facebook官方提供的metro bundle,此方案是fb在0.50版本引入的,并随着RN版本的迭代不断完善。也即是说,只要你使用的是0.50以上的RN版本,就可以使用metro bundle进行差分包进行热更新。
配置内容比较多,这里主要看createModuleIdFactory和processModuleFilte两个配置参数。
原理篇
RN启动分析
为了更好的理解RN的分包和和加载机制,下面通过源码来看看RN的启动过程。
注:本篇使用基于最新的0.57.7版本进行分析
1,JS端启动流程
index.js 作为RN应用的默认入口,源码如下:
AppRegistry是所有 RN应用的 JS 入口,应用的根组件通过AppRegistry.registerComponent方法注册自己,然后原生系统才可以加载应用的代码包并且在启动完成之后通过调用AppRegistry.runApplication来真正运行应用。registerComponent对应的源码如下:
RN项目index.js文件的在调用registerComponent 方法时默认传入了 appKey、ComponentProvider 两个参数,而section是可以不用传的。然后,registerComponent方法会调用renderApplication方法参数并调用了 renderApplication 方法。renderApplication的源码如下:
renderApplication中调用了AppContainer组件来封装当前rootVIew组件,并最终调用AppRegistry.runApplication来启动应用程序。
在 runApplication 方法中,RN会通过 runnables[appKey] && runnables[appKey].run 来检查是否可以找到appKey对应的module组件,如果没有,则会抛出异常。
那么,RN编写的页面又是如何在Android系统中显示的呢?那就得看看RN的Android端源码了。
2,Android启动流程
打开RN的Android项目,可以发现,Android的src目录下就只有MainActivity和 MainApplication 两个Java类。其中,MainActivity 为原生层应用程序的入口文件,MainApplication为Android应用程序入口文件。 MainActivity.java文件的源码如下:
MainActivity类的代码很简单,该类继承自 ReactActivity 并实现 getMainComponentName 方法,getMainComponentName方法返回与 AppRegistry.registerComponent 的 appKey 相同的名称。
MainApplication类也比较简单,源码如下:
MainApplication 主要完成了三件事:
实现 ReactApplication 接口,重写 getReactNativeHost 方法,返回ReactNativeHost实例。
定义并初始化 ReactNativeHost,实现getUseDeveloperSupport、getPackages、getJSMainModuleName 方法,完成初始化设置。
在 onCreate 方法中,调用SoLoader的init方法,启动C++层逻辑代码的初始化加载。
ReactActivity
MainActivity 继承 ReactActivity 类,并重写了getMainComponentName 方法,并且方法的返回值需要和我们在JS端的值保持一致。ReactActivity最核心的就是ReactActivityDelegate。
很明显,ReactActivity 类采用了委托的方式,将所有行为全权交给了 ReactActivityDelegate 去处理。这样做的好处是,降低代码耦合,提升了可扩展性。下面我们看一下ReactActivityDelegate类:
ReactActivityDelegate类重点关注loadApp方法, loadApp 方法主要做了三件事:
创建 RootView实例;
调用 RootView 实例的 startReactApplication 方法,将ReactInstanceManager实例、appKey、启动时初始化参数作为参数传递过去;
将 ReactRootView 设置为 MainActivity 布局视图;
ReactRootView
在loadApp里面,首先创建一个ReactRootView,这个mReactRootView继承自FrameLayout,作为界面的跟布局,然后调用mReactRootView.startReactApplication()方法启动RN应用。涉及的源码如下:
这里会执行到mReactInstanceManager.createReactContextInBackground()这个方法去生成reactnative的上下文对象。
上面代码首先将mHasStartedCreatingInitialContext置为true,然后在 startReactApplication 方法中调用了 ReactInstanceManager 实例的 createReactContextInBackground 方法。
ReactInstanceManager
在 recreateReactContextInBackgroundInner 方法中,首先判断当前环境是否为开发者模式,在开发者模式下会执行 onJSBundleLoadedFromServer 方法从服务器加载 jsBundle文件。否则执行 recreateReactContextInBackgroundFromBundleLoader 方法从本地目录加载。
在 recreateReactContextInBackgroundFromBundleLoader 方法中调用了 recreateReactContextInBackground(mJavaScriptExecutorFactory, mBundleLoader) 方法。jsExecutorFactory 为 C++ 和 JS 双向通信的中转站。
jsBundleLoader 为 bundle 加载器,根据 ReactNativeHost 中的配置决定从哪里加载bundle文件。
接下来,我们看一下 runCreateReactContextOnNewThread 方法。
执行到这个的时候,系统最终会开启异步任务ReactContextInitAsyncTask来创建上下文ReactApplicationContext,在ReactContextInitAsyncTask的doInBackground会调用createReactContext方法
ReactInstanceManager
在这个方法里面,先生成nativeModuleRegistryBuilder和jsModulesBuilder,nativeModuleRegistryBuilder用来创建JavaModule注册表,JavaModule注册表将所有的JavaModule注册到CatalystInstance中;jsModulesBuilder用来创建JavaScriptModule注册表,JavaScriptModule注册表将所有的JavaScriptModule注册到CatalystInstance中。接着会执行下到 processPackage(coreModulesPackage,nativeModuleRegistryBuilder,jsModulesBuilder)代码,CoreModulesPackage里面封装了RN Framework(包括native和js端)核心功能,包括:通信、调试等,调用processPackage将coreModulesPackage里面对应的NativeModules注册到JavaModule注册表中,对应的JSModules注册到JavaScriptModule注册表中,底下就会执行用户自定义的ReactPackage,将对应的modules注册到相应的注册表中,JavaModule注册表和JavaScriptModule注册表注册完毕之后,就是去生成一个catalystInstance,这个类主要是负责三端的通信(通过ReactBridge,在catalystInstance的构造函数中调用initializeBridge方法生成),接着调用setGlobalVariable(Native方法)把Java Registry转换为Json,再由C++层传送到JS层。catalystInstance相应的处理执行完之后,将其与reactContext关联起来,最后通过catalystInstance加载bundle文件。
总的来说,createReactContext方法主要完成了以下操作:
构建 ReactApplicationContext;
注册 Packages 原生模块;
构建 CatalystInstance 实例;
通过 CatalystInstance 实例调用C++层代码逻辑;
调用 CatalystInstance 实例的 runJSBundle 方法加载 JSBundle。
到这里,我们基本开清楚了原生是如何加载JSBundle的:即通过 CatalystInstance 来加载 JSBundle 文件。
下面让我们继续看 runJSBundle方法:
在 runJSBundle 方法中通过JSBundleLoader的 loadScript 方法去加载JSBundle,不同的加载方式 JSBundleLoader 实现方式不同。
JSBundleLoader
JSBundleLoader主要用于存储 JS 包的信息,允许 CatalystInstance 通过 ReactBridge 加载正确的包。
runJSBundle的源码如下:
JSBundleLoader 类中提供了很多种 JSBundle 文件的加载方式,并且可以看到每种加载方式都是借助了CatalystInstanceImpl实例来实现。来看 CatalystInstanceImpl 中的具体实现:
可以看到,下面就调用jni层面的代码CatalystInstanceImpl.cpp里面的代码,C++层的代码我们不用太关心,只需要知道,经过这一步之后,js和java层面就可以相互调用类。如果想要看c++的实现,可以在node_modules的ReactAndroid目录中查看。
在之前的 runCreateReactContextOnNewThread 方法中,在creatReactContext之后还有一句核心的代码。
ReactContext创建完毕之后,ReactContextInitAsyncTask就会执行onPostExecute中的setupReactContext(reactContext)方法。
attachRootViewToInstance方法会将 rootview 与 catalystInstance 进行绑定。
在 attachRootViewToInstance 方法中 设置 rootView tag,并执行 runApplication 方法。
runApplication最终调用的是catalystInstance.getJSModule(AppRegistry.class).runApplication(jsAppModuleName, appParams), AppRegistry.class是JS层暴露给Java层的接口方法。它的真正实现在AppRegistry.js里,在文章开始时,我们已经对它进行了简单介绍,AppRegistry.js 是运行所有RN应用的JS层入口。此时调用JS进行渲染,在通过UIManagerModule将JS组件转换成Android组件,最终显示在ReactRootView上。
本文转载自异步社区。
原文链接:https://www.epubit.com/articleDetails?id=N430cc4fa-4b89-4d26-9ae8-d2791ac23416
- 点赞
- 收藏
- 关注作者
评论(0)