Kahn's blogs

RN环境 Android原生和JS交互

2017/09/15

背景

本人作为Android开发,以Android开发的角度记录一下集成RN,及和RN开发同事的配合需要做的事情。主要就是互相交互的问题。其实官方文档上已经说的很清楚啦,但是不够口语化,自己再叙述一边,加深一下印象。

JS调用原生的方法

官方举了一个JS调用原生类Toast的例子,在这里就照搬咯。在RN的世界,一般都是以Module为单位的。所以原生想提供一套具有关联性的方法,就应该把它归集成一个Module提供给JS。

编写Module类

1
public class ToastModule extends ReactContextBaseJavaModule

新建一个类,集成RN的类ReactContextBaseJavaModule,那么这个类就是一个可以为JS提供方法的Module啦。

1
2
3
4
@ReactMethod
public void show(String message, int duration) {
Toast.makeText(getReactApplicationContext(), message, duration).show();
}

在一个方法上加一个@ReactMethod注解,那么这个就是一个提供给JS调用的方法。JS那边调用的参数类型对应为:

下面的参数类型在@ReactMethod注明的方法中,会被直接映射到它们对应的JavaScript类型。

参数类型

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

再给这个Module起一个名字,复写ReactContextBaseJavaModule的getName方法,返回一个名字。JS里面就用这个名字调!

1
2
3
4
Override
public String getName() {
return "ToastExample";
}

到这儿Module就写完了,怎么提供给JS呢?

注册Module

原生模块注册Module给JS调用是以package为单位的。一组Module是一个package。这个package是什么呢?以什么类型存在呢?

React-Native提供了一个ReactPackage接口,接口里有个名为createNativeModules的方法,返回值为List,NativeModule是一个接口。我们写的Module类集成自ReactContextBaseJavaModule类,ReactContextBaseJavaModule又集成自BaseJavaModule,BaseJavaModule实现了NativeModule接口。很明显了,我们只要实现ReactPackage接口的createNativeModules,把我们写好的Module类返回,就OK了!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public class TestUtilsPackage 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写好了,完事具备,只欠注册。
还记得ReactInstanceManager吗?上篇blog里面有。

每个RN的界面(其实就是个view),有配有一个ReactInstanceManager。ReactInstanceManager生成的时候,就可以给它加上Package了。我们生成ReactInstanceManager的时候,使用的是建造者模式,ReactInstanceManager.builder().addPackage(new TestUtilsPackage())完成注册,后面JS就可以调用咯。

回调函数

官方文档上说,@ReactMethod注解的方法必须是一个void类型的。那么我们怎么把返回值反给JS呢?RN提供两个种回调方式

第一种:Callback

在@ReactMethod注解的方法上,参数列表末尾添加两个参数,Callback errorCallback,Callback successCallback,注意,这个Callback是com.facebook.react.bridge.Callback。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
package com.facebook.react.bridge;
/**
* Interface that represent javascript callback function which can be passed to the native module
* as a method parameter.
*/
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);
}

在@ReactMethod注解的方法里面回调callbabck的invoke方法就可以了。invoke是个可变参,参数类型参见上面的参数类型介绍。

第二种:Promise

该接口为com.facebook.react.bridge.Promise,把它作为最后一个参数,就可以使用了。

注意

  • 不管是Callback,还是Promises,都只能调用一次,千万不要把它缓存起来多次调用!!!
  • 夸语言的调用方式都是异步的。不管是Callback,还是Promises回调的时候,都是往消息循环中发送一条消息。

给JS发事件

1
2
3
4
5
6
7
8
9
10
11
12
...
private void sendEvent(ReactContext reactContext,
String eventName,
@Nullable WritableMap params) {
reactContext
.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit(eventName, params);
}
...
WritableMap params = Arguments.createMap();
...
sendEvent(reactContext, "keyboardWillShow", params);

ReactContext对象可以从ReactInstanceManager中获取。

总结

原生和JS的互动,基本手段差不多就这些了。还有一个什么从startActivityForResult获取结果。感觉现在很少有人用吧?基本调用都很简单,但是要封装成能用的类库,供各个业务通用使用,还是需要有一些封装手段的。第一篇讲了原生集成RN环境的基本开发环境搭建,本篇说的是互相调用。计划再弄一篇,原生集成RN,原生端的代码需要怎么开发,还有基本原理。