ReactNative开发——RN与android Native交互初探
环境
window10,reactnative 0.44版
RN调用android方法
1、导入NativeModules
组件
import {NativeModules} from 'react-native';
2、在android中创建一个类继承自ReactContextBaseJavaModule
,并定义一个方法供RN调用。
比如:
public class ExampleInterface extends ReactContextBaseJavaModule {
public ExampleInterface(ReactApplicationContext reactContext) {
super(reactContext);
}
@Override
public String getName() {
return "ExampleInterface";
}
/**
* 这里是原生代码处理消息的函数。
* <p>
* 回调参数的对应关系,java -> js
* Boolean -> Bool
* Integer -> Number
* Double -> Number
* Float -> Number
* String -> String
* Callback -> function
* ReadableMap -> Object
* ReadableArray -> Array
*
* @param msg RN传过来的参数
* @return void 函数不能又返回值
*/
@ReactMethod
public void handleMessage(String msg) {
Log.i("RNMessage", "receive message from RN:" + msg);
}
}
我们使用注解@ReactMethod
表名我们的方法可以被RN调用,其中参数的类型可以是一下几种:
Boolean -> Bool
Integer -> Number
Double -> Number
Float -> Number
String -> String
Callback -> function
ReadableMap -> Object
ReadableArray -> Array
3、创建一个React包管理类,将我们定义的NativeMoudle加入进去:
public class AnExampleReactPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> nativeModules = new ArrayList<>();
/*在这里加入开发的接口*/
nativeModules.add(new ExampleInterface(reactContext));
return nativeModules;
}
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
4、在MainApplication代码中注入ReactNativeHost
的地方添加定义的包管理类。
package com.project04;
import android.app.Application;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader;
import java.util.Arrays;
import java.util.List;
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 AnExampleReactPackage()
);
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
}
}
5 、使用RN调用
NativeModules.ExampleInterface.handleMessage("I press button.");
我们在js端使用这句代码,就可以调用Android代码了。
Native使用Callback/Promise回调RN
传入Callback
我们在我们的定义的NativeMoudle(ExampleInterface.java)中定义一个方法供RN调用,其中有一个参数为Callback,它对应js的function,我们使用
callback.invoke(msg);
即可以调用到js了。
示例:
@ReactMethod
public void handleCallback(String msg, Callback callback) {
Log.i(TAG, "handleCallback: msg:" + msg);
Log.i(TAG, "开始回调 js");
callback.invoke(msg);
}
Js端
NativeModules.ExampleInterface.handleCallback('i will be print', (msg) => {
console.log(msg);
});
在ReactNative开发中,即使原生代码正确的调用了回调函数,在ReactNative侧对应的回到函数也不会立即执行,因为混合开发中的桥接机制是异步的。
使用Promise
在Andorid平台混合开发中,同样支持使用原生代码实现Promise机制,当被桥接的原生代码函数的最后一个参数为Promise时,这个函数会返回一个JavaScript的Promise对象给他对应的JavaScript方法。
示例:
Native侧
@ReactMethod
public void handlePromise(String msg, Promise promise) {
try {
promise.resolve(msg);
} catch (Exception e) {
promise.reject(e);
}
}
RN侧:
/**
* 测试Promise
*/
pressPromise() {
/*NativeModule.ExampleInterface.handlePromise 返回的是一个Promise对象*/
NativeModules.ExampleInterface.handlePromise('Promise').then((msg) => {
console.log(msg)
})
.catch((error) => {
console.log(error)
});
}
发送事件到JavaScript
原生代码在没有被调用的情况往JavaScript发送事件通知,最简单的办法就是通过RCTDeviceEventEmitter
,这可以通过ReactContext来获取对应的引用。
Native侧:
/**
* 向RN发送消息
*
* @param msg
*/
private void sendMsgToRN(String msg) {
mContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("AndroidToRNMessage", msg);
}
RN 测:
import {DeviceEventEmitter} from 'react-native';
componentWillMount() {
DeviceEventEmitter.addListener('AndroidToRNMessage', this.handleAndroidMessage.bind(this));
}
跨语言常量
在Native端,通过重写我们自定义的NativeMoudle中的 getConstant方法,可以将Andorid原生代码中定义的常量提供给React Native侧。
Native侧:
@Nullable
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
constants.put("AA", "我是一个常量,我来自Native");
return constants;
}
RN侧:
NativeModules.ExampleInterface.AA
Native层Activity相关的回调
在我们自定义的NativeMoulde(ExampleInterface.java)中,在构造方法中得到ReactApplicationContext
之后可以添加2个回调函数。
1.添加Activity声明周期回调reactContext.addLifecycleEventListener
LifecycleEventListener的定义如下:
*/
public interface LifecycleEventListener {
/**
* Called either when the host activity receives a resume event (e.g. {@link Activity#onResume} or
* if the native module that implements this is initialized while the host activity is already
* resumed. Always called for the most current activity.
*/
void onHostResume();
/**
* Called when host activity receives pause event (e.g. {@link Activity#onPause}. Always called
* for the most current activity.
*/
void onHostPause();
/**
* Called when host activity receives destroy event (e.g. {@link Activity#onDestroy}. Only called
* for the last React activity to be destroyed.
*/
void onHostDestroy();
}
2.添加ActivityEvent回调 reactContext.addActivityEventListener
ActivityEventListener
的定义如下:
public interface ActivityEventListener {
/**
* Called when host (activity/service) receives an {@link Activity#onActivityResult} call.
*/
void onActivityResult(Activity activity, int requestCode, int resultCode, Intent data);
/**
* Called when a new intent is passed to the activity
*/
void onNewIntent(Intent intent);
}
实战
实现功能
1.在RN界面有一个按钮点击之,跳转到原生选取联系人界面
2.选择完联系人之后,会回到Native的onActivityResult
方法
3.我们从返回的方法中得到联系人信息,将他发送到ReactNative测。
使用JavaScript调用Native方法,并在Native方法中启动选择联系人界面
RN侧
/**
* 调用Native页面选择联系人
*/
pressSelectContract() {
console.log('pressSelectContract');
// 调用Native页面
NativeModules.ExampleInterface.handleMessage("I press button.");
}
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>
这是一个常量:{NativeModules.ExampleInterface.AA}
</Text>
<Text style={styles.welcome}>
{this.state.msg}
</Text>
<Button
title="点击选择联系人"
onPress={this.pressSelectContract}
accessibilityLabel='这个是这个button的Label'
/>
< Button
title="测试Callback"
onPress={this.pressCallback}/>
< Button
title="测试Promise"
onPress={this.pressPromise}/>
</View >
);
}
Native侧
@ReactMethod
public void handleMessage(String msg) {
Log.i("RNMessage", "receive message from RN:" + msg);
/*调用联系人页面*/
Intent intent = new Intent();
intent.setAction(Intent.ACTION_PICK);
intent.setType(ContactsContract.Contacts.CONTENT_TYPE);
this.mContext.startActivityForResult(intent, REQUEST_CONTACTS_CODE, new Bundle());
}
在回调中获取联系人信息,并发送给RN
private void setupActivityResultListener(ReactApplicationContext reactContext) {
reactContext.addActivityEventListener(new BaseActivityEventListener() {
@Override
public void onActivityResult(Activity activity, int requestCode,
int resultCode, Intent data) {
if (requestCode != REQUEST_CONTACTS_CODE || resultCode != Activity.RESULT_OK) {
return;
}
/*如果选取ok,开始读取联系人信息*/
String msg = pareContactMsg(data.getData());
/*发送给RN*/
sendMsgToRN(msg);
}
});
}
/**
* 向RN发送消息
*
* @param msg
*/
private void sendMsgToRN(String msg) {
mContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("AndroidToRNMessage", msg);
}
/**
* 从返回的uri中查询出联系人信息。
*
* @param uri
* @return
*/
private String pareContactMsg(Uri uri) {
Cursor cursor = null;
Cursor phoneNumberCursor = null;
String msg = "";
try {
cursor = mContext.getContentResolver().query(uri, null, null, null, null);
if (null != cursor && cursor.moveToFirst()) {
long id = cursor.getLong(cursor.getColumnIndex(ContactsContract.Contacts._ID));
String name = cursor.
getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
int hasPhoneNumber = cursor
.getInt(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER));
String phoneNumber = "";
if (hasPhoneNumber == 1) {
phoneNumberCursor = mContext.getContentResolver().query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract
.CommonDataKinds.Phone.CONTACT_ID + " = ?",
new String[]{String.valueOf(id)}, null);
if (phoneNumberCursor != null && phoneNumberCursor.moveToFirst()) {
phoneNumber = phoneNumberCursor.getString(
phoneNumberCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)
);
}
}
msg = "{姓名: " + name + ", 电话号码:" + phoneNumber + "}";
}
} finally {
if (null != phoneNumberCursor) {
phoneNumberCursor.close();
}
if (null != cursor) {
cursor.close();
}
return msg;
}
}
ReactNative侧接受到联系人信息之后,使用setState重新渲染界面
handleAndroidMessage(androidMeg) {
this.setState({msg: androidMeg});
}
componentWillMount() {
DeviceEventEmitter.addListener('AndroidToRNMessage', this.handleAndroidMessage.bind(this));
}
完整代码
ReactNative:
index.adroid.js
/**
* Sample React Native App
* https://github.com/facebook/react-native
* @flow
*/
import React, {Component} from 'react';
import {
AppRegistry,
StyleSheet,
Text,
View,
Button,
NativeModules,
DeviceEventEmitter,
} from 'react-native';
/**
* 获取一个联系人的信息
*/
export default class Project04 extends Component {
state = {
/*初始化msg*/
msg: '暂时无信息',
}
/**
* 调用Native页面选择联系人
*/
pressSelectContract() {
console.log('pressSelectContract');
// 调用Native页面
NativeModules.ExampleInterface.handleMessage("I press button.");
}
/**
* 测试Callback
*/
pressCallback() {
NativeModules.ExampleInterface.handleCallback('i will be print', (msg) => {
console.log(msg);
});
}
/**
* 测试Promise
*/
pressPromise() {
/*NativeModule.ExampleInterface.handlePromise 返回的是一个Promise对象*/
NativeModules.ExampleInterface.handlePromise('Promise').then((msg) => {
console.log(msg)
})
.catch((error) => {
console.log(error)
});
}
handleAndroidMessage(androidMeg) {
this.setState({msg: androidMeg});
}
componentWillMount() {
DeviceEventEmitter.addListener('AndroidToRNMessage', this.handleAndroidMessage.bind(this));
}
componentWillUnmount() {
DeviceEventEmitter.removeAllListeners();
}
render() {
return (
<View style={styles.container}>
<Text style={styles.welcome}>
这是一个常量:{NativeModules.ExampleInterface.AA}
</Text>
<Text style={styles.welcome}>
{this.state.msg}
</Text>
<Button
title="点击选择联系人"
onPress={this.pressSelectContract}
accessibilityLabel='这个是这个button的Label'
/>
< Button
title="测试Callback"
onPress={this.pressCallback}/>
< Button
title="测试Promise"
onPress={this.pressPromise}/>
</View >
);
}
}
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('Project04', () => Project04);
Native测:
MainActivity:
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 "Project04";
}
}
MainApplication.java
package com.project04;
import android.app.Application;
import com.facebook.react.ReactApplication;
import com.facebook.react.ReactNativeHost;
import com.facebook.react.ReactPackage;
import com.facebook.react.shell.MainReactPackage;
import com.facebook.soloader.SoLoader;
import java.util.Arrays;
import java.util.List;
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 AnExampleReactPackage()
);
}
};
@Override
public ReactNativeHost getReactNativeHost() {
return mReactNativeHost;
}
@Override
public void onCreate() {
super.onCreate();
SoLoader.init(this, /* native exopackage */ false);
}
}
ExampleInterface.java
package com.project04;
import android.app.Activity;
import android.content.Intent;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract;
import android.util.Log;
import com.facebook.react.bridge.BaseActivityEventListener;
import com.facebook.react.bridge.Callback;
import com.facebook.react.bridge.LifecycleEventListener;
import com.facebook.react.bridge.Promise;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.modules.core.DeviceEventManagerModule;
import java.util.HashMap;
import java.util.Map;
import javax.annotation.Nullable;
/**
* Created by blueberry on 5/27/2017.
*/
public class ExampleInterface extends ReactContextBaseJavaModule {
private static final String TAG = "ExampleInterface";
public static final int REQUEST_CONTACTS_CODE = 100;
private ReactApplicationContext mContext;
public ExampleInterface(ReactApplicationContext reactContext) {
super(reactContext);
setupLifecycleEventListener(reactContext);
setupActivityResultListener(reactContext);
this.mContext = reactContext;
}
private void setupActivityResultListener(ReactApplicationContext reactContext) {
reactContext.addActivityEventListener(new BaseActivityEventListener() {
@Override
public void onActivityResult(Activity activity, int requestCode,
int resultCode, Intent data) {
if (requestCode != REQUEST_CONTACTS_CODE || resultCode != Activity.RESULT_OK) {
return;
}
/*如果选取ok,开始读取联系人信息*/
String msg = pareContactMsg(data.getData());
/*发送给RN*/
sendMsgToRN(msg);
}
});
}
private void setupLifecycleEventListener(ReactApplicationContext reactContext) {
reactContext.addLifecycleEventListener(new LifecycleEventListener() {
@Override
public void onHostResume() {
Log.i(TAG, "onHostResume: ");
}
@Override
public void onHostPause() {
Log.i(TAG, "onHostPause: ");
}
@Override
public void onHostDestroy() {
Log.i(TAG, "onHostDestroy: ");
}
});
}
/**
* 向RN发送消息
*
* @param msg
*/
private void sendMsgToRN(String msg) {
mContext.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class)
.emit("AndroidToRNMessage", msg);
}
/**
* 从返回的uri中查询出联系人信息。
*
* @param uri
* @return
*/
private String pareContactMsg(Uri uri) {
Cursor cursor = null;
Cursor phoneNumberCursor = null;
String msg = "";
try {
cursor = mContext.getContentResolver().query(uri, null, null, null, null);
if (null != cursor && cursor.moveToFirst()) {
long id = cursor.getLong(cursor.getColumnIndex(ContactsContract.Contacts._ID));
String name = cursor.
getString(cursor.getColumnIndex(ContactsContract.Contacts.DISPLAY_NAME));
int hasPhoneNumber = cursor
.getInt(cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER));
String phoneNumber = "";
if (hasPhoneNumber == 1) {
phoneNumberCursor = mContext.getContentResolver().query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract
.CommonDataKinds.Phone.CONTACT_ID + " = ?",
new String[]{String.valueOf(id)}, null);
if (phoneNumberCursor != null && phoneNumberCursor.moveToFirst()) {
phoneNumber = phoneNumberCursor.getString(
phoneNumberCursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)
);
}
}
msg = "{姓名: " + name + ", 电话号码:" + phoneNumber + "}";
}
} finally {
if (null != phoneNumberCursor) {
phoneNumberCursor.close();
}
if (null != cursor) {
cursor.close();
}
return msg;
}
}
@Override
public String getName() {
return "ExampleInterface1";
}
/**
* 这里是原生代码处理消息的函数。
* <p>
* 回调参数的对应关系,java -> js
* Boolean -> Bool
* Integer -> Number
* Double -> Number
* Float -> Number
* String -> String
* Callback -> function
* ReadableMap -> Object
* ReadableArray -> Array
*
* @param msg RN传过来的参数
* @return void 函数不能又返回值
*/
@ReactMethod
public void handleMessage(String msg) {
Log.i("RNMessage", "receive message from RN:" + msg);
/*调用联系人页面*/
Intent intent = new Intent();
intent.setAction(Intent.ACTION_PICK);
intent.setType(ContactsContract.Contacts.CONTENT_TYPE);
this.mContext.startActivityForResult(intent, REQUEST_CONTACTS_CODE, new Bundle());
}
@ReactMethod
public void handleCallback(String msg, Callback callback) {
Log.i(TAG, "handleCallback: msg:" + msg);
Log.i(TAG, "开始回调 js");
callback.invoke(msg);
}
@ReactMethod
public void handlePromise(String msg, Promise promise) {
try {
promise.resolve(msg);
} catch (Exception e) {
promise.reject(e);
}
}
/**
* 返回常量
*
* @return 常量字典
*/
@Nullable
@Override
public Map<String, Object> getConstants() {
final Map<String, Object> constants = new HashMap<>();
constants.put("AA", "我是一个常量,我来自Native");
return constants;
}
}
AnExampleReactPackage.java
package com.project04;
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 blueberry on 5/27/2017.
* React包管理类
*/
public class AnExampleReactPackage implements ReactPackage {
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
List<NativeModule> nativeModules = new ArrayList<>();
/*在这里加入开发的接口*/
nativeModules.add(new ExampleInterface(reactContext));
return nativeModules;
}
@Override
public List<Class<? extends JavaScriptModule>> createJSModules() {
return Collections.emptyList();
}
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
return Collections.emptyList();
}
}
遇到的坑
- 我同时使用android Studio 和WebStrom开发的时候,如果用了AndroidStuiod运行运行了项目,以后在WebStrom运行的时候会报错说:无法删除 app/build目录下的文件,解决办法,手动删除那个文件夹就好了。原因可能是 AndoridStuio创建的build目录,使用WebStrom无权限删除那个目录导致的。
参考
ReacNative 中文文档 http://reactnative.cn/docs/0.44/native-modules-android.html#content
ReactNative 英文文档 https://facebook.github.io/react-native/docs/native-modules-android.html