React Native学习之RN与原生通信

React Native与原生通信,本文适配Android原生与RN的混合开发

实例一:页面跳转

RN触发Android原生的方法:(启动一个新的原生界面Activity)界面切换

开启意图一定要添加语句:

intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);

MyNativeModule.java

public class MyNativeModule extends ReactContextBaseJavaModule {

    private ReactApplicationContext context;

    public MyNativeModule(ReactApplicationContext reactContext) {
        super(reactContext);
        context = reactContext;
    }

    @Override
    public String getName() {
        //一定要返回一个名字,在RN代码里面需要使用这个名字来调用该类的方法
        return "MyNativeModule";
    }

    //函数不能有返回值,因为被调用的原生代码是异步的,原生代码执行结束之后只能通过回调函数或者发送消息给RN
    //必须声明ReactMethod注解
    @ReactMethod
    public void rnCallNative(String msg) {
        Toast.makeText(context, msg, Toast.LENGTH_LONG).show();

        Intent intent = new Intent(context, MyActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //必须添加这条语句,否则报错
        context.startActivity(intent);
    }
}

MyActivity.java

public class MyActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_my);
    }

    public void onBack(View v) {
        finish();
    }
}

activity_my.xml

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    xmlns:android="http://schemas.android.com/apk/res/android"
    >
    <Button
        android:text="原生按钮 - 点击退出"
        android:onClick="onBack"
        android:layout_centerInParent="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:background="#3BC1FF"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:textColor="#FFFFFF"
        />
</RelativeLayout>

index.android.js

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';
import {
    AppRegistry,
    StyleSheet,
    Text,
    View,
    NativeModules
} from 'react-native';

class RNAPP extends Component {
    render() {
        return (
            <View style={styles.container}>
                <Text style={styles.welcome}>
                    Welcome to React Native!
                </Text>
                <Text style={styles.welcome}>
                    React Native混合原生开发
                </Text>
                <Text style={styles.instructions}>
                    Powered by rnapp.cc
                </Text>
                <Text onPress={this.callNative.bind(this)} style={styles.btn}>页面跳转</Text>
            </View>
        );
    }

    callNative() {
        NativeModules.MyNativeModule.rnCallNative('React Native 调用原生模块的方法\nPowered by RNAPP.CC');
    }
}

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,
    },
    btn: {
        marginTop: 50,
        marginLeft: 10,
        marginRight: 10,
        width: 200,
        height: 35,
        backgroundColor: '#3BC1FF',
        color: '#fff',
        lineHeight: 24,
        fontWeight: 'bold',
        textAlign: 'center',
        textAlignVertical:'center'
    },
});

AppRegistry.registerComponent('RNAPP', () => RNAPP);

实例二:数据传递

实例:RN调用原生的界面,用户操作原生界面后将结果发送给RN侧(从RN侧启动Android原生的选择联系人界面,用户选择后,将其电话号码发送给RN侧)

注意在 AndroidManifest.xml 中添加读写通讯录权限

<uses-permission android:name="android.permission.READ_CONTACTS"/>
<uses-permission android:name="android.permission.WRITE_CONTACTS"/>
  • 启动选择联系人
Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
Bundle bundle = new Bundle();
context.startActivityForResult(intent, REQUEST_CODE, bundle);
  • 处理选择联系人后的结果(onActivityResult)
public void onActivityResult(int requestCode, int resultCode, Intent data) {
    super.onActivityResult(requestCode, resultCode, data);
    if(requestCode!= MyNativeModule.REQUEST_CODE || resultCode!=RESULT_OK)
        return;

    Uri contactUri = data.getData();
    Cursor cursor = managedQuery(contactUri, null, null, null, null);
    cursor.moveToFirst();
    String phoneNumber = getContactPhone(cursor);

    //将phoneNumber发送给RN侧,需要调用MyNativeModule对象里面的方法
    MainApplication.getMyReactPackage().getMyNativeModule().sendMsgToRn(phoneNumber);
}
  • 发送消息给RN侧
