本文主题:如何实现原生代码的复用,即如何将原生模块封装。
有时候我们的应用需要进行访问原生平台系统的API接口,但是React Native可能还没有封装相应功能组件。还有可能我们需要
去复用一些原生java代码而不是让JavaScript重新去实现一遍。或者我们可能需要些一些更加高级的功能代码,所线程相关的。例如:
图片处理,数据库以及一些高级功能扩展之类的。
React Native平台的开发其实本身也是可以让你写纯原生代码并且还可以让你访问原生平台的功能。这是一个比较高级的功能不
过官方还是不推荐你在平时开发中使用这样的开发形式。但是如果你具备这样的开发能力,也是还是不错的。特别当在React Native
暂时未提供部分原生功能或者模块,那么你可以使用这样的方法进行封装扩展。今天我们就来看一下原生组件的封装扩展方法。
总体来说,就是当我们需要复用部分原生代码时,比如复用一个原生方法,此时就需要将原生方法进行封装,只暴露出一个接口
来让React-Native调用。本博客以一个Toast消息来作用案例来讲解如何封装原生模块。
步骤:
- 用Android Studio打开一个已经存在的RN项目,即用AS打开 项目文件夹/android/build.gradle文件。
- 在Android原生这边创建一个类继承ReactContextBaseJavaModule,这个类里边放我们需要被RN调用的方法,将其封装成一个原生模块。
- 在Android原生这边创建一个类实现接口ReactPackage包管理器,并把第二步创建的类加到原生模块(NativeModule)列表里。
- 将第三步创建的包管理器添加到ReactPackage列表里(getPackage方法里)
- 在RN中去调用原生模块,必须import NativeModule模块。
首先大家肯定已经安装好了Android Studio,打开build.gradle文件之后,会发现其实连android/app文件夹也一并打开了。其中java文件夹中存放原生代码,也就是将我们要复用的原生代码放进来。大家可以打开 项目文件夹/android/app自行查看各级目录。在默认的包下,创建上边第二步和第三步所需的类。截图如下:
MyNativeModule.java代码如下:
package com.reactnative; import android.widget.Toast; import android.content.Context; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; /** * Created by Administrator on 2016/10/18. */ public class MyNativeModule extends ReactContextBaseJavaModule { private Context mContext; public MyNativeModule(ReactApplicationContext reactContext) { super(reactContext); mContext = reactContext; } @Override public String getName() { //返回的这个名字是必须的,在rn代码中需要这个名字来调用该类的方法。 return "MyNativeModule"; } //函数不能有返回值,因为被调用的原生代码是异步的,原生代码执行结束之后只能通过回调函数或者发送信息给rn那边。 @ReactMethod public void rnCallNative(String msg){ Toast.makeText(mContext,msg,Toast.LENGTH_SHORT).show(); } }
本类中存放我们要复用的原生方法,继承了ReactContextBaseJavaModule类,并且实现了其getName()方法,构造方法也是必须的。按着Alt+Enter程序会自动提示。接着定义了一个方法,该方法必须使用注解@ReactMethod标明,说明是RN要调用的方法。
MyReactPackage.java代码如下:
import com.facebook.react.ReactPackage; import com.facebook.react.bridge.JavaScriptModule; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.uimanager.ViewManager; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * Created by Administrator on 2016/10/18. */ public class MyReactPackage implements ReactPackage { @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { List<NativeModule> modules=new ArrayList<>(); //将我们创建的类添加进原生模块列表中 modules.add(new MyNativeModule(reactContext)); return modules; } @Override public List<Class<? extends JavaScriptModule>> createJSModules() { //返回值需要修改 return Collections.emptyList(); } @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { //返回值需要修改 return Collections.emptyList(); } }
MainApplication.java代码如下:
package com.reactnative; import android.app.Application; import android.util.Log; import com.facebook.react.ReactApplication; import com.facebook.react.ReactInstanceManager; import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactPackage; import com.facebook.react.shell.MainReactPackage; import java.util.Arrays; import java.util.List; public class MainApplication extends Application implements ReactApplication { private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { @Override protected boolean getUseDeveloperSupport() { return BuildConfig.DEBUG; } @Override protected List<ReactPackage> getPackages() { return Arrays.<ReactPackage>asList( new MainReactPackage(), //将我们创建的包管理器给添加进来 new MyReactPackage() ); } }; @Override public ReactNativeHost getReactNativeHost() { return mReactNativeHost; } }
MainActivity.java代码如下:
package com.reactnative; import com.facebook.react.ReactActivity; public class MainActivity extends ReactActivity { /** * Returns the name of the main component registered from JavaScript. * This is used to schedule rendering of the component. */ @Override protected String getMainComponentName() { return "reactNative"; } }
接着我们需要编写index.android.js文件
代码如下:
/** * Sample React Native App * https://github.com/facebook/react-native * @flow */ import React, { Component } from ‘react‘; import { AppRegistry, StyleSheet, Text, NativeModules, View } from ‘react-native‘; class reactNative extends Component { render() { return ( <View style={styles.container}> <Text style={styles.welcome} onPress={this.call_button.bind(this)} > React Native 调用原生方法! </Text> <Text style={styles.instructions}> To get started, edit index.android.js </Text> <Text style={styles.instructions}> Double tap R on your keyboard to reload,{‘\n‘} Shake or press menu button for dev menu </Text> </View> ); } call_button(){ NativeModules.MyNativeModule.rnCallNative(‘调用原生方法的Demo‘); } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: ‘center‘, alignItems: ‘center‘, backgroundColor: ‘#F5FCFF‘, }, welcome: { fontSize: 20, textAlign: ‘center‘, margin: 10, }, instructions: { textAlign: ‘center‘, color: ‘#333333‘, marginBottom: 5, }, }); AppRegistry.registerComponent(‘reactNative‘, () => reactNative);
我们将一个文本框绑定了一个事件,首先import导入NativeModule,当点击文本时将会调用call_button()方法。然后传入一个上下文对象,也就是一句话,通过调用原生方法rnCallNative( )方法来实现。
来分析一下程序运行流程:
(1)在配置文件AndroidManifest.xml中,android:name=".MainApplication",则MainApplication.java会执行。
(2)在MainApplication.java中,有我们创建的包管理器对象。程序加入MyReactPackage.java中。
(3)在MyReactPackage.java中,将我们自己创建的模块加入了原生模块列表中,程序进入MyNativeModule.java中。
(4)在MyNativeModule.java中,有我们需要被复用的原生方法rnCallNative( )。
程序运行结果如下所示,当点击第一行文本时,出现Toast消息。
封装原生方法升级篇:
(1)如何封装复杂方法,实现更多的功能?
在上文中,我们封装了一个简单的方法—弹出Toast 提醒框。但是大家看到可能很郁闷,心里想,我TM 要封装的方法也不会这么简单呀,能不能封装点复杂的方法,用来实现更多的功能?(毕竟是我们想要复用的方法,肯定实现了很多比较牛逼的功能。)
答:能!!!
如何实现呢?以上面所述项目为例讲解。
- 首先,将原生的java 文件复制到RN项目中存放原生代码的位置,如下图所示。
- 然后,在MyNativeMoudle.java中写一个可以被RN调用的方法,以注解@ReactMethod表明。
- 其次,在上一步所述方法内部可以任意调用原生方法,实现更加复杂的功能。
- 最后,在RN中调用第二步所写的方法。
Demo如下:
ManiActivity.java
package com.firstproject; import com.facebook.react.ReactActivity; public class MainActivity extends ReactActivity { /** * Returns the name of the main component registered from JavaScript. * This is used to schedule rendering of the component. */ @Override protected String getMainComponentName() { return "FirstProject"; } }
ManiApplication.java
package com.firstproject; import android.app.Application; import android.util.Log; import com.facebook.react.ReactApplication; import com.facebook.react.ReactInstanceManager; import com.facebook.react.ReactNativeHost; import com.facebook.react.ReactPackage; import com.facebook.react.shell.MainReactPackage; import java.util.Arrays; import java.util.List; public class MainApplication extends Application implements ReactApplication { private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) { @Override protected boolean getUseDeveloperSupport() { return BuildConfig.DEBUG; } @Override protected List<ReactPackage> getPackages() { return Arrays.<ReactPackage>asList( new MainReactPackage(), new MyPackage() ); } }; @Override public ReactNativeHost getReactNativeHost() { return mReactNativeHost; } }
MyModule.java
package com.firstproject; import android.content.Context; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.bridge.ReactContextBaseJavaModule; import com.facebook.react.bridge.ReactMethod; /** * Created by Administrator on 2016/10/30. */ public class MyModule extends ReactContextBaseJavaModule { //建立上下文对象 public Context myContext; public MyModule(ReactApplicationContext reactContext) { super(reactContext); myContext=reactContext; } @Override public String getName() { return "MyModule"; } @ReactMethod public void showTime() { new Test().getTime(myContext); } }
MyPackage.java
package com.firstproject; import com.facebook.react.ReactPackage; import com.facebook.react.bridge.JavaScriptModule; import com.facebook.react.bridge.NativeModule; import com.facebook.react.bridge.ReactApplicationContext; import com.facebook.react.uimanager.ViewManager; import java.util.ArrayList; import java.util.Collections; import java.util.List; /** * Created by Administrator on 2016/10/30. */ public class MyPackage implements ReactPackage { @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { List<NativeModule> modules=new ArrayList<>(); modules.add(new MyModule(reactContext)); return modules; } @Override public List<Class<? extends JavaScriptModule>> createJSModules() { return Collections.emptyList(); } @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { return Collections.emptyList(); } }
Test.java作为我们要复用的原生类,里边有要复用的原生方法。
package com.firstproject; import android.content.Context; import android.util.Log; import android.widget.Toast; import java.text.SimpleDateFormat; import java.util.Date; /** * Created by Administrator on 2016/10/30. */ public class Test { public void getTime(Context ctx) { SimpleDateFormat formatDate=new SimpleDateFormat("yyyy年MM月dd日 HH:mm:ss"); Date date=new Date(System.currentTimeMillis()); //获取当前时间 String s=formatDate.format(date); Log.e("HHH",s); Toast.makeText(ctx,s,Toast.LENGTH_SHORT).show(); } }
index.android.js如下:
/** * Sample React Native App * https://github.com/facebook/react-native * @flow */ import React, { Component } from ‘react‘; import { AppRegistry, StyleSheet, Text, NativeModules, View } from ‘react-native‘; export default class FirstProject extends Component { render() { return ( <View style={styles.container}> <Text style={styles.welcome} //给此处的文字绑定一个事件,其中callNative为要调用的方法名。 onPress={this.callNative.bind(this)} > 点击此处文字调用原生方法! </Text> <Text style={styles.instructions}> 此Demo演示如何调用Android原生中的复杂方法。 </Text> </View> ); } callNative() { NativeModules.MyModule.showTime(); } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: ‘center‘, alignItems: ‘center‘, backgroundColor: ‘#F5FCFF‘, }, welcome: { fontSize: 20, textAlign: ‘center‘, margin: 10, }, instructions: { textAlign: ‘center‘, color: ‘#333333‘, marginBottom: 5, }, }); AppRegistry.registerComponent(‘FirstProject‘, () => FirstProject);
程序运行结果如下:
在上边的Demo中,主要是原生类Test.java。我们在要被RN调用的方法中调用原生类中的方法。和最开始的案
例不同的是,这里说明了,方法可以调用,我们N多原生类都可以直接粘贴复制过来。这样就可以实现调用复杂方
法实现强大功能了。
注意:当Android原生中涉及到权限的使用时,记得在AndroidManifest.xml中添加相应权限,如下图所示。
(2)如何实现数据从Android 原生回调到RN前端界面?
我们都知道,要被RN调用的方法必须是void 类型,即没有返回值,但是项目中很多地方都需要返回数据。那怎
么实现呢?
如图所示:我们定义一个方法,使用Callback, 在这个方法中,建立并且开启一个线程,
使用callback. invoke( XXXX)实现数据向RN前端的传递。
其中,MyMainActivity.java文件为我们的一个原生类,按照上面的要求复制到RN项目中存放原生代码的地
方。而ReceiveData为这个类的一个变量。
如何在RN项目中调用?
代码如下:
/** * Sample React Native App * https://github.com/facebook/react-native * @flow */ import React, { Component } from ‘react‘; import { AppRegistry, StyleSheet, Text, NativeModules, View, TouchableOpacity } from ‘react-native‘; export default class lh extends Component { constructor(props) { super(props); this.state = { global: ‘这个是预定的接受信息‘, } } render() { var Globle="null"; return ( <View style={styles.container}> <TouchableOpacity style={styles.button1} onPress={this.call_button_show.bind(this)}> <Text style={styles.welcome} > 显示信息 </Text> </TouchableOpacity> <Text style={styles.welcome} > {this.state.global} </Text> </View> ); } call_button_show(){ Globle="null"; NativeModules.MyModule.getResult((result)=>{this.setState({global:result,});}); } } const styles = StyleSheet.create({ container: { flex: 1, justifyContent: ‘center‘, alignItems: ‘center‘, backgroundColor: ‘#F5FCFF‘, }, button1:{ height: 40, width: 100, marginTop:1, backgroundColor:‘gray‘, }, welcome: { fontSize: 20, textAlign: ‘center‘, margin: 10, }, instructions: { textAlign: ‘center‘, color: ‘#333333‘, marginBottom: 5, }, }); AppRegistry.registerComponent(‘lh‘, () => lh);
当我们点击按钮时,将会调用getResult方法,并且将ReceiveData变量的值传递给RN前端的result变量中,利用
setState来实现界面的更新。
至此,我们实现了RN复用原生代码,即将原生模块封装成一个接口,在RN中调用。并且可以封装更加复杂
的方法,同时实现了数据回调,即将数据从原生模块中传递到RN前端。
程序源代码1下载,请见GitHub:https://github.com/chaohuangtianjie994/ReactNative-call-NativeMethod
Demo2的改动不大,大家可以自行改动哦。
如果对你有帮助,记得点赞哦