← 返回首页

React Native 原生模块开发:桥接 Android 与 iOS

从 TurboModules 新架构出发,编写支持双端的原生模块,实现 JS 与 Native 的高性能通信。

TurboModules 概述

TurboModules 是 React Native 新架构的核心部分,用 JSI(JavaScript Interface)替代了旧的异步 Bridge。关键改进:

  • 同步调用:JS 可以同步调用 Native 方法,无需等待异步回调
  • 类型安全:通过 CodeGen 从 TypeScript 规格文件自动生成 Native 接口
  • 懒加载:模块只在首次使用时才初始化,减少启动时间

定义 TypeSpec(CodeGen 规格文件)

新架构的原生模块从 TypeScript 规格文件开始:

// NativeDeviceInfo.ts(放在 src/specs/ 目录)
import type { TurboModule } from 'react-native';
import { TurboModuleRegistry } from 'react-native';

export interface Spec extends TurboModule {
  getDeviceModel(): string;
  getBatteryLevel(): Promise<number>;
  isCharging(): Promise<boolean>;
}

export default TurboModuleRegistry.getEnforcing<Spec>('DeviceInfo');

Android 端实现(Kotlin)

// DeviceInfoModule.kt
package com.myapp.deviceinfo

import android.content.Context
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Build
import com.facebook.react.bridge.*
import com.facebook.react.module.annotations.ReactModule

@ReactModule(name = DeviceInfoModule.NAME)
class DeviceInfoModule(reactContext: ReactApplicationContext) :
    NativeDeviceInfoSpec(reactContext) {

    companion object {
        const val NAME = "DeviceInfo"
    }

    override fun getName() = NAME

    override fun getDeviceModel(): String {
        return "${Build.MANUFACTURER} ${Build.MODEL}"
    }

    override fun getBatteryLevel(): Double {
        val intent = reactApplicationContext.registerReceiver(
            null, IntentFilter(Intent.ACTION_BATTERY_CHANGED)
        )
        val level = intent?.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) ?: -1
        val scale = intent?.getIntExtra(BatteryManager.EXTRA_SCALE, -1) ?: -1
        return if (level < 0 || scale < 0) -1.0
        else level.toDouble() / scale * 100
    }

    override fun isCharging(): Boolean {
        val bm = reactApplicationContext.getSystemService(Context.BATTERY_SERVICE) as BatteryManager
        return bm.isCharging
    }
}

iOS 端实现(Objective-C / Swift)

// DeviceInfoModule.swift
import Foundation
import UIKit

@objc(DeviceInfoModule)
class DeviceInfoModule: NSObject {

    @objc static func requiresMainQueueSetup() -> Bool { false }

    @objc func getDeviceModel() -> String {
        return UIDevice.current.model + " " + UIDevice.current.systemVersion
    }

    @objc func getBatteryLevel(
        _ resolve: @escaping RCTPromiseResolveBlock,
        reject: @escaping RCTPromiseRejectBlock
    ) {
        UIDevice.current.isBatteryMonitoringEnabled = true
        let level = UIDevice.current.batteryLevel
        if level < 0 {
            reject("UNAVAILABLE", "Battery monitoring not available", nil)
            return
        }
        resolve(Int(level * 100))
    }

    @objc func isCharging(
        _ resolve: @escaping RCTPromiseResolveBlock,
        reject: @escaping RCTPromiseRejectBlock
    ) {
        UIDevice.current.isBatteryMonitoringEnabled = true
        resolve(UIDevice.current.batteryState == .charging)
    }
}

JS 层封装

在 JS 层封装一个干净的 API,隐藏原生模块的复杂性:

// deviceInfo.ts
import NativeDeviceInfo from './specs/NativeDeviceInfo';

export const DeviceInfo = {
  getModel(): string {
    return NativeDeviceInfo.getDeviceModel();
  },

  async getBattery(): Promise<{ level: number; charging: boolean }> {
    const [level, charging] = await Promise.all([
      NativeDeviceInfo.getBatteryLevel(),
      NativeDeviceInfo.isCharging(),
    ]);
    return { level: Math.round(level), charging };
  },
};

// 使用
const { level, charging } = await DeviceInfo.getBattery();
console.log(`电量: ${level}%, 充电中: ${charging}`);

发送事件到 JS(Native → JS)

// Android:发送事件
private fun sendEvent(name: String, params: WritableMap) {
    reactApplicationContext
        .getJSModule(DeviceEventManagerModule.RCTDeviceEventEmitter::class.java)
        .emit(name, params)
}

// 在电量变化时触发
sendEvent("BatteryChanged", Arguments.createMap().apply {
    putInt("level", newLevel)
    putBoolean("charging", isCharging)
})
// JS 端监听
import { NativeEventEmitter, NativeModules } from 'react-native';

const emitter = new NativeEventEmitter(NativeModules.DeviceInfo);

useEffect(() => {
  const sub = emitter.addListener('BatteryChanged', ({ level, charging }) => {
    setBattery({ level, charging });
  });
  return () => sub.remove(); // 组件卸载时取消监听!
}, []);

调试技巧

  • Android:用 adb logcat 过滤 Native 日志,或在 Android Studio Logcat 中搜索模块名
  • iOS:Xcode Console 查看 Swift/OC 输出,NSLog()print() 都会显示
  • JS 端:Metro 终端会显示未捕获的 Promise 错误,注意 red screen 里的堆栈信息
  • CodeGen 生成的文件在 android/build/generated/ 和 iOS DerivedData 中,可用于确认接口签名是否正确