深入理解 Flutter/ReactNative 等框架中的 Bridge (桥接) 机制,以及 JavaScript 如何与原生模块进行通信。

各位观众老爷们,大家好!今天咱们来聊聊 Flutter 和 React Native 里面那个神秘兮兮的 Bridge,也就是桥接机制。这玩意儿听起来高大上,其实就是让 JavaScript (简称 JS) 和原生的 Java/Kotlin (Android) 或者 Objective-C/Swift (iOS) 模块勾搭上的媒婆。

开场白:JS 与原生,跨次元的爱恋

咱们都知道,Flutter 和 React Native 这些框架的核心思想是 “一次编写,到处运行”。这意味着我们用一套 JS 代码,就能在 Android 和 iOS 两个平台上跑起来。但是,JS 毕竟是解释型语言,性能上和直接跑在硬件上的原生代码还是有差距的。而且,很多时候我们需要调用一些只有原生才能访问的硬件资源,比如摄像头、GPS、蓝牙等等。

这时候,Bridge 就闪亮登场了!它就像一座桥梁,连接了 JS 的世界和原生的世界,让它们可以互相通信,各取所需。

第一幕:Bridge 的基本原理

Bridge 的核心思想是异步消息传递。JS 通过某种方式(比如 JSON 序列化)把要执行的任务和参数打包成一个消息,然后发送给原生。原生收到消息后,解析出任务和参数,执行相应的操作,然后把结果也打包成消息,再发送回 JS。

整个过程就像这样:

JS —> Bridge —> 原生 —> Bridge —> JS

之所以要用异步消息传递,是因为 JS 是单线程的。如果 JS 直接同步调用原生代码,会导致 UI 线程阻塞,造成卡顿。异步消息传递可以让 JS 在后台默默地等待原生返回结果,而不会影响 UI 的渲染。

第二幕:React Native 的 Bridge

在 React Native 中,Bridge 主要通过 Native Modules 实现。咱们来举个例子:

假设我们要写一个获取设备名称的 Native Module。

1. 原生模块 (Android – Java):

package com.example.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;

import android.os.Build;

public class DeviceInfoModule extends ReactContextBaseJavaModule {

    private static ReactApplicationContext reactContext;

    DeviceInfoModule(ReactApplicationContext context) {
        super(context);
        reactContext = context;
    }

    @Override
    public String getName() {
        return "DeviceInfo"; // This is the name used in JavaScript
    }

    @ReactMethod
    public void getDeviceName(Promise promise) {
        String deviceName = Build.MODEL;
        promise.resolve(deviceName);
    }
}

2. 原生模块 (iOS – Objective-C):

#import <React/RCTBridgeModule.h>

@interface DeviceInfo : NSObject <RCTBridgeModule>
@end

#import "DeviceInfo.h"
#import <UIKit/UIKit.h>

@implementation DeviceInfo

RCT_EXPORT_MODULE();

RCT_EXPORT_METHOD(getDeviceName:(RCTPromiseResolveBlock)resolve
                  rejecter:(RCTPromiseRejectBlock)reject)
{
  NSString *deviceName = [UIDevice currentDevice].name;
  resolve(deviceName);
}

@end

3. 注册模块 (Android):

package com.example.myapp;

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;
import java.util.Collections;
import java.util.List;

public class DeviceInfoPackage implements ReactPackage {
    @Override
    public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
        List<NativeModule> modules = new ArrayList<>();

        modules.add(new DeviceInfoModule(reactContext));

        return modules;
    }

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

在你的 MainApplication.java 文件中,你需要添加这个 package:

@Override
protected List<ReactPackage> getPackages() {
  @SuppressWarnings("UnnecessaryLocalVariable")
  List<ReactPackage> packages = new PackageList(this).getPackages();
  // Packages that cannot be autolinked yet can be added manually here, for example:
  packages.add(new DeviceInfoPackage());
  return packages;
}

4. JS 代码:

import { NativeModules } from 'react-native';

const { DeviceInfo } = NativeModules;

async function getDeviceName() {
  try {
    const name = await DeviceInfo.getDeviceName();
    console.log('Device Name:', name);
    return name;
  } catch (e) {
    console.error(e);
    return 'Unknown Device';
  }
}

export default getDeviceName;

在这个例子中,我们做了以下事情:

  • 定义原生模块: 分别在 Android 和 iOS 上实现了 DeviceInfoModuleDeviceInfo 类/对象,其中包含一个 getDeviceName 方法,用于获取设备名称。@ReactMethodRCT_EXPORT_METHOD 宏分别用于标记可以被 JS 调用的方法。注意 Promise 在 Android 中和 RCTPromiseResolveBlockRCTPromiseRejectBlock 在 iOS 中,允许原生代码异步返回结果给 JS。
  • 注册原生模块: 在 Android 上,我们需要创建一个 ReactPackage 来注册我们的 Native Module。
  • JS 调用: 在 JS 中,我们通过 NativeModules.DeviceInfo.getDeviceName() 来调用原生模块的 getDeviceName 方法。NativeModules 对象是由 React Native 框架自动生成的,它包含了所有已注册的 Native Modules。