public void sendMsgToRn(String msg) {
    //将消息msg发送给RN侧
    context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("AndroidToRNMessage", msg);
}

源码:

MyNativeModule.java

public class MyNativeModule extends ReactContextBaseJavaModule {

    private ReactApplicationContext context;
    public static final int REQUEST_CODE = 1;

    public MyNativeModule(ReactApplicationContext reactContext) {
        super(reactContext);
        context = reactContext;
    }

    @Override
    public String getName() {
        //一定要返回一个名字,在RN代码里面需要使用这个名字来调用该类的方法
        return "MyNativeModule";
    }

    //函数不能有返回值,因为被调用的原生代码是异步的,原生代码执行结束之后只能通过回调函数或者发送消息给RN
    //必须声明ReactMethod注解
    @ReactMethod
    public void showNativeMsg(String msg) {
        Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
    }

    //必须声明ReactMethod注解
    @ReactMethod
    public void newNativePage() {
        /*
        Intent intent = new Intent(context, MyActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //必须添加这条语句,否则报错
        context.startActivity(intent);
        */
        Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
        Bundle bundle = new Bundle();
        context.startActivityForResult(intent, REQUEST_CODE, bundle);
    }

    public void sendMsgToRn(String msg) {
        //将消息msg发送给RN侧
        context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("AndroidToRNMessage", msg);
    }
}

MainActivity.java

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 "AwesomeProject";
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        if(requestCode!= MyNativeModule.REQUEST_CODE || resultCode!=RESULT_OK)
            return;

        Uri contactUri = data.getData();
        Cursor cursor = managedQuery(contactUri, null, null, null, null);
        cursor.moveToFirst();
        String phoneNumber = getContactPhone(cursor);

        //将phoneNumber发送给RN侧,需要调用MyNativeModule对象里面的方法
        MainApplication.getMyReactPackage().getMyNativeModule().sendMsgToRn(phoneNumber);
    }

    private String getContactPhone(Cursor cursor) {
        int phoneColumn = cursor.getColumnIndex(ContactsContract.Contacts.HAS_PHONE_NUMBER);
        int phoneNum = cursor.getInt(phoneColumn);
        String result = "";
        if (phoneNum > 0) {
            // 获得联系人的ID号
            int idColumn = cursor.getColumnIndex(ContactsContract.Contacts._ID);
            String contactId = cursor.getString(idColumn);
            // 获得联系人电话的cursor
            Cursor phone = getContentResolver().query(
                    ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null,
                    ContactsContract.CommonDataKinds.Phone.CONTACT_ID  + "=" + contactId, null, null);
            if (phone.moveToFirst()) {
                for (; !phone.isAfterLast(); phone.moveToNext()) {
                    int index = phone.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER);
                    int typeindex = phone.getColumnIndex(ContactsContract.CommonDataKinds.Phone.TYPE);
                    int phoneType = phone.getInt(typeindex);
                    String phoneNumber = phone.getString(index);
                    result = phoneNumber;
                    /*
                    switch (phoneType) {
                    case 2:
                        result = phoneNumber;
                        break;
                    default:
                         break;
                    }
                    */
                }
                if (!phone.isClosed()) {
                    phone.close();
                }
            }
        }
        return result;
    }
}

MainApplication.java

public class MainApplication extends Application implements ReactApplication {

    private static final MyReactPackage myReactPackage = new MyReactPackage();

    public static MyReactPackage getMyReactPackage() {
        return myReactPackage;
    }

    private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
        @Override
        protected boolean getUseDeveloperSupport() {
            return BuildConfig.DEBUG;
        }

        @Override
        protected List<ReactPackage> getPackages() {
            return Arrays.<ReactPackage>asList(
                new MainReactPackage(), myReactPackage
            );
        }
    };

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

MyReactPackage.java

public class MyReactPackage implements ReactPackage {

    private MyNativeModule myNativeModule;

