Web 开发有一个经典问题:「浏览器中从输入 URL 到页面渲染的这个过程中都发生了什么?」
据我考据这个问题起码有十年历史了。在日新月异学不动的前端圈子里,这个问题能一直被问,就是因为因为它是个非常好的问题,涉及非常多的知识点,平时做一些性能优化,都可以从这个问题出发,分析性能瓶颈,然后对症下药进行优化。
不过今天我们不谈 Web 的性能优化,只是借助刚刚的那个那个经典问题的分析思路,从 React Native 的启动到页面的第一次渲染完成,结合 React Native 的源码和 1.0 的新架构,一一分析 React Native 的启动性能优化之路。
阅读提醒:
1.文章中的源码内容为 RN 0.64 版本
2.源码分析内容涉及
Objective-C
、Java
、C++
、JavaScript
四门语言,我尽量讲得通俗易懂一些,若实在不理解可以直接看结论
React Native 作为一个 Web 前端友好的混合开发框架,启动时可以大致分为两个部分:
其中 Native 容器启动在现有架构(版本号小于 1.0.0)里:大致可以分为 3 个部分:
容器初始化后,舞台就交给了 JavaScript,流程可以细分为 2 个部分:
最后 JS Thread 把计算好的布局信息发送到 Native 端,计算 Shadow Tree,最后由 UI Thread 进行布局和渲染。
关于渲染部分的性能优化可以见我之前写的《React Native 性能优化指南》[1],我从渲染、图片、动画、长列表等方向介绍了 RN 渲染优化的常见套路,感兴趣的读者可以前往查看,我这里就不多介绍了。
上面的几个步骤,我画了一张图,下面我以这张图为目录,从左向右介绍各个步骤的优化方向:
提示:React Native 初始化时,有可能多个任务并行执行,所以上图只能表示 React Native 初始化的大致流程,并不和实际代码的执行时序一一对应。
想提升 React Native 应用的性能,最一劳永逸的方法就是升级 RN 的大版本了。我们的应用从 0.59 升级到 0.62 之后,我们的 APP 没有做任何的性能优化工作,启动时间直接缩短了 1/2。当 React Native 的新架构发布后,启动速度和渲染速度都会大大加强。
当然,RN 的版本升级并不容易(横跨 iOS Android JS 三端,兼容破坏性更新),我之前写过一篇《React Native 升级指南(0.59 -> 0.62)》[2]的文章,如果有升级想法的老铁可以阅读参考一下。
容器的初始化肯定是从 APP 的入口文件开始分析,下面我会挑选一些关键代码,梳理一下初始化的流程。
AppDelegate.m
是 iOS 的入口文件,代码非常精简,主要内容如下所示:
// AppDelegate.m
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
// 1.初始化一个 RCTBridge 实现加载 jsbundle 的方法
RCTBridge *bridge = [[RCTBridge alloc] initWithDelegate:self launchOptions:launchOptions];
// 2.利用 RCTBridge 初始化一个 RCTRootView
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:@"RN64"
initialProperties:nil];
// 3.初始化 UIViewController
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
UIViewController *rootViewController = [UIViewController new];
// 4.将 RCTRootView 赋值给 UIViewController 的 view
rootViewController.view = rootView;
self.window.rootViewController = rootViewController;
[self.window makeKeyAndVisible];
return YES;
}
总的来看入口文件就做了三件事:
RCTBridge
实现加载 jsbundle 的方法RCTBridge
初始化一个 RCTRootView
RCTRootView
赋值给 UIViewController
的 view 实现 UI 的挂载从入口源码我们可以发现,所有的初始化工作都指向RCTRootView
,所以接下来我们看看 RCTRootView
干了些啥。
我们先看一下 RCTRootView
的头文件,删繁就简,我们只看我们关注的一些方法:
// RCTRootView.h
@interface RCTRootView : UIView
// AppDelegate.m 中用到的初始化方法
- (instancetype)initWithBridge:(RCTBridge *)bridge
moduleName:(NSString *)moduleName
initialProperties:(nullable NSDictionary *)initialProperties NS_DESIGNATED_INITIALIZER;
从头文件看出:
RCTRootView
继承自 UIView
,所以它本质上就是一个 UI 组件;RCTRootView
调用 initWithBridge
初始化时要传入一个已经初始化的 RCTBridge
在 RCTRootView.m
文件里,initWithBridge
初始化时会监听一系列的 JS 加载监听函数,监听到 JS Bundle 文件加载结束后,就会调用 JS 里的 AppRegistry.runApplication()
,启动 RN 应用。
分析到这里,我们发现 RCTRootView.m
只是实现了对 RCTBridge
的的各种事件监听,并不是初始化的核心,所以我们就又要转到 RCTBridge
这个文件上去。
RCTBridge.m
里,初始化的调用路径有些长,全贴源码有些长,总之最后调用的是 (void)setUp
,核心代码如下:
- (Class)bridgeClass
{
return [RCTCxxBridge class];
}
- (void)setUp {
// 获取bridgeClass 默认是 RCTCxxBridge
Class bridgeClass = self.bridgeClass;
// 初始化 RTCxxBridge
self.batchedBridge = [[bridgeClass alloc] initWithParentBridge:self];
// 启动 RTCxxBridge
[self.batchedBridge start];
}
我们可以看到,RCTBridge
的初始化又指向了 RTCxxBridge
。
RTCxxBridge
可以说是 React Native 初始化的核心,我查阅了一些资料,貌似 RTCxxBridge
曾用名为 RCTBatchedBridge
,所以可以粗暴的把这两个类当成一回事儿。
因为在 RCTBridge
里调用了 RTCxxBridge
的 start
方法,我们就从 start
方法来看看做了些什么。
// RTCxxBridge.mm
- (void)start {
// 1.初始化 JSThread,后续所有的 js 代码都在这个线程里面执行
_jsThread = [[NSThread alloc] initWithTarget:[self class] selector:@selector(runRunLoop) object:nil];
[_jsThread start];
// 创建并行队列
dispatch_group_t prepareBridge = dispatch_group_create();
// 2.注册所有的 native modules
[self registerExtraModules];
(void)[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO];
// 3.初始化 JSExecutorFactory 实例
std::shared_ptr<JSExecutorFactory> executorFactory;
// 4.初始化底层 Instance 实例,也就是 _reactInstance
dispatch_group_enter(prepareBridge);
[self ensureOnJavaScriptThread:^{
[weakSelf _initializeBridge:executorFactory];
dispatch_group_leave(prepareBridge);
}];
// 5.加载 js 代码
dispatch_group_enter(prepareBridge);
__block NSData *sourceCode;
[self
loadSource:^(NSError *error, RCTSource *source) {
if (error) {
[weakSelf handleError:error];
}
sourceCode = source.data;
dispatch_group_leave(prepareBridge);
}
onProgress:^(RCTLoadingProgress *progressData) {
}
];
// 6.等待 native moudle 和 JS 代码加载完毕后就执行 JS
dispatch_group_notify(prepareBridge, dispatch_get_global_queue(QOS_CLASS_USER_INTERACTIVE, 0), ^{
RCTCxxBridge *strongSelf = weakSelf;
if (sourceCode && strongSelf.loading) {
[strongSelf executeSourceCode:sourceCode sync:NO];
}
});
}
上面代码比较长,里面用到了 GCD 多线程的一些知识点,用文字描述大致是如下的流程:
_jsThread
native modules
js
和 Native
之间的桥和 js 运行环境RCTMessageThread
,初始化 _reactInstance
其实上面的六个点都可以深挖下去,但是本节涉及到的源码内容到这里就可以了,感兴趣的读者可以结合我最后给出的参考资料和 React Native 源码深挖探索一下。
和 iOS 一样,启动流程我们先从入口文件开始分析,我们先看 MainActivity.java
:
MainActivity
继承自 ReactActivity
,ReactActivity
又继承自 AppCompatActivity
:
// MainActivity.java
public class MainActivity extends ReactActivity {
// 返回组件名,和 js 入口注册名字一致
@Override
protected String getMainComponentName() {
return "rn_performance_demo";
}
}
我们再从 Android 的入口文件 MainApplication.java
开始分析:
// MainApplication.java
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new ReactNativeHost(this) {
// 返回 app 需要的 ReactPackage,添加需要加载的模块,
// 这个地方就是我们在项目中添加依赖包时需要添加第三方 package 的地方
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
return packages;
}
// js bundle 入口文件,设置为 index.js
@Override
protected String getJSMainModuleName() {
return "index";
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
// SoLoader:加载C++底层库
SoLoader.init(this, /* native exopackage */ false);
}
}
ReactApplication
接口很简单,要求我们创建一个 ReactNativeHost
对象:
public interface ReactApplication {
ReactNativeHost getReactNativeHost();
}
从上面的分析我们可以看出一切指向了 ReactNativeHost
这个类,下面我们就看一下它。
ReactNativeHost
主要的工作就是创建了 ReactInstanceManager
:
public abstract class ReactNativeHost {
protected ReactInstanceManager createReactInstanceManager() {
ReactMarker.logMarker(ReactMarkerConstants.BUILD_REACT_INSTANCE_MANAGER_START);
ReactInstanceManagerBuilder builder =
ReactInstanceManager.builder()
// 应用上下文
.setApplication(mApplication)
// JSMainModulePath 相当于应用首页的 js Bundle,可以传递 url 从服务器拉取 js Bundle
// 当然这个只在 dev 模式下可以使用
.setJSMainModulePath(getJSMainModuleName())
// 是否开启 dev 模式
.setUseDeveloperSupport(getUseDeveloperSupport())
// 红盒的回调
.setRedBoxHandler(getRedBoxHandler())
.setJavaScriptExecutorFactory(getJavaScriptExecutorFactory())
.setUIImplementationProvider(getUIImplementationProvider())
.setJSIModulesPackage(getJSIModulePackage())
.setInitialLifecycleState(LifecycleState.BEFORE_CREATE);
// 添加 ReactPackage
for (ReactPackage reactPackage : getPackages()) {
builder.addPackage(reactPackage);
}
// 获取 js Bundle 的加载路径
String jsBundleFile = getJSBundleFile();
if (jsBundleFile != null) {
builder.setJSBundleFile(jsBundleFile);
} else {
builder.setBundleAssetName(Assertions.assertNotNull(getBundleAssetName()));
}
ReactInstanceManager reactInstanceManager = builder.build();
return reactInstanceManager;
}
}
我们再回到 ReactActivity
,它自己并没有做什么事情,所有的功能都由它的委托类 ReactActivityDelegate
来完成,所以我们直接看ReactActivityDelegate
是怎么实现的:
public class ReactActivityDelegate {
protected void onCreate(Bundle savedInstanceState) {
String mainComponentName = getMainComponentName();
mReactDelegate =
new ReactDelegate(
getPlainActivity(), getReactNativeHost(), mainComponentName, getLaunchOptions()) {
@Override
protected ReactRootView createRootView() {
return ReactActivityDelegate.this.createRootView();
}
};
if (mMainComponentName != null) {
// 载入 app 页面
loadApp(mainComponentName);
}
}
protected void loadApp(String appKey) {
mReactDelegate.loadApp(appKey);
// Activity 的 setContentView() 方法
getPlainActivity().setContentView(mReactDelegate.getReactRootView());
}
}
onCreate()
的时候又实例化了一个 ReactDelegate
,我们再看看它的实现。
在 ReactDelegate.java
里,我没看见它做了两件事:
ReactRootView
作为根视图getReactNativeHost().getReactInstanceManager()
启动 RN 应用public class ReactDelegate {
public void loadApp(String appKey) {
if (mReactRootView != null) {
throw new IllegalStateException("Cannot loadApp while app is already running.");
}
// 创建 ReactRootView 作为根视图
mReactRootView = createRootView();
// 启动 RN 应用
mReactRootView.startReactApplication(
getReactNativeHost().getReactInstanceManager(), appKey, mLaunchOptions);
}
}
基础的启动流程本节涉及到的源码内容到这里就可以了,感兴趣的读者可以结合我最后给出的参考资料和 React Native 源码深挖探索一下。
对于 React Native 为主体的应用,APP 启动后就要立马初始化 RN 容器,基本上没有什么优化思路;但是 Native 为主的混合开发 APP 却有招:
既然初始化耗时最长,我们在正式进入 React Native 容器前提前初始化不就好了?
这个方法非常的常见,因为很多 H5 容器也是这样做的。正式进入 WebView 网页前,先做一个 WebView 容器池,提前初始化 WebView,进入 H5 容器后,直接加载数据渲染,以达到网页秒开的效果。
RN 容器池这个概念看着很玄乎,其实就是一个 Map
,key
为 RN 页面的 componentName
(即 AppRegistry.registerComponent(appName, Component)
中传入的 appName
),value
就是一个已经实例化的 RCTRootView/ReactRootView
。
APP 启动后找个触发时机提前初始化,进入 RN 容器前先读容器池,如果有匹配的容器,直接拿来用即可,没有匹配的再重新初始化。
写两个很简单的案例,iOS 可以如下图所示,构建 RN 容器池:
@property (nonatomic, strong) NSMutableDictionary<NSString *, RCTRootView *> *rootViewRool;
// 容器池
-(NSMutableDictionary<NSString *, RCTRootView *> *)rootViewRool {
if (!_rootViewRool) {
_rootViewRool = @{}.mutableCopy;
}
return _rootViewRool;
}
// 缓存 RCTRootView
-(void)cacheRootView:(NSString *)componentName path:(NSString *)path props:(NSDictionary *)props bridge:(RCTBridge *)bridge {
// 初始化
RCTRootView *rootView = [[RCTRootView alloc] initWithBridge:bridge
moduleName:componentName
initialProperties:props];
// 实例化后要加载到屏幕的最下面,否则不能触发视图渲染
[[UIApplication sharedApplication].keyWindow.rootViewController.view insertSubview:rootView atIndex:0];
rootView.frame = [UIScreen mainScreen].bounds;
// 把缓存好的 RCTRootView 放到容器池中
NSString *key = [NSString stringWithFormat:@"%@_%@", componentName, path];
self.rootViewRool[key] = rootView;
}
// 读取容器
-(RCTRootView *)getRootView:(NSString *)componentName path:(NSString *)path props:(NSDictionary *)props bridge:(RCTBridge *)bridge {
NSString *key = [NSString stringWithFormat:@"%@_%@", componentName, path];
RCTRootView *rootView = self.rootViewRool[key];
if (rootView) {
return rootView;
}
// 兜底逻辑
return [[RCTRootView alloc] initWithBridge:bridge moduleName:componentName initialProperties:props];
}
Android 如下构建 RN 容器池:
private HashMap<String, ReactRootView> rootViewPool = new HashMap<>();
// 创建容器
private ReactRootView createRootView(String componentName, String path, Bundle props, Context context) {
ReactInstanceManager bridgeInstance = ((ReactApplication) application).getReactNativeHost().getReactInstanceManager();
ReactRootView rootView = new ReactRootView(context);
if(props == null) {
props = new Bundle();
}
props.putString("path", path);
rootView.startReactApplication(bridgeInstance, componentName, props);
return rootView;
}
// 缓存容器
public void cahceRootView(String componentName, String path, Bundle props, Context context) {
ReactRootView rootView = createRootView(componentName, path, props, context);
String key = componentName + "_" + path;
// 把缓存好的 RCTRootView 放到容器池中
rootViewPool.put(key, rootView);
}
// 读取容器
public ReactRootView getRootView(String componentName, String path, Bundle props, Context context) {
String key = componentName + "_" + path;
ReactRootView rootView = rootViewPool.get(key);
if (rootView != null) {
rootView.setAppProperties(newProps);
rootViewPool.remove(key);
return rootView;
}
// 兜底逻辑
return createRootView(componentName, path, props, context);
}
当然,由于每次 RCTRootView/ReactRootView
都要占用一定的内存,所以什么时候实例化,实例化几个容器,池的大小限制,什么时候清除容器,都需要结合业务进行实践和摸索。
iOS 的 Native Modules 有 3 块儿内容,大头是中间的 _initializeModules
函数:
// RCTCxxBridge.mm
- (void)start {
// 初始化 RCTBridge 时调用 initWithBundleURL_moduleProvider_launchOptions 中的 moduleProvider 返回的 native modules
[self registerExtraModules];
// 注册所有的自定义 Native Module
(void)[self _initializeModules:RCTGetModuleClasses() withDispatchGroup:prepareBridge lazilyDiscovered:NO];
// 初始化所有懒加载的 native module,只有用 Chrome debug 时才会调用
[self registerExtraLazyModules];
}
我们看看 _initializeModules
函数做了什么:
// RCTCxxBridge.mm
- (NSArray<RCTModuleData *> *)_initializeModules:(NSArray<Class> *)modules
withDispatchGroup:(dispatch_group_t)dispatchGroup
lazilyDiscovered:(BOOL)lazilyDiscovered
{
for (RCTModuleData *moduleData in _moduleDataByID) {
if (moduleData.hasInstance && (!moduleData.requiresMainQueueSetup || RCTIsMainQueue())) {
// Modules that were pre-initialized should ideally be set up before
// bridge init has finished, otherwise the caller may try to access the
// module directly rather than via `[bridge moduleForClass:]`, which won't
// trigger the lazy initialization process. If the module cannot safely be
// set up on the current thread, it will instead be async dispatched
// to the main thread to be set up in _prepareModulesWithDispatchGroup:.
(void)[moduleData instance];
}
}
_moduleSetupComplete = YES;
[self _prepareModulesWithDispatchGroup:dispatchGroup];
}
根据 _initializeModules
和 _prepareModulesWithDispatchGroup
的注释,可以看出 iOS 在 JS Bundle 加载的过程中(在 JSThead 线程进行),同时在主线程初始化所有的 Native Modules。
结合前面的源码分析,我们可以看出 React Native iOS 容器初始化的时候,会初始化所有的 Native Modules,若 Native Modules
比较多,就会影响 Android RN 容器的启动时间。
关于 Native Modules 的注册,其实在 MainApplication.java
这个入口文件里已经给出了线索:
// MainApplication.java
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
return packages;
}
由于 0.60 之后 React Native 启用了 auto link
,安装的第三方 Native Modules 都在 PackageList
里,所以我们只要 getPackages()
一下就能获取 auto link
的 Modules。
源码里,在 ReactInstanceManager.java
这个文件中,会运行 createReactContext()
创建 ReactContext
,这里面有一步就是注册 nativeModules
的注册表:
// ReactInstanceManager.java
private ReactApplicationContext createReactContext(
JavaScriptExecutor jsExecutor,
JSBundleLoader jsBundleLoader) {
// 注册 nativeModules 注册表
NativeModuleRegistry nativeModuleRegistry = processPackages(reactContext, mPackages, false);
}
根据函数调用,我们追踪到 processPackages()
这个函数里,利用一个 for 循环把 mPackages 里的 Native Modules 全部加入注册表:
// ReactInstanceManager.java
private NativeModuleRegistry processPackages(
ReactApplicationContext reactContext,
List<ReactPackage> packages,
boolean checkAndUpdatePackageMembership) {
// 创建 JavaModule 注册表 Builder,用来创建 JavaModule 注册表,
// JavaModule 注册表将所有的 JavaModule 注册到 CatalystInstance 中
NativeModuleRegistryBuilder nativeModuleRegistryBuilder =
new NativeModuleRegistryBuilder(reactContext, this);
// 给 mPackages 加锁
// mPackages 类型为 List<ReactPackage>,与 MainApplication.java 里的 packages 对应
synchronized (mPackages) {
for (ReactPackage reactPackage : packages) {
try {
// 循环处理我们在 Application 里注入的 ReactPackage,处理的过程就是把各自的 Module 添加到对应的注册表中
processPackage(reactPackage, nativeModuleRegistryBuilder);
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
}
}
}
NativeModuleRegistry nativeModuleRegistry;
try {
// 生成 Java Module 注册表
nativeModuleRegistry = nativeModuleRegistryBuilder.build();
} finally {
Systrace.endSection(TRACE_TAG_REACT_JAVA_BRIDGE);
ReactMarker.logMarker(BUILD_NATIVE_MODULE_REGISTRY_END);
}
return nativeModuleRegistry;
}
最后调用 processPackage()
进行真正的注册:
// ReactInstanceManager.java
private void processPackage(
ReactPackage reactPackage,
NativeModuleRegistryBuilder nativeModuleRegistryBuilder
) {
nativeModuleRegistryBuilder.processPackage(reactPackage);
}
从上面的流程可以看出,Android 注册 Native Modules
的时候是同步全量注册的,若 Native Modules
比较多,就会影响 Android RN 容器的启动时间。
说实话,Native Modules 全量绑定在现有的架构里是无解的:不管这个 Native Methods 你有没有用到,容器启动时先全部初始化一遍。在新的 RN 架构里,TurboModules 会解决这个问题(本文下一小节会介绍)。
如果非要说优化,其实还有个思路,你不是全量初始化吗,那我让 Native Modules 的数量减少不就行了?新架构里有一步叫做 Lean Core,就是精简 React Native 核心,把一些功能/组件从 RN 的主工程项目里移出去(例如 WebView
组件),交给社区维护,你想用的时候再单独下载集成。
这样做的好处主要有几点:
现在 Lean Core 的工作基本已经完成,更多讨论可见官方 issues 讨论区[3],我们只要同步升级 React Native 版本就可以享用 Lean Core 的成果。
React Native 新架构已经跳票快两年了,每次问进度,官方回复都是“别催了别催了在做了在做了”。
我个人去年期待了一整年,但是啥都没等到,所以 RN 啥时候更新到 1.0.0 版本,我已经不在乎了。虽然 RN 官方一直在鸽,但是不得不说他们的新架构还是有些东西的,市面上存在关于 RN 新架构的文章和视频我基本都看了一遍,所以个人对新架构还是有个整体的认知。
因为新架构还没有正式放出,所以具体细节上肯定还存在一些差异,具体执行细节还是要等 React Native 官方为准。
JSI 的全名是 JavaScript Interface,一个用 C++ 写的框架,作用是支持 JS 直接调用 Native 方法,而不是现在通过 Bridge 异步通讯。
JS 直接调用 Native 如何理解呢?我们举一个最简单的例子。在浏览器上调用 setTimeout``document.getElementById
这类 API 的时候,其实就是在 JS 侧直接调用 Native Code,我们可以在浏览器控制台里验证一下:
比如说我执行了一条命令:
let el = document.createElement('div')
变量 el
持有的不是一个 JS 对象,而是一个在 C++ 中被实例化的对象。对于 el 持有的这个对象我们再设置一下相关属性:
el.setAttribute('width', 100)
这时候其实是 JS 同步调用 C++ 中的 setWidth
方法,改变这个元素的宽度。
React Native 新架构中的 JSI,主要就是起这个作用的,借助 JSI,我们可以用 JS 直接获得 C++ 对象的引用(Host Objects),进而直接控制 UI,直接调用 Native Modules 的方法,省去 bridge 异步通讯的开销。
下面我们举个小例子,来看一下 Java/OC 如何借助 JSI 向 JS 暴露同步调用的方法。
#pragma once
#include <string>
#include <unordered_map>
#include <jsi/jsi.h>
// SampleJSIObject 继承自 HostObject,表示这个一个暴露给 JS 的对象
// 对于 JS 来说,JS 可以直接同步调用这个对象上的属性和方法
class JSI_EXPORT SampleJSIObject : public facebook::jsi::HostObject {
public:
// 第一步
// 将 window.__SampleJSIObject 暴露给JavaScript
// 这是一个静态函数,一般在应用初始化时从 ObjC/Java 中调用
static void SampleJSIObject::install(jsi::Runtime &runtime) {
runtime.global().setProperty(
runtime,
"__sampleJSIObject",
jsi::Function::createFromHostFunction(
runtime,
jsi::PropNameID::forAscii(runtime, "__SampleJSIObject"),
1,
[binding](jsi::Runtime& rt, const jsi::Value& thisVal, const jsi::Value* args, size_t count "binding") {
// 返回调用 window.__SampleJSIObject 时得到的内容
return std::make_shared<SampleJSIObject>();
}));
}
// 类似于 getter,每次 JS 访问这个对象的时候,都要经过这个方法,作用类似于一个包装器
// 比如说我们调用 window.__sampleJSIObject.method1(),这个方法就会被调用
jsi::Value TurboModule::get(jsi::Runtime& runtime, const jsi::PropNameID& propName) {
// 调用方法名
// 比如说调用 window.__sampleJSIObject.method1() 时,propNameUtf8 就是 method1
std::string propNameUtf8 = propName.utf8(runtime);
return jsi::Function::createFromHostFunction(
runtime,
propName,
argCount,
[](facebook::jsi::Runtime &rt, const facebook::jsi::Value &thisVal, const facebook::jsi::Value *args, size_t count "") {
if (propNameUtf8 == 'method1') {
// 调用 method1 时,相关的函数处理逻辑
}
});
}
std::vector<PropNameID> getPropertyNames(Runtime& rt){
}
}
上面的例子比较简短,想要深入了解 JSI,可以看《React Native JSI Challenge》[4]这篇文章或直接阅读源码。
经过前面的源码分析,我们可以得知,现有架构里,Native 初始化时会全量加载 native modules,随着业务的迭代,native modules 只会越来越多,这里的耗时会越来越长。
TurboModules 就可以一次性解决这个问题。在新架构里,native modules 是懒加载的,也就是说只有你调用相应的 native modules 时才会初始化加载,这样就解决了初始化全量加载耗时较长的问题。
TurboModules 的调用路径大概是这样的:
global.__turboModuleProxy
SampleTurboModule
,我们先在 JavaScript 侧执行 require('NativeSampleTurboModule')
TurboModuleRegistry.getEnforcing()
,然后就会调用 global.__turboModuleProxy("SampleTurboModule")
global.__turboModuleProxy
的时候,就会调用第一步 JSI 暴露的 Native 方法,这时候 C++ 层通过传入的字符串 "SampleTurboModule",找到 ObjC/Java 的实现,最后返回一个对应的 JSI 对象SampleTurboModule
的 JSI 对象,就可以用 JavaScript 同步调用 JSI 对象上的属性和方法通过上面的步骤,我们可以看到借助 TurboModules, Native Modules 只有初次调用的时候才会加载,这样就彻底干掉 React Native 容器初始化时全量加载 Native Modules 时的时间;同时我们可以借助 JSI 实现 JS 和 Native 的同步调用,耗时更少,效率更高。
本文主要从 Native 的角度出发,从源码分析 React Native 现有架构的启动流程,总结了几个 Native 层的性能优化点;最后又简单介绍了一下React Native 的新架构。下一篇文章我会讲解如何从 JavaScript 入手,优化 React Native 的启动速度。
本文由哈喽比特于3年以前收录,如有侵权请联系我们。
文章来源:https://mp.weixin.qq.com/s/bP8WvUGpK_-Q4915Ts9Gpg
京东创始人刘强东和其妻子章泽天最近成为了互联网舆论关注的焦点。有关他们“移民美国”和在美国购买豪宅的传言在互联网上广泛传播。然而,京东官方通过微博发言人发布的消息澄清了这些传言,称这些言论纯属虚假信息和蓄意捏造。
日前,据博主“@超能数码君老周”爆料,国内三大运营商中国移动、中国电信和中国联通预计将集体采购百万台规模的华为Mate60系列手机。
据报道,荷兰半导体设备公司ASML正看到美国对华遏制政策的负面影响。阿斯麦(ASML)CEO彼得·温宁克在一档电视节目中分享了他对中国大陆问题以及该公司面临的出口管制和保护主义的看法。彼得曾在多个场合表达了他对出口管制以及中荷经济关系的担忧。
今年早些时候,抖音悄然上线了一款名为“青桃”的 App,Slogan 为“看见你的热爱”,根据应用介绍可知,“青桃”是一个属于年轻人的兴趣知识视频平台,由抖音官方出品的中长视频关联版本,整体风格有些类似B站。
日前,威马汽车首席数据官梅松林转发了一份“世界各国地区拥车率排行榜”,同时,他发文表示:中国汽车普及率低于非洲国家尼日利亚,每百户家庭仅17户有车。意大利世界排名第一,每十户中九户有车。
近日,一项新的研究发现,维生素 C 和 E 等抗氧化剂会激活一种机制,刺激癌症肿瘤中新血管的生长,帮助它们生长和扩散。
据媒体援引消息人士报道,苹果公司正在测试使用3D打印技术来生产其智能手表的钢质底盘。消息传出后,3D系统一度大涨超10%,不过截至周三收盘,该股涨幅回落至2%以内。
9月2日,坐拥千万粉丝的网红主播“秀才”账号被封禁,在社交媒体平台上引发热议。平台相关负责人表示,“秀才”账号违反平台相关规定,已封禁。据知情人士透露,秀才近期被举报存在违法行为,这可能是他被封禁的部分原因。据悉,“秀才”年龄39岁,是安徽省亳州市蒙城县人,抖音网红,粉丝数量超1200万。他曾被称为“中老年...
9月3日消息,亚马逊的一些股东,包括持有该公司股票的一家养老基金,日前对亚马逊、其创始人贝索斯和其董事会提起诉讼,指控他们在为 Project Kuiper 卫星星座项目购买发射服务时“违反了信义义务”。
据消息,为推广自家应用,苹果现推出了一个名为“Apps by Apple”的网站,展示了苹果为旗下产品(如 iPhone、iPad、Apple Watch、Mac 和 Apple TV)开发的各种应用程序。
特斯拉本周在美国大幅下调Model S和X售价,引发了该公司一些最坚定支持者的不满。知名特斯拉多头、未来基金(Future Fund)管理合伙人加里·布莱克发帖称,降价是一种“短期麻醉剂”,会让潜在客户等待进一步降价。
据外媒9月2日报道,荷兰半导体设备制造商阿斯麦称,尽管荷兰政府颁布的半导体设备出口管制新规9月正式生效,但该公司已获得在2023年底以前向中国运送受限制芯片制造机器的许可。
近日,根据美国证券交易委员会的文件显示,苹果卫星服务提供商 Globalstar 近期向马斯克旗下的 SpaceX 支付 6400 万美元(约 4.65 亿元人民币)。用于在 2023-2025 年期间,发射卫星,进一步扩展苹果 iPhone 系列的 SOS 卫星服务。
据报道,马斯克旗下社交平台𝕏(推特)日前调整了隐私政策,允许 𝕏 使用用户发布的信息来训练其人工智能(AI)模型。新的隐私政策将于 9 月 29 日生效。新政策规定,𝕏可能会使用所收集到的平台信息和公开可用的信息,来帮助训练 𝕏 的机器学习或人工智能模型。
9月2日,荣耀CEO赵明在采访中谈及华为手机回归时表示,替老同事们高兴,觉得手机行业,由于华为的回归,让竞争充满了更多的可能性和更多的魅力,对行业来说也是件好事。
《自然》30日发表的一篇论文报道了一个名为Swift的人工智能(AI)系统,该系统驾驶无人机的能力可在真实世界中一对一冠军赛里战胜人类对手。
近日,非营利组织纽约真菌学会(NYMS)发出警告,表示亚马逊为代表的电商平台上,充斥着各种AI生成的蘑菇觅食科普书籍,其中存在诸多错误。
社交媒体平台𝕏(原推特)新隐私政策提到:“在您同意的情况下,我们可能出于安全、安保和身份识别目的收集和使用您的生物识别信息。”
2023年德国柏林消费电子展上,各大企业都带来了最新的理念和产品,而高端化、本土化的中国产品正在不断吸引欧洲等国际市场的目光。
罗永浩日前在直播中吐槽苹果即将推出的 iPhone 新品,具体内容为:“以我对我‘子公司’的了解,我认为 iPhone 15 跟 iPhone 14 不会有什么区别的,除了序(列)号变了,这个‘不要脸’的东西,这个‘臭厨子’。