React Native 原生和RN互相调用以及事件监听

Demo太简单。现给它扩展为 多 Package + 原生 / RN 互调 + EventEmitter 事件通知。

1、创建Toast原生模块,测试RN调用原生应用。代码如下:

Kotlin 复制代码
package com.example.androidapp.module

import android.widget.Toast
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod

// RN调用原生Toast的模块
class ToastModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
    // 必须重写:RN中调用该模块的名称(比如RN中用NativeModules.ToastModule)
    override fun getName(): String {
        return "ToastModule"
    }

    // ReactMethod注解:暴露给RN调用的方法(必须是void返回,参数支持基本类型/字符串)
    @ReactMethod
    fun showToast(message: String, duration: Int) {
        // 在主线程执行UI操作
        reactApplicationContext.runOnUiQueueThread {
            Toast.makeText(reactApplicationContext, message, duration).show()
        }
    }
}

2、创建EventEmitter事件模块(原生->RN通知):

Kotlin 复制代码
package com.example.androidapp.module

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

// 原生给RN发送事件通知的模块
class EventEmitterModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
    override fun getName(): String {
        return "EventEmitterModule"
    }

    // 暴露给RN的方法:启动原生定时发送事件
    @ReactMethod
    fun startSendEvent() {
        // 模拟原生每隔2秒给RN发一次消息
        Thread {
            for (i in 1..5) {
                Thread.sleep(2000)
                // 发送事件:事件名「NativeEvent」,携带参数「message」
                sendEvent("NativeEvent", mapOf("message" to "原生发送的第${i}条消息"))
            }
        }.start()
    }

    // 核心方法:发送事件到RN
    private fun sendEvent(eventName: String, params: Map<String, String>) {
        reactApplicationContext
            .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
            .emit(eventName, params)
    }
}

3、新建自定义package(注册上述俩个模块):

Kotlin 复制代码
package com.example.androidapp.rnpkg

import com.example.androidapp.module.EventEmitterModule
import com.example.androidapp.module.ToastModule
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

// 自定义Package:注册所有自定义原生模块
class CustomReactPackage : ReactPackage {
    // 注册原生模块(方法/事件类)
    override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
        val modules = ArrayList<NativeModule>()
        modules.add(ToastModule(reactContext)) // 注册Toast模块
        modules.add(EventEmitterModule(reactContext)) // 注册EventEmitter模块
        return modules
    }

    // 注册自定义原生UI组件(本次demo用不到,返回空列表)
    override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
        return emptyList()
    }
}

4、修改MainApplication(注册多个package)

Kotlin 复制代码
package com.example.androidapp // 替换为你的实际包名

import android.app.Application
import com.example.androidapp.rnpkg.CustomReactPackage
import com.facebook.react.ReactApplication
import com.facebook.react.ReactNativeHost
import com.facebook.react.ReactPackage
import com.facebook.react.defaults.DefaultReactNativeHost
import com.facebook.react.shell.MainReactPackage
import com.facebook.soloader.SoLoader
import java.util.ArrayList

class MainApplication : Application(), ReactApplication {
    override val reactNativeHost: ReactNativeHost = object : DefaultReactNativeHost(this) {
        override fun getPackages(): List<ReactPackage> {
            val packages = ArrayList<ReactPackage>()
            packages.add(MainReactPackage()) // 核心Package(必传)
            packages.add(CustomReactPackage()) // 自定义Package(新增)
            return packages
        }

        override fun getJSMainModuleName(): String {
            return "index"
        }

        override fun getUseDeveloperSupport(): Boolean {
            return true
        }

        override val isNewArchEnabled: Boolean
            get() = false
        override val isHermesEnabled: Boolean
            get() = true

        override fun getBundleAssetName(): String {
            return "index.android.bundle"
        }
    }

    override fun onCreate() {
        super.onCreate()
        SoLoader.init(this, false)
    }
}

5、修改RN代码(App.tsx),实现双向通信

javascript 复制代码
import React, { useEffect, useState } from 'react';
import { View, Text, TouchableOpacity, NativeModules, DeviceEventEmitter, StyleSheet } from 'react-native';

// 1. 获取原生模块(和原生模块的getName()对应)
const { ToastModule, EventEmitterModule } = NativeModules;

const App = () => {
  const [eventMessages, setEventMessages] = useState<string[]>([]); // 存储原生发送的事件

  // 2. RN→原生:调用原生Toast方法
  const callNativeToast = () => {
    // 调用原生ToastModule的showToast方法(参数对应原生方法)
    ToastModule.showToast("RN调用原生Toast成功! \n   吕布: \"我被酒色所伤,竟如此憔悴,从今天开始戒酒!\"", 1); // 1=Toast.LENGTH_LONG
  };

  // 3. 监听原生发送的事件(EventEmitter)
  useEffect(() => {
    // 启动原生定时发送事件
    EventEmitterModule.startSendEvent();

    // 监听原生事件:事件名「NativeEvent」(和原生sendEvent的eventName一致)
    const eventListener = DeviceEventEmitter.addListener('NativeEvent', (params) => {
      console.log("收到原生事件:", params);
      setEventMessages(prev => [...prev, params.message]);
    });

    // 组件卸载时移除监听
    return () => {
      eventListener.remove();
    };
  }, []);

  return (
    <View style={styles.container}>
      <Text style={styles.title}>RN-原生互调 Demo</Text>
      {/* RN→原生按钮 */}
      <TouchableOpacity style={styles.button} onPress={callNativeToast}>
        <Text style={styles.buttonText}>点击调用原生Toast</Text>
      </TouchableOpacity>

      {/* 显示原生发送的事件 */}
      <Text style={styles.subTitle}>原生发送的事件:</Text>
      {eventMessages.map((msg, index) => (
        <Text key={index} style={styles.message}>{msg}</Text>
      ))}
    </View>
  );
};

