React Native JSI:实现RN与原生通信

·  阅读 2641
React Native JSI:实现RN与原生通信

本文的第二遍,已发布讲解了如何将原有项目中的brightModule 迁移到 jsiModule。

什么是JSI

React Native JSI (JavaScript Interface) 可以使 JavaScript原生模块 更快、更简单的通信。它也是React Native 新的架构体系中Fabric UI层 和 Turbo 模块的核心部分。

JSI有什么不同

JSI 移除了原生代码和JavaScript代码之间的桥接(bridge),同时也省去了两端相互调用时大量的JSON序列化和反序列化操作。JSI为原生和JS交互打开了新的大门。下面是一些JSI的特点:

  1. JavaScript Interface 允许我们向JavaScript 运行时注册方法。这些方法在js环境中可以通过 global对象获取并调用。
  2. 我们完全可以使用C++或者在iOS里使用OC ,在Android里使用Java实现这些注册方法。
  3. 原先使用bridge 的方式实现的原生模块可以通过增加一层C++,快速转化为通过JSI实现。
  4. iOS端实现非常简单,因为C++和OC 可以方便的实现混编。
  5. Android中,我们需要通过JNI 做一些转化。
  6. 这些方法可以是完全同步的,这意味着不必强制使用async。await

在iOS中使用JSI

下面我们将一步一步的在iOS工程中使用JSI实现原生与JS的通信。

创建一个新的React Native 项目

npx react-native init jsiDemo
复制代码

iOS端配置

在iOS项目目录中创建C++文件,example.hexample.cpp

example.h

#ifndef EXAMPLE_H
#define EXAMPLE_H

namespace facebook {
	namespace jsi {
		class Runtime;
	}
}

namespace example {
	void install(facebook::jsi::Runtime &jsiRuntime);
}
#endif /* EXAMPLE_H */
复制代码

example.m

#include "example.h"
#include <jsi/jsi.h>
using namespace facebook::jsi;
using namespace std;

namespace example {
	void install(Runtime &jsiRuntime) {  
    auto helloWorld = Function::createFromHostFunction(jsiRuntime,
                                                       PropNameID::forAscii(jsiRuntime,
                                                                            "helloWorld"),
                                                       0,
                                                       [](Runtime &runtime,
                                                          const Value &thisValue,
                                                          const Value *arguments,
                                                          size_t count) -> Value {
        string helloworld = "helloworld";
        return Value(runtime, String::createFromUtf8(runtime,helloworld));
    });
    
    jsiRuntime.global().setProperty(jsiRuntime, "helloWorld", move(helloWorld));
    
	}
}
复制代码

在上面的代码中,我们使用 createFromHostFunction 方法创建了一个方法,并通过setProperty 方法将其注册到js运行时。

接下来,我们需要创建一个moudle, 在moudle中执行install 方法。

我们创建OC 文件,SimpleJsi.hSimpleJsi.mm

SimpleJsi.h

#import <React/RCTBridgeModule.h>
@interface SimpleJsi : NSObject <RCTBridgeModule>
@property (nonatomic, assign) BOOL setBridgeOnMainQueue;
@end
复制代码

SimpleJsi.mm

#import "SimpleJsi.h"
#import <React/RCTBridge+Private.h>
#import <React/RCTUtils.h>
#import <jsi/jsi.h>
#import "example.h"
#import <sys/utsname.h>

using namespace facebook::jsi;
using namespace std;

@implementation SimpleJsi

@synthesize bridge = _bridge;
@synthesize methodQueue = _methodQueue;

RCT_EXPORT_MODULE()

+ (BOOL)requiresMainQueueSetup {
    
    return YES;
}

- (void)setBridge:(RCTBridge *)bridge {
    _bridge = bridge;
    _setBridgeOnMainQueue = RCTIsMainQueue();
    [self installLibrary];
}

- (void)installLibrary {
    
    RCTCxxBridge *cxxBridge = (RCTCxxBridge *)self.bridge;
    
    if (!cxxBridge.runtime) {
        
        /**
         * This is a workaround to install library
         * as soon as runtime becomes available and is
         * not recommended. If you see random crashes in iOS
         * global.xxx not found etc. use this.
         */
        
        dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 0.001 * NSEC_PER_SEC),
                       dispatch_get_main_queue(), ^{
            /**
             When refreshing the app while debugging, the setBridge
             method is called too soon. The runtime is not ready yet
             quite often. We need to install library as soon as runtime
             becomes available.
             */
            [self installLibrary];
            
        });
        return;
    }
    
    example::install(*(facebook::jsi::Runtime *)cxxBridge.runtime);
}

