今天学习了一下在react-native中调用原生安卓模块的使用,发现很多网上的文章都是直接照抄的文档,这样会有一些坑,导致最后无法运行或者成功调用,所以写下这个博客来分享,同时也记录一下学习过程,内容完整,浅显易懂,仔细看下来肯定会学会的。

学习理由:

有时候App需要访问平台API,但React Native可能还没有相应的模块包装;或者你需要复用一些Java代码,而不是用Javascript重新实现一遍;又或者你需要实现某些高性能的、多线程的代码,譬如图片处理、数据库、或者各种高级扩展等等。



我们把React Native设计为可以在其基础上编写真正的原生代码,并且可以访问平台所有的能力。这是一个相对高级的特性,我们并不认为它应当在日常开发的过程中经常出现,但具备这样的能力是很重要的。如果React Native还不支持某个你需要的原生特性,你应当可以自己实现该特性的封装。


本向导会用Toast作为例子。假设我们希望可以从Javascript发起一个Toast消息(Android中的一种会在屏幕下方弹出、保持一段时间的消息通知)

我们首先来创建一个原生模块。一个原生模块是一个继承了ReactContextBaseJavaModule的Java类,它可以实现一些JavaScript所需的功能。我们这里的目标是可以在JavaScript里写ToastAndroid.show('Awesome', ToastAndroid.SHORT);,来调起一个Toast通知。



先看一下我的目录:

ios android react native 调用c语言库 reactnative调用原生_android

1. 首先,我们创建一个java类 MyToast:

这个类一定要继承自 ReactContextBaseJavaModule ,



public class MyToast extends ReactContextBaseJavaModule {

    private static final String DURATION_SHORT_KEY = "SHORT";
    private static final String DURATION_LONG_KEY = "LONG";

    public MyToast(ReactApplicationContext reactContext) {
        super(reactContext);
    }

    /**
     * 一个可选的方法getContants返回了需要导出给JavaScript使用的常量。
     * 它并不一定需要实现,但在定义一些可以被JavaScript同步访问到的预定义的值时非常有用。
     */
    @Nullable
    @Override
    public Map<String, Object> getConstants() {
        final Map<String, Object> constants = new HashMap();
        constants.put(DURATION_LONG_KEY, Toast.LENGTH_LONG);
        constants.put(DURATION_SHORT_KEY, Toast.LENGTH_SHORT);

        return constants;
    }

    /**
     * 要导出一个方法给JavaScript使用,Java方法需要使用注解@ReactMethod。
     * 方法的返回类型必须为void。React Native的跨语言访问是异步进行的,
     * 所以想要给JavaScript返回一个值的唯一办法是使用回调函数或者发送事件
     * @param message
     * @param duration
     */
    @ReactMethod
    public void show(String message, int duration) {
        Toast.makeText(getReactApplicationContext(), message, duration).show();
    }

    /**
     * 模块名前的RCT前缀会被自动移除。
     * 所以如果返回的字符串为"RCTmytoast",在JavaScript端依然可以通过React.NativeModules.mytoast访问到这个模块。
     * @return
     */
    @Override
    public String getName() {
        return "mytoast";
    }
}



ReactContextBaseJavaModule要求派生类实现getName方法。这个函数用于返回一个字符串名字,这个名字在JavaScript端标记这个模块。这里我们把这个模块叫做mytoast,这样就可以在JavaScript中通过React.NativeModules.mytoast访问到这个模块


注意,文档上写的是ToastAndroid,但是RN已经内置了一个名为ToastAndroid的模块,所以如果你在练习时完全照抄,那么运行时会报错名字冲突!所以我在这里选择了另外一个名字:mytoast 。


@Override
  public String getName() {
    return "mytoast";
  }



参数类型

下面的参数类型在@ReactMethod注明的方法中,会被直接映射到它们对应的JavaScript类型(例如上面的show方法,它被标明了@ReactMethod,所以它的参数会映射成下面这样)。

Boolean -> Bool
Integer -> Number
Double -> Number
Float -> Number
String -> String
Callback -> function
ReadableMap -> Object
ReadableArray -> Array


上面方法中的参数( String message, int duration) 在javascript中就会转变成String和Number类型。

2.注册模块

在Java这边要做的最后一件事就是注册这个模块。我们需要在应用的Package类的createNativeModules方法中添加这个模块。如果模块没有被注册,它也无法在JavaScript中被访问到。

创建一个类,这个类要实现ReactPackage接口:


/**
 * Created by zhuoy on 2017/6/12.
 */

public class MyExampleToast implements ReactPackage {

    /**
     * 需要在应用的Package类的createNativeModules方法中添加这个模块。
     * 如果模块没有被注册,它也无法在JavaScript中被访问到。
     * @param reactContext
     * @return
     */
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {

        List<NativeModule> modules = new ArrayList<>();
        modules.add(new MyToast(reactContext));
        return modules;
    }

    @Override
    public List<Class<? extends JavaScriptModule>> createJSModules() {
        return Collections.emptyList();
    }

    @Override
    public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
        return Collections.emptyList();
    }
}



这个package需要在MainApplication.java文件的getPackages方法中提供。这个文件位于你的react-native应用文件夹的android目录中。具体路径是: android/app/src/main/java/com/your-app-name/MainApplication.java.


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() {
            return Arrays.<ReactPackage>asList(
                    new MainReactPackage(),
                    new MyExampleToast()         //我们添加的方法
            );
        }

    };

    @Override
    public ReactNativeHost getReactNativeHost() {
        return mReactNativeHost;
    }

    @Override
    public void onCreate() {
        super.onCreate();
        SoLoader.init(this, /* native exopackage */ false);
    }
}



在getPackages中添加我们刚刚写的package。


3.封装模块

为了让你的功能从JavaScript端访问起来更为方便,通常我们都会把原生模块封装成一个JavaScript模块。这不是必须的,但省下了每次都从NativeModules中获取对应模块的步骤。这个JS文件也可以用于添加一些其他JavaScript端实现的功能。

创建一个名为MyToast的js文件,注意创建文件的位置:



/**
 * Created by zhuoy on 2017/6/12.
 */
'use strict'
import {NativeModules} from 'react-native';
export default NativeModules.mytoast;



这里的mytoast就是我们之前写java文件中返回的字符串。

4.使用

ios android react native 调用c语言库 reactnative调用原生_List_02


import MyToast from './MyToast';


render() {
   
    return (
        <View>
           
            <Text onPress={() => MyToast.show('卓原',MyToast.SHORT)}>弹出Toast</Text>

        </View>
    )
}



如果不执行第三部的封装,想使用的话请这样写:

记得要先引入NativeModules:


import {
    其他需要用的组件,
    NativeModules
} from 'react-native';



<Text onPress={() => NativeModules.mytoast.show('卓原',
NativeModules.mytoast.SHORT)}>弹出Toast</Text>



可以看到,这样写代码稍显复杂,所以推荐封装一下再使用。

ios android react native 调用c语言库 reactnative调用原生_react-native_03

OK,大功告成。