有时候我们的App需要访问平台API,并且React Native可能还没有相应的模块包装;或者你需要复用一些Java代码,而不是用Javascript重新实现一遍;又或者你需要实现某些高性能的、多线程的代码,譬如图片处理、数据库、或者各种高级扩展等等。
而用React Native可以在它的基础上编写真正原生的代码,并且可以访问平台所有的能力。如果React Native还不支持某个你需要的原生特性,你应当可以自己实现该特性的封装。不过在开始编写代码使用原生模块前,有一个知识点需要掌握,免得又坑进去了。
在使用React Native的时候,经常会看到这么一段代码
<code class=" hljs javascript">var React = require('react-native');</code>
那么require这个语句的作用到底是什么呢,下面的流程提取自require() 源码解读
当遇到 require(X) 时,按下面的顺序处理。
X X.js X.json X.node
(1)如果 X 是内置模块(比如 require(‘http’))
a. 返回该模块。
b. 不再继续执行。
(2)如果 X 以 “./” 或者 “/” 或者 “../” 开头
a. 根据 X 所在的父模块,确定 X 的绝对路径。
b. 将 X 当成文件,依次查找下面文件,只要其中有一个存在,就返回该文件,不再继续执行。c. 将 X 当成目录,依次查找下面文件,只要其中有一个存在,就返回该文件,不再继续执行。
X/package.json(main字段) X/index.js X/index.json X/index.node(3)如果 X 不带路径
a. 根据 X 所在的父模块,确定 X 可能的安装目录。
b. 依次在每个目录中,将 X 当成文件名或目录名加载。
(4) 抛出 “not found”以上就是require语句的整个执行过程。那么require(‘react-native’);请求的到底是什么呢,其实就是node_modules\react-native\Libraries\react-native\react-native.js这个文件,该文件中导出了一些常用的组件,其源码如下
<code class=" hljs javascript">/** * Copyright (c) 2015-present, Facebook, Inc. * All rights reserved. * * This source code is licensed under the BSD-style license found in the * LICENSE file in the root directory of this source tree. An additional grant * of patent rights can be found in the PATENTS file in the same directory. * * @flow */ 'use strict'; // Export React, plus some native additions. // // The use of Object.create/assign is to work around a Flow bug (#6560135). // Once that is fixed, change this back to // // var ReactNative = {...require('React'), /* additions */} // var ReactNative = Object.assign(Object.create(require('React')), { // Components ActivityIndicatorIOS: require('ActivityIndicatorIOS'), DatePickerIOS: require('DatePickerIOS'), DrawerLayoutAndroid: require('DrawerLayoutAndroid'), Image: require('Image'), ListView: require('ListView'), MapView: require('MapView'), Modal: require('Modal'), Navigator: require('Navigator'), NavigatorIOS: require('NavigatorIOS'), PickerIOS: require('PickerIOS'), ProgressBarAndroid: require('ProgressBarAndroid'), ProgressViewIOS: require('ProgressViewIOS'), ScrollView: require('ScrollView'), SegmentedControlIOS: require('SegmentedControlIOS'), SliderIOS: require('SliderIOS'), SnapshotViewIOS: require('SnapshotViewIOS'), Switch: require('Switch'), SwitchAndroid: require('SwitchAndroid'), SwitchIOS: require('SwitchIOS'), TabBarIOS: require('TabBarIOS'), Text: require('Text'), TextInput: require('TextInput'), ToastAndroid: require('ToastAndroid'), ToolbarAndroid: require('ToolbarAndroid'), TouchableHighlight: require('TouchableHighlight'), TouchableNativeFeedback: require('TouchableNativeFeedback'), TouchableOpacity: require('TouchableOpacity'), TouchableWithoutFeedback: require('TouchableWithoutFeedback'), View: require('View'), ViewPagerAndroid: require('ViewPagerAndroid'), WebView: require('WebView'), // APIs ActionSheetIOS: require('ActionSheetIOS'), AdSupportIOS: require('AdSupportIOS'), AlertIOS: require('AlertIOS'), Animated: require('Animated'), AppRegistry: require('AppRegistry'), AppStateIOS: require('AppStateIOS'), AsyncStorage: require('AsyncStorage'), BackAndroid: require('BackAndroid'), CameraRoll: require('CameraRoll'), Dimensions: require('Dimensions'), Easing: require('Easing'), ImagePickerIOS: require('ImagePickerIOS'), InteractionManager: require('InteractionManager'), LayoutAnimation: require('LayoutAnimation'), LinkingIOS: require('LinkingIOS'), NetInfo: require('NetInfo'), PanResponder: require('PanResponder'), PixelRatio: require('PixelRatio'), PushNotificationIOS: require('PushNotificationIOS'), Settings: require('Settings'), StatusBarIOS: require('StatusBarIOS'), StyleSheet: require('StyleSheet'), VibrationIOS: require('VibrationIOS'), // Plugins DeviceEventEmitter: require('RCTDeviceEventEmitter'), NativeAppEventEmitter: require('RCTNativeAppEventEmitter'), NativeModules: require('NativeModules'), Platform: require('Platform'), processColor: require('processColor'), requireNativeComponent: require('requireNativeComponent'), // Prop Types EdgeInsetsPropType: require('EdgeInsetsPropType'), PointPropType: require('PointPropType'), // See http://facebook.github.io/react/docs/addons.html addons: { LinkedStateMixin: require('LinkedStateMixin'), Perf: undefined, PureRenderMixin: require('ReactComponentWithPureRenderMixin'), TestModule: require('NativeModules').TestModule, TestUtils: undefined, batchedUpdates: require('ReactUpdates').batchedUpdates, cloneWithProps: require('cloneWithProps'), createFragment: require('ReactFragment').create, update: require('update'), }, }); if (__DEV__) { ReactNative.addons.Perf = require('ReactDefaultPerf'); ReactNative.addons.TestUtils = require('ReactTestUtils'); } module.exports = ReactNative; </code>
了解了这个知识点后,我们来自定义一个模块,去使用原生的模块。假设有这么一个需求,我们需要使用Andorid中的Log类,但是React Native并没有为我们进行封装,那么我们自己动手实现一下吧。
我们需要继承 ReactContextBaseJavaModule 这个抽象类,重写 getName() 函数,用于返回一个字符串,这个字符串在 JavaScript 端标记这个模块,暴露一个函数给javascript端,并且使用注解 @ReactMethod 进行标记,该函数的返回值必须为void,React Native的跨语言访问是异步进行的,所以想要给JavaScript返回一个值的唯一办法是使用 回调函数 或者 发送事件 。 我们需要实现一个类实现 ReactPackage 接口,该接口中有三个抽象函数待实现,分别是 createNativeModules , createJSModules , createViewManagers ,这三个函数中,我们需要实现的最关键的函数就是 createNativeModules ,在该函数中我们需要添加前一步创建的 ReactContextBaseJavaModule 子类 构建 ReactInstanceManager 的实例时,通过调用 addPackage() 函数,将上一步实现的ReactPackage添加进去。接下来我们来实现代码。为了简单方便,这里只演示Log类中的d方法,即Log.d(String tag,String msg)
第一步,继承ReactContextBaseJavaModule类,重写getName()方法,因为是Log模块,所以直接返回字符串Log,暴露一个d方法给javascript端,返回值为void,只用注解进行标记。最终的代码如下。
public class LogModule extends ReactContextBaseJavaModule{ private static final String MODULE_NAME="Log"; public LogModule(ReactApplicationContext reactContext) { super(reactContext); } @Override public String getName() { return MODULE_NAME; } @ReactMethod public void d(String tag,String msg){ Log.d(tag,msg); } }
第二步,实现ReactPackage接口,在createNativeModules函数中添加我们的日志模块。其余两个函数返回空的List即可。
createNativeModules(ReactApplicationContext reactContext) { List<nativemodule> modules=new ArrayList<>(); modules.add(new LogModule(reactContext)); return modules; } @Override public List<class<? extends="" javascriptmodule="">> createJSModules() { return Collections.emptyList(); } @Override public List<viewmanager> createViewManagers(ReactApplicationContext reactContext) { return Collections.emptyList(); } }
public class AppReactPackage implements ReactPackage { @Override public List<nativemodule> createNativeModules(ReactApplicationContext reactContext) { List<nativemodule> modules=new ArrayList<>(); modules.add(new LogModule(reactContext)); return modules; } @Override public List<class<? extends="" javascriptmodule="">> createJSModules() { return Collections.emptyList(); } @Override public List<viewmanager> createViewManagers(ReactApplicationContext reactContext) { return Collections.emptyList(); } }
第三步,添加AppReactPackage 到ReactInstanceManager的实例中去,在我们的MainActivity中可以看到这么一段代码
mReactInstanceManager = ReactInstanceManager.builder() .setApplication(getApplication()) .setBundleAssetName("index.android.bundle") .setJSMainModuleName("index.android") .addPackage(new MainReactPackage()) .setUseDeveloperSupport(BuildConfig.DEBUG) .setInitialLifecycleState(LifecycleState.RESUMED) .build();
我们再build函数之前调用addPackage进行添加即可,最终代码如下。
mReactInstanceManager = ReactInstanceManager.builder() .setApplication(getApplication()) .setBundleAssetName("index.android.bundle") .setJSMainModuleName("index.android") .addPackage(new MainReactPackage()) .addPackage(new AppReactPackage()) .setUseDeveloperSupport(BuildConfig.DEBUG) .setInitialLifecycleState(LifecycleState.RESUMED) .build();
可以看到我们增加了一行.addPackage(new AppReactPackage())
这样,在Java端我们要做的就做完了,接下来就是javascript端了,这时候编译一下apk重新后运行,接下来我们来编写javascript端。
如果你不嫌麻烦,每次都要从NativeModules来访问我们的Log,那么现在你可以直接在javascript中进行访问了。就像这样子。
<code class=" hljs lasso"> var React = require('react-native'); var { NativeModules, } = React; var Log1= NativeModules.Log; Log1.d("Log1","LOG");</code>
但是,假如我再增加一个需求,就是当Log类在java层打印出一个日志的之后,希望在js端也输出以下这个日志,那么你会怎么做呢,或许你会说,这个简单,我再输出一下js的日志就ok了。就像这样子。
<code class=" hljs coffeescript"> var React = require('react-native'); var { NativeModules, } = React; var Log1= NativeModules.Log; Log1.d("Log1","LOG"); console("Log1","LOG");</code>
没错是没错,就是看着蛋疼,不好维护不说,通样的代码你得写多少遍。
这时候,我们就有必要封装一下javascript端的代码了,在index.android.js文件同目录下新建一个log.js,输入如下代码。
<code class=" hljs javascript">'use strict'; var { NativeModules } = require('react-native'); var RCTLog= NativeModules.Log; var Log = { d: function ( tag: string, msg: string ): void { console.log(tag,msg); RCTLog.d(tag, msg); }, }; module.exports = Log;</code>
代码很简单,我们通过NativeModules拿到我们的Log模块在本地的实现,赋值给变量RCTLog,并且还声明了一个Log变量,里面有一个函数d,调用了RCTLog的d函数,并且在调用前输出了javascript端的日志。最后使用module.exports=Log将Log变量导出
接下来就是引用log.js文件了,看过上面的require语句的解析,这对你应该不成问题了。
这还没完,我们再提一个需求,就是我们希望这个Log模块能够提供一个常量,也就是TAG供我们使用,而这个常量定义在java层,以便以后我们使用的时候如果不想输入TAG,可以直接使用这个默认的TAG,就像这样子<code class=" hljs lasso">var Log=require('./log'); Log.d("TAG","111");</code>
<code class=" hljs javascript">'use strict'; var { NativeModules } = require('react-native'); var RCTLog= NativeModules.Log; var Log = { TAG: RCTLog.TAG, d: function ( tag: string, msg: string ): void { console.log(tag,msg); RCTLog.d(tag, msg); }, }; module.exports = Log;</code>
这样虽然我们可以使用Log.TAG返回到这个值了,由于我们java层没有定义TAG,所以这时候会报错。因此我们需要在java层返回这个值,这又要怎么做呢,别急。我们重新回过头来看看我们实现的类 LogModule ,我们继续在该类中定义两个常量
<code class=" hljs java">private static final String TAG_KEY = "TAG"; private static final String TAG_VALUE = "LogModule";</code>
什么用呢,看常量名字就是到了,key和value,键值对,我们希望通过TAG_KEY拿到TAG_VALUE ,也就是我们日志要用到的TAG,怎么实现呢。重写 getConstants 函数即可。
getConstants() { final Map<string, object=""> constants = MapBuilder.newHashMap(); constants.put(TAG_KEY, TAG_VALUE); return constants; }
@Override public Map<string, object=""> getConstants() { final Map<string, object=""> constants = MapBuilder.newHashMap(); constants.put(TAG_KEY, TAG_VALUE); return constants; }
这时候重写编译运行一下,你就可以在javascript层通过Log.TAG就可以访问到对应的值了,值为LogModule,而为什么是Log.TAG而不是其他的值呢,因为我们constants中put进去的键就是TAG。
那么这个有什么作用呢,还记得android中我们的Toast的使用,显示时间有两个值吗,一个是Toast.LENGTH_SHORT,另一个是Toast.LENGTH_LONG,我们希望在javascript层通用有这么两个常量可以使用,那么就可以使用这种方法。我们可以看看系统的ToastAndroid的实现。
首先看java层
看到没有,在getConstants函数中暴露了两个值给javascript层,SHORT对应java层的Toast.LENGTH_SHORT,LONG对应java层的Toast.LENGTH_LONG。接着看javascript层的代码getConstants() { final Map<string, object=""> constants = MapBuilder.newHashMap(); constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT); constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG); return constants; } @ReactMethod public void show(String message, int duration) { Toast.makeText(getReactApplicationContext(), message, duration).show(); } } " data-snippet-id="ext.b6ffc6da30953242ea41fd1abab455a3" data-snippet-saved="false" data-csrftoken="Ws1sIYY7-0-swsiDcqXaHz8nq3x0PdVnFdw4" data-codota-status="done"><code class=" hljs java">public class ToastModule extends ReactContextBaseJavaModule { private static final String DURATION_SHORT_KEY = "SHORT"; private static final String DURATION_LONG_KEY = "LONG"; public ToastModule(ReactApplicationContext reactContext) { super(reactContext); } @Override public String getName() { return "ToastAndroid"; } @Override public Map<string, object=""> getConstants() { final Map<string, object=""> constants = MapBuilder.newHashMap(); constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT); constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG); return constants; } @ReactMethod public void show(String message, int duration) { Toast.makeText(getReactApplicationContext(), message, duration).show(); } }
<code class=" hljs javascript">'use strict'; var RCTToastAndroid = require('NativeModules').ToastAndroid; var ToastAndroid = { SHORT: RCTToastAndroid.SHORT, LONG: RCTToastAndroid.LONG, show: function ( message: string, duration: number ): void { RCTToastAndroid.show(message+"lizhangqu", duration); }, }; module.exports = ToastAndroid;</code>
直接可以通过定义的变量SHORT或者LONG访问到,最终我们的使用就是这样子的。
<code class=" hljs javascript">var React = require('react-native'); var { ToastAndroid } = React; ToastAndroid.show("toast",ToastAndroid.SHORT);</code>
这还没完,这仅仅是没有返回值的情况,假如有返回值情况又是怎么样呢,比如javascript调用java层的方法,但是java层需要将结果返回javascript,没错,答案就是回调!,最典型的一个场景就是javascript层调用java层的网络请求方法,java层拿到网络数据后需要将结果返回给javascript层。通用的,我们用最快的速度实现一下这个模块。
继承ReactContextBaseJavaModule,实现getName方法,返回值为Net,暴露一个getResult方法给javascript,并进行注解,注意这个函数有一个Callback类型的入参,返回结果就是通过这个进行回调
public class NetModule extends ReactContextBaseJavaModule { private static final String MODULE_NAME="Net"; public NetModule(ReactApplicationContext reactContext) { super(reactContext); } @Override public String getName() { return MODULE_NAME; } @ReactMethod public void getResult(String url,final Callback callback){ Log.e("TAG","正在请求数据"); new Thread(new Runnable() { @Override public void run() { try { String result="这是结果"; Thread.sleep(1000);//模拟网络请求 callback.invoke(true,result); } catch (Exception e) { e.printStackTrace(); } } }).start(); } }
Callback的定义如下,它是一个接口,invoke函数的入参是个数是任意的。
public interface Callback { /** * Schedule javascript function execution represented by this {@link Callback} instance * * @param args arguments passed to javascript callback method via bridge */ public void invoke(Object... args); }
在前面的AppReactPackage类createNativeModules函数中注册该模块
modules.add(new NetModule(reactContext));
之后新建一个net.js文件,实现javascript层
<code class=" hljs javascript">'use strict'; var { NativeModules } = require('react-native'); var RCTNet= NativeModules.Net; var Net = { getResult: function ( url: string, callback:Function, ): void { RCTNet.getResult(url,callback); }, }; module.exports = Net;</code>
进行使用
{ console.log("callback",code,result); } );" data-snippet-id="ext.107e880f3149ba75eadfdf8a2d0708f1" data-snippet-saved="false" data-csrftoken="j4Ei9vvD-5rn_ieDw5bD-7eRkjmp4yKJgSW8" data-codota-status="done"><code class=" hljs coffeescript">var Net=require('./net'); Net.getResult( "http://baidu.com", (code,result)=>{ console.log("callback",code,result); } );
如果不出意外,在java层将输出日志
11-20 22:30:53.598 25323-1478/com.awesomeproject E/TAG: 正在请求数据
在javascript层,控制台将输出
callback true 这是结果
以上就是回调的一个示例,你可以简单想象成java层的网络请求模型,主线程开启子线程请求数据,子线程拿到数据后回调对应的方法使用handler通知主线程返回结果。
基本上,掌握了上面的内容,对原始模块的使用也差不多了,本篇文章是基于官方文档的最佳实践Native Modules
Android React Native使用原生模块
最新推荐文章于 2024-04-18 09:28:15 发布