有时候 App 需要访问平台 API,但 React Native 可能还没有相应的模块包装;或者你需要复用一些 Java 代码,而不是用 Javascript 重新实现一遍;又或者你需要实现某些高性能的、多线程的代码,譬如图片处理、数据库、或者各种高级扩展等等。
我们把 React Native 设计为可以在其基础上编写真正的原生代码,并且可以访问平台所有的能力。这是一个相对高级的特性,我们并不认为它应当在日常开发的过程中经常出现,但具备这样的能力是很重要的。如果 React Native 还不支持某个你需要的原生特性,你应当可以自己实现该特性的封装
Toast 模块
本向导会用Toast作为例子。假设我们希望可以从 Javascript 发起一个 Toast 消息(一种会在屏幕下方弹出、保持一段时间的消息通知)。
我们首先来创建一个原生模块。一个原生模块是一个继承了
ReactContextBaseJavaModule
的 Java 类,它可以实现一些 JavaScript 所需的功能。我们这里的目标是可以在 JavaScript 里写ToastExample.show('Awesome', ToastExample.SHORT);
,来调起一个短暂的 Toast 通知。创建一个新的 Java 类并命名为
ToastModule.java
,放置到android/app/src/main/java/com/your-app-name/
目录下,其具体代码如下:// ToastModule.java package com.your-app-name; 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 ToastModule extends ReactContextBaseJavaModule { private static ReactApplicationContext reactContext; private static final String DURATION_SHORT_KEY = "SHORT"; private static final String DURATION_LONG_KEY = "LONG"; public ToastModule(ReactApplicationContext reactContext) { super(reactContext); reactContext = context; } }
ReactContextBaseJavaModule
要求派生类实现getName
方法。这个函数用于返回一个字符串名字,这个名字在 JavaScript 端标记这个模块。这里我们把这个模块叫做ToastExample
,这样就可以在 JavaScript 中通过NativeModules.ToastExample
访问到这个模块。译注:RN 已经内置了一个名为 ToastAndroid 的模块,所以在练习时请勿使用 ToastAndroid 的名字,否则运行时会报错名字冲突!@Override public String getName() { return "ToastExample"; }
一个可选的方法
getContants
返回了需要导出给 JavaScript 使用的常量。它并不一定需要实现,但在定义一些可以被 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; }
要导出一个方法给 JavaScript 使用,Java 方法需要使用注解
@ReactMethod
。方法的返回类型必须为void
。React Native 的跨语言访问是异步进行的,所以想要给 JavaScript 返回一个值的唯一办法是使用回调函数或者发送事件(参见下文的描述)。@ReactMethod public void show(String message, int duration) { Toast.makeText(getReactApplicationContext(), message, duration).show(); }
参数类型
下面的参数类型在
@ReactMethod
注明的方法中,会被直接映射到它们对应的 JavaScript 类型。Boolean -> Bool Integer -> Number Double -> Number Float -> Number String -> String Callback -> function ReadableMap -> Object ReadableArray -> Array
例如:上面show方法中的参数( String message, int duration) 在javascript中就会转变成String和Number类型
注册模块
在 Java 这边要做的最后一件事就是注册这个模块。我们需要在应用的 Package 类的
createNativeModules
方法中添加这个模块。如果模块没有被注册,它也无法在 JavaScript 中被访问到。创建一个新的 Java 类并命名为
CustomToastPackage.java
,放置到android/app/src/main/java/com/your-app-name/
目录下,其具体代码如下:// CustomToastPackage.java package com.your-app-name; import com.facebook.react.ReactPackage; 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; public class CustomToastPackage implements ReactPackage { @Override public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) { return Collections.emptyList(); } @Override public List<NativeModule> createNativeModules( ReactApplicationContext reactContext) { List<NativeModule> modules = new ArrayList<>(); modules.add(new ToastModule(reactContext)); return modules; } }
这个 package 需要在
MainApplication.java
文件的getPackages
方法中提供。这个文件位于你的 react-native 应用文件夹的 android 目录中。具体路径是:android/app/src/main/java/com/your-app-name/MainApplication.java
.// MainApplication.java ... import com.your-app-name.CustomToastPackage; // <-- 引入你自己的包 ... protected List<ReactPackage> getPackages() { @SuppressWarnings("UnnecessaryLocalVariable") List<ReactPackage> packages = new PackageList(this).getPackages(); // Packages that cannot be autolinked yet can be added manually here, for example: // packages.add(new MyReactNativePackage()); packages.add(new CustomToastPackage()); // <-- 添加这一行,类名替换成你的Package类的名字 name. return packages; }
为了让你的功能从 JavaScript 端访问起来更为方便,通常我们都会把原生模块封装成一个 JavaScript 模块。这不是必须的,但省下了每次都从
NativeModules
中获取对应模块的步骤。这个 JS 文件也可以用于添加一些其他 JavaScript 端实现的功能。// ToastExample.js /** * This exposes the native ToastExample module as a JS module. This has a * function 'show' which takes the following parameters: * * 1. String message: A string with the text to toast * 2. int duration: The duration of the toast. May be ToastExample.SHORT or * ToastExample.LONG */ import { NativeModules } from 'react-native'; // 下一句中的ToastExample即对应上文 // public String getName()中返回的字符串 export default NativeModules.ToastExample;
现在,在别处的 JavaScript 代码中可以这样调用你的方法:
import ToastExample from './ToastExample'; ToastExample.show('Awesome', ToastExample.SHORT);
上面是官网教程,很详细了,,合进工程后会报错
TypeError: null is not an object (evaluating '_ToastExample.default.show') //如果是这个错,关掉再装一次.
改前:
public ToastModule(ReactApplicationContext reactContext) {
super(reactContext);
reactContext = context;
}改后:
public ToastModule(ReactApplicationContext context) {
super(context);
reactContext = context;
}
至此正常!
最后贴上文件内部
ToastModule.java:
// ToastModule.java
package com.awesomeproject;
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 ToastModule extends ReactContextBaseJavaModule {
private static ReactApplicationContext reactContext;
private static final String DURATION_SHORT_KEY = "SHORT";
private static final String DURATION_LONG_KEY = "LONG";
public ToastModule(ReactApplicationContext context) {
super(context);
reactContext = context;
}
//JavaScript 中通过NativeModules.ToastExample访问到这个模块
@Override
public String getName() {
return "ToastExample";
}
//一个可选的方法getContants返回了需要导出给 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;
}
@ReactMethod
public void show(String message, int duration) {
Toast.makeText(getReactApplicationContext(), message, duration).show();
}
}
MainApplication.java:
package com.awesomeproject;
import android.app.Application;
import android.content.Context;
import com.facebook.react.PackageList;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactInstanceManager;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.soloader.SoLoader;
import java.lang.reflect.InvocationTargetException;
import java.util.List;
import com.awesomeproject.CustomToastPackage; // <-- 引入你自己的包
public class MainApplication extends Application implements ReactApplication {
private final ReactNativeHost mReactNativeHost =
new ReactNativeHost(this) {
@Override
public boolean getUseDeveloperSupport() {
return BuildConfig.DEBUG;
}
@Override
protected List<ReactPackage> getPackages() {
@SuppressWarnings("UnnecessaryLocalVariable")
List<ReactPackage> packages = new PackageList(this).getPackages();
// Packages that cannot be autolinked yet can be added manually here, for example:
// packages.add(new MyReactNativePackage());
packages.add(new CustomToastPackage()); // <-- 添加这一行,类名替换成你的Package类的名字 name.
return packages;
}
@Override
protected String getJSMainModuleName() {
return "index";
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
}
/**
* Loads Flipper in React Native templates. Call this in the onCreate method with something like
* initializeFlipper(this, getReactNativeHost().getReactInstanceManager());
*
* @param context
* @param reactInstanceManager
*/
private static void initializeFlipper(
Context context, ReactInstanceManager reactInstanceManager) {
if (BuildConfig.DEBUG) {
try {
/*
We use reflection here to pick up the class that initializes Flipper,
since Flipper library is not available in release mode
*/
Class<?> aClass = Class.forName("com.awesomeproject.ReactNativeFlipper");
aClass
.getMethod("initializeFlipper", Context.class, ReactInstanceManager.class)
.invoke(null, context, reactInstanceManager);
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
}
}
}
CustomToastPackage.java:
// CustomToastPackage.java
package com.awesomeproject;
import com.facebook.react.ReactPackage;
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;
public class CustomToastPackage implements ReactPackage {
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
@Override
public List<NativeModule> createNativeModules(
ReactApplicationContext reactContext) {
List<NativeModule> modules = new ArrayList<>();
modules.add(new ToastModule(reactContext));
return modules;
}
}
ToastExample.js:
// ToastExample.js
/**
* This exposes the native ToastExample module as a JS module. This has a
* function 'show' which takes the following parameters:
*
* 1. String message: A string with the text to toast
* 2. int duration: The duration of the toast. May be ToastExample.SHORT or
* ToastExample.LONG
*/
import { NativeModules } from 'react-native';
// 下一句中的ToastExample即对应上文
// public String getName()中返回的字符串
export default NativeModules.ToastExample;
App.js:
import React,{useState} from 'react'; //导入React
import { Text, View, TextInput, Image,Button,TouchableOpacity } from 'react-native'; //导入react-native里的Text组件
import ToastExample from './ToastExample';
export default App =() => {
return (
<View>
<TouchableOpacity onPress={()=>ToastExample.show('Awesome', ToastExample.SHORT)}>
<Text>显示toast</Text>
</TouchableOpacity>
</View>
);
}