前言
RN主要用于编写UI,原生API的调用、网络通信等等复杂的逻辑则大多是通过原生代码去实现的。那么RN和原生代码是怎么交互的呢?
官网在此
以下以安卓平台原生为例,我们按照官网的教程来实现这样一个交互demo,借此来了解互相调用的用法。不过我们的起点是一个刚刚创建的RN应用(通过
npx react-native init
来创建的),可能有些操作已经存在,不过这些步骤不会因此而被忽略。
思路
开始之前先来理清楚一下思路。
首先是RN调用原生接口:
- 第一步,很显然的我们要在android里实现一个方法,实现具体的逻辑供RN调用。
- 第二步,我们需要通过RN框架把这个方法“传递”给RN应用。
- 第三步,RN应用调用“传递”过来的方法,实现RN对原生的调用。
而原生对RN的调用则刚好相反。
那开始试试吧。
定义原生方法
假设我们要通过原生逻辑来显示一个Toast(这种方式逻辑简单、表现明显),我们先定义一个ToastUtil类,并且为其增加一个显示Toast的方法show()。
public class ToastUtil {
public void show(Context context,String message){
Toast.makeText(context, message, Toast.LENGTH_SHORT).show();
}
}
将原生方法通过RN框架“传给”RN应用
现在需要把我们的工具类交给RN并由其传递给RN应用了。分析官方示例,将工具类交给RN的路径抽离如下图所示:
- RN框架提供了一个ReactApplication接口,正如它名字想表达的一样,这是一个要由android的Application实现的接口。
public interface ReactApplication {
/** Get the default {@link ReactNativeHost} for this app. */
ReactNativeHost getReactNativeHost();
}
其中要求实现方法getReactNativeHost。对于已经接入RN的android示例代码来说,这些操作是现成的。
public class MainApplication extends Application implements ReactApplication {
@Override
public ReactNativeHost getReactNativeHost() {
return reactNativeHost;
}}
- 所以需要创建一个ReactNativeHost对象给ReactApplication 。这些在继承RN框架的时候也是现成的。不过我们发现RN框架本身似乎还有一些其他的Package要返回,那么我们自己的只要add在后面就可以了。
ReactNativeHost reactNativeHost =new ReactNativeHost(this){
@Override
public boolean getUseDeveloperSupport() {
return false;
}
@Override
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;
}
};
- ReactPackage是接口,我们想要返回自己的ReactPackage就需要实现下这个接口,需要实现的接口里就包括了:createNativeModules
public interface ReactPackage {
/**
* @param reactContext react application context that can be used to create modules
* @return list of native modules to register with the newly created catalyst instance
*/
@NonNull
List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext);
/** @return a list of view managers that should be registered with {@link UIManagerModule} */
@NonNull
List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext);
}
- 所以就需要一个ReactContextBaseJavaModule交给我们刚刚实现的ReactPackage了。到这终于到了我们要“传递”的工具类出场了。没错,我们的工具类都要定义成“ReactContextBaseJavaModule”以方便RN框架去“传递”。
逻辑基本就是这样了,那么现在开始动手实现吧。
首先,就是改造我们刚才的工具类,因为要RN认识的特殊类才能被“传输”,将刚才的ToastUtil继承下ReactContextBaseJavaModule。多了个需要实现的类:getName
public class ToastUtil extends ReactContextBaseJavaModule {
private static ReactApplicationContext reactApplicationContext;
public ToastUtil(@NonNull ReactApplicationContext reactContext) {
super(reactContext);
reactApplicationContext=reactContext;
}
@ReactMethod
public void show(String message){
Toast.makeText(reactApplicationContext, message, Toast.LENGTH_SHORT).show();
}
@NonNull
@Override
public String getName() {
return "ToastUtil";
}
}
getName返回一个自定义的字符串,是当前这个工具类的标识符信息。RN应用就是通过这个字符串来找到它想要的工具类的。仔细观察会发现show方法被注解了@ReactMethod,没错,希望在RN中调用的方法都要用这个注解来声明下。这个时候突然发现了一个问题:RN中有Context这个上下文对象吗?再看官网教程,果然直接用了一个传进来的ReactApplicationContext,好吧,还是要传进来并保存下这个ReactApplicationContext留作后用。(你可能会想直接从别的地方拿过来的Context不也行么,结果是不传ReactApplicationContext进来工具类无法正常使用,不仅是你,你实现的ReactContextBaseJavaModule也需要这么一个Contex啊,所以就成了上面这个样子了)
其次,需要实现一个ReactPackage,在它的createNativeModules方法中返回我们的工具类ToastUtil。
public class ToastPackage implements ReactPackage {
@NonNull
@Override
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new ToastUtil(reactContext));
return modules;
}
@NonNull
@Override
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
然后需要把我们自己的ReactPackage加到实现了ReactApplication的ReactNativeHost对象的实现方法getPackages里。
@Override
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());
packages.add(new ToastPackage());
return packages;
}
剩下的实现ReactAplication并返回ReactNativeHost的操作一早就已经默认实现了。现在终于把自己定义的工具类交给RN了。
在RN中调用原生方法
我们已经按照RN的要求定义了自己的工具类,并把它交给RN了,RN会帮助我们把这个类“传输”到RN应用中,接下来我们就要尝试在RN应用中来调用了。
import React from 'react';
import {NativeModules, Button, View} from 'react-native';
export default class App extends React.Component {
_onPress() {
NativeModules.ToastUtil.show('哈哈');
}
render(): React$Node {
return (
<View>
<Button onPress={this._onPress} title={'显示Toast'} />
</View>
);
}
}
从原生传过来的工具类都在NativeModules里,所以我们需要先引入NativeModules。那么怎么定位到具体的我们的工具类呢?还记得实现工具类里继承了一个getName的方法么,之前说它是那个工具类的标识符,现在派上用场了。之前返回的字符串是“ToastUtil”,那么我们的工具类就是: NativeModules.ToastUtil了,可以直接在RN应用中通过
NativeModules.ToastUtil.show('哈哈');
来调到原生的ToastUtil.show(‘哈哈’)方法了。点击按钮就能看到熟悉的Toast了。
ok,到这入门就结束了。
总结
对于一个init出来的RN项目,快速实现RN对原生方法调用的操作如下:
- 创建一个类A继承ReactContextBaseJavaModule,实现的getName返回字符串如“X”,可以使用构造方法获取context,编写自己的要提供给RN的方法y()并使用@ReactMethod注解自定义的方法。
- 新建一个类B实现ReactPackage接口,并把上面创建的工具类A在createNativeModules里返回。
- 在已经实现了ReactApplication的Application里,找到getReactNativeHost中返回的ReactNativeHost,并在其实现类的getPackages方法中,在返回的List上,增加上面创建的ReactPackage实现类B。
- 在RN中import NativeModules from ‘react-native’,并通过NativeModules.X.y()的方式调用原生方法。