    public MyNativeModule getMyNativeModule() {
        return myNativeModule;
    }

    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();
        myNativeModule = new MyNativeModule(reactContext);
        modules.add(myNativeModule);
        return modules;
    }

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

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

index.android.js

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';
import {
    AppRegistry,
    StyleSheet,
    Text,
    View,
    NativeModules,
    DeviceEventEmitter
} from 'react-native';

class RNAPP extends Component {
    componentWillMount(){
        DeviceEventEmitter.addListener('AndroidToRNMessage', this.handleAndroidMessage);
    }

    componentWillunMount() {
        DeviceEventEmitter.remove('AndroidToRNMessage', this.handleAndroidMessage);
    }

    handleAndroidMessage = (msg) => {
        console.log(msg);
        NativeModules.MyNativeModule.showNativeMsg('手机号码是:' + msg + '\nPowered by RNAPP.CC');
    }

    render() {
        return (
            <View style={styles.container}>
                <Text style={styles.welcome}>
                    Welcome to React Native!
                </Text>
                <Text style={styles.welcome}>
                    React Native混合原生开发
                </Text>
                <Text style={styles.instructions}>
                    Powered by rnapp.cc
                </Text>
                <Text onPress={this.newNativePage.bind(this)} style={styles.btn}>调用通讯录</Text>
            </View>
        );
    }

    newNativePage() {
        NativeModules.MyNativeModule.newNativePage();
    }
}

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,
    },
    btn: {
        marginTop: 50,
        marginLeft: 10,
        marginRight: 10,
        width: 200,
        height: 35,
        backgroundColor: '#3BC1FF',
        color: '#fff',
        lineHeight: 24,
        fontWeight: 'bold',
        textAlign: 'center',
        textAlignVertical:'center'
    },
});

AppRegistry.registerComponent('RNAPP', () => RNAPP);

React Native混合原生开发之RN与原生通信扩展知识点

回调函数

import com.facebook.react.bridge.Callback;

缺点:

  • 只应当调用一次,多次调用会有不可预期的结果
  • Android侧无法主动通过回调函数向RN侧发送消息

推荐使用消息机制而不是回调函数,另外在RN侧对应的回调函数不会立即执行,因为桥接机制是异步的。

@ReactMethod
public void rnCallNativeCallback(Callback errorCallback, Callback successCallback){
    try {
        successCallback.invoke(100, 100, 200, 200); //调用回调函数,返回结果
    } catch (IllegalViewOperationException e) {
        errorCallback.invoke(e.getMessage());
    }
}

Promise机制

import com.facebook.react.bridge.Promise;

如果被桥接的原生方法的最后一个参数是一个Promise对象,那么该方法会返回一个JS的Promise对象给与它对应的js方法。

@ReactMethod
public void rnCallNativePromise(String msg, Promise promise) {
    try {
        //业务逻辑处理
        Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
        String componentName = getCurrentActivity().getComponentName().toString();
        promise.resolve(componentName);
    }catch (Exception e){
        promise.reject("100", e.getMessage()); //Promise 失败
    }
}

跨语言常量

为了保持Android侧与RN侧常量的一致性,可以将Android原生代码的常量暴露给RN侧。在MyNativeModule类中复写getConstants(),在RN侧使用NativeModules.MyNativeModule.author

@Nullable
@Override
public Map<String, Object> getConstants() {
    final Map<String, Object> constants = new HashMap<>();
    constants.put("URL", "http://www.appblog.cn/");
    constants.put("port", "8081");
    constants.put("ip", "192.168.1.100");
    constants.put("author", "AppBlog.CN");
    return constants;
}
<Text style={styles.instructions}>
    URL: {NativeModules.MyNativeModule.URL}
</Text>
<Text style={styles.instructions}>
    Author: {NativeModules.MyNativeModule.author}
</Text>

多线程机制

如果原生代码模块执行需要较长的时间,应当自己启动一个线程并在线程中执行!最后通过回调方法进行获取执行结果即可。

