1 | // Copyright 2014 The Flutter Authors. All rights reserved. |
---|---|
2 | // Use of this source code is governed by a BSD-style license that can be |
3 | // found in the LICENSE file. |
4 | |
5 | import 'dart:async'; |
6 | import 'dart:developer'; |
7 | |
8 | import 'package:flutter/foundation.dart'; |
9 | |
10 | import '_background_isolate_binary_messenger_io.dart' |
11 | if (dart.library.js_util) '_background_isolate_binary_messenger_web.dart'; |
12 | |
13 | import 'binary_messenger.dart'; |
14 | import 'binding.dart'; |
15 | import 'debug.dart'; |
16 | import 'message_codec.dart'; |
17 | import 'message_codecs.dart'; |
18 | |
19 | export '_background_isolate_binary_messenger_io.dart' |
20 | if (dart.library.js_util) '_background_isolate_binary_messenger_web.dart'; |
21 | |
22 | export 'binary_messenger.dart' show BinaryMessenger; |
23 | export 'binding.dart' show RootIsolateToken; |
24 | export 'message_codec.dart' show MessageCodec, MethodCall, MethodCodec; |
25 | |
26 | /// Profile and print statistics on Platform Channel usage. |
27 | /// |
28 | /// When this is true statistics about the usage of Platform Channels will be |
29 | /// printed out periodically to the console and Timeline events will show the |
30 | /// time between sending and receiving a message (encoding and decoding time |
31 | /// excluded). |
32 | /// |
33 | /// The statistics include the total bytes transmitted and the average number of |
34 | /// bytes per invocation in the last quantum. "Up" means in the direction of |
35 | /// Flutter to the host platform, "down" is the host platform to flutter. |
36 | bool get shouldProfilePlatformChannels => |
37 | kProfilePlatformChannels || (!kReleaseMode && debugProfilePlatformChannels); |
38 | |
39 | /// Controls whether platform channel usage can be debugged in release mode. |
40 | /// |
41 | /// See also: |
42 | /// |
43 | /// * [shouldProfilePlatformChannels], which checks both |
44 | /// [kProfilePlatformChannels] and [debugProfilePlatformChannels] for the |
45 | /// current run mode. |
46 | /// * [debugProfilePlatformChannels], which determines whether platform |
47 | /// channel usage can be debugged in non-release mode. |
48 | const bool kProfilePlatformChannels = false; |
49 | |
50 | bool _profilePlatformChannelsIsRunning = false; |
51 | const Duration _profilePlatformChannelsRate = Duration(seconds: 1); |
52 | final Expando<BinaryMessenger> _profiledBinaryMessengers = Expando<BinaryMessenger>(); |
53 | |
54 | class _ProfiledBinaryMessenger implements BinaryMessenger { |
55 | const _ProfiledBinaryMessenger(this.proxy, this.channelTypeName, this.codecTypeName); |
56 | final BinaryMessenger proxy; |
57 | final String channelTypeName; |
58 | final String codecTypeName; |
59 | |
60 | @override |
61 | Future<void> handlePlatformMessage( |
62 | String channel, |
63 | ByteData? data, |
64 | PlatformMessageResponseCallback? callback, |
65 | ) { |
66 | return proxy.handlePlatformMessage(channel, data, callback); |
67 | } |
68 | |
69 | Future<ByteData?>? sendWithPostfix(String channel, String postfix, ByteData? message) async { |
70 | _debugRecordUpStream(channelTypeName, '$channel $postfix', codecTypeName, message); |
71 | final TimelineTask timelineTask = |
72 | TimelineTask()..start('Platform Channel send$channel$postfix'); |
73 | final ByteData? result; |
74 | try { |
75 | result = await proxy.send(channel, message); |
76 | } finally { |
77 | timelineTask.finish(); |
78 | } |
79 | _debugRecordDownStream(channelTypeName,'$channel$postfix', codecTypeName, result); |
80 | return result; |
81 | } |
82 | |
83 | @override |
84 | Future<ByteData?>? send(String channel, ByteData? message) => |
85 | sendWithPostfix(channel,'', message); |
86 | |
87 | @override |
88 | void setMessageHandler(String channel, MessageHandler? handler) { |
89 | proxy.setMessageHandler(channel, handler); |
90 | } |
91 | } |
92 | |
93 | class _PlatformChannelStats { |
94 | _PlatformChannelStats(this.channel, this.codec, this.type); |
95 | |
96 | final String channel; |
97 | final String codec; |
98 | final String type; |
99 | |
100 | int _upCount = 0; |
101 | int _upBytes = 0; |
102 | int get upBytes => _upBytes; |
103 | void addUpStream(int bytes) { |
104 | _upCount += 1; |
105 | _upBytes += bytes; |
106 | } |
107 | |
108 | int _downCount = 0; |
109 | int _downBytes = 0; |
110 | int get downBytes => _downBytes; |
111 | void addDownStream(int bytes) { |
112 | _downCount += 1; |
113 | _downBytes += bytes; |
114 | } |
115 | |
116 | double get averageUpPayload => _upBytes / _upCount; |
117 | double get averageDownPayload => _downBytes / _downCount; |
118 | } |
119 | |
120 | final Map<String, _PlatformChannelStats> _profilePlatformChannelsStats = |
121 | <String, _PlatformChannelStats>{}; |
122 | |
123 | Future<void> _debugLaunchProfilePlatformChannels() async { |
124 | if (!_profilePlatformChannelsIsRunning) { |
125 | _profilePlatformChannelsIsRunning = true; |
126 | await Future<dynamic>.delayed(_profilePlatformChannelsRate); |
127 | _profilePlatformChannelsIsRunning = false; |
128 | final StringBuffer log = StringBuffer(); |
129 | log.writeln('Platform Channel Stats:'); |
130 | final List<_PlatformChannelStats> allStats = _profilePlatformChannelsStats.values.toList(); |
131 | // Sort highest combined bandwidth first. |
132 | allStats.sort( |
133 | (_PlatformChannelStats x, _PlatformChannelStats y) => |
134 | (y.upBytes + y.downBytes) - (x.upBytes + x.downBytes), |
135 | ); |
136 | for (final _PlatformChannelStats stats in allStats) { |
137 | log.writeln( |
138 | ' (name:"${stats.channel}" type:"${stats.type}" codec:"${stats.codec}" upBytes:${stats.upBytes}upBytes_avg:${stats.averageUpPayload.toStringAsFixed(1)}downBytes:${stats.downBytes}downBytes_avg:${stats.averageDownPayload.toStringAsFixed(1)})', |
139 | ); |
140 | } |
141 | debugPrint(log.toString()); |
142 | _profilePlatformChannelsStats.clear(); |
143 | } |
144 | } |
145 | |
146 | void _debugRecordUpStream( |
147 | String channelTypeName, |
148 | String name, |
149 | String codecTypeName, |
150 | ByteData? bytes, |
151 | ) { |
152 | final _PlatformChannelStats stats = |
153 | _profilePlatformChannelsStats[name] ??= _PlatformChannelStats( |
154 | name, |
155 | codecTypeName, |
156 | channelTypeName, |
157 | ); |
158 | stats.addUpStream(bytes?.lengthInBytes ?? 0); |
159 | _debugLaunchProfilePlatformChannels(); |
160 | } |
161 | |
162 | void _debugRecordDownStream( |
163 | String channelTypeName, |
164 | String name, |
165 | String codecTypeName, |
166 | ByteData? bytes, |
167 | ) { |
168 | final _PlatformChannelStats stats = |
169 | _profilePlatformChannelsStats[name] ??= _PlatformChannelStats( |
170 | name, |
171 | codecTypeName, |
172 | channelTypeName, |
173 | ); |
174 | stats.addDownStream(bytes?.lengthInBytes ?? 0); |
175 | _debugLaunchProfilePlatformChannels(); |
176 | } |
177 | |
178 | BinaryMessenger _findBinaryMessenger() { |
179 | return !kIsWeb && ServicesBinding.rootIsolateToken == null |
180 | ? BackgroundIsolateBinaryMessenger.instance |
181 | : ServicesBinding.instance.defaultBinaryMessenger; |
182 | } |
183 | |
184 | /// A named channel for communicating with platform plugins using asynchronous |
185 | /// message passing. |
186 | /// |
187 | /// Messages are encoded into binary before being sent, and binary messages |
188 | /// received are decoded into Dart values. The [MessageCodec] used must be |
189 | /// compatible with the one used by the platform plugin. This can be achieved |
190 | /// by creating a basic message channel counterpart of this channel on the |
191 | /// platform side. The Dart type of messages sent and received is [T], |
192 | /// but only the values supported by the specified [MessageCodec] can be used. |
193 | /// The use of unsupported values should be considered programming errors, and |
194 | /// will result in exceptions being thrown. The null message is supported |
195 | /// for all codecs. |
196 | /// |
197 | /// The logical identity of the channel is given by its name. Identically named |
198 | /// channels will interfere with each other's communication. |
199 | /// |
200 | /// All [BasicMessageChannel]s provided by the Flutter framework guarantee FIFO |
201 | /// ordering. Applications can assume messages sent via a built-in |
202 | /// [BasicMessageChannel] are delivered in the same order as they're sent. |
203 | /// |
204 | /// See: <https://flutter.dev/to/platform-channels/> |
205 | class BasicMessageChannel<T> { |
206 | /// Creates a [BasicMessageChannel] with the specified [name], [codec] and |
207 | /// [binaryMessenger]. |
208 | /// |
209 | /// The default [ServicesBinding.defaultBinaryMessenger] instance is used if |
210 | /// [binaryMessenger] is null. |
211 | const BasicMessageChannel(this.name, this.codec, {BinaryMessenger? binaryMessenger}) |
212 | : _binaryMessenger = binaryMessenger; |
213 | |
214 | /// The logical channel on which communication happens, not null. |
215 | final String name; |
216 | |
217 | /// The message codec used by this channel, not null. |
218 | final MessageCodec<T> codec; |
219 | |
220 | /// The messenger which sends the bytes for this channel. |
221 | /// |
222 | /// On the root isolate or web, this defaults to the |
223 | /// [ServicesBinding.defaultBinaryMessenger]. In other contexts the default |
224 | /// value is a [BackgroundIsolateBinaryMessenger] from |
225 | /// [BackgroundIsolateBinaryMessenger.ensureInitialized]. |
226 | BinaryMessenger get binaryMessenger { |
227 | final BinaryMessenger result = _binaryMessenger ?? _findBinaryMessenger(); |
228 | return shouldProfilePlatformChannels |
229 | ? _profiledBinaryMessengers[this] ??= _ProfiledBinaryMessenger( |
230 | result, |
231 | runtimeType.toString(), // ignore: no_runtimetype_tostring |
232 | codec.runtimeType.toString(), |
233 | ) |
234 | : result; |
235 | } |
236 | |
237 | final BinaryMessenger? _binaryMessenger; |
238 | |
239 | /// Sends the specified [message] to the platform plugins on this channel. |
240 | /// |
241 | /// Returns a [Future] which completes to the received response, which may |
242 | /// be null. |
243 | Future<T?> send(T message) async { |
244 | return codec.decodeMessage(await binaryMessenger.send(name, codec.encodeMessage(message))); |
245 | } |
246 | |
247 | /// Sets a callback for receiving messages from the platform plugins on this |
248 | /// channel. Messages may be null. |
249 | /// |
250 | /// The given callback will replace the currently registered callback for this |
251 | /// channel, if any. To remove the handler, pass null as the `handler` |
252 | /// argument. |
253 | /// |
254 | /// The handler's return value is sent back to the platform plugins as a |
255 | /// message reply. It may be null. |
256 | void setMessageHandler(Future<T> Function(T? message)? handler) { |
257 | if (handler == null) { |
258 | binaryMessenger.setMessageHandler(name, null); |
259 | } else { |
260 | binaryMessenger.setMessageHandler(name, (ByteData? message) async { |
261 | return codec.encodeMessage(await handler(codec.decodeMessage(message))); |
262 | }); |
263 | } |
264 | } |
265 | |
266 | // Looking for setMockMessageHandler? |
267 | // See this shim package: packages/flutter_test/lib/src/deprecated.dart |
268 | } |
269 | |
270 | /// A named channel for communicating with platform plugins using asynchronous |
271 | /// method calls. |
272 | /// |
273 | /// Method calls are encoded into binary before being sent, and binary results |
274 | /// received are decoded into Dart values. The [MethodCodec] used must be |
275 | /// compatible with the one used by the platform plugin. This can be achieved |
276 | /// by creating a method channel counterpart of this channel on the |
277 | /// platform side. The Dart type of arguments and results is `dynamic`, |
278 | /// but only values supported by the specified [MethodCodec] can be used. |
279 | /// The use of unsupported values should be considered programming errors, and |
280 | /// will result in exceptions being thrown. The null value is supported |
281 | /// for all codecs. |
282 | /// |
283 | /// The logical identity of the channel is given by its name. Identically named |
284 | /// channels will interfere with each other's communication. |
285 | /// |
286 | /// {@template flutter.services.method_channel.FIFO} |
287 | /// All [MethodChannel]s provided by the Flutter framework guarantee FIFO |
288 | /// ordering. Applications can assume method calls sent via a built-in |
289 | /// [MethodChannel] are received by the platform plugins in the same order as |
290 | /// they're sent. |
291 | /// {@endtemplate} |
292 | /// |
293 | /// See: <https://flutter.dev/to/platform-channels/> |
294 | @pragma('vm:keep-name') |
295 | class MethodChannel { |
296 | /// Creates a [MethodChannel] with the specified [name]. |
297 | /// |
298 | /// The [codec] used will be [StandardMethodCodec], unless otherwise |
299 | /// specified. |
300 | /// |
301 | /// The default [ServicesBinding.defaultBinaryMessenger] instance is used if |
302 | /// [binaryMessenger] is null. |
303 | const MethodChannel( |
304 | this.name, [ |
305 | this.codec = const StandardMethodCodec(), |
306 | BinaryMessenger? binaryMessenger, |
307 | ]) : _binaryMessenger = binaryMessenger; |
308 | |
309 | /// The logical channel on which communication happens, not null. |
310 | final String name; |
311 | |
312 | /// The message codec used by this channel, not null. |
313 | final MethodCodec codec; |
314 | |
315 | /// The messenger which sends the bytes for this channel. |
316 | /// |
317 | /// On the root isolate or web, this defaults to the |
318 | /// [ServicesBinding.defaultBinaryMessenger]. In other contexts the default |
319 | /// value is a [BackgroundIsolateBinaryMessenger] from |
320 | /// [BackgroundIsolateBinaryMessenger.ensureInitialized]. |
321 | BinaryMessenger get binaryMessenger { |
322 | final BinaryMessenger result = _binaryMessenger ?? _findBinaryMessenger(); |
323 | return shouldProfilePlatformChannels |
324 | ? _profiledBinaryMessengers[this] ??= _ProfiledBinaryMessenger( |
325 | result, |
326 | runtimeType.toString(), // ignore: no_runtimetype_tostring |
327 | codec.runtimeType.toString(), |
328 | ) |
329 | : result; |
330 | } |
331 | |
332 | final BinaryMessenger? _binaryMessenger; |
333 | |
334 | /// Backend implementation of [invokeMethod]. |
335 | /// |
336 | /// The `method` and `arguments` arguments are used to create a [MethodCall] |
337 | /// object that is passed to the [codec]'s [MethodCodec.encodeMethodCall] |
338 | /// method. The resulting message is then sent to the embedding using the |
339 | /// [binaryMessenger]'s [BinaryMessenger.send] method. |
340 | /// |
341 | /// If the result is null and `missingOk` is true, this returns null. (This is |
342 | /// the behavior of [OptionalMethodChannel.invokeMethod].) |
343 | /// |
344 | /// If the result is null and `missingOk` is false, this throws a |
345 | /// [MissingPluginException]. (This is the behavior of |
346 | /// [MethodChannel.invokeMethod].) |
347 | /// |
348 | /// Otherwise, the result is decoded using the [codec]'s |
349 | /// [MethodCodec.decodeEnvelope] method. |
350 | /// |
351 | /// The `T` type argument is the expected return type. It is treated as |
352 | /// nullable. |
353 | @optionalTypeArgs |
354 | Future<T?> _invokeMethod<T>(String method, {required bool missingOk, dynamic arguments}) async { |
355 | final ByteData input = codec.encodeMethodCall(MethodCall(method, arguments)); |
356 | final ByteData? result = |
357 | shouldProfilePlatformChannels |
358 | ? await (binaryMessenger as _ProfiledBinaryMessenger).sendWithPostfix( |
359 | name, |
360 | '#$method', |
361 | input, |
362 | ) |
363 | : await binaryMessenger.send(name, input); |
364 | if (result == null) { |
365 | if (missingOk) { |
366 | return null; |
367 | } |
368 | throw MissingPluginException('No implementation found for method$methodon channel$name'); |
369 | } |
370 | return codec.decodeEnvelope(result) as T?; |
371 | } |
372 | |
373 | /// Invokes a [method] on this channel with the specified [arguments]. |
374 | /// |
375 | /// The static type of [arguments] is `dynamic`, but only values supported by |
376 | /// the [codec] of this channel can be used. The same applies to the returned |
377 | /// result. The values supported by the default codec and their platform-specific |
378 | /// counterparts are documented with [StandardMessageCodec]. |
379 | /// |
380 | /// The generic argument `T` of the method can be inferred by the surrounding |
381 | /// context, or provided explicitly. If it does not match the returned type of |
382 | /// the channel, a [TypeError] will be thrown at runtime. `T` cannot be a class |
383 | /// with generics other than `dynamic`. For example, `Map<String, String>` |
384 | /// is not supported but `Map<dynamic, dynamic>` or `Map` is. |
385 | /// |
386 | /// Returns a [Future] which completes to one of the following: |
387 | /// |
388 | /// * a result (possibly null), on successful invocation; |
389 | /// * a [PlatformException], if the invocation failed in the platform plugin; |
390 | /// * a [MissingPluginException], if the method has not been implemented by a |
391 | /// platform plugin. |
392 | /// |
393 | /// The following code snippets demonstrate how to invoke platform methods |
394 | /// in Dart using a MethodChannel and how to implement those methods in Java |
395 | /// (for Android) and Objective-C (for iOS). |
396 | /// |
397 | /// {@tool snippet} |
398 | /// |
399 | /// The code might be packaged up as a musical plugin, see |
400 | /// <https://flutter.dev/to/develop-packages>: |
401 | /// |
402 | /// ```dart |
403 | /// abstract final class Music { |
404 | /// static const MethodChannel _channel = MethodChannel('music'); |
405 | /// |
406 | /// static Future<bool> isLicensed() async { |
407 | /// // invokeMethod returns a Future |
408 | /// // the return value is null by treating null as false. |
409 | /// return _channel.invokeMethod<bool>('isLicensed').then<bool>((bool? value) => value ?? false); |
410 | /// } |
411 | /// |
412 | /// static Future<List<Song>> songs() async { |
413 | /// // invokeMethod here returns a Future |
414 | /// // List |
415 | /// // code thus cannot assume e.g. List |
416 | /// // the actual values involved would support such a typed container. |
417 | /// // The correct type cannot be inferred with any value of `T`. |
418 | /// final List<dynamic>? songs = await _channel.invokeMethod<List<dynamic>>('getSongs'); |
419 | /// return songs?.cast<Map<String, Object?>>().map<Song>(Song.fromJson).toList() ?? <Song>[]; |
420 | /// } |
421 | /// |
422 | /// static Future<void> play(Song song, double volume) async { |
423 | /// // Errors occurring on the platform side cause invokeMethod to throw |
424 | /// // PlatformExceptions. |
425 | /// try { |
426 | /// return _channel.invokeMethod('play', <String, dynamic>{ |
427 | /// 'song': song.id, |
428 | /// 'volume': volume, |
429 | /// }); |
430 | /// } on PlatformException catch (e) { |
431 | /// throw ArgumentError('Unable to play ${song.title}: ${e.message}'); |
432 | /// } |
433 | /// } |
434 | /// } |
435 | /// |
436 | /// class Song { |
437 | /// Song(this.id, this.title, this.artist); |
438 | /// |
439 | /// final String id; |
440 | /// final String title; |
441 | /// final String artist; |
442 | /// |
443 | /// static Song fromJson(Map<String, Object?> json) { |
444 | /// return Song(json['id']! as String, json['title']! as String, json['artist']! as String); |
445 | /// } |
446 | /// } |
447 | /// ``` |
448 | /// {@end-tool} |
449 | /// |
450 | /// {@tool snippet} |
451 | /// |
452 | /// Java (for Android): |
453 | /// |
454 | /// ```java |
455 | /// // Assumes existence of an Android MusicApi. |
456 | /// public class MusicPlugin implements MethodCallHandler { |
457 | /// @Override |
458 | /// public void onMethodCall(MethodCall call, Result result) { |
459 | /// switch (call.method) { |
460 | /// case "isLicensed": |
461 | /// result.success(MusicApi.checkLicense()); |
462 | /// break; |
463 | /// case "getSongs": |
464 | /// final List<MusicApi.Track> tracks = MusicApi.getTracks(); |
465 | /// final List<Object> json = ArrayList<>(tracks.size()); |
466 | /// for (MusicApi.Track track : tracks) { |
467 | /// json.add(track.toJson()); // Map |
468 | /// } |
469 | /// result.success(json); |
470 | /// break; |
471 | /// case "play": |
472 | /// final String song = call.argument("song"); |
473 | /// final double volume = call.argument("volume"); |
474 | /// try { |
475 | /// MusicApi.playSongAtVolume(song, volume); |
476 | /// result.success(null); |
477 | /// } catch (MusicalException e) { |
478 | /// result.error("playError", e.getMessage(), null); |
479 | /// } |
480 | /// break; |
481 | /// default: |
482 | /// result.notImplemented(); |
483 | /// } |
484 | /// } |
485 | /// // Other methods elided. |
486 | /// } |
487 | /// ``` |
488 | /// {@end-tool} |
489 | /// |
490 | /// {@tool snippet} |
491 | /// |
492 | /// Objective-C (for iOS): |
493 | /// |
494 | /// ```objectivec |
495 | /// @interface MusicPlugin : NSObject<FlutterPlugin> |
496 | /// @end |
497 | /// |
498 | /// // Assumes existence of an iOS Broadway Play Api. |
499 | /// @implementation MusicPlugin |
500 | /// - (void)handleMethodCall:(FlutterMethodCall*)call result:(FlutterResult)result { |
501 | /// if ([@"isLicensed" isEqualToString:call.method]) { |
502 | /// result([NSNumber numberWithBool:[BWPlayApi isLicensed]]); |
503 | /// } else if ([@"getSongs" isEqualToString:call.method]) { |
504 | /// NSArray* items = [BWPlayApi items]; |
505 | /// NSMutableArray* json = [NSMutableArray arrayWithCapacity:items.count]; |
506 | /// for (final BWPlayItem* item in items) { |
507 | /// [json addObject:@{ @"id":item.itemId, @"title":item.name, @"artist":item.artist }]; |
508 | /// } |
509 | /// result(json); |
510 | /// } else if ([@"play" isEqualToString:call.method]) { |
511 | /// NSString* itemId = call.arguments[@"song"]; |
512 | /// NSNumber* volume = call.arguments[@"volume"]; |
513 | /// NSError* error = nil; |
514 | /// BOOL success = [BWPlayApi playItem:itemId volume:volume.doubleValue error:&error]; |
515 | /// if (success) { |
516 | /// result(nil); |
517 | /// } else { |
518 | /// result([FlutterError errorWithCode:[NSString stringWithFormat:@"Error %ld", error.code] |
519 | /// message:error.domain |
520 | /// details:error.localizedDescription]); |
521 | /// } |
522 | /// } else { |
523 | /// result(FlutterMethodNotImplemented); |
524 | /// } |
525 | /// } |
526 | /// // Other methods elided. |
527 | /// @end |
528 | /// ``` |
529 | /// {@end-tool} |
530 | /// |
531 | /// See also: |
532 | /// |
533 | /// * [invokeListMethod], for automatically returning typed lists. |
534 | /// * [invokeMapMethod], for automatically returning typed maps. |
535 | /// * [StandardMessageCodec] which defines the payload values supported by |
536 | /// [StandardMethodCodec]. |
537 | /// * [JSONMessageCodec] which defines the payload values supported by |
538 | /// [JSONMethodCodec]. |
539 | /// * <https://api.flutter.dev/javadoc/io/flutter/plugin/common/MethodCall.html> |
540 | /// for how to access method call arguments on Android. |
541 | @optionalTypeArgs |
542 | Future<T?> invokeMethod<T>(String method, [dynamic arguments]) { |
543 | return _invokeMethod<T>(method, missingOk: false, arguments: arguments); |
544 | } |
545 | |
546 | /// An implementation of [invokeMethod] that can return typed lists. |
547 | /// |
548 | /// Dart generics are reified, meaning that an untyped `List<dynamic>` cannot |
549 | /// masquerade as a `List<T>`. Since [invokeMethod] can only return dynamic |
550 | /// lists, we instead create a new typed list using [List.cast]. |
551 | /// |
552 | /// See also: |
553 | /// |
554 | /// * [invokeMethod], which this call delegates to. |
555 | Future<List<T>?> invokeListMethod<T>(String method, [dynamic arguments]) async { |
556 | final List<dynamic>? result = await invokeMethod<List<dynamic>>(method, arguments); |
557 | return result?.cast<T>(); |
558 | } |
559 | |
560 | /// An implementation of [invokeMethod] that can return typed maps. |
561 | /// |
562 | /// Dart generics are reified, meaning that an untyped `Map<dynamic, dynamic>` |
563 | /// cannot masquerade as a `Map<K, V>`. Since [invokeMethod] can only return |
564 | /// dynamic maps, we instead create a new typed map using [Map.cast]. |
565 | /// |
566 | /// See also: |
567 | /// |
568 | /// * [invokeMethod], which this call delegates to. |
569 | Future<Map<K, V>?> invokeMapMethod<K, V>(String method, [dynamic arguments]) async { |
570 | final Map<dynamic, dynamic>? result = await invokeMethod<Map<dynamic, dynamic>>( |
571 | method, |
572 | arguments, |
573 | ); |
574 | return result?.cast<K, V>(); |
575 | } |
576 | |
577 | /// Sets a callback for receiving method calls on this channel. |
578 | /// |
579 | /// The given callback will replace the currently registered callback for this |
580 | /// channel, if any. To remove the handler, pass null as the |
581 | /// `handler` argument. |
582 | /// |
583 | /// If the future returned by the handler completes with a result, that value |
584 | /// is sent back to the platform plugin caller wrapped in a success envelope |
585 | /// as defined by the [codec] of this channel. If the future completes with |
586 | /// a [PlatformException], the fields of that exception will be used to |
587 | /// populate an error envelope which is sent back instead. If the future |
588 | /// completes with a [MissingPluginException], an empty reply is sent |
589 | /// similarly to what happens if no method call handler has been set. |
590 | /// Any other exception results in an error envelope being sent. |
591 | void setMethodCallHandler(Future<dynamic> Function(MethodCall call)? handler) { |
592 | assert( |
593 | _binaryMessenger != null || BindingBase.debugBindingType() != null, |
594 | 'Cannot set the method call handler before the binary messenger has been initialized. ' |
595 | 'This happens when you call setMethodCallHandler() before the WidgetsFlutterBinding ' |
596 | 'has been initialized. You can fix this by either calling WidgetsFlutterBinding.ensureInitialized() ' |
597 | 'before this or by passing a custom BinaryMessenger instance to MethodChannel().', |
598 | ); |
599 | binaryMessenger.setMessageHandler( |
600 | name, |
601 | handler == null ? null : (ByteData? message) => _handleAsMethodCall(message, handler), |
602 | ); |
603 | } |
604 | |
605 | Future<ByteData?> _handleAsMethodCall( |
606 | ByteData? message, |
607 | Future<dynamic> Function(MethodCall call) handler, |
608 | ) async { |
609 | final MethodCall call = codec.decodeMethodCall(message); |
610 | try { |
611 | return codec.encodeSuccessEnvelope(await handler(call)); |
612 | } on PlatformException catch (e) { |
613 | return codec.encodeErrorEnvelope(code: e.code, message: e.message, details: e.details); |
614 | } on MissingPluginException { |
615 | return null; |
616 | } catch (error) { |
617 | return codec.encodeErrorEnvelope(code:'error', message: error.toString()); |
618 | } |
619 | } |
620 | |
621 | // Looking for setMockMethodCallHandler or checkMethodCallHandler? |
622 | // See this shim package: packages/flutter_test/lib/src/deprecated.dart |
623 | } |
624 | |
625 | /// A [MethodChannel] that ignores missing platform plugins. |
626 | /// |
627 | /// When [invokeMethod] fails to find the platform plugin, it returns null |
628 | /// instead of throwing an exception. |
629 | /// |
630 | /// {@macro flutter.services.method_channel.FIFO} |
631 | class OptionalMethodChannel extends MethodChannel { |
632 | /// Creates a [MethodChannel] that ignores missing platform plugins. |
633 | const OptionalMethodChannel(super.name, [super.codec, super.binaryMessenger]); |
634 | |
635 | @override |
636 | Future<T?> invokeMethod<T>(String method, [dynamic arguments]) async { |
637 | return super._invokeMethod<T>(method, missingOk: true, arguments: arguments); |
638 | } |
639 | } |
640 | |
641 | /// A named channel for communicating with platform plugins using event streams. |
642 | /// |
643 | /// Stream setup requests are encoded into binary before being sent, |
644 | /// and binary events and errors received are decoded into Dart values. |
645 | /// The [MethodCodec] used must be compatible with the one used by the platform |
646 | /// plugin. This can be achieved by creating an [EventChannel] counterpart of |
647 | /// this channel on the platform side. The Dart type of events sent and received |
648 | /// is `dynamic`, but only values supported by the specified [MethodCodec] can |
649 | /// be used. |
650 | /// |
651 | /// The logical identity of the channel is given by its name. Identically named |
652 | /// channels will interfere with each other's communication. |
653 | /// |
654 | /// See: <https://flutter.dev/to/platform-channels/> |
655 | class EventChannel { |
656 | /// Creates an [EventChannel] with the specified [name]. |
657 | /// |
658 | /// The [codec] used will be [StandardMethodCodec], unless otherwise |
659 | /// specified. |
660 | /// |
661 | /// Neither [name] nor [codec] may be null. The default [ServicesBinding.defaultBinaryMessenger] |
662 | /// instance is used if [binaryMessenger] is null. |
663 | const EventChannel( |
664 | this.name, [ |
665 | this.codec = const StandardMethodCodec(), |
666 | BinaryMessenger? binaryMessenger, |
667 | ]) : _binaryMessenger = binaryMessenger; |
668 | |
669 | /// The logical channel on which communication happens, not null. |
670 | final String name; |
671 | |
672 | /// The message codec used by this channel, not null. |
673 | final MethodCodec codec; |
674 | |
675 | /// The messenger which sends the bytes for this channel. |
676 | /// |
677 | /// On the root isolate or web, this defaults to the |
678 | /// [ServicesBinding.defaultBinaryMessenger]. In other contexts the default |
679 | /// value is a [BackgroundIsolateBinaryMessenger] from |
680 | /// [BackgroundIsolateBinaryMessenger.ensureInitialized]. |
681 | BinaryMessenger get binaryMessenger => _binaryMessenger ?? _findBinaryMessenger(); |
682 | final BinaryMessenger? _binaryMessenger; |
683 | |
684 | /// Sets up a broadcast stream for receiving events on this channel. |
685 | /// |
686 | /// Returns a broadcast [Stream] which emits events to listeners as follows: |
687 | /// |
688 | /// * a decoded data event (possibly null) for each successful event |
689 | /// received from the platform plugin; |
690 | /// * an error event containing a [PlatformException] for each error event |
691 | /// received from the platform plugin. |
692 | /// |
693 | /// Errors occurring during stream activation or deactivation are reported |
694 | /// through the [FlutterError] facility. Stream activation happens only when |
695 | /// stream listener count changes from 0 to 1. Stream deactivation happens |
696 | /// only when stream listener count changes from 1 to 0. |
697 | Stream<dynamic> receiveBroadcastStream([dynamic arguments]) { |
698 | final MethodChannel methodChannel = MethodChannel(name, codec); |
699 | late StreamController<dynamic> controller; |
700 | controller = StreamController<dynamic>.broadcast( |
701 | onListen: () async { |
702 | binaryMessenger.setMessageHandler(name, (ByteData? reply) async { |
703 | if (reply == null) { |
704 | controller.close(); |
705 | } else { |
706 | try { |
707 | controller.add(codec.decodeEnvelope(reply)); |
708 | } on PlatformException catch (e) { |
709 | controller.addError(e); |
710 | } |
711 | } |
712 | return null; |
713 | }); |
714 | try { |
715 | await methodChannel.invokeMethod<void>('listen', arguments); |
716 | } catch (exception, stack) { |
717 | FlutterError.reportError( |
718 | FlutterErrorDetails( |
719 | exception: exception, |
720 | stack: stack, |
721 | library:'services library', |
722 | context: ErrorDescription('while activating platform stream on channel$name'), |
723 | ), |
724 | ); |
725 | } |
726 | }, |
727 | onCancel: () async { |
728 | binaryMessenger.setMessageHandler(name, null); |
729 | try { |
730 | await methodChannel.invokeMethod<void>('cancel', arguments); |
731 | } catch (exception, stack) { |
732 | FlutterError.reportError( |
733 | FlutterErrorDetails( |
734 | exception: exception, |
735 | stack: stack, |
736 | library:'services library', |
737 | context: ErrorDescription('while de-activating platform stream on channel$name'), |
738 | ), |
739 | ); |
740 | } |
741 | }, |
742 | ); |
743 | return controller.stream; |
744 | } |
745 | } |
746 |
Definitions
- shouldProfilePlatformChannels
- kProfilePlatformChannels
- _profilePlatformChannelsIsRunning
- _profilePlatformChannelsRate
- _profiledBinaryMessengers
- _ProfiledBinaryMessenger
- _ProfiledBinaryMessenger
- handlePlatformMessage
- sendWithPostfix
- send
- setMessageHandler
- _PlatformChannelStats
- _PlatformChannelStats
- upBytes
- addUpStream
- downBytes
- addDownStream
- averageUpPayload
- averageDownPayload
- _profilePlatformChannelsStats
- _debugLaunchProfilePlatformChannels
- _debugRecordUpStream
- _debugRecordDownStream
- _findBinaryMessenger
- BasicMessageChannel
- BasicMessageChannel
- binaryMessenger
- send
- setMessageHandler
- MethodChannel
- MethodChannel
- binaryMessenger
- _invokeMethod
- invokeMethod
- invokeListMethod
- invokeMapMethod
- setMethodCallHandler
- _handleAsMethodCall
- OptionalMethodChannel
- OptionalMethodChannel
- invokeMethod
- EventChannel
- EventChannel
- binaryMessenger
Learn more about Flutter for embedded and desktop on industrialflutter.com