Platform Channel (iOS)

1. 基本使用

Basic Message Channel

  final BasicMessageChannel _bmc = const BasicMessageChannel('basic_message', StringCodec());
  String _string = 'null';
  _requestMesssage() async {
    // 这种方式会通过原生端调用 `callback` 返回消息, 不会触发 `setMessageHandler`
    _string = await _bmc.send('i need a string');
    setState(() {});
  }

  _handleMessage() {
    // 原生端通过`bmc.send()`触发
    _bmc.setMessageHandler((message) async {
      setState(() {
        if (message is String) {
          _string = message;
        }
      });
      return 'i\' dart side, i receive a message: $message';
    });
  }

  @override
  void initState() {
    super.initState();
    _handleMessage();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Basic Message Channel'),
      ),
      body: child: Text(_string),
      floatingActionButton: ElevatedButton(
        onPressed: () {
          _requestMesssage();
        },
        child: const Text('request message'),
      ),
    );
  }

Method Channel

  final MethodChannel _mc = const MethodChannel('method_channel');
  bool _changed = false;
  String _message = 'null';
  _invokeMethod() async {
    _message = await _mc.invokeMethod('getString', 1321);
    setState(() {});
  }

  _handleMethod() {
    _mc.setMethodCallHandler((call) async {
      switch (call.method) {
        case 'changeColor':
          setState(() {
            _changed = !_changed;
            print(call.arguments);
          });
          break;
        default:
      }
    });
  }

  @override
  void initState() {
    super.initState();
    _handleMethod();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Method Channle'),
      ),
      body: Container(
              width: 200,
              height: 100,
              color: _changed ? Colors.red : Colors.blue,
              child: Text(_message),
            ),
      floatingActionButton: ElevatedButton(
          onPressed: () {
            _invokeMethod();
          },
          child: const Text('invoke method')),
    );
  }

Event Channel

  final EventChannel _ec = const EventChannel('event_channel');
  int _num = 0;

  _onEvent(event) {
    if (event is int) {
      print(event);
      setState(() {
        _num = event;
      });
    }
  }

  late Stream _stream;
  @override
  void initState() {
    super.initState();
    _ec.receiveBroadcastStream().listen(_onEvent);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('Method Channle'),
      ),
      body: Container(
              width: 200,
              height: 100,
              color: Colors.blue,
              child: Text('$_num'),
            ),
      floatingActionButton: ElevatedButton(
          onPressed: () {
            _stream.listen(_onEvent);
          },
          child: const Text('invoke method')),
    );
  }

iOS代码

#import "AppDelegate.h"
#import "GeneratedPluginRegistrant.h"

@interface EventHander : NSObject<FlutterStreamHandler>
@property (nonatomic, strong, nullable)FlutterEventSink events;
@property (nonatomic, assign)int count;
@end

@implementation EventHander

- (instancetype)init {
    NSLog(@"event hander init");
    if (self = [super init]) {
        [self startTimer];
    }
    return self;
}

- (void)startTimer {
    [NSTimer scheduledTimerWithTimeInterval:2 repeats:true block:^(NSTimer * _Nonnull timer) {
        self.count++;
        if (self.events != nil) {
            self.events([[NSNumber alloc] initWithInt:self.count]);
        }
    }];
}

- (FlutterError * _Nullable)onCancelWithArguments:(id _Nullable)arguments {
    NSLog(@"%@", arguments);
    self.events = nil;
    return nil;
}

- (FlutterError * _Nullable)onListenWithArguments:(id _Nullable)arguments eventSink:(nonnull FlutterEventSink)events {
    NSLog(@"%@", arguments);
    self.events = events;
    return nil;
}

- (void)dealloc {
    NSLog(@"dealloc");
}

@end

@implementation AppDelegate

- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
    
    FlutterViewController *rootVC = (FlutterViewController *)self.window.rootViewController;
    
    // MARK: Basic Message Channel
    FlutterBasicMessageChannel *bmc = [[FlutterBasicMessageChannel alloc] initWithName:@"basic_message" binaryMessenger:rootVC.binaryMessenger codec:[FlutterStringCodec sharedInstance]];
    
    // 收到消息后回复
    [bmc setMessageHandler:^(id  _Nullable message, FlutterReply  _Nonnull callback) {
        NSString *callbackStr = [NSString stringWithFormat:@"i revice a message: %@", message];
        callback(callbackStr);
    }];
    
    // 主动发送消息 -- 需要等待flutter端初始化platform Channel
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 3*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
        [bmc sendMessage:@"i'm ios side, i send this message"];
    });
    
    // MARK: Method Channel
    FlutterMethodChannel *mc = [[FlutterMethodChannel alloc] initWithName:@"method_channel" binaryMessenger:rootVC.binaryMessenger codec:[FlutterStandardMethodCodec sharedInstance]];
    __weak typeof(mc) w_mc = mc;
    [mc setMethodCallHandler:^(FlutterMethodCall * _Nonnull call, FlutterResult  _Nonnull result) {
        __strong typeof(w_mc) s_mc = w_mc;
        NSLog(@"w_mc:%@", w_mc);
        NSLog(@"s_mc:%@", s_mc);
        if ([call.method isEqualToString:@"getString"]) {
            result([NSString stringWithFormat:@"%@", call.arguments]);
        }
        // ios调用flutter端 - 这里的调用不触发, 因为没有任何对象持有`mc`, 在`didFinishLaunchingWithOptions` 运行完毕后 `mc`会被释放
        [s_mc invokeMethod:@"changeColor" arguments:nil];
    }];
    dispatch_after(dispatch_time(DISPATCH_TIME_NOW, 5*NSEC_PER_SEC), dispatch_get_main_queue(), ^{
        // ios调用flutter端
        [mc invokeMethod:@"changeColor" arguments:nil];
    });
    
    // MARK: Event Channel
    /*
     EventChannel 是对 MethodChannel和Stream的封装,
     */
    FlutterEventChannel *ec = [FlutterEventChannel eventChannelWithName:@"event_channel" binaryMessenger:rootVC.binaryMessenger];
    [ec setStreamHandler:[[EventHander alloc] init]];
    
    
    [GeneratedPluginRegistrant registerWithRegistry:self];
    return [super application:application didFinishLaunchingWithOptions:launchOptions];
}

@end

2. 原理

·1. Basic Message Channel & Method Channel

Platform Channel的数据传递都是通过BinaryMessenger+Codec,在运行时BinaryMessenger的实现是_DefaultBinaryMessenger,源码位于binding.dart文件

import 'dart:ui' as ui;
// ......
class _DefaultBinaryMessenger extends BinaryMessenger {
  const _DefaultBinaryMessenger._();

  @override
  Future<void> handlePlatformMessage(
    String channel,
    ByteData? message,
    ui.PlatformMessageResponseCallback? callback,
  ) async {
    ui.channelBuffers.push(channel, message, (ByteData? data) {
      if (callback != null)
        callback(data);
    });
  }

  @override
  Future<ByteData?> send(String channel, ByteData? message) {
    final Completer<ByteData?> completer = Completer<ByteData?>();
    ui.PlatformDispatcher.instance.sendPlatformMessage(channel, message, (ByteData? reply) {
      try {
        completer.complete(reply);
      } catch (exception, stack) {
        FlutterError.reportError(FlutterErrorDetails(
          exception: exception,
          stack: stack,
          library: 'services library',
          context: ErrorDescription('during a platform message response callback'),
        ));
      }
    });
    return completer.future;
  }

  @override
  void setMessageHandler(String channel, MessageHandler? handler) {
    if (handler == null) {
      ui.channelBuffers.clearListener(channel);
    } else {
      ui.channelBuffers.setListener(channel, (ByteData? data, ui.PlatformMessageResponseCallback callback) async {
        ByteData? response;
        try {
          response = await handler(data);
        } catch (exception, stack) {
          FlutterError.reportError(FlutterErrorDetails(
            exception: exception,
            stack: stack,
            library: 'services library',
            context: ErrorDescription('during a platform message callback'),
          ));
        } finally {
          callback(response);
        }
      });
    }
  }
}

