Flutter - 原生交互 - 相机Camera - 01

环境

Flutter 3.29

macOS Sequoia 15.4.1

Xcode 16.3

集成

Flutter提供了camera插件来拍照和录视频,它提供了一系列可用的相机,并使用特定的相机展示相机预览、拍照、录视频。

添加依赖

  • camera: 提供使用设备相机模块的工具
  • path_provider: 寻找存储图片的正确路径
  • path: 创建适配任何平台的路径
sh 复制代码
flutter pub add camera path_provider path

执行完成后iOS工程的GeneratedPluginRegistrant.m文件会自动生成对应的集成代码

objc 复制代码
#if __has_include(<camera_avfoundation/CameraPlugin.h>)
#import <camera_avfoundation/CameraPlugin.h>
#else
@import camera_avfoundation;
#endif

#if __has_include(<path_provider_foundation/PathProviderPlugin.h>)
#import <path_provider_foundation/PathProviderPlugin.h>
#else
@import path_provider_foundation;
#endif
...

[CameraPlugin registerWithRegistrar:[registry registrarForPlugin:@"CameraPlugin"]];


[PathProviderPlugin registerWithRegistrar:[registry registrarForPlugin:@"PathProviderPlugin"]];

分析

[registry registrarForPlugin:@"CameraPlugin"]

AppDelegate.swfitapplication(_:didFinishLaunchingWithOptions:)中将FlutterAppDelegate的子类AppDelegate对象作为参数传入并调用该方法

swift 复制代码
GeneratedPluginRegistrant.register(with: self)

源码: FlutterAppDelegate.mm

objc 复制代码
- (NSObject<FlutterPluginRegistrar>*)registrarForPlugin:(NSString*)pluginKey {
  /// <1> 获取应用的flutterRootViewController对象
  FlutterViewController* flutterRootViewController = [self rootFlutterViewController];
  if (flutterRootViewController) {
    /// <4> 返回一个FlutterEngine对象
    return [[flutterRootViewController pluginRegistry] registrarForPlugin:pluginKey];
  }
  return nil;
}

// Returns the key window's rootViewController, if it's a FlutterViewController.
// Otherwise, returns nil.
- (FlutterViewController*)rootFlutterViewController {
  ///- (FlutterViewController*(^ rootFlutterViewControllerGetter) (void))
  /// <2> 检查是否有外部注入,有则使用自定义的回调获取FlutterViewController对象
  if (_rootFlutterViewControllerGetter != nil) {
    return _rootFlutterViewControllerGetter();
  }
  /// <3> 没有则检查window的rootViewController属性,如果是FlutterViewController则返回,否则返回nil
  UIViewController* rootViewController = _window.rootViewController;
  if ([rootViewController isKindOfClass:[FlutterViewController class]]) {
    return (FlutterViewController*)rootViewController;
  }
  return nil;
}
[flutterRootViewController pluginRegistry]

第<4>步中的方法调用在 FlutterViewController.mm

objc 复制代码
///  pluginRegistry方法获得一个遵守FlutterPluginRegistry协议的对象
- (id<FlutterPluginRegistry>)pluginRegistry {
    return self.engine;
}
...