@end
复制代码

setBridge 方法中,我们调用了 exampleinstall 方法,完成方法的注册。

RN端配置

修改App.js

import React from 'react';
import type {Node} from 'react';
import {Text, View, Button} from 'react-native';

const App: () => Node = () => {
  const [result, setResult] = React.useState();

  const press = () => {
    setResult(global.helloWorld());
  };
  return (
    // eslint-disable-next-line react-native/no-inline-styles
    <View style={{backgroundColor: '#FFFFFF', height: '100%'}}>
      <View style={{height: '10%'}} />
      <Button onPress={press} title="按钮" />
      <Text>{'调用helloword:' + result}</Text>
    </View>
  );
};

export default App;
复制代码

点击按钮之后,发现result的值为helloworld

结果

image-20210815162526375

上面我们实现了js 调用原生,但没有参数,接下来我们实现一个单参数的调用。

js调用带参数的原生方法

我们在example.cppinstall 方法中增加multiply方法的注册,从arguments 中取出入参。

auto multiply = Function::createFromHostFunction(jsiRuntime,
                                                     PropNameID::forAscii(jsiRuntime,
                                                                          "multiply"),
                                                     2,
                                                     [](Runtime &runtime,
                                                        const Value &thisValue,
                                                        const Value *arguments,
                                                        size_t count) -> Value {
        int x = arguments[0].getNumber();
        int y = arguments[1].getNumber();
        return Value(x * y); 
    });
jsiRuntime.global().setProperty(jsiRuntime, "multiply", move(multiply));
复制代码

然后修改App.js

import React from 'react';
import type {Node} from 'react';
import {Text, View, Button} from 'react-native';

const App: () => Node = () => {
  const [result, setResult] = React.useState();

  const press = () => {
    setResult(global.multiply(2, 2));
  };
  return (
    // eslint-disable-next-line react-native/no-inline-styles
    <View style={{backgroundColor: '#FFFFFF', height: '100%'}}>
      <View style={{height: '10%'}} />
      <Button onPress={press} title="按钮" />
      <Text>{'2*2 = ' + result}</Text>
    </View>
  );
};

export default App;
复制代码

结果

image-20210815170244542

原生调用JS

原生调用js主要通过jsiRuntime.global().getPropertyAsFunction(jsiRuntime, "jsMethod").call(jsiRuntime);方法实现。

首先我们在js中增加一个js方法。我们修改App.js 在上面增加jsMethod 方法。

import React from 'react';
import type {Node} from 'react';
import {Text, View, Button} from 'react-native';

const App: () => Node = () => {
  const [result, setResult] = React.useState();

  global.jsMethod = () => {
    alert('hello jsMethod');
  };

  const press = () => {
    setResult(global.multiply(2, 2));
  };
  return (
    // eslint-disable-next-line react-native/no-inline-styles
    <View style={{backgroundColor: '#FFFFFF', height: '100%'}}>
      <View style={{height: '10%'}} />
      <Button onPress={press} title="按钮" />
      <Text>{'2*2 = ' + result}</Text>
    </View>
  );
};

export default App;
复制代码

在原生端,我们假设在进入应用的时候触发调用js方法,我们修改AppDelegate中的applicationWillEnterForeground 方法。

- (void)applicationWillEnterForeground:(UIApplication *)application {
  SimpleJsi *jsi = [self.bridge moduleForName:@"SimpleJsi"];
  [jsi calljs];
}
复制代码

通过moduleForName方法获取SimpleJsi对象, 然后通过SimpleJsi中的calljs 方法。

- (void)calljs {
  RCTCxxBridge *cxxBridge = (RCTCxxBridge *)self.bridge;
  Runtime &jsiRuntime = *(facebook::jsi::Runtime *)cxxBridge.runtime;
  jsiRuntime.global().getPropertyAsFunction(jsiRuntime, "jsMethod").call(jsiRuntime);
}
复制代码

