Platform Channel (iOS)

1. 基本使用

Basic Message Channel

  final BasicMessageChannel _bmc = const BasicMessageChannel('basic_message', StringCodec());
  String _string = 'null';
  _requestMesssage() async {
    // 這種方式會通過原生端調(diào)用 `callback` 返回消息, 不會觸發(fā) `setMessageHandler`
    _string = await _bmc.send('i need a string');
    setState(() {});
  }

  _handleMessage() {
    // 原生端通過`bmc.send()`觸發(fā)
    _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);
    }];
    
    // 主動發(fā)送消息 -- 需要等待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調(diào)用flutter端 - 這里的調(diào)用不觸發(fā), 因為沒有任何對象持有`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調(diào)用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的數(shù)據(jù)傳遞都是通過BinaryMessenger+Codec,在運行時BinaryMessenger的實現(xiàn)是_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的主要區(qū)別是編解碼器不同,同時后者會調(diào)用_handleAsMethodCall晦炊,其目的是將二進制數(shù)據(jù)轉(zhuǎn)化為MethodCall類型。

_DefaultBinaryMessenger中,可以看到ui.PlatformDispatcher.instance.sendPlatformMessage唉俗,是Basic Message ChannelsendMethod ChannelinvokeMethod的后續(xù)調(diào)用,最終調(diào)用的是C++配椭。

ui.channelBuffers.setListener則是setMessageHandler(Basic Message Channel)和setMethodCallHandler(Method Channel)的后續(xù)調(diào)用虫溜。這里將我們傳入的handler包裝在ChannelCallback內(nèi),然后推入ui.channelBuffers股缸,當有消息從原生端傳過來衡楞,ui.PlatformDispatcher會去ui.channelBuffers查詢對應的Channel,從中觸發(fā)回調(diào)敦姻。

·2. Event Channel

Event Channel其實就是對Method Channel的封裝瘾境。在使用Event Channel的使用調(diào)用了receiveBroadcastStream方法,內(nèi)部代碼如下

  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回調(diào)會在Stream首次被訂閱的時候回調(diào)寄雀。所以當我們在外部運行_ec.receiveBroadcastStream().listen(_onEvent);Stream產(chǎn)生的訂閱,于是onListen被調(diào)用陨献。然后在onListen中調(diào)用:

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

并在Method Channel的回調(diào)中將原生端返回的數(shù)據(jù)注入Stream

controller.add(codec.decodeEnvelope(reply))

onCancel則在Stream沒有訂閱者時被調(diào)用盒犹,在這里停止Method Channel

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

end

精力有限,有誤請指正眨业。

?著作權歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末急膀,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子龄捡,更是在濱河造成了極大的恐慌卓嫂,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,324評論 6 498
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件聘殖,死亡現(xiàn)場離奇詭異晨雳,居然都是意外死亡,警方通過查閱死者的電腦和手機奸腺,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,356評論 3 392
  • 文/潘曉璐 我一進店門餐禁,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人突照,你說我怎么就攤上這事帮非。” “怎么了?”我有些...
    開封第一講書人閱讀 162,328評論 0 353
  • 文/不壞的土叔 我叫張陵末盔,是天一觀的道長筑舅。 經(jīng)常有香客問我,道長陨舱,這世上最難降的妖魔是什么翠拣? 我笑而不...
    開封第一講書人閱讀 58,147評論 1 292
  • 正文 為了忘掉前任,我火速辦了婚禮游盲,結(jié)果婚禮上心剥,老公的妹妹穿的比我還像新娘。我一直安慰自己背桐,他們只是感情好,可當我...
    茶點故事閱讀 67,160評論 6 388
  • 文/花漫 我一把揭開白布蝉揍。 她就那樣靜靜地躺著链峭,像睡著了一般。 火紅的嫁衣襯著肌膚如雪又沾。 梳的紋絲不亂的頭發(fā)上弊仪,一...
    開封第一講書人閱讀 51,115評論 1 296
  • 那天,我揣著相機與錄音杖刷,去河邊找鬼励饵。 笑死,一個胖子當著我的面吹牛滑燃,可吹牛的內(nèi)容都是我干的役听。 我是一名探鬼主播,決...
    沈念sama閱讀 40,025評論 3 417
  • 文/蒼蘭香墨 我猛地睜開眼表窘,長吁一口氣:“原來是場噩夢啊……” “哼典予!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起乐严,我...
    開封第一講書人閱讀 38,867評論 0 274
  • 序言:老撾萬榮一對情侶失蹤瘤袖,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后昂验,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體捂敌,經(jīng)...
    沈念sama閱讀 45,307評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,528評論 2 332
  • 正文 我和宋清朗相戀三年既琴,在試婚紗的時候發(fā)現(xiàn)自己被綠了占婉。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,688評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡甫恩,死狀恐怖锐涯,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情填物,我是刑警寧澤纹腌,帶...
    沈念sama閱讀 35,409評論 5 343
  • 正文 年R本政府宣布霎终,位于F島的核電站,受9級特大地震影響升薯,放射性物質(zhì)發(fā)生泄漏莱褒。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,001評論 3 325
  • 文/蒙蒙 一涎劈、第九天 我趴在偏房一處隱蔽的房頂上張望广凸。 院中可真熱鬧,春花似錦蛛枚、人聲如沸谅海。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,657評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽扭吁。三九已至,卻和暖如春盲镶,著一層夾襖步出監(jiān)牢的瞬間侥袜,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 32,811評論 1 268
  • 我被黑心中介騙來泰國打工溉贿, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留枫吧,地道東北人。 一個月前我還...
    沈念sama閱讀 47,685評論 2 368
  • 正文 我出身青樓宇色,卻偏偏與公主長得像九杂,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子宣蠕,可洞房花燭夜當晚...
    茶點故事閱讀 44,573評論 2 353

推薦閱讀更多精彩內(nèi)容