/// engine是FlutterEngine对象
- (void)sharedSetupWithProject:(nullable FlutterDartProject*)project
                  initialRoute:(nullable NSString*)initialRoute {
...
engine = [[FlutterEngine alloc] initWithName:@"io.flutter"
                                         project:project
                          allowHeadlessExecution:self.engineAllowHeadlessExecution
                              restorationEnabled:self.restorationIdentifier != nil];
...
_engine = engine;
...              

- (instancetype)initWithEngine:(FlutterEngine*)engine
                       nibName:(nullable NSString*)nibName
                        bundle:(nullable NSBundle*)nibBundle {
...
_engine = engine;          

FlutterPluginRegistry协议的继承结构

FlutterEngine对象调用registrarForPlugin:方法

源码: FlutterEngine.mm

objc 复制代码
/// 文件: FlutterEngine.mm
...
/**
 * All registrars returned from registrarForPlugin:
 */
@property(nonatomic, readonly)
    NSMutableDictionary<NSString*, FlutterEngineRegistrar*>* pluginRegistrars;
...

- (id<FlutterPluginRegistrar>)registrarForPlugin:(NSString*)pluginName {
  /// <5> 检查可变字典中是否已存在插件名的key
  id<FlutterPluginRegistrar> registrar = self.pluginRegistrars[pluginName];
  if (!registrar) {
    /// <6> 首次注册,生成FlutterEngineRegistrar对象并持有pluginName和弱引用FlutterEngine 对象

    /// 为什么是弱引用?
    /// 文件:FlutterViewController.m 强引入了FlutterEngine对象
    /// @property(nonatomic, readonly) FlutterEngine* engine;
    
    FlutterEngineRegistrar* registrarImpl =
        [[FlutterEngineRegistrar alloc] initWithPlugin:pluginName flutterEngine:self];
    /// 接收传入的pluginName  + self即FlutterEngine对象
    /// @interface FlutterEngineRegistrar : NSObject <FlutterPluginRegistrar>
    /// @property(nonatomic, weak) FlutterEngine* flutterEngine;
    /// @implementation FlutterEngineRegistrar {
    ///   NSString* _pluginKey;
    /// }
    
    /// 因为FlutterViewController.m 已经强引入了FlutterEngine对象,所以这里的flutterEngine弱引用即可
    /// @property(nonatomic, readonly) FlutterEngine* engine;
    /// 
    /// <7> 添加到FlutterEngine对象的可变字典中
    self.pluginRegistrars[pluginName] = registrarImpl;
    registrar = registrarImpl;
  }
  /// <8> 返回FlutterEngineRegistrar注册对象,其中保存FlutterEngine相关的信息,负责与Flutter的iOS插件交互
  return registrar;
}
[CameraPlugin registerWithRegistrar:registrar]
swift 复制代码
/// camera插件的类定义
public final class CameraPlugin: NSObject, FlutterPlugin {
objc 复制代码
@protocol FlutterPlugin <NSObject, FlutterApplicationLifeCycleDelegate>
@required
+ (void)registerWithRegistrar:(NSObject<FlutterPluginRegistrar>*)registrar;

Flutter中的iOS插件遵守FlutterPlugin协议且必须实现+registerWithRegistrar:方法

以camera插件为例,CameraPlugin.swift是对外的Swift的接口包装

swift 复制代码
public static func register(with registrar: FlutterPluginRegistrar) {
    let instance = CameraPlugin(
      /// 文件: FlutterEngine.mm
      /// <9> 从FlutterEngineRegistrar对象中获取纹理的对象
      /// - (NSObject<FlutterTextureRegistry>*)textures {
      ///   return _flutterEngine.textureRegistry;
      /// }
      registry: registrar.textures(),
      /// Returns a `FlutterBinaryMessenger` for creating Dart/iOS communication
      /// channels to be used by the plugin.
      /// <10> 从FlutterEngineRegistrar返回Dart与iOS原生消息的对象
      messenger: registrar.messenger(),
      globalAPI: FCPCameraGlobalEventApi(binaryMessenger: registrar.messenger()),
      deviceDiscoverer: FLTDefaultCameraDeviceDiscoverer(),
      permissionManager: FLTCameraPermissionManager(
        permissionService: FLTDefaultPermissionService()),
      deviceFactory: { name in
        // TODO(RobertOdrowaz) Implement better error handling and remove non-null assertion
        FLTDefaultCaptureDevice(device: AVCaptureDevice(uniqueID: name)!)
      },
      captureSessionFactory: { FLTDefaultCaptureSession(captureSession: AVCaptureSession()) },
      captureDeviceInputFactory: FLTDefaultCaptureDeviceInputFactory(),
      captureSessionQueue: DispatchQueue(label: "io.flutter.camera.captureSessionQueue")
    )
    
    /// <11>设置Dart相机API的消息通道
    SetUpFCPCameraApi(registrar.messenger(), instance)
  }
registrar.messenger()

从前面可知registrar是一个FlutterEngineRegistrar

objc 复制代码
/// 文件: FlutterEngine.mm
@implementation FlutterEngineRegistrar {
...
- (NSObject<FlutterBinaryMessenger>*)messenger {
  /// 返回的是FlutterEngineRegistrar对象绑定的FlutterEngine中的binaryMessenger属性
  return _flutterEngine.binaryMessenger;
}
...

@implementation FlutterEngine {
...
FlutterBinaryMessengerRelay* _binaryMessenger;
...

/// FlutterEngine对象中的binaryMessenger属性是FlutterBinaryMessengerRelay对象
/// 且parent属性关联的是FlutterEngine对象
_binaryMessenger = [[FlutterBinaryMessengerRelay alloc] initWithParent:self];
SetUpFCPCameraApi(registrar.messenger(), instance)

代码执行进入文件 message.g.m

objc 复制代码
void SetUpFCPCameraApiWithSuffix(id<FlutterBinaryMessenger> binaryMessenger,
                                 NSObject<FCPCameraApi> *api, NSString *messageChannelSuffix) {
  messageChannelSuffix = messageChannelSuffix.length > 0
                             ? [NSString stringWithFormat:@".%@", messageChannelSuffix]
                             : @"";
  /// Returns the list of available cameras.
  /// 建立设备的相机可用列表API
  {
    FlutterBasicMessageChannel *channel = [[FlutterBasicMessageChannel alloc]
           initWithName:[NSString stringWithFormat:@"%@%@",
                                                   @"dev.flutter.pigeon.camera_avfoundation."
                                                   @"CameraApi.getAvailableCameras",
                                                   messageChannelSuffix]
        binaryMessenger:binaryMessenger
                  codec:FCPGetMessagesCodec()];
    if (api) {
      NSCAssert(
          [api respondsToSelector:@selector(availableCamerasWithCompletion:)],
          @"FCPCameraApi api (%@) doesn't respond to @selector(availableCamerasWithCompletion:)",
          api);
      [channel setMessageHandler:^(id _Nullable message, FlutterReply callback) {
        [api availableCamerasWithCompletion:^(
                 NSArray<FCPPlatformCameraDescription *> *_Nullable output,
                 FlutterError *_Nullable error) {
          callback(wrapResult(output, error));
        }];
      }];
    } else {
      [channel setMessageHandler:nil];
    }
  }
  ...
  /// 绑定一系列相机操作的API
                                 

FlutterBasicMessageChannel类在FlutterChannel.m中,先初始化一个FlutterBasicMessageChannel对象,实现上只是接收外界参数

objc 复制代码
- (instancetype)initWithName:(NSString*)name
             binaryMessenger:(NSObject<FlutterBinaryMessenger>*)messenger
                       codec:(NSObject<FlutterMessageCodec>*)codec
                   taskQueue:(NSObject<FlutterTaskQueue>*)taskQueue {
  self = [super init];
  NSAssert(self, @"Super init cannot be nil");
  _name = [name copy];
  _messenger = messenger;
  _codec = codec;
  _taskQueue = taskQueue;
  return self;
}
[channel setMessageHandler:^(id _Nullable message, FlutterReply callback) ...];

接着判断入参的api是否不为空,api是生成的CameraPlugin对象,所以不为空,然后消息的回调

objc 复制代码
- (void)setMessageHandler:(FlutterMessageHandler)handler {
  /// 未自定义回调时,这里应该是多次调用则清空上一次的,然后再重新创建
  if (!handler) {
    if (_connection > 0) {
      [_messenger cleanUpConnection:_connection];
      _connection = 0;
    } else {
      [_messenger setMessageHandlerOnChannel:_name binaryMessageHandler:nil];
    }
    return;
  }

  // Grab reference to avoid retain on self.
  // `self` might be released before the block, so the block needs to retain the codec to
  // make sure it is not released with `self`
  /// 从前面的代码可以知道这个self即channel对象只有一个局部对象在持有,所以超过作用域会被回收,所以这里接收到codec
  NSObject<FlutterMessageCodec>* codec = _codec;
  FlutterBinaryMessageHandler messageHandler = ^(NSData* message, FlutterBinaryReply callback) {
    handler([codec decode:message], ^(id reply) {
      callback([codec encode:reply]);
    });
  };
  _connection = SetMessageHandler(_messenger, _name, messageHandler, _taskQueue);
}
objc 复制代码
static FlutterBinaryMessengerConnection SetMessageHandler(
    NSObject<FlutterBinaryMessenger>* messenger,
    NSString* name,
    FlutterBinaryMessageHandler handler,
    NSObject<FlutterTaskQueue>* taskQueue) {
  /// 是否要在指定的任务队列上执行
  /// name在这里是 dev.flutter.pigeon.camera_avfoundation.CameraApi.getAvailableCameras...
  /// handler是设置的回调
  /// 发送给FlutterBinaryMessengerRelay对象
  if (taskQueue) {
    NSCAssert([messenger respondsToSelector:@selector(setMessageHandlerOnChannel:
                                                            binaryMessageHandler:taskQueue:)],
              @"");
    return [messenger setMessageHandlerOnChannel:name
                            binaryMessageHandler:handler
                                       taskQueue:taskQueue];
  } else {
    return [messenger setMessageHandlerOnChannel:name binaryMessageHandler:handler];
  }
}

进入到FlutterBinaryMessengerRelay对象的setMessageHandlerOnChannel:binaryMessageHandler:taskQueue:

文件: FlutterBinaryMessengerRelay.mm

objc 复制代码
- (FlutterBinaryMessengerConnection)setMessageHandlerOnChannel:(NSString*)channel
                                          binaryMessageHandler:(FlutterBinaryMessageHandler)handler
                                                     taskQueue:
                                                         (NSObject<FlutterTaskQueue>*)taskQueue {
  /// parent就是engine对象,因此又回到engine上setMessageHandlerOnChannel:binaryMessageHandler:                                         taskQueue:
  if (self.parent) {
    return [self.parent setMessageHandlerOnChannel:channel
                              binaryMessageHandler:handler
                                         taskQueue:taskQueue];
  } else {
    FML_LOG(WARNING) << "Communicating on a dead channel.";
    return -1;
  }
}

文件: FlutterEngine.mm

objc 复制代码
- (FlutterBinaryMessengerConnection)
    setMessageHandlerOnChannel:(NSString*)channel
          binaryMessageHandler:(FlutterBinaryMessageHandler)handler
                     taskQueue:(NSObject<FlutterTaskQueue>* _Nullable)taskQueue {
  NSParameterAssert(channel);
  if (_shell && _shell->IsSetup()) {
    /// 获取原生平台的线程,并传入channel名,回调,任务队列
    self.platformView->GetPlatformMessageHandlerIos()->SetMessageHandler(channel.UTF8String,
                                                                         handler, taskQueue);
    /// std::unique_ptr<flutter::ConnectionCollection> _connections;
    /// 
    ///  文件:connection_collection.mm
    ///  ConnectionCollection::Connection   ConnectionCollection::AquireConnection(const std::string& name) {
    ///   Connection nextConnection = ++counter_;
    ///   connections_[name] = nextConnection;
    ///   return nextConnection;
    /// }
    /// FlutterEngine对象中的连接集合属性,AcquireConnection方法让connections字典中key为channel的计数加1
    return _connections->AquireConnection(channel.UTF8String);
  } else {
    NSAssert(!handler, @"Setting a message handler before the FlutterEngine has been run.");
    // Setting a handler to nil for a channel that has not yet been set up is a no-op.
    return flutter::ConnectionCollection::MakeErrorConnection(-1);
  }
}

文件: platform_view_ios.h

objc 复制代码
/// 调用GetPlatformMessageHandlerIos即返回platform_message_handler_属性
class PlatformViewIOS final : public PlatformView {
...
std::shared_ptr<PlatformMessageHandlerIos> GetPlatformMessageHandlerIos() const {
    return platform_message_handler_;
  }
...

文件: platform_view_ios.mm

objc 复制代码
PlatformViewIOS::PlatformViewIOS(PlatformView::Delegate& delegate,
                                 const std::shared_ptr<IOSContext>& context,
                                 __weak FlutterPlatformViewsController* platform_views_controller,
                                 const flutter::TaskRunners& task_runners)
    : PlatformView(delegate, task_runners),
      ios_context_(context),
      platform_views_controller_(platform_views_controller),
      accessibility_bridge_([this](bool enabled) { PlatformView::SetSemanticsEnabled(enabled); }),
      /// 从初始化列表中可以看出platform_message_handler_的值通过GetPlatformTaskRunner获取UI的主线程
      platform_message_handler_(
          new PlatformMessageHandlerIos(task_runners.GetPlatformTaskRunner())) {}

到这里Flutter的插件的原生代码部分已经将channel,回调,执行队列(可选)给原生平台的主线程。

获取可用相机列表

dart 复制代码
/// Dart端调用获取可用摄像头列表
final cameras = await availableCameras();

文件: camera_controller.dart

dart 复制代码
/// Completes with a list of available cameras.
///
/// May throw a [CameraException].
Future<List<CameraDescription>> availableCameras() async {
  return CameraPlatform.instance.availableCameras();
}

调用CameraPlatform对象的availableCameras方法

文件: camera_platform.dart

dart 复制代码
abstract class CameraPlatform extends PlatformInterface {
  /// Constructs a CameraPlatform.
  CameraPlatform() : super(token: _token);
  ...
  /// Completes with a list of available cameras.
  ///
  /// This method returns an empty list when no cameras are available.
  Future<List<CameraDescription>> availableCameras() {
    throw UnimplementedError('availableCameras() is not implemented.');
  }

CameraPlatform是个抽象类,要找具体的实现。找到camera插件的pubspec.yaml

yaml 复制代码
flutter:
  plugin:
    implements: camera
    platforms:
      ios:
        pluginClass: CameraPlugin
        dartPluginClass: AVFoundationCamera

dartPluginClass: Optional. The Dart class that serves as the entry point for a Flutter plugin. This can be used with the Android, iOS, Linux macOS, and Windows platforms.

因此camera插件dart的入口应该是AVFoundationCamera这个类,它继承了上面的CameraPlatform抽象类

文件: avfoundation_camera.dart

dart 复制代码
class AVFoundationCamera extends CameraPlatform {
  /// Creates a new AVFoundation-based [CameraPlatform] implementation instance.
  AVFoundationCamera({@visibleForTesting CameraApi? api})
      : _hostApi = api ?? CameraApi();
  ...
  @override
  Future<List<CameraDescription>> availableCameras() async {
    try {
      return (await _hostApi.getAvailableCameras())
          .map(cameraDescriptionFromPlatform)
          .toList();
    } on PlatformException catch (e) {
      throw CameraException(e.code, e.message);
    }
  }

从上面代码可知实际调用的是_hostApigetAvailableCameras方法

dart 复制代码
Future<List<PlatformCameraDescription>> getAvailableCameras() async {
    /// 前面建立channel时已经传入了,这样原生执行完相关方法后能通过channel调用对应的回调
    final String pigeonVar_channelName =
        'dev.flutter.pigeon.camera_avfoundation.CameraApi.getAvailableCameras$pigeonVar_messageChannelSuffix';
    
    /// A named channel for communicating with platform plugins using asynchronous message passing.
    /// 创建异步消息
    final BasicMessageChannel<Object?> pigeonVar_channel =
        BasicMessageChannel<Object?>(
      pigeonVar_channelName,
      pigeonChannelCodec,
      binaryMessenger: pigeonVar_binaryMessenger,
    );
    /// 阻塞**发送消息**
    final List<Object?>? pigeonVar_replyList =
        await pigeonVar_channel.send(null) as List<Object?>?;
    
    /// 消息为空抛异常
    if (pigeonVar_replyList == null) {
      throw _createConnectionError(pigeonVar_channelName);
    } else if (pigeonVar_replyList.length > 1) {
      throw PlatformException(
        code: pigeonVar_replyList[0]! as String,
        message: pigeonVar_replyList[1] as String?,
        details: pigeonVar_replyList[2],
      );
    } else if (pigeonVar_replyList[0] == null) {
      throw PlatformException(
        code: 'null-error',
        message: 'Host platform returned null value for non-null return value.',
      );
    } else {
      /// 得到可用列表转换为Flutter上的摄像头描述的对象
      return (pigeonVar_replyList[0] as List<Object?>?)!
          .cast<PlatformCameraDescription>();
    }
  }
await pigeonVar_channel.send(null) as List<Object?>?;
dart 复制代码
/// Sends the specified [message] to the platform plugins on this channel.
  ///
  /// Returns a [Future] which completes to the received response, which may
  /// be null.
  Future<T?> send(T message) async {
    /// 调用binaryMessenger对象编码发送消息 && 得到返回后再解码
    return codec.decodeMessage(await binaryMessenger.send(name, codec.encodeMessage(message)));
  }

Flutter Dart端发送指定channel消息 && 调用原来添加的回调走到Flutter原生插件部分这里,比如当前走到获取可用摄像头的回调

swift 复制代码
...
[api availableCamerasWithCompletion:^(
                 NSArray<FCPPlatformCameraDescription *> *_Nullable output,
                 FlutterError *_Nullable error) {
          callback(wrapResult(output, error));
        }];

即调用api(CameraPlugin)的availableCamerasWithCompletion:方法

swift 复制代码
extension CameraPlugin: FCPCameraApi {
  public func availableCameras(
    completion: @escaping ([FCPPlatformCameraDescription]?, FlutterError?) -> Void
  ) {
    captureSessionQueue.async { [weak self] in
      guard let strongSelf = self else { return }

      var discoveryDevices: [AVCaptureDevice.DeviceType] = [
        .builtInWideAngleCamera,
        .builtInTelephotoCamera,
      ]
      ...
      /// 前置,后置等摄像头,然后统一添加到reply这个数组对象
      for device in devices {
        var lensFacing: FCPPlatformCameraLensDirection

        switch device.position {
        case .back:
          lensFacing = .back
        case .front:
          lensFacing = .front
        case .unspecified:
      ...
        }
        reply.append(cameraDescription)
      }
      
      /// 最后执行callback(wrapResult(output, error));
      /// 获取成功后,Flutter的Dart部分可以获取到可用的摄像头列表
      completion(reply, nil)
}

总结

  1. 原生插件将方法的channel(约定的字符串),回调设置给Flutter原生平台代码(C++ && OC)
  2. Flutter的Dart层发送消息并传递channel,Flutter根据channel找到设置的回调(Dart -> C++ && OC),然后执行原生插件提供的方法(C++ && OC)
  3. 获取执行结果并返回给Flutter的原生平台代码(C++ && OC),再发送给Dart代码(C++ && OC -> Dart)

参考

  1. camera
  2. 使用 Camera 插件实现拍照功能
  3. do-not-use-buildcontexts-across-async-gaps
  4. Flutter pubspec options
  5. 一张图理解Flutter中Dart与原生环境通信
相关推荐
liulian09161 小时前
Flutter for OpenHarmony 跨平台开发:单位转换功能实战指南
flutter
千码君20162 小时前
Trae:一些关于flutter和 go前后端开发构建的分享
android·flutter·gradle·android-studio·trae·vibe code
maaath3 小时前
【maaath】Flutter for OpenHarmony 手表配饰应用实战开发
flutter·华为·harmonyos
maaath4 小时前
【maaath】Flutter for OpenHarmony 跨平台计算器应用开发实践
flutter·华为·harmonyos
空中海6 小时前
iOS 动态分析、抓包与 Frida Hook
ios·职场和发展·蓝桥杯
maaath9 小时前
【maaath】Flutter for OpenHarmony 闹钟时钟应用开发实战
flutter·华为·harmonyos
maaath9 小时前
【maaath】Flutter for OpenHarmony 短信管理应用实战
flutter·华为·harmonyos
空中海10 小时前
iOS 静态逆向、IPA 结构与 Mach-O 分析
ios·华为·harmonyos
Mr -老鬼10 小时前
EasyClick 双端自动化智能体|Android&iOS 全平台 EC 脚本开发助手
android·ios·自动化·易点云测·#easyclick·#ios自动化
maaath10 小时前
【maaath】Flutter for OpenHarmony打造跨平台便签备忘录应用
flutter·华为·harmonyos