react native使用原生模块

    xiaoxiao2021-04-17  32

    有时候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(); } } 12345678910111213141516171819202122232425262728293031323334353637383940414243444546 12345678910111213141516171819202122232425262728293031323334353637383940414243444546

    注册模块

    上面已经编写好了导出给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); } 123456789101112131415161718192021 123456789101112131415161718192021

    可以看到这里有三个方法需要实现,其中,当前最关心的就是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(); } } 12345678910111213141516171819202122232425262728293031323334353637 12345678910111213141516171819202122232425262728293031323334353637 在MainActivity中添加当前模块  可以看到在MainActivity的onCreate方法中,有这样一段代码: mReactInstanceManager = ReactInstanceManager.builder() .setApplication(getApplication()) .setBundleAssetName("index.android.bundle") .setJSMainModuleName("index.android") .addPackage(new MainReactPackage()) .setUseDeveloperSupport(BuildConfig.DEBUG) .setInitialLifecycleState(LifecycleState.RESUMED) .build(); 12345678 12345678

    在当前代码里已经添加了一个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(); 123456789 123456789

    在javascript中使用原生模块

    使用也很简单,只需要这样一句代码就可以了:

    // 这里的MyToast就是在MyToastModule的getName中返回的module名称 React.NativeModules.MyToast.show('调用系统的toast啦', ToastAndroid.SHORT); 12 12 _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> ); } 1234567891011 1234567891011

    现在效果如下: 

    将原生模块封装

    为了在js里使用方便,可以将原生模块封装成一个JavaScript模块,在index.android.js同目录下新建一个toast.js文件: 

    toast.js

    'use strict'; var { NativeModules } = require('react-native'); // 这里的MyToast就是在MyToastModule 的getName返回的模块名 module.exports = NativeModules.MyToast; 12345 12345

    此时使用起来就很方便了,如下:

    // 引入当前的模块,注意toast是文件名 var MyToast = require('./toast'); // 这里的MyToast是toast.js文件通过module.exports给我们的 MyToast.show('调用系统的tdsaf', ToastAndroid.SHORT); 1234 1234

    参数类型

    下面的参数类型在@ReactMethod注明的方法中,会被直接映射到它们对应的JavaScript类型

    Boolean -> Bool Integer -> Number Double -> Number Float -> Number String -> String Callback -> function ReadableMap -> Object ReadableArray -> Array 12345678 12345678

    回调函数

    有时候,在js中调用android中自定义模块中的方法,需要有返回值,此时就需要使用到RN为我们封装好的一个Callback接口。

    package com.facebook.react.bridge; public interface Callback { public void invoke(Object... args); } 123456789 123456789

    可以看到,通过我们在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(); } } } 123456789101112131415161718192021222324252627282930313233343536373839 123456789101112131415161718192021222324252627282930313233343536373839

    注册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; } 123456789 123456789

    创建callback.js

    在与index.android.js同目录下创建callback.js,用来导出当前CallbackModule 模块。 'use strict'; var { NativeModules } = require('react-native'); module.exports = NativeModules.MyCallback; 1234 1234

    使用MyCallback

    MyCallback的使用和MyToast相同,这里我将两者结合起来使用,代码如下:

    var MyToast = require('./toast'); var MyCallback = require('./callback'); MyCallback.getAddResult(3,2,(code,result)=>{ console.log("callback",code,result); MyToast.show(code + result,ToastAndroid.SHORT); }); 1234567 1234567

    此时效果如下: 

    发送事件到JavaScript

    另外,原生的代码也可以在没有被调用的情况下向js发送事件通知。通过RCTDeviceEventEmitter来实现。这里依然以刚才的CallbakModule为例:

    @ReactMethod public void getAddResult(int number1, int number2, 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(); } } 12345678910111213141516171819 12345678910111213141516171819

    在componentDidMount方法中注册事件

    componentDidMount:function(){ //使用DeviceEventEmitter注册事件 DeviceEventEmitter.addListener('toastMe',(e)=>{ MyToast.show(e.first,ToastAndroid.SHORT); }); }, 123456 123456

    这里注册的事件后,一旦调用getAddResult方法,就会回到toastMe方法。此时效果如下: 

    转载请注明原文地址: https://ju.6miu.com/read-673385.html

    最新回复(0)