各位观众老爷们,大家好!今天咱们来聊聊 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 上实现了
DeviceInfoModule
和DeviceInfo
类/对象,其中包含一个getDeviceName
方法,用于获取设备名称。@ReactMethod
和RCT_EXPORT_METHOD
宏分别用于标记可以被 JS 调用的方法。注意Promise
在 Android 中和RCTPromiseResolveBlock
和RCTPromiseRejectBlock
在 iOS 中,允许原生代码异步返回结果给 JS。 - 注册原生模块: 在 Android 上,我们需要创建一个
ReactPackage
来注册我们的 Native Module。 - JS 调用: 在 JS 中,我们通过
NativeModules.DeviceInfo.getDeviceName()
来调用原生模块的getDeviceName
方法。NativeModules
对象是由 React Native 框架自动生成的,它包含了所有已注册的 Native Modules。
React Native Bridge 的工作流程:
- JS 调用:
DeviceInfo.getDeviceName()
调用触发。 - 序列化: React Native 将函数调用和参数序列化为 JSON 消息。
- 发送到原生: 该消息通过 Bridge 发送到对应的原生平台 (Android 或 iOS)。
- 原生处理: 原生平台接收到消息,反序列化,并调用
DeviceInfoModule
(Android) 或DeviceInfo
(iOS) 的getDeviceName
方法。 - 原生执行: 原生代码执行
Build.MODEL
(Android) 或[UIDevice currentDevice].name
(iOS) 获取设备名称。 - 返回结果: 设备名称通过
promise.resolve
(Android) 或resolve
(iOS) 返回。 - 序列化结果: 原生平台将结果序列化为 JSON 消息。
- 发送回 JS: 结果通过 Bridge 发送回 JS。
- 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 的工作流程:
- Dart 调用:
platform.invokeMethod('getBatteryLevel')
调用触发。 - 序列化: Flutter 将函数调用 (
'getBatteryLevel'
) 和任何参数 (本例中没有) 序列化为消息。 - 发送到原生: 该消息通过 Platform Channel 发送到对应的原生平台 (Android 或 iOS)。
- 原生处理: 原生平台接收到消息,并根据 Channel 的名称和方法名,调用相应的方法 (
getBatteryLevel
在 Android 的MainActivity
或 iOS 的AppDelegate
中)。 - 原生执行: 原生代码执行获取电池电量的操作 (
BatteryManager.BATTERY_PROPERTY_CAPACITY
在 Android 中,device.batteryLevel
在 iOS 中)。 - 返回结果: 电池电量值通过
result.success
(Android) 或result
(iOS) 返回。 - 序列化结果: 原生平台将结果序列化为消息。
- 发送回 Dart: 结果通过 Platform Channel 发送回 Dart。
- 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 有一些性能上的限制,但通过合理的优化,我们可以充分发挥它的潜力,让我们的应用在各个平台上都能流畅运行。
好了,今天的讲座就到这里。希望大家有所收获! 散会!