// 样式
const styles = StyleSheet.create({
  container: {
    flex: 1,
    justifyContent: 'center',
    alignItems: 'center',
    backgroundColor: '#f5f5f5',
    padding: 20,
  },
  title: {
    fontSize: 20,
    fontWeight: 'bold',
    marginBottom: 30,
  },
  button: {
    backgroundColor: '#2196F3',
    padding: 10,
    borderRadius: 5,
    marginBottom: 30,
  },
  buttonText: {
    color: 'white',
    fontSize: 16,
  },
  subTitle: {
    fontSize: 16,
    fontWeight: 'bold',
    alignSelf: 'flex-start',
    marginBottom: 10,
  },
  message: {
    fontSize: 14,
    color: '#666',
    alignSelf: 'flex-start',
    marginBottom: 5,
  },
});

export default App;

说明,const [eventMessages, setEventMessages] = useState<string[]>([]); 这句代码,eventMessages是一个字符串数组,setEventMessages是设置该字符串数组的函数。当通过该函数修改字符串数组,会自动触发页面刷新。userState函数如下:

6、重新打包运行

cd /d E:\android\projects\RNDemo4\RNApp\RNApp

npx react-native bundle --platform android --dev false --entry-file index.js --bundle-output android/app/src/main/assets/index.android.bundle --assets-dest android/app/src/main/res/

再把 RN 工程android/app/src/main/assets/index.android.bundle复制到安卓工程app/src/main/assets/。

重新运行原生安卓工程。 崩溃了,日志:

报错位置:

原因是参数类型错误,事件参数用了mapOf("message" to "xxx")(Java 的SingletonMap),RN 桥接无法直接识别,须用 RN 提供的WritableMap。

修改这个类的代码:

Kotlin 复制代码
package com.example.androidapp.module

import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.WritableNativeMap
import com.facebook.react.modules.core.DeviceEventManagerModule

// 原生给RN发送事件通知的模块
class EventEmitterModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
    override fun getName(): String {
        return "EventEmitterModule"
    }

    // 暴露给RN的方法:启动原生定时发送事件
    @ReactMethod
    fun startSendEvent() {
        // 模拟原生每隔2秒给RN发一次消息
        Thread {
            for (i in 1..5) {
                Thread.sleep(2000)
                // 发送事件:事件名「NativeEvent」,携带参数「message」
                val params = WritableNativeMap()
                params.putString("message", "原生发送的第${i}条消息")
                sendEvent("NativeEvent", params)
            }
        }.start()
    }

    // 核心方法:发送事件到RN
    private fun sendEvent(eventName: String, params: WritableMap) { // 参数改为WritableMap
        reactApplicationContext
            .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
            .emit(eventName, params)
    }
}

再次运行,按钮下面依次显示了5条消息:

ok. 点击按钮,显示了Toast消息:

ok. 虽然运行没问题,但是有个地方需要修复下,在子线程给RN发通知时,需要通过RN的桥接线程发送事件,而不是直接在子线程调用,再贴下EventEmitterModule代码:

Kotlin 复制代码
package com.example.androidapp.module

import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.WritableMap
import com.facebook.react.bridge.WritableNativeMap
import com.facebook.react.modules.core.DeviceEventManagerModule

// 原生给RN发送事件通知的模块
class EventEmitterModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
    override fun getName(): String {
        return "EventEmitterModule"
    }

    // 暴露给RN的方法:启动原生定时发送事件
    @ReactMethod
    fun startSendEvent() {
        // 模拟原生每隔2秒给RN发一次消息
        Thread {
            for (i in 1..5) {
                Thread.sleep(2000)
                reactApplicationContext.runOnNativeModulesQueueThread { // 通过RN的桥接线程发送事件(而非直接在子线程调用)
                    // 发送事件:事件名「NativeEvent」,携带参数「message」
                    val params = WritableNativeMap() // 使用RN的WritableMap构建参数(而非普通Map)
                    params.putString("message", "原生发送的第${i}条消息")
                    sendEvent("NativeEvent", params)
                }
            }
        }.start()
    }

    // 核心方法:发送事件到RN
    private fun sendEvent(eventName: String, params: WritableMap) { // 参数改为WritableMap
        reactApplicationContext
            .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
            .emit(eventName, params)
    }
}

ok.

相关推荐
菩提小狗1 小时前
小迪安全2023-2024|第14天:信息打点-JS架构&框架识别&泄漏提取&API接口枚举&FUZZ爬虫&笔记|web安全|渗透测试|
javascript·安全·架构
Never_Satisfied1 小时前
在JavaScript / HTML中,模板克隆并添加监听的注意事项
前端·javascript·html
哈哈浩丶2 小时前
LK(little kernel)-3:LK的启动流程-作为Android的bootloarder
android·linux·服务器
Android系统攻城狮10 小时前
Android tinyalsa深度解析之pcm_get_delay调用流程与实战(一百一十九)
android·pcm·tinyalsa·音频进阶·android hal·audio hal
·云扬·12 小时前
MySQL基于位点的主从复制完整部署指南
android·mysql·adb
千里马-horse12 小时前
Building a Simple Engine -- Mobile Development -- Platform considerations
android·ios·rendering·vulkan
吴声子夜歌13 小时前
RxJava——Subscriber
android·echarts·rxjava
曲幽16 小时前
FastAPI实战:WebSocket长连接保持与心跳机制,从入门到填坑
javascript·python·websocket·keep-alive·fastapi·heartbeat·connection
锅包一切16 小时前
【蓝桥杯JavaScript基础入门】一、JavaScript基础
开发语言·前端·javascript·蓝桥杯