有时候我们的App需要访问平台API,并且React Native可能还没有相应的模块包装;或者你需要复用一些Java代码,而不是用Javascript重新实现一遍;又或者你需要实现某些高性能的、多线程的代码,譬如图片处理、数据库、或者各种高级扩展等等。
而用React Native可以在它的基础上编写真正原生的代码,并且可以访问平台所有的能力。如果React Native还不支持某个你需要的原生特性,你应当可以自己实现该特性的封装。

不过在开始编写代码使用原生模块前,有一个知识点需要掌握,免得又坑进去了。

在使用React Native的时候,经常会看到这么一段代码

var React = require('react-native');

那么require这个语句的作用到底是什么呢,下面的流程提取自require() 源码解读

当遇到 require(X) 时,按下面的顺序处理。
(1)如果 X 是内置模块(比如 require(‘http’))
  a. 返回该模块。
  b. 不再继续执行。
(2)如果 X 以 “./” 或者 “/” 或者 “../” 开头
a. 根据 X 所在的父模块,确定 X 的绝对路径。
b. 将 X 当成文件,依次查找下面文件,只要其中有一个存在,就返回该文件,不再继续执行。

  • X
  • X.js
  • X.json
  • X.node

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这个文件,该文件中导出了一些常用的组件,其源码如下