React Native Bridge 的工作流程:

  1. JS 调用: DeviceInfo.getDeviceName() 调用触发。
  2. 序列化: React Native 将函数调用和参数序列化为 JSON 消息。
  3. 发送到原生: 该消息通过 Bridge 发送到对应的原生平台 (Android 或 iOS)。
  4. 原生处理: 原生平台接收到消息,反序列化,并调用 DeviceInfoModule (Android) 或 DeviceInfo (iOS) 的 getDeviceName 方法。
  5. 原生执行: 原生代码执行 Build.MODEL (Android) 或 [UIDevice currentDevice].name (iOS) 获取设备名称。
  6. 返回结果: 设备名称通过 promise.resolve (Android) 或 resolve (iOS) 返回。
  7. 序列化结果: 原生平台将结果序列化为 JSON 消息。
  8. 发送回 JS: 结果通过 Bridge 发送回 JS。
  9. JS 处理: JS 接收到消息,反序列化,并使用 Promise 接收结果,更新 UI 或执行其他操作。

第三幕:Flutter 的 Platform Channels

Flutter 使用 Platform Channels 来实现 Bridge 的功能。它的原理和 React Native 类似,也是通过异步消息传递。

咱们也来举个例子:

假设我们要写一个获取电池电量的 Platform Channel。

1. 原生代码 (Android – Kotlin):

package com.example.myapp

import androidx.annotation.NonNull
import io.flutter.embedding.android.FlutterActivity
import io.flutter.embedding.engine.FlutterEngine
import io.flutter.plugin.common.MethodChannel
import android.content.Context
import android.content.ContextWrapper
import android.content.Intent
import android.content.IntentFilter
import android.os.BatteryManager
import android.os.Build.VERSION
import android.os.Build.VERSION_CODES

class MainActivity: FlutterActivity() {
    private val CHANNEL = "samples.flutter.dev/battery"

    override fun configureFlutterEngine(@NonNull flutterEngine: FlutterEngine) {
        super.configureFlutterEngine(flutterEngine)
        MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler {
            call, result ->
            if (call.method == "getBatteryLevel") {
                val batteryLevel = getBatteryLevel()

                if (batteryLevel != -1) {
                    result.success(batteryLevel)
                } else {
                    result.error("UNAVAILABLE", "Battery level not available.", null)
                }
            } else {
                result.notImplemented()
            }
        }
    }

    private fun getBatteryLevel(): Int {
        val batteryLevel: Int = if (VERSION.SDK_INT >= VERSION_CODES.LOLLIPOP) {
            val batteryManager = getSystemService(Context.BATTERY_SERVICE) as BatteryManager
            batteryManager.getIntProperty(BatteryManager.BATTERY_PROPERTY_CAPACITY)
        } else {
            val intent = ContextWrapper(applicationContext).registerReceiver(null, IntentFilter(Intent.ACTION_BATTERY_CHANGED))
            intent!!.getIntExtra(BatteryManager.EXTRA_LEVEL, -1) * 100 / intent.getIntExtra(BatteryManager.EXTRA_SCALE, -1)
        }

        return batteryLevel
    }
}

2. 原生代码 (iOS – Swift):

import UIKit
import Flutter

@UIApplicationMain
@objc class AppDelegate: FlutterAppDelegate {
  override func application(
    _ application: UIApplication,
    didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?
  ) -> Bool {
    let controller : FlutterViewController = window?.rootViewController as! FlutterViewController
    let batteryChannel = FlutterMethodChannel(name: "samples.flutter.dev/battery",
                                              binaryMessenger: controller.binaryMessenger)
    batteryChannel.setMethodCallHandler({
      (call: FlutterMethodCall, result: @escaping FlutterResult) -> Void in
      guard call.method == "getBatteryLevel" else {
        result(FlutterMethodNotImplemented)
        return
      }

      self.receiveBatteryLevel(result: result)
    })

    GeneratedPluginRegistrant.register(with: self)
    return super.application(application, didFinishLaunchingWithOptions: launchOptions)
  }

  private func receiveBatteryLevel(result: FlutterResult) {
    let device = UIDevice.current
    device.isBatteryMonitoringEnabled = true
    if device.batteryState == UIDevice.BatteryState.unknown {
      result(FlutterError(code: "UNAVAILABLE",
                          message: "Battery info unavailable",
                          details: nil))
    } else {
      result(Int(device.batteryLevel * 100))
    }
  }
}

3. Dart 代码:

