本文由 简悦 SimpRead转码,原文地址 blog.nparashuram.com
在上一篇文章中,我写到了利用 React Native 内置的仪表来了解 t......
在上一篇文章中,我写到了利用 React Native 的内置仪器来了解 React Native 应用程序的启动路径。有了这些数据,我们就可以开始优化与加载 React Native 相对应的时间线的各个部分。
目标: 这篇文章记录了如何改善拥有大量原生模块的应用程序的启动时间。
背景
原生模块 可让 JavaScript 调用 Java 或 ObjectiveC 中的方法,而 React Native 充满活力的生态系统中的原生模块可满足应用程序所需的大多数场景。安装原生代码就像运行 npm install 和 react-native link 一样简单。因此,许多应用程序最终都开始建立这些模块的大型注册表。
虽然许多本地模块在应用生命周期的后期才会使用,但现在的设置会在用户启动应用时立即初始化所有模块。将初始化时间推近模块实际使用的时间,有助于加快 React Native 应用程序的启动时间。
今天添加原生模块
如今要添加原生模块,大多数人都会使用 react-native link 提供的便利。除了修改 Gradle 文件,它还会更新 MainApplication.java 以初始化模块。例如,当使用 react-native link 添加 DeviceInfo 模块 时,文件中包含包列表的函数如下所示。
@Override
protected List<ReactPackage> getPackages() {
return Arrays.<ReactPackage>asList(
new MainReactPackage()
new RNDeviceInfo()
);
}
这种初始化方式不仅 "类加载 "了 RNDeviceInfo,还调用了构造函数。结果发现,设备信息模块还执行了一些非繁琐的计算,增加了应用程序的启动时间。
优化机会
- 在启动过程中,React Native 其实并不需要完整的模块,它只需要知道模块的名称和其他一些细节,这样就可以将模块添加到列表中。当我们在代码中调用 require('react-native-device-info') 时,它仍然不会生成一个模块实例,而只是提供了方法及其签名。当只有一个方法被实际调用时,才需要构建实际的模块。
- 此外,原生模块的开源结构决定了所有模块即使不提供 ViewManagers 或 JavaScript 模块,也必须封装在 React 包中,从而增加了一层额外的抽象。
懒惰的 React 包
通过使用 Java Provider 封装构造器,我们仍然可以在不调用构造器的情况下设置本地模块。 这可以通过使用 LazyReactPackage 代替默认的 ReactPackage 来实现。
使用这种提供模式的一个示例是 MainReactPackage。要在应用程序中采用这种模式,我们需要进入本地模块,并在 LazyReactPackage 包装的列表中添加从 ReactContextBaseJavaModule 扩展而来的类。
请注意,在使用 LazyReactPackage 时,我们仍然需要提供有关该原生模块的其他信息,而这些信息是通过评估该模块获得的。因此,我们有了 getReactModuleInfos 方法,该方法可提供额外信息,如类名、模块名、是否为 C++ 模块、能否被重载等。这些信息既可以像示例中那样手工制作,也可以使用 ReactModuleSpecProcessor 中的预处理步骤生成(如果集成到构建系统中)。
Turbo React 包
即使上述 Lazy React Package 方法不调用构造器,但由于提供者的存在,最终仍会创建匿名类。在大型的多代码应用程序中,它还会在启动时为原生模块执行类加载。我们可以使用 TurboReactPackage 并使用getModule 方法来消除这些副作用,该方法会返回给定名称的本地模块。
这个项目 有一个完整的 React Native 应用程序,它使用这种模式来快速加载模块。该项目还使用了 ReactMarkers,并显示在启动过程中,PROCESS_PACKAGES 步骤的时间仅为原来的一小部分。
该软件包也是向新的TurboModule 架构迈进的一步,在该架构中,JavaScript 可以通过名称获取本地模块。
在添加本地模块时,我建议使用 TurboReactPackage。
默认速度快
React Native 目前已经支持 LazyReactPackage 和较新的 TurboReactPackage。 要使其与 react-native link 配合使用,需要进行以下更改。
- 如果原生模块没有视图管理器或 JSModules,则不要添加已暴露的包
- 而是寻找最终扩展了 ReactContextBaseModule 的类,并将其添加到 MainApplication 中维护的列表中
- 在同一个包中生成 ReactModuleInfo 所需的元数据,这样我们就能获得所有信息,而无需评估实际的包。
为了防止出现破坏性更改,我们可以使用一个标志来指定我们希望使用这种新方式在较新的项目中添加本地模块。
这些技术已经可以用于混合应用程序,因为它们无论如何都不会使用 react-native 链接。我们可以将 ReactPackage 改为使用 TurboReactPackage,并更改方法以利用性能节省。
更多优化
即使我们推迟加载原生模块,某些原生模块还是会在启动过程中被调用。在调用过程中,JavaScript 线程会暂停。如果将原生模块设置为 "懒 "模块,JavaScript 就会在实际调用前初始化原生模块,从而延长等待时间。对于启动过程中的原生模块,我们仍然可以在一个单独的线程中进行初始化,与解析 JavaScript 的过程并行。 我们可以简单地生成一个新线程,然后调用 CatalystInstance.getModule(moduleName) 以线程安全的方式初始化模块。
下一步
在后续文章中,我计划介绍如何优化第一次网络调用,以及如何在 React Native 第一个屏幕显示之前添加自定义加载状态。在此期间,如果您对这篇文章有任何疑问,或者您对自己能否在应用程序中使用此功能发表了意见,请在 twitter 上联系我。