可通过import 'package:flutter/services.dart';找到export 'src/services/binding.dart';阅读上面的代码。

Basic Message ChannelMethod Channel的主要区别是编解码器不同,同时后者会调用_handleAsMethodCall,其目的是将二进制数据转化为MethodCall类型。

_DefaultBinaryMessenger中,可以看到ui.PlatformDispatcher.instance.sendPlatformMessage,是Basic Message ChannelsendMethod ChannelinvokeMethod的后续调用,最终调用的是C++

ui.channelBuffers.setListener则是setMessageHandler(Basic Message Channel)和setMethodCallHandler(Method Channel)的后续调用。这里将我们传入的handler包装在ChannelCallback内,然后推入ui.channelBuffers,当有消息从原生端传过来,ui.PlatformDispatcher会去ui.channelBuffers查询对应的Channel,从中触发回调。

·2. Event Channel

Event Channel其实就是对Method Channel的封装。在使用Event Channel的使用调用了receiveBroadcastStream方法,内部代码如下

  Stream<dynamic> receiveBroadcastStream([dynamic arguments]) {
    String name = 'event_channel';
    MethodCodec codec = const StandardMethodCodec();
    MethodChannel methodChannel = MethodChannel(name, codec);
    BinaryMessenger binaryMessenger = ServicesBinding.instance.defaultBinaryMessenger;
    late StreamController<dynamic> controller;
    controller = StreamController<dynamic>.broadcast(onListen: () async {
      binaryMessenger.setMessageHandler(name, (ByteData? reply) async {
        if (reply == null) {
          controller.close();
        } else {
          try {
            controller.add(codec.decodeEnvelope(reply));
          } on PlatformException catch (e) {
            controller.addError(e);
          }
        }
        return null;
      });
      try {
        await methodChannel.invokeMethod<void>('listen', arguments);
      } catch (exception, stack) {
        FlutterError.reportError(FlutterErrorDetails(
          exception: exception,
          stack: stack,
          library: 'services library',
          context: ErrorDescription('while activating platform stream on channel $name'),
        ));
      }
    }, onCancel: () async {
      binaryMessenger.setMessageHandler(name, null);
      try {
        await methodChannel.invokeMethod<void>('cancel', arguments);
      } catch (exception, stack) {
        FlutterError.reportError(FlutterErrorDetails(
          exception: exception,
          stack: stack,
          library: 'services library',
          context: ErrorDescription('while de-activating platform stream on channel $name'),
        ));
      }
    });
    return controller.stream;
  }

先将代码简化如下:

late StreamController<dynamic> controller;
controller = StreamController<dynamic>.broadcast(onListen: () async {
  print('on listen');
}, onCancel: () async {
  print('on cancel');
});
return controller.stream;

这里的重点就是broadcast方法,其onListen回调会在Stream首次被订阅的时候回调。所以当我们在外部运行_ec.receiveBroadcastStream().listen(_onEvent);Stream产生的订阅,于是onListen被调用。然后在onListen中调用:

await methodChannel.invokeMethod<void>('listen', arguments)

并在Method Channel的回调中将原生端返回的数据注入Stream

controller.add(codec.decodeEnvelope(reply))

onCancel则在Stream没有订阅者时被调用,在这里停止Method Channel

binaryMessenger.setMessageHandler(name, null);
try {
  await methodChannel.invokeMethod<void>('cancel', arguments);
} catch (exception, stack) {
  ......
}

end

精力有限,有误请指正。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,723评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,080评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,604评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,440评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,431评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,499评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,893评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,541评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,751评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,547评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,619评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,320评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,890评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,896评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,137评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,796评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,335评论 2 342

推荐阅读更多精彩内容