import 'dart:async';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatefulWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  State<MyApp> createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  static const platform = MethodChannel('samples.flutter.dev/battery');

  String _batteryLevel = 'Unknown battery level.';

  Future<void> _getBatteryLevel() async {
    String batteryLevel;
    try {
      final int result = await platform.invokeMethod('getBatteryLevel');
      batteryLevel = 'Battery level at $result % .';
    } on PlatformException catch (e) {
      batteryLevel = "Failed to get battery level: '${e.message}'.";
    }

    setState(() {
      _batteryLevel = batteryLevel;
    });
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        appBar: AppBar(
          title: const Text('Battery Level'),
        ),
        body: Center(
          child: Column(
            mainAxisAlignment: MainAxisAlignment.spaceEvenly,
            children: [
              ElevatedButton(
                onPressed: _getBatteryLevel,
                child: const Text('Get Battery Level'),
              ),
              Text(_batteryLevel),
            ],
          ),
        ),
      ),
    );
  }
}

在这个例子中,我们做了以下事情:

  • 定义 Platform Channel: 在 Android 和 iOS 上,我们都创建了一个 MethodChannel,并指定了一个唯一的名称 "samples.flutter.dev/battery"。这个名称就像一个邮政编码,确保 JS 发送的消息能准确地到达目的地。
  • 处理方法调用: 在原生代码中,我们通过 setMethodCallHandler 来监听 JS 发送的消息。当收到消息时,我们根据消息的 method 属性来判断要执行哪个操作。
  • JS 调用: 在 Dart 代码中,我们通过 platform.invokeMethod('getBatteryLevel') 来调用原生代码的 getBatteryLevel 方法。

Flutter Platform Channel 的工作流程:

  1. Dart 调用: platform.invokeMethod('getBatteryLevel') 调用触发。
  2. 序列化: Flutter 将函数调用 ('getBatteryLevel') 和任何参数 (本例中没有) 序列化为消息。
  3. 发送到原生: 该消息通过 Platform Channel 发送到对应的原生平台 (Android 或 iOS)。
  4. 原生处理: 原生平台接收到消息,并根据 Channel 的名称和方法名,调用相应的方法 (getBatteryLevel 在 Android 的 MainActivity 或 iOS 的 AppDelegate 中)。
  5. 原生执行: 原生代码执行获取电池电量的操作 (BatteryManager.BATTERY_PROPERTY_CAPACITY 在 Android 中,device.batteryLevel 在 iOS 中)。
  6. 返回结果: 电池电量值通过 result.success (Android) 或 result (iOS) 返回。
  7. 序列化结果: 原生平台将结果序列化为消息。
  8. 发送回 Dart: 结果通过 Platform Channel 发送回 Dart。
  9. Dart 处理: Dart 接收到消息,反序列化,并更新 UI 或执行其他操作。

第四幕:Bridge 的性能优化

Bridge 虽然强大,但也是性能瓶颈之一。每次 JS 和原生通信,都需要进行序列化和反序列化,这会消耗大量的 CPU 资源。

为了优化 Bridge 的性能,我们可以采取以下措施:

  • 减少 Bridge 的调用次数: 尽量把一些计算密集型的任务放在原生代码中执行,减少 JS 和原生的通信次数。
  • 使用更高效的序列化方式: 比如 Protobuf,它比 JSON 更轻量级,序列化和反序列化的速度也更快。
  • 使用缓存: 对于一些不经常变化的数据,可以缓存在原生代码中,避免每次都通过 Bridge 获取。
  • 批量处理: 如果需要进行多次类似的操作,可以把这些操作打包成一个批处理请求,一次性发送给原生。

表格总结:React Native vs Flutter Bridge

特性 React Native Flutter
主要机制 Native Modules Platform Channels
通信方式 异步消息传递 (JSON 序列化) 异步消息传递 (二进制消息编解码)
代码风格 JS 调用原生组件/模块 Dart 调用原生方法
性能考量 序列化/反序列化开销,JS 线程阻塞 方法调用开销,二进制消息处理开销
适用场景 需要大量原生组件和功能的复杂应用 性能敏感型应用,需要精细控制 UI 渲染的应用
扩展性/灵活性 依赖第三方库,模块化程度高 官方支持,更易于扩展和维护

第五幕: Bridge 的未来

随着技术的发展,Bridge 也在不断进化。未来,我们可以期待以下方面的改进:

  • 更高效的通信协议: 比如 gRPC,它基于 HTTP/2,支持双向流和多路复用,可以大大提高通信效率。
  • 更智能的序列化方式: 比如自动生成序列化代码,减少手动编写序列化代码的工作量。
  • 更强大的调试工具: 比如可以跨 JS 和原生代码进行调试,方便定位问题。

谢幕: Bridge,连接世界的桥梁

Bridge 是 Flutter 和 React Native 等跨平台框架的核心技术之一。它让 JS 和原生代码可以互相协作,共同构建强大的应用程序。虽然 Bridge 有一些性能上的限制,但通过合理的优化,我们可以充分发挥它的潜力,让我们的应用在各个平台上都能流畅运行。

好了,今天的讲座就到这里。希望大家有所收获! 散会!

发表回复

您的邮箱地址不会被公开。 必填项已用 * 标注