Android中UI等待时间过长会报ANR异常。

监听ActivityResult与Android生命周期事件

implements ActivityEventListener, LifecycleEventListener
//增加监听,让当前类的onActivityResult处理返回
context.addActivityEventListener(this);
//在构造函数中注册生命周期事件的监听接口
context.addLifecycleEventListener(this);

如果我们需要监听Activity的生命周期,例如nResume、onPause等等方法,如果要实现这样的功能,那么当前模块类需要实现LifcycleEventListener接口并且在该模块类的构造函数中进行注册该接口。

ActivityEventListener 接口方法

  • void onActivityResult(int requestCode, int resultCode, Intent data)
  • void onNewIntent(Intent intent)

LifecycleEventListener 接口方法

  • void onHostResume()
  • void onHostPause()
  • void onHostDestroy()

初始启动的Activity设定

在AndroidManifest.xml文件中 带intent-filter的Activity 可以设定开始启动的activity是RN的还是原生的

源码

MyNativeModule.java

public class MyNativeModule extends ReactContextBaseJavaModule implements ActivityEventListener, LifecycleEventListener {

    private ReactApplicationContext context;
    public static final int REQUEST_CODE = 1;

    public MyNativeModule(ReactApplicationContext reactContext) {
        super(reactContext);
        context = reactContext;
        //增加监听,让当前类的onActivityResult处理返回
        context.addActivityEventListener(this);
        //在构造函数中注册生命周期事件的监听接口
        context.addLifecycleEventListener(this);
    }

    @Override
    public String getName() {
        //一定要返回一个名字,在RN代码里面需要使用这个名字来调用该类的方法
        return "MyNativeModule";
    }

    //函数不能有返回值,因为被调用的原生代码是异步的,原生代码执行结束之后只能通过回调函数或者发送消息给RN
    //必须声明ReactMethod注解
    @ReactMethod
    public void showNativeMsg(String msg) {
        Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
    }

    //必须声明ReactMethod注解
    @ReactMethod
    public void rnCallNative() {
        /*
        Intent intent = new Intent(context, MyActivity.class);
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); //必须添加这条语句,否则报错
        context.startActivity(intent);
        */
        Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
        Bundle bundle = new Bundle();
        context.startActivityForResult(intent, REQUEST_CODE, bundle);
    }

    public void sendMsgToRn(String msg) {
        //将消息msg发送给RN侧
        context.getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter.class).emit("AndroidToRNMessage", msg);
    }

    @ReactMethod
    public void rnCallNativeCallback(Callback errorCallback, Callback successCallback){
        try {
            successCallback.invoke(100, 100, 200, 200); //调用回调函数,返回结果
        } catch (IllegalViewOperationException e) {
            errorCallback.invoke(e.getMessage());
        }
    }

    @ReactMethod
    public void rnCallNativePromise(String msg, Promise promise) {
        try {
            //业务逻辑处理
            Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
            String componentName = getCurrentActivity().getComponentName().toString();
            promise.resolve(componentName);
        }catch (Exception e){
            promise.reject("100", e.getMessage()); //Promise 失败
        }
    }

    @Nullable
    @Override
    public Map<String, Object> getConstants() {
        final Map<String, Object> constants = new HashMap<>();
        constants.put("URL", "http://www.rnapp.cc/");
        constants.put("port", "8081");
        constants.put("ip", "192.168.1.100");
        constants.put("author", "RNAPP.CC");
        return constants;
    }

    @Override
    public void onActivityResult(int requestCode, int resultCode, Intent data) {
        Log.i(TAG, "requestCode=" + requestCode + ", resultCode=" + resultCode);
        if (requestCode!=REQUEST_CODE || resultCode!=RESULT_OK)
            return;
        Log.i(TAG, "在原生模块类里实现监听接口ActivityEventListener");
        sendMsgToRn("在原生模块类里实现监听接口ActivityEventListener");
    }

    @Override
    public void onNewIntent(Intent intent) {

    }

    @Override
    public void onHostResume() {
        Log.i(TAG, "onActivityResume");
    }

    @Override
    public void onHostPause() {
        Log.i(TAG, "onActivityPause");
    }

    @Override
    public void onHostDestroy() {
        Log.i(TAG, "onActivityDestroy");
    }
}