/** * 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;

了解了这个知识点后,我们来自定义一个模块,去使用原生的模块。假设有这么一个需求,我们需要使用Andorid中的Log类,但是React Native并没有为我们进行封装,那么我们自己动手实现一下吧。

  • 我们需要继承ReactContextBaseJavaModule这个抽象类,重写getName()函数,用于返回一个字符串,这个字符串在JavaScript端标记这个模块,暴露一个函数给javascript端,并且使用注解@ReactMethod进行标记,该函数的返回值必须为void,React Native的跨语言访问是异步进行的,所以想要给JavaScript返回一个值的唯一办法是使用回调函数或者发送事件
  • 我们需要实现一个类实现ReactPackage接口,该接口中有三个抽象函数待实现,分别是createNativeModulescreateJSModulescreateViewManagers,这三个函数中,我们需要实现的最关键的函数就是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即可。

public class AppReactPackage implements ReactPackage {    @Override    public List createNativeModules(ReactApplicationContext reactContext) {        List modules=new ArrayList<>();        modules.add(new LogModule(reactContext));        return modules;    }    @Override    public List> createJSModules() {        return Collections.emptyList();    }    @Override    public List 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中进行访问了。就像这样子。

var React = require('react-native');var {  NativeModules,} = React;var Log1= NativeModules.Log;Log1.d("Log1","LOG");

但是,假如我再增加一个需求,就是当Log类在java层打印出一个日志的之后,希望在js端也输出以下这个日志,那么你会怎么做呢,或许你会说,这个简单,我再输出一下js的日志就ok了。就像这样子。

var React = require('react-native');var {  NativeModules,} = React;var Log1= NativeModules.Log;Log1.d("Log1","LOG");console("Log1","LOG");

没错是没错,就是看着蛋疼,不好维护不说,通样的代码你得写多少遍。

这时候,我们就有必要封装一下javascript端的代码了,在index.android.js文件同目录下新建一个log.js,输入如下代码。

'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;

代码很简单,我们通过NativeModules拿到我们的Log模块在本地的实现,赋值给变量RCTLog,并且还声明了一个Log变量,里面有一个函数d,调用了RCTLog的d函数,并且在调用前输出了javascript端的日志。最后使用module.exports=Log将Log变量导出

上面我们规定了参数类型为string,java与javascript之间参数类型的对应关系如下

Boolean -> BoolInteger -> NumberDouble -> NumberFloat -> NumberString -> StringCallback -> functionReadableMap -> ObjectReadableArray -> Array

接下来就是引用log.js文件了,看过上面的require语句的解析,这对你应该不成问题了。

var Log=require('./log');Log.d("TAG","111");

这还没完,我们再提一个需求,就是我们希望这个Log模块能够提供一个常量,也就是TAG供我们使用,而这个常量定义在java层,以便以后我们使用的时候如果不想输入TAG,可以直接使用这个默认的TAG,就像这样子

var Log=require('./log');Log.d(Log.TAG,"111");

那么这个要怎么实现呢,很显然,我们需要在log.js中加入这个变量,就像这样子

'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;

这样虽然我们可以使用Log.TAG返回到这个值了,由于我们java层没有定义TAG,所以这时候会报错。因此我们需要在java层返回这个值,这又要怎么做呢,别急。我们重新回过头来看看我们实现的类LogModule,我们继续在该类中定义两个常量

private static final String TAG_KEY = "TAG";private static final String TAG_VALUE = "LogModule";

什么用呢,看常量名字就是到了,key和value,键值对,我们希望通过TAG_KEY拿到TAG_VALUE ,也就是我们日志要用到的TAG,怎么实现呢。重写getConstants函数即可。

  @Override    public Map getConstants() {        final Map 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层

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 getConstants() {    final Map 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();  }}

看到没有,在getConstants函数中暴露了两个值给javascript层,SHORT对应java层的Toast.LENGTH_SHORT,LONG对应java层的Toast.LENGTH_LONG。接着看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;

直接可以通过定义的变量SHORT或者LONG访问到,最终我们的使用就是这样子的。

var React = require('react-native');var {  ToastAndroid} = React;ToastAndroid.show("toast",ToastAndroid.SHORT);

这还没完,这仅仅是没有返回值的情况,假如有返回值情况又是怎么样呢,比如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层

'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;

进行使用

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通知主线程返回结果。

除了回调之外,还可以使用事件调用javascript层的方法,我们以第一个LogModule为例,假设调用了java层方法后,我们希望发送一个事件给javascript,在javascript层再次进行打印输出。则d方法修改下面的代码

 @ReactMethod    public void d(String tag,String msg){        Log.d(tag, msg);        //发送事件给javascript层        WritableMap params = Arguments.createMap();        params.putString("TAG",tag);        params.putString("MSG",msg);        getReactApplicationContext()                .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)                .emit("logInConsole", params);//对应的javascript层的事件名为logInConsole,注册该事件即可进行回调    }

而对应的log.js并不需要做什么修改,我们只需要在想获得该事件的地方注册事件即可,比如我们想在主界面接收该事件,则在index.android.js中进行注册,注册的方式有两种。

第一种如下

'use strict';var React = require('react-native');var {  AppRegistry,  StyleSheet,  View,  DeviceEventEmitter,} = React;//注意下面两个模块的引入var Log=require('./log');var Subscribable = require('Subscribable');var AwesomeProject = React.createClass({  mixins: [Subscribable.Mixin],//这句必须要加  render: function() {    return (           );  },  componentDidMount:function(){    Log.d("tag","tag");//调用java层log方法,之后就会回调对应的事件(前提是注册了事件)    //注册事件    this.addListenerOn(DeviceEventEmitter,                       'logInConsole',                       this.logInConsole);  },  logInConsole:function(event){    console.log(event);  },});var styles = StyleSheet.create({  container:{  },});AppRegistry.registerComponent('AwesomeProject', () => AwesomeProject);

控制台输出效果如下

这种方式显得代码有点多,还有另外一种方式,代码如下

'use strict';var React = require('react-native');var {  AppRegistry,  StyleSheet,  View,  DeviceEventEmitter,} = React;//只需要引入Logvar Log=require('./log');var AwesomeProject = React.createClass({  render: function() {    return (           );  },  componentDidMount:function(){    Log.d("tag","tag");//调用java层log方法,之后就会回调对应的事件(前提是注册了事件)    //直接使用DeviceEventEmitter进行事件注册    DeviceEventEmitter.addListener('logInConsole',(e)=>{       console.log(e);    });  },});var styles = StyleSheet.create({  container:{  },});AppRegistry.registerComponent('AwesomeProject', () => AwesomeProject);

效果是一样的。都会在控制台输出。

基本上,掌握了上面的内容,对原始模块的使用也差不多了,本篇文章是基于官方文档的最佳实践Native Modules,但是该文档坑太多,还需谨慎参考。

更多相关文章

  1. 【quickhybrid】Android端的项目实现
  2. Android应用程序资源管理器(Asset Manager)的创建过程分析
  3. android底层HAL层深入了解
  4. Android(安卓)系统中 gps Location Service 的实现与架构,本文可
  5. Android串口设备的应用实现方案以及与WEB的交互
  6. Android是否可以实现静默安装模式
  7. 浅谈Java中Collections.sort对List排序的两种方法
  8. 箭头函数的基础使用
  9. Python技巧匿名函数、回调函数和高阶函数

随机推荐

  1. 用Kotlin写RecyclerView,item使用Relative
  2. Android之判断是否有网封装类
  3. Ubuntu下编译AOSP步骤
  4. android 读 txt
  5. Windows下git下载android source
  6. Android编译系统(Android.mk文件详解-仅
  7. Android初级之路-Android系统架构简介
  8. Android(安卓)Studio FFMPEG 入门
  9. Android(安卓)Launcher启动流程
  10. Android(安卓)中 Button 的基本使用