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