有时候App需要访问平台API,但React Native可能还没有相应的模块包装;或者你需要复用一些Java代码,而不是用Javascript重新实现一遍;又或者你需要实现某些高性能的、多线程的代码,譬如图片处理、数据库、或者各种高级扩展等等。
使用步骤 ##
在RN中使用系统原生模块需要如下步骤:
- 创建一个原生模块。一个原生模块是一个继承了ReactContextBaseJavaModule的Java类,派生实现getName 返回一个字符串名字,这个名字在JavaScript端标记这个模块
- 一个可选的方法getContants返回了需要导出给JavaScript使用的常量。它并不一定需要实现,但在定义一些可以被JavaScript同步访问到的预定义的值时非常有用
- 导出一个方法给JavaScript使用,Java方法需要使用注解@ReactMethod。方法的返回类型必须为void
- 注册模块。我们需要在应用的Package类的createNativeModules方法中添加这个模块
编写模块
下面,我以官方的demo为栗子,来显示一个在js中调用android原生toast的demo,首先需要编写一个类继承自ReactContextBaseJavaModule。
package com.secondproject;
import android.widget.Toast;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import java.util.Map;
import java.util.HashMap;
public class MyToastModule extends ReactContextBaseJavaModule {
private static final String DURATION_SHORT_KEY = "SHORT";
private static final String DURATION_LONG_KEY = "LONG";
public MyToastModule(ReactApplicationContext reactContext) {
super(reactContext);
}
/** * getName方法。这个函数用于返回一个字符串名字,就是js中的模块名 */
@Override
public String getName() {
return "MyToast";
}
/** * 返回了需要导出给JavaScript使用的常量 */
@Override
public Map<String,Object> getConstants() {
final Map<String,Object> constants = new HashMap<>();
constants.put(DURATION_SHORT_KEY,Toast.LENGTH_SHORT);
constants.put(DURATION_LONG_KEY,Toast.LENGTH_LONG);
return constants;
}
/** * 导出给js使用的方法,需要使用注解@ReactMethod。方法的返回类型必须为void */
@ReactMethod
public void show(String message,int duration) {
Toast.makeText(getReactApplicationContext(),message,duration).show();
}
}
注册模块
上面已经编写好了导出给js使用的模块,下面还需要注册当前模块,如果模块没有被注册,它也无法在JavaScript中被访问到。注册模块份两步:
- 编写类实现ReactPackage接口
- 在当前应用的MainActivity.java中添加当前模块
我们先来看下系统的ReactPackage接口:
node_modules\react-native\ReactAndroid\src\main\java\com\facebook\react\ReactPackage.java
package com.facebook.react;
import java.util.List;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.uimanager.UIManagerModule;
import com.facebook.react.uimanager.ViewManager;
public interface ReactPackage {
List<NativeModule> createNativeModules(ReactApplicationContext reactContext);
List<Class<? extends JavaScriptModule>> createJSModules();
List<ViewManager> createViewManagers(ReactApplicationContext reactContext);
}
可以看到这里有三个方法需要实现,其中,当前最关心的就是createNativeModules方法的实现。
- MyReactPackage.java
package com.secondproject;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.JavaScriptModule;
import com.facebook.react.uimanager.ViewManager;
import java.util.ArrayList;
import java.util.List;
import java.util.Collections;
public class MyReactPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
// 这里的MyToastModule是之前他添加的module
modules.add(new MyToastModule(reactContext));
return modules;
}
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
// TODO Auto-generated method stub
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
// TODO Auto-generated method stub
return Collections.emptyList();
}
}
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(getApplication())
.setBundleAssetName("index.android.bundle")
.setJSMainModuleName("index.android")
.addPackage(new MainReactPackage())
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
在当前代码里已经添加了一个package,.addPackage(new MainReactPackage()),这里将自己之前定义的MyReactPackage 同样加进去即可:
mReactInstanceManager = ReactInstanceManager.builder()
.setApplication(getApplication())
.setBundleAssetName("index.android.bundle")
.setJSMainModuleName("index.android")
.addPackage(new MainReactPackage())
.addPackage(new MyReactPackage())
.setUseDeveloperSupport(BuildConfig.DEBUG)
.setInitialLifecycleState(LifecycleState.RESUMED)
.build();
在javascript中使用原生模块
使用也很简单,只需要这样一句代码就可以了:
// 这里的MyToast就是在MyToastModule的getName中返回的module名称
React.NativeModules.MyToast.show('调用系统的toast啦',ToastAndroid.SHORT);
_onPress: function(duration,content) { React.NativeModules.MyToast.show('调用系统的toast啦',ToastAndroid.SHORT); },render: function() { return ( <View style={{flexDirection: 'column'}}> <Text onPress={()=> this._onPress(ToastAndroid.SHORT,'this is short')} style={styles.button}>点击调用原生API</Text> </View> ); }
现在效果如下:
将原生模块封装
为了在js里使用方便,可以将原生模块封装成一个JavaScript模块,在index.android.js同目录下新建一个toast.js文件:
toast.js
'use strict';
var { NativeModules } = @H_403_411@require('react-native');
// 这里的MyToast就是在MyToastModule 的getName返回的模块名
module.exports = NativeModules.MyToast;
此时使用起来就很方便了,如下:
// 引入当前的模块,注意toast是文件名
var MyToast = @H_403_411@require('./toast');
// 这里的MyToast是toast.js文件通过module.exports给我们的
MyToast.show('调用系统的tdsaf',ToastAndroid.SHORT);
参数类型
下面的参数类型在@ReactMethod注明的方法中,会被直接映射到它们对应的JavaScript类型
Boolean -> Bool
@H_403_411@Integer -> Number
Double -> Number
Float -> Number
@H_403_411@String -> @H_403_411@String
Callback -> function
ReadableMap -> Object
ReadableArray -> @H_403_411@Array
回调函数
有时候,在js中调用android中自定义模块中的方法,需要有返回值,此时就需要使用到RN为我们封装好的一个Callback接口。
package com.facebook.react.bridge;
public interface Callback {
public void invoke(Object... args);
}
可以看到,通过我们在java中调用invoke方法,将结果返回给js的,返回的参数类型和个数都是不限的。
新建CallbackModule
package com.secondproject;
import android.widget.Toast;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.Callback;
import java.util.Map;
import java.util.HashMap;
public class CallbackModule extends ReactContextBaseJavaModule {
public CallbackModule(ReactApplicationContext reactContext) {
super(reactContext);
}
// 返回js中使用当前模块的名称
@Override
public String getName() {
return "MyCallback";
}
@ReactMethod
public void getAddResult(int number1,int number2,Callback callback) {
try {
int result = number1 + number2;
//通过invoke方法将结果传递给js
callback.invoke("结果是:",result);
} catch (Exception e) {
e.printStackTrace();
}
}
}
注册CallbackModule 模块
记得在之前的MyReactPackage中注册当前模块:
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new MyToastModule(reactContext));
modules.add(new CallbackModule(reactContext));
return modules;
}
创建callback.js
- 在与index.android.js同目录下创建callback.js,用来导出当前CallbackModule 模块。
'use strict';
var { NativeModules } = @H_403_411@require('react-native');
module.exports = NativeModules.MyCallback;
使用MyCallback
MyCallback的使用和MyToast相同,这里我将两者结合起来使用,代码如下:
var MyToast = @H_403_411@require('./toast');
var MyCallback = @H_403_411@require('./callback');
MyCallback.getAddResult(3,2,(code,result)=>{ @H_403_411@console.log("callback",code,result); MyToast.show(code + result,ToastAndroid.SHORT); });
此时效果如下:
发送事件到JavaScript
另外,原生的代码也可以在没有被调用的情况下向js发送事件通知。通过RCTDeviceEventEmitter来实现。这里依然以刚才的CallbakModule为例:
@ReactMethod
public void getAddResult(int number1,Callback callback) {
try {
WritableMap params = Arguments.createMap();
//通过WritableMap传递一个参数给js,也可以传递多个
params.putString("first","i am first");
getReactApplicationContext()
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("toastMe",params);//对应的javascript层的事件名为toastMe,注册该事件即可进行回调
int result = number1 + number2;
callback.invoke("结果是:",result);
} catch (Exception e) {
e.printStackTrace();
}
}
在componentDidMount方法中注册事件
componentDidMount:function(){
//使用DeviceEventEmitter注册事件
DeviceEventEmitter.addListener('toastMe',(e)=>{
MyToast.show(e.first,ToastAndroid.SHORT);
});
},