index.android.js

/**
 * Sample React Native App
 * https://github.com/facebook/react-native
 * @flow
 */

import React, { Component } from 'react';
import {
    AppRegistry,
    StyleSheet,
    Text,
    View,
    NativeModules,
    DeviceEventEmitter
} from 'react-native';

class RNAPP extends Component {
    componentWillMount(){
        DeviceEventEmitter.addListener('AndroidToRNMessage', this.handleAndroidMessage);
    }

    componentWillunMount() {
        DeviceEventEmitter.remove('AndroidToRNMessage', this.handleAndroidMessage);
    }

    handleAndroidMessage = (msg) => {
        console.log(msg);
        NativeModules.MyNativeModule.showNativeMsg('返回:' + msg + '\nPowered by RNAPP.CC');
    }

    render() {
        return (
            <View style={styles.container}>
                <Text style={styles.welcome}>
                    Welcome to React Native!
                </Text>
                <Text style={styles.welcome}>
                    React Native混合原生开发
                </Text>
                <Text style={styles.instructions}>
                    Powered by rnapp.cc
                </Text>
                <Text onPress={this.callNative.bind(this)} style={styles.btn}>调用原生函数 - 消息机制</Text>
                <Text onPress={this.callNativeCallback.bind(this)} style={styles.btn}>调用原生函数 - 回调函数</Text>
                <Text onPress={this.callNativePromise.bind(this)} style={styles.btn}>调用原生函数 - Promise</Text>
                <Text style={styles.welcome} >
                    Android原生暴露的常量
                </Text>
                <Text style={styles.instructions}>
                    URL: {NativeModules.MyNativeModule.URL}
                </Text>
                <Text style={styles.instructions}>
                    Author: {NativeModules.MyNativeModule.author}
                </Text>
            </View>
        );
    }

    callNative() {
        NativeModules.MyNativeModule.rnCallNative();
    }

    callNativeCallback = () => {
        NativeModules.MyNativeModule.rnCallNativeCallback(
            (msg) => {
                console.log(msg);
            },
            (x, y, width, height) => {
                console.log('坐标: (' + x + ',' + y + '), 宽: ' + width + ', 高: ' + height);
                NativeModules.MyNativeModule.showNativeMsg('坐标: (' + x + ',' + y + '), 宽: ' + width + ', 高: ' + height);
            }
        );
    }

    callNativePromise = () => {
        NativeModules.MyNativeModule.rnCallNativePromise('React Native Promise机制调用原生模块的方法').then(
            (msg) => {
                console.log('Promise成功:' + msg);
            }
        ).catch(
            (err) => {
                console.log(err);
            }
        );
    }
}

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,
    },
    btn: {
        marginTop: 20,
        marginLeft: 10,
        marginRight: 10,
        width: 260,
        height: 35,
        backgroundColor: '#3BC1FF',
        color: '#fff',
        lineHeight: 24,
        fontWeight: 'bold',
        textAlign: 'center',
        textAlignVertical:'center'
    },
});

AppRegistry.registerComponent('RNAPP', () => RNAPP);

版权声明:
作者:Joe.Ye
链接:https://www.appblog.cn/index.php/2023/02/25/react-native-learning-rn-and-native-communication/
来源:APP全栈技术分享
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
打赏
海报
React Native学习之RN与原生通信
React Native与原生通信,本文适配Android原生与RN的混合开发 实例一:页面跳转 RN触发Android原生的方法:(启动一个新的原生界面Activity)界面切换 开启意……
<<上一篇
下一篇>>
文章目录
关闭
目 录