rn 原生交互

React Native 与原生交互 & 原生组件开发

1.RN Bridge 架构演进

┌─────────────────────────────────────────┐

│        JavaScript (React)              │

├─────────────────────────────────────────┤

│    Shadow Tree (Yoga Layout)          │

├─────────────────────────────────────────┤

│    Bridge / JSI (新架构)                │ <- 通信层

├─────────────────────────────────────────┤

│    Native Modules (Java/Kotlin)        │

├─────────────────────────────────────────┤

│        Android Platform                │

└─────────────────────────────────────────┘

2.原生模块开发 (Native Module)

2.1 创建原生模块 - Android

<kotlin>

// ToastModule.ktpackage com.myappimport android.widget.Toastimport com.facebook.react.bridge.*import com.facebook.react.module.annotations.ReactModule@ReactModule(name = ToastModule.NAME)class ToastModule(private val reactContext: ReactApplicationContext) :    ReactContextBaseJavaModule(reactContext) {        companion object {        const val NAME = "ToastModule"    }        override fun getName() = NAME        // 同步方法    @ReactMethod    fun show(message: String, duration: Int) {        UiThreadUtil.runOnUiThread {            Toast.makeText(                reactApplicationContext,                message,                if (duration == 0) Toast.LENGTH_SHORT else Toast.LENGTH_LONG            ).show()        }    }        // 异步方法 - 使用 Promise    @ReactMethod    fun fetchUserData(userId: String, promise: Promise) {        try {            // 模拟异步操作            Thread {                Thread.sleep(1000)                val userData = WritableNativeMap().apply {                    putString("id", userId)                    putString("name", "John Doe")                    putInt("age", 25)                }                promise.resolve(userData)            }.start()        } catch (e: Exception) {            promise.reject("FETCH_ERROR", e.message, e)        }    }        // 异步方法 - 使用 Callback    @ReactMethod    fun getData(callback: Callback) {        try {            val data = "Native Data"            callback.invoke(null, data) // 第一个参数是 error        } catch (e: Exception) {            callback.invoke(e.message, null)        }    }        // 发送事件到 JS    private fun sendEvent(eventName: String, params: WritableMap?) {        reactApplicationContext            .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)            .emit(eventName, params)    }        @ReactMethod    fun startLocationTracking() {        // 持续发送位置更新        locationManager.requestLocationUpdates { location ->            val params = WritableNativeMap().apply {                putDouble("latitude", location.latitude)                putDouble("longitude", location.longitude)            }            sendEvent("onLocationUpdate", params)        }    }        // 导出常量    override fun getConstants(): Map<String, Any> {        return mapOf(            "SHORT_DURATION" to Toast.LENGTH_SHORT,            "LONG_DURATION" to Toast.LENGTH_LONG        )    }}

2.2 注册原生模块

<kotlin>

// MyAppPackage.ktclass MyAppPackage : ReactPackage {    override fun createNativeModules(        reactContext: ReactApplicationContext    ): List<NativeModule> {        return listOf(            ToastModule(reactContext),            CameraModule(reactContext),            // 添加其他模块        )    }        override fun createViewManagers(        reactContext: ReactApplicationContext    ): List<ViewManager<*, *>> {        return listOf(            CustomVideoViewManager(),            // 添加其他视图管理器        )    }}// MainApplication.ktclass MainApplication : Application(), ReactApplication {    override val reactNativeHost: ReactNativeHost = object : DefaultReactNativeHost(this) {        override fun getPackages(): List<ReactPackage> {            return PackageList(this).packages.apply {                add(MyAppPackage()) // 添加自定义包            }        }    }}

2.3 JavaScript 端使用

<javascript>

// ToastModule.jsimport { NativeModules, NativeEventEmitter } from 'react-native';const { ToastModule } = NativeModules;// 使用同步方法ToastModule.show('Hello from Native!', ToastModule.SHORT_DURATION);// 使用 Promiseconst fetchUser = async (userId) => {  try {    const userData = await ToastModule.fetchUserData(userId);    console.log('User data:', userData);  } catch (error) {    console.error('Error fetching user:', error);  }};// 使用 CallbackToastModule.getData((error, data) => {  if (error) {    console.error(error);  } else {    console.log('Data:', data);  }});// 监听事件const eventEmitter = new NativeEventEmitter(ToastModule);const subscription = eventEmitter.addListener('onLocationUpdate', (location) => {  console.log('Location:', location);});// 记得取消订阅subscription.remove();

3.原生 UI 组件开发

3.1 创建自定义视图 - Android

<kotlin>

// CustomVideoView.ktclass CustomVideoView(context: Context) : VideoView(context) {        var onPlaybackComplete: (() -> Unit)? = null        init {        setOnCompletionListener {            onPlaybackComplete?.invoke()        }    }        fun setVideoPath(path: String) {        super.setVideoPath(path)        start()    }        fun pauseVideo() {        if (isPlaying) {            pause()        }    }        fun resumeVideo() {        if (!isPlaying) {            start()        }    }}

3.2 创建 ViewManager

<kotlin>

// CustomVideoViewManager.kt@ReactModule(name = CustomVideoViewManager.REACT_CLASS)class CustomVideoViewManager : SimpleViewManager<CustomVideoView>() {        companion object {        const val REACT_CLASS = "CustomVideoView"        const val COMMAND_PLAY = 1        const val COMMAND_PAUSE = 2    }        override fun getName() = REACT_CLASS        override fun createViewInstance(reactContext: ThemedReactContext): CustomVideoView {        return CustomVideoView(reactContext)    }        // Props 设置    @ReactProp(name = "videoPath")    fun setVideoPath(view: CustomVideoView, path: String?) {        path?.let { view.setVideoPath(it) }    }        @ReactProp(name = "autoPlay", defaultBoolean = true)    fun setAutoPlay(view: CustomVideoView, autoPlay: Boolean) {        if (autoPlay) {            view.start()        }    }        @ReactProp(name = "volume", defaultFloat = 1.0f)    fun setVolume(view: CustomVideoView, volume: Float) {        // 设置音量    }        // 导出事件    override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any> {        return mapOf(            "onPlaybackComplete" to mapOf(                "registrationName" to "onPlaybackComplete"            ),            "onPlaybackError" to mapOf(                "registrationName" to "onPlaybackError"            )        )    }        // 处理命令    override fun getCommandsMap(): Map<String, Int> {        return mapOf(            "play" to COMMAND_PLAY,            "pause" to COMMAND_PAUSE        )    }        override fun receiveCommand(        view: CustomVideoView,        commandId: String,        args: ReadableArray?    ) {        when (commandId.toInt()) {            COMMAND_PLAY -> view.resumeVideo()            COMMAND_PAUSE -> view.pauseVideo()        }    }        // 发送事件到 JS    private fun sendEvent(view: CustomVideoView, eventName: String, data: WritableMap?) {        val reactContext = view.context as ReactContext        reactContext.getJSModule(RCTEventEmitter::class.java)            .receiveEvent(view.id, eventName, data)    }}

3.3 JavaScript 端包装组件

<javascript>

// CustomVideoView.jsimport React, { useRef, useImperativeHandle, forwardRef } from 'react';import {  requireNativeComponent,  UIManager,  findNodeHandle,  ViewStyle } from 'react-native';const NativeCustomVideoView = requireNativeComponent('CustomVideoView');const CustomVideoView = forwardRef((props, ref) => {  const nativeRef = useRef(null);    useImperativeHandle(ref, () => ({    play: () => {      UIManager.dispatchViewManagerCommand(        findNodeHandle(nativeRef.current),        UIManager.CustomVideoView.Commands.play,        []      );    },    pause: () => {      UIManager.dispatchViewManagerCommand(        findNodeHandle(nativeRef.current),        UIManager.CustomVideoView.Commands.pause,        []      );    }  }));    const handlePlaybackComplete = (event) => {    if (props.onPlaybackComplete) {      props.onPlaybackComplete(event.nativeEvent);    }  };    return (    <NativeCustomVideoView      ref={nativeRef}      {...props}      onPlaybackComplete={handlePlaybackComplete}    />  );});// 使用组件export default function VideoPlayer() {  const videoRef = useRef(null);    return (    <CustomVideoView      ref={videoRef}      videoPath="/path/to/video.mp4"      autoPlay={true}      volume={0.5}      style={{ width: '100%', height: 200 }}      onPlaybackComplete={() => console.log('Video completed')}    />  );}

4.复杂交互示例 - 相机模块

<kotlin>

// CameraModule.kt@ReactModule(name = CameraModule.NAME)class CameraModule(private val reactContext: ReactApplicationContext) :    ReactContextBaseJavaModule(reactContext),    ActivityEventListener {        companion object {        const val NAME = "CameraModule"        const val REQUEST_IMAGE_CAPTURE = 1    }        private var imagePromise: Promise? = null        init {        reactContext.addActivityEventListener(this)    }        override fun getName() = NAME        @ReactMethod    fun takePicture(options: ReadableMap, promise: Promise) {        val currentActivity = currentActivity                if (currentActivity == null) {            promise.reject("E_ACTIVITY_DOES_NOT_EXIST", "Activity doesn't exist")            return        }                imagePromise = promise                val takePictureIntent = Intent(MediaStore.ACTION_IMAGE_CAPTURE)                if (takePictureIntent.resolveActivity(reactContext.packageManager) != null) {            currentActivity.startActivityForResult(                takePictureIntent,                REQUEST_IMAGE_CAPTURE            )        } else {            promise.reject("E_CAMERA_NOT_AVAILABLE", "Camera not available")        }    }        override fun onActivityResult(        activity: Activity?,        requestCode: Int,        resultCode: Int,        data: Intent?    ) {        if (requestCode == REQUEST_IMAGE_CAPTURE) {            if (resultCode == Activity.RESULT_OK) {                val imageBitmap = data?.extras?.get("data") as? Bitmap                                if (imageBitmap != null) {                    // 转换为 Base64                    val outputStream = ByteArrayOutputStream()                    imageBitmap.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)                    val base64 = Base64.encodeToString(                        outputStream.toByteArray(),                        Base64.DEFAULT                    )                                        val result = WritableNativeMap().apply {                        putString("base64", base64)                        putInt("width", imageBitmap.width)                        putInt("height", imageBitmap.height)                    }                                        imagePromise?.resolve(result)                } else {                    imagePromise?.reject("E_NO_IMAGE_DATA", "No image data")                }            } else {                imagePromise?.reject("E_PICKER_CANCELLED", "User cancelled")            }                        imagePromise = null        }    }}

5.新架构 - Turbo Modules 和 Fabric

5.1 Turbo Module (使用 JSI)

<cpp>

// TurboModule 示例 - C++#include <jsi/jsi.h>using namespace facebook;class NativeSampleModule : public jsi::HostObject {public:    jsi::Value get(jsi::Runtime& runtime, const jsi::PropNameID& name) override {        auto methodName = name.utf8(runtime);                if (methodName == "multiply") {            return jsi::Function::createFromHostFunction(                runtime,                name,                2, // 参数数量                [](jsi::Runtime& runtime, const jsi::Value& thisValue,                    const jsi::Value* arguments, size_t count) -> jsi::Value {                                        if (count != 2) {                        throw jsi::JSError(runtime, "multiply expects 2 arguments");                    }                                        double a = arguments[0].asNumber();                    double b = arguments[1].asNumber();                                        return jsi::Value(a * b);                }            );        }                return jsi::Value::undefined();    }};

5.2 Fabric 组件

<kotlin>

// Fabric 组件示例@ReactModule(name = CustomViewManager.REACT_CLASS)class CustomViewManager : ViewGroupManager<CustomView>() {        companion object {        const val REACT_CLASS = "CustomView"    }        override fun getName() = REACT_CLASS        override fun createViewInstance(        reactContext: ThemedReactContext    ) = CustomView(reactContext)        // Fabric 特定的 Shadow Node    override fun createShadowNodeInstance(): ReactShadowNode {        return CustomViewShadowNode()    }        override fun getShadowNodeClass() = CustomViewShadowNode::class.java}

6.性能优化建议

<kotlin>

// 1. 批量操作@ReactMethodfun batchOperation(operations: ReadableArray, promise: Promise) {    val results = WritableNativeArray()        for (i in 0 until operations.size()) {        val op = operations.getMap(i)        // 处理操作        results.pushMap(processOperation(op))    }        promise.resolve(results)}// 2. 使用线程池private val executor = Executors.newFixedThreadPool(4)@ReactMethodfun heavyOperation(data: String, promise: Promise) {    executor.execute {        try {            val result = performHeavyWork(data)            promise.resolve(result)        } catch (e: Exception) {            promise.reject("ERROR", e.message)        }    }}// 3. 避免频繁的 Bridge 调用class OptimizedModule {    private val cache = mutableMapOf<String, Any>()        @ReactMethod(isBlockingSynchronousMethod = true)    fun getCachedValue(key: String): Any? {        return cache[key]    }}

总结

React Native 原生交互的关键点:

Bridge/JSI是核心通信机制Native Module处理逻辑功能ViewManager管理原生 UI 组件事件系统实现双向通信新架构(Turbo Modules & Fabric) 提供更好的性能

相比 Flutter,RN 的原生交互更加灵活,可以直接复用现有的原生代码,但性能开销相对较大。

©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

友情链接更多精彩内容