结果

image-20210815173834083

原生调用带参数的JS方法

多参数调用和无参调用类似,只是在call方法后面增加了参数列表。

首先我们先在js侧定义方法,修改app.js

import React from 'react';
import type {Node} from 'react';
import {Text, View, Button} from 'react-native';

const App: () => Node = () => {
  const [result, setResult] = React.useState();

  global.jsMethod = () => {
    alert('hello jsMethod');
  };

  global.jsMultiply = (x, y) => {
    alert('x * y = ' + x * y);
  };

  const press = () => {
    setResult(global.multiply(2, 2));
  };
  return (
    // eslint-disable-next-line react-native/no-inline-styles
    <View style={{backgroundColor: '#FFFFFF', height: '100%'}}>
      <View style={{height: '10%'}} />
      <Button onPress={press} title="按钮" />
      <Text>{'2*2 = ' + result}</Text>
    </View>
  );
};

export default App;
复制代码

然后我们修改原生端的调用

AppDelegate.m

- (void)applicationWillEnterForeground:(UIApplication *)application {
  SimpleJsi *jsi =  [self.bridge moduleForName:@"SimpleJsi"];
//  [jsi calljs];
  [jsi callJsMultiply:4 y:4];
}
复制代码

SimpleJsi.m

- (void)callJsMultiply:(int)x y:(int) y {
  RCTCxxBridge *cxxBridge = (RCTCxxBridge *)self.bridge;
  Runtime &jsiRuntime = *(facebook::jsi::Runtime *)cxxBridge.runtime;
  jsiRuntime.global().getPropertyAsFunction(jsiRuntime, "jsMultiply").call(jsiRuntime, x, y);
}
复制代码

结果

image-20210815173743532

在原生端调用js的函数参数

在原生中调用js参数中的函数,需要通过arguments[i].getObject(runtime).getFunction(runtime).call(runtime, value);的方式触发。

首先我们在example.cpp 中新注册一个方法multiplyWithCallback

auto multiplyWithCallback = Function::createFromHostFunction(jsiRuntime,
                                                                 PropNameID::forAscii(jsiRuntime,
                                                                                      "multiplyWithCallback"),
                                                                 3,
                                                                 [](Runtime &runtime,
                                                                    const Value &thisValue,
                                                                    const Value *arguments,
                                                                    size_t count) -> Value {
        int x = arguments[0].getNumber();
        int y = arguments[1].getNumber();
        //调用callback
        arguments[2].getObject(runtime).getFunction(runtime).call(runtime, x * y);
        
        return Value();
        
    });
    
    jsiRuntime.global().setProperty(jsiRuntime, "multiplyWithCallback", move(multiplyWithCallback));
复制代码

在js侧进行调用, 修改app.js

import React from 'react';
import type {Node} from 'react';
import {Text, View, Button} from 'react-native';

const App: () => Node = () => {
  const [result, setResult] = React.useState();

  global.jsMethod = () => {
    alert('hello jsMethod');
  };

  global.jsMultiply = (x, y) => {
    alert('x * y = ' + x * y);
  };

  const press = () => {
    // setResult(global.multiply(2, 2));
    global.multiplyWithCallback(4, 5, alertResult);
  };

  const alertResult = res => {
    alert(res);
  };

  return (
    // eslint-disable-next-line react-native/no-inline-styles
    <View style={{backgroundColor: '#FFFFFF', height: '100%'}}>
      <View style={{height: '10%'}} />
      <Button onPress={press} title="按钮" />
      <Text>{'2*2 = ' + result}</Text>
    </View>
  );
};

export default App;
复制代码

点击按钮之后,会调用multiplyWithCallbackalertResult 方式传递给原生。

结果

image-20210815174440262

总结

上面就是本文对JSI 的介绍,文中的代码,你可以在公众号回复 JSI 获取GitHub 下载地址。

问题

在RN Debug的情况下,global.xx 无法找到对应的方法,个人也没有头绪,如果你有方法解决,请联系我,非常感谢。

参考资料

blog.notesnook.com/getting-sta…
reactnative.maxieewong.com/
github.com/react-nativ…
ospfranco.com/

分类:
前端
分类:
前端
收藏成功!
已添加到「」, 点击更改