From a047358c5c8d6cdb395d833fdd365ae1492e7e32 Mon Sep 17 00:00:00 2001 From: Kenzie Davisson <43759233+kenzieschmoll@users.noreply.github.com> Date: Wed, 11 Oct 2023 10:45:06 -0700 Subject: [PATCH] Add `profilePlatformChannels` service extension (#136051) We will expose this from a button in DevTools. https://github.com/flutter/devtools/issues/6166 --- .../flutter/lib/src/services/binding.dart | 12 ++++++++ packages/flutter/lib/src/services/debug.dart | 14 +++++++++ .../lib/src/services/platform_channel.dart | 18 +++++++++-- .../lib/src/services/service_extensions.dart | 13 ++++++++ .../foundation/service_extensions_test.dart | 30 ++++++++++++++++++- 5 files changed, 83 insertions(+), 4 deletions(-) diff --git a/packages/flutter/lib/src/services/binding.dart b/packages/flutter/lib/src/services/binding.dart index 99690718159..28f43946643 100644 --- a/packages/flutter/lib/src/services/binding.dart +++ b/packages/flutter/lib/src/services/binding.dart @@ -12,8 +12,10 @@ import 'package:flutter/scheduler.dart'; import 'asset_bundle.dart'; import 'binary_messenger.dart'; +import 'debug.dart'; import 'hardware_keyboard.dart'; import 'message_codec.dart'; +import 'platform_channel.dart'; import 'restoration.dart'; import 'service_extensions.dart'; import 'system_channels.dart'; @@ -224,6 +226,16 @@ mixin ServicesBinding on BindingBase, SchedulerBinding { ); return true; }()); + + if (!kReleaseMode) { + registerBoolServiceExtension( + name: ServicesServiceExtensions.profilePlatformChannels.name, + getter: () async => debugProfilePlatformChannels, + setter: (bool value) async { + debugProfilePlatformChannels = value; + }, + ); + } } /// Called in response to the `ext.flutter.evict` service extension. diff --git a/packages/flutter/lib/src/services/debug.dart b/packages/flutter/lib/src/services/debug.dart index ce63157aaf7..e16ee9dc7af 100644 --- a/packages/flutter/lib/src/services/debug.dart +++ b/packages/flutter/lib/src/services/debug.dart @@ -41,3 +41,17 @@ bool debugAssertAllServicesVarsUnset(String reason) { }()); return true; } + +/// Controls whether platform channel usage can be debugged in non-release mode. +/// +/// This value is modified by calls to the +/// [ServicesServiceExtensions.profilePlatformChannels] service extension. +/// +/// See also: +/// +/// * [shouldProfilePlatformChannels], which checks both +/// [kProfilePlatformChannels] and [debugProfilePlatformChannels] for the +/// current run mode. +/// * [kProfilePlatformChannels], which determines whether platform channel +/// usage can be debugged in release mode. +bool debugProfilePlatformChannels = false; diff --git a/packages/flutter/lib/src/services/platform_channel.dart b/packages/flutter/lib/src/services/platform_channel.dart index cba9e1654d4..ce7e89ad315 100644 --- a/packages/flutter/lib/src/services/platform_channel.dart +++ b/packages/flutter/lib/src/services/platform_channel.dart @@ -12,6 +12,7 @@ import '_background_isolate_binary_messenger_io.dart' import 'binary_messenger.dart'; import 'binding.dart'; +import 'debug.dart'; import 'message_codec.dart'; import 'message_codecs.dart'; @@ -32,6 +33,17 @@ export 'message_codec.dart' show MessageCodec, MethodCall, MethodCodec; /// The statistics include the total bytes transmitted and the average number of /// bytes per invocation in the last quantum. "Up" means in the direction of /// Flutter to the host platform, "down" is the host platform to flutter. +bool get shouldProfilePlatformChannels => kProfilePlatformChannels || (!kReleaseMode && debugProfilePlatformChannels); + +/// Controls whether platform channel usage can be debugged in release mode. +/// +/// See also: +/// +/// * [shouldProfilePlatformChannels], which checks both +/// [kProfilePlatformChannels] and [debugProfilePlatformChannels] for the +/// current run mode. +/// * [debugProfilePlatformChannels], which determines whether platform +/// channel usage can be debugged in non-release mode. const bool kProfilePlatformChannels = false; bool _profilePlatformChannelsIsRunning = false; @@ -190,7 +202,7 @@ class BasicMessageChannel { /// [BackgroundIsolateBinaryMessenger.ensureInitialized]. BinaryMessenger get binaryMessenger { final BinaryMessenger result = _binaryMessenger ?? _findBinaryMessenger(); - return kProfilePlatformChannels + return shouldProfilePlatformChannels ? _profiledBinaryMessengers[this] ??= _ProfiledBinaryMessenger( // ignore: no_runtimetype_tostring result, runtimeType.toString(), codec.runtimeType.toString()) @@ -279,7 +291,7 @@ class MethodChannel { /// [BackgroundIsolateBinaryMessenger.ensureInitialized]. BinaryMessenger get binaryMessenger { final BinaryMessenger result = _binaryMessenger ?? _findBinaryMessenger(); - return kProfilePlatformChannels + return shouldProfilePlatformChannels ? _profiledBinaryMessengers[this] ??= _ProfiledBinaryMessenger( // ignore: no_runtimetype_tostring result, runtimeType.toString(), codec.runtimeType.toString()) @@ -310,7 +322,7 @@ class MethodChannel { Future _invokeMethod(String method, { required bool missingOk, dynamic arguments }) async { final ByteData input = codec.encodeMethodCall(MethodCall(method, arguments)); final ByteData? result = - kProfilePlatformChannels ? + shouldProfilePlatformChannels ? await (binaryMessenger as _ProfiledBinaryMessenger).sendWithPostfix(name, '#$method', input) : await binaryMessenger.send(name, input); if (result == null) { diff --git a/packages/flutter/lib/src/services/service_extensions.dart b/packages/flutter/lib/src/services/service_extensions.dart index 9211b75ea2d..3c1c9cfa100 100644 --- a/packages/flutter/lib/src/services/service_extensions.dart +++ b/packages/flutter/lib/src/services/service_extensions.dart @@ -11,6 +11,19 @@ /// The String value for each of these extension names should be accessed by /// calling the `.name` property on the enum value. enum ServicesServiceExtensions { + /// Name of service extension that, when called, will toggle whether + /// statistics about the usage of Platform Channels will be printed out + /// periodically to the console and Timeline events will show the time between + /// sending and receiving a message (encoding and decoding time excluded). + /// + /// See also: + /// + /// * [debugProfilePlatformChannels], which is the flag that this service + /// extension exposes. + /// * [ServicesBinding.initServiceExtensions], where the service extension is + /// registered. + profilePlatformChannels, + /// Name of service extension that, when called, will evict an image from the /// rootBundle cache and cause the image cache to be cleared. /// diff --git a/packages/flutter/test/foundation/service_extensions_test.dart b/packages/flutter/test/foundation/service_extensions_test.dart index 7b6d36a8358..88056b25897 100644 --- a/packages/flutter/test/foundation/service_extensions_test.dart +++ b/packages/flutter/test/foundation/service_extensions_test.dart @@ -183,7 +183,7 @@ void main() { // framework, excluding any that are for the widget inspector (see // widget_inspector_test.dart for tests of the ext.flutter.inspector service // extensions). Any test counted here must be tested in this file! - const int serviceExtensionCount = 29; + const int serviceExtensionCount = 30; expect(binding.extensions.length, serviceExtensionCount + widgetInspectorExtensionCount - disabledExtensions); expect(testedExtensions, hasLength(serviceExtensionCount)); @@ -597,6 +597,34 @@ void main() { testedExtensions.add(RenderingServiceExtensions.profileRenderObjectLayouts.name); }); + test('Service extensions - profilePlatformChannels', () async { + Map result; + + expect(debugProfilePlatformChannels, false); + + result = await binding.testExtension(ServicesServiceExtensions.profilePlatformChannels.name, {}); + expect(result, {'enabled': 'false'}); + expect(debugProfilePlatformChannels, false); + + result = await binding.testExtension(ServicesServiceExtensions.profilePlatformChannels.name, {'enabled': 'true'}); + expect(result, {'enabled': 'true'}); + expect(debugProfilePlatformChannels, true); + + result = await binding.testExtension(ServicesServiceExtensions.profilePlatformChannels.name, {}); + expect(result, {'enabled': 'true'}); + expect(debugProfilePlatformChannels, true); + + result = await binding.testExtension(ServicesServiceExtensions.profilePlatformChannels.name, {'enabled': 'false'}); + expect(result, {'enabled': 'false'}); + expect(debugProfilePlatformChannels, false); + + result = await binding.testExtension(ServicesServiceExtensions.profilePlatformChannels.name, {}); + expect(result, {'enabled': 'false'}); + expect(debugProfilePlatformChannels, false); + + testedExtensions.add(ServicesServiceExtensions.profilePlatformChannels.name); + }); + test('Service extensions - evict', () async { Map result; bool completed;