有时候App需要访问API,但React Native可能还没有相应的模块包装;或者你需要复用一些Java代码,而不是用JavaScript重新实现一遍;又或者你需要实现某些够性能的、多线程的代码,譬如图片处理、数据库、或者各种高级扩展等等;
我们React Native设计为可以在其基础上编写真正代码的原生代码,并且可以访问平台的所有能力。这是一个相对高级的特性,我们并不认为它应当在日常开发过程中经常出现,但是具备这样的能力是很重要的。如果React Native还不支持某个你需要的原生特性,你应该可以自己实现该特性的封装。
一、Toast、Log模块
本向导会用Toast、Log作为例子,假设我们希望可以从JavaScript发起一个Toas消息(Andorid中的一种会在屏幕下方弹出、保持一段时间消息通知),打印Andorid的Logcat日志。
1.我们首先在项目的源码目录来创建一个原生的模块ToastModule.java,LogModule.java。一个原生的模块是一个集成了ReactCoontextBaseJavaModule的Java类,它可以实现一些JavaScript所需的功能。
TostModule1.java类
public class TostModule1 extends ReactContextBaseJavaModule{ private static final String DURATION_SHORT_KEY = "SHORT"; private static final String DURATION_LONG_KEY = "LONG"; public TostModule1(ReactApplicationContext reactContext) { super(reactContext); } //ReactContextBaseJavaModule要求派生类实现getName方法。这个函数用于返回一个字符串名字,这个名字在JavaScript端标记这个模块。这里我们把这个模块叫做Toast1,这样就可以在JavaScript中通过React.NativeModules.Toast1访问到这个模块。 @Override public String getName() { return "Toast1"; } //一个可选的方法getContants返回了需要导出给JavaScript使用的常量。它并不一定需要实现,但在定义一些可以被JavaScript同步访问到的预定义的值时非常有用。 @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; } //要导出一个方法给JavaScript使用,Java方法需要使用注解@ReactMethod。方法的返回类型必须为void。React Native的跨语言访问是异步进行的,所以想要给JavaScript返回一个值的唯一办法是使用回调函数或者发送事件。 @ReactMethod public void show(final String message,final int duration) { UiThreadUtil.runOnUiThread(new Runnable() { @Override public void run(){ Toast.makeText(getReactApplicationContext(),message,duration).show(); } }); } }LogModule.java类
public class LogModule extends ReactContextBaseJavaModule{ public LogModule(ReactApplicationContext reactContext) { super(reactContext); } @Override public String getName() { return "Log"; } @ReactMethod public void d(String tag,String msg){ Log.d(tag,msg); } }2.参数类型
3.
在Java这边要做的最后一件事就是注册这个模块。我们需要在应用的Package类的
createNativeModules
方法中添加这个模块。如果模块没有被注册,它也无法在JavaScript中被访问到。
AppReactPackage.java类
public class AppReactPackage implements ReactPackage{ @Override public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) { List<NativeModule> modules = new ArrayList<>(); modules.add(new LogModule(reactContext)); modules.add(new TostModule1(reactContext)); return modules; } @Override public List<Class<? extends JavaScriptModule>> createJSModules() { return Collections.emptyList(); } @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { return Collections.emptyList(); } }4.这个package需要在MainActivity.java文件注册提供。
MainActivity.java文件
public class MainActivity extends AppCompatActivity implements DefaultHardwareBackBtnHandler { private ReactRootView mReactRootView; private ReactInstanceManager mReactInstanceManager; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); mReactRootView = new ReactRootView(this); 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(); mReactRootView.startReactApplication(mReactInstanceManager,"MyAwesomeApp",null); setContentView(mReactRootView); } @Override public void invokeDefaultOnBackPressed() { super.onBackPressed(); } @Override protected void onPause() { super.onPause(); if (mReactInstanceManager != null) { mReactInstanceManager.onPause(); } } @Override protected void onResume() { super.onResume(); if (mReactInstanceManager != null) { mReactInstanceManager.onResume(this,this); } } @Override public void onBackPressed() { if (mReactInstanceManager != null) { mReactInstanceManager.onBackPressed(); } else { super.onBackPressed(); } } @Override public boolean onKeyUp(int keyCode,KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_MENU && mReactInstanceManager != null) { mReactInstanceManager.showDevOptionsDialog(); return true; } return super.onKeyUp(keyCode,event); } }5.为了让你的功能从JavaScript端访问起来更为方便,通常我们都会把原生模块封装成一个JavaScript模块。这不是必须的,但省下了每次都从NativeModules中获取对应模块的步骤。这个JS文件也可以用于添加一些其他JavaScript端实现的功能。在你的 应用根目录下创建ToastMoudle.js文件 。
ToastAndroid.js文件
var { NativeModules } = require('react-native'); module.exports = NativeModules.ToastAndroid;6.最后,我们这里的目标是可以在JavaScript里调用相关的API。
index.android.js文件
'use strict'; var React = require('react'); var ReactNative = require('react-native'); var ToastAndroid = require('./ToastAndroid'); var { Text,View,StyleSheet,AppRegistry,NativeModules,} = ReactNative; var Log1 = NativeModules.Log; Log1.d("Log1","LOG"); ToastAndroid.show("Tost1",ToastAndroid.SHORT); class MyAwesomeApp extends React.Component { render() { return ( <View style={styles.container}> <Text style={styles.hello}>Hello,World</Text> </View> ) } } var styles = StyleSheet.create({ container: { flex: 1,justifyContent: 'center',},hello: { fontSize: 20,textAlign: 'center',margin: 10,}); AppRegistry.registerComponent('MyAwesomeApp',() => MyAwesomeApp);7.运行App,并在LogCat中展示如下:
二、回调函数
原生模块还支持一种特殊的参数——回调函数。它提供了一个函数来把返回值传回给JavaScript。最典型的一个场景就是javascript层调用java层的网络请求方法,java层拿到网络数据后需要将结果返回给javascript层。下面我们就依次为实例,演示一下:
NetModule.java类
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(); } }2. 在前面的AppReactPackage类createNativeModules函数中注册该模块(同上忽略);
Net.js文件
'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;index.android.js文件
import React from 'react'; import { ..... ...... } from 'react-native'; var WebView=require('./RTCWebView'); var Net=require('./Net'); Net.getResult("http://baidu.com",(code,result)=>{ console.log("callback",code,result); }); ...... ..... AppRegistry.registerComponent('AwesomeProject',() => MyAwesomeApp);4.Debug运行如下,在Chrome的Develop Tools中显示如下: