打破边界(一):实战编写 Android/iOS 原生模块 (Native Modules)

157 阅读2分钟

作为 Android 开发者,我们经常需要调用底层的 API(如蓝牙、传感器、特定的 SDK 等)。在 React Native (RN) 中,当纯 JS 无法满足需求时,我们就需要编写 原生模块 (Native Modules) 。

本篇文章将带你了解如何在 RN 中编写原生模块,并与你熟悉的 Android/Kotlin 开发概念进行对比。

1. 原生模块的基本概念

在 RN 旧架构中,JS 与原生层的通信通过 Bridge(桥接) 进行,消息被序列化为 JSON 异步传递。这类似于 Android 中的 Handler 跨线程通信或者 IPC(进程间通信)。

在新架构(Turbo Native Modules / JSI)中,JS 可以直接持有 C++ 对象的引用并同步调用,类似于 JNI 的直接调用,性能大幅提升。不过,这里我们先从最经典的写法入手。

2. 编写 Android 原生模块

在 Android 侧,编写一个原生模块需要继承 ReactContextBaseJavaModule。

Kotlin vs JS 通信

在 Kotlin 中,我们这样定义一个模块:

package com.myapp
​
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.Promise
​
class MyCustomModule(reactContext: ReactApplicationContext) : ReactContextBaseJavaModule(reactContext) {
​
    // 必须重写 getName,这是 JS 端调用该模块的名称
    override fun getName(): String {
        return "MyCustomModule"
    }
​
    // 使用 @ReactMethod 暴露给 JS 的方法
    @ReactMethod
    fun doSomething(message: String, promise: Promise) {
        try {
            val result = "Android received: $message"
            // 相当于 Kotlin Coroutines 的 resume 或是 callback 的 onSuccess
            promise.resolve(result) 
        } catch (e: Exception) {
            promise.reject("Error", e)
        }
    }
}

概念对比分析:

  1. ReactContextBaseJavaModule:相当于 Android 里的一个单例或 Service 类,它的生命周期绑定在 ReactContext 上。
  2. @ReactMethod:类似于 Android 中 WebView 的 @JavascriptInterface。它告诉 RN 框架,这个方法需要暴露给 JS 端。
  3. Promise:在 JS 端使用的是 Promise。在 Android 侧传入 com.facebook.react.bridge.Promise,它的 resolve() 和 reject() 就等价于我们在 Kotlin 协程中使用的 Continuation.resume,或者常见的网络请求 Callback.onSuccess/onError。

3. 注册模块

写完 Module 后,需要将其注册到 ReactPackage 中:

class MyCustomPackage : ReactPackage {
    override fun createNativeModules(reactContext: ReactApplicationContext): List<NativeModule> {
        return listOf(MyCustomModule(reactContext))
    }
​
    override fun createViewManagers(reactContext: ReactApplicationContext): List<ViewManager<*, *>> {
        return emptyList()
    }
}

这类似于在 AndroidManifest.xml 中注册 Service 或者在 Dagger/Hilt 中提供 Module。最后将 MyCustomPackage 添加到 MainApplication.kt 的 getPackages() 列表中。

4. 在 JS 端调用

import { NativeModules } from 'react-native';
​
const { MyCustomModule } = NativeModules;
​
async function testModule() {
  try {
    const response = await MyCustomModule.doSomething("Hello from JS!");
    console.log(response); // 输出: Android received: Hello from JS!
  } catch (error) {
    console.error(error);
  }
}

5. 总结与进阶

  • 同步 vs 异步:默认情况下 @ReactMethod 是异步的(运行在 Native Modules 线程,类似 Dispatchers.IO)。如果你需要同步方法(阻塞 JS 线程),可以使用 @ReactMethod(isBlockingSynchronousMethod = true),但要慎用,避免卡顿主线程。
  • 事件发送:如果 Android 需要主动给 JS 发消息(比如传感器数据流),可以使用 DeviceEventManagerModule.RCTDeviceEventEmitter。这就类似于 Android 中的 EventBus 或 Kotlin 的 SharedFlow/BroadcastChannel。

掌握了原生模块的编写,你就可以在 RN 中随心所欲地调用任何 Android 原生 SDK 了!