| // Copyright 2014 The Flutter Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| import 'dart:ui' as ui; |
| |
| import 'assertions.dart'; |
| import 'constants.dart'; |
| import 'diagnostics.dart'; |
| |
| const bool _kMemoryAllocations = bool.fromEnvironment('flutter.memory_allocations'); |
| |
| /// If true, Flutter objects dispatch the memory allocation events. |
| /// |
| /// By default, the constant is true for debug mode and false |
| /// for profile and release modes. |
| /// To enable the dispatching for release mode, pass the compilation flag |
| /// `--dart-define=flutter.memory_allocations=true`. |
| const bool kFlutterMemoryAllocationsEnabled = _kMemoryAllocations || kDebugMode; |
| |
| const String _dartUiLibrary = 'dart:ui'; |
| |
| class _FieldNames { |
| static const String eventType = 'eventType'; |
| static const String libraryName = 'libraryName'; |
| static const String className = 'className'; |
| } |
| |
| /// A lifecycle event of an object. |
| abstract class ObjectEvent{ |
| /// Creates an instance of [ObjectEvent]. |
| ObjectEvent({ |
| required this.object, |
| }); |
| |
| /// Reference to the object. |
| /// |
| /// The reference should not be stored in any |
| /// long living place as it will prevent garbage collection. |
| final Object object; |
| |
| /// The representation of the event in a form, acceptable by a |
| /// pure dart library, that cannot depend on Flutter. |
| /// |
| /// The method enables code like: |
| /// ```dart |
| /// void myDartMethod(Map<Object, Map<String, Object>> event) {} |
| /// MemoryAllocations.instance |
| /// .addListener((ObjectEvent event) => myDartMethod(event.toMap())); |
| /// ``` |
| Map<Object, Map<String, Object>> toMap(); |
| } |
| |
| /// A listener of [ObjectEvent]. |
| typedef ObjectEventListener = void Function(ObjectEvent); |
| |
| /// An event that describes creation of an object. |
| class ObjectCreated extends ObjectEvent { |
| /// Creates an instance of [ObjectCreated]. |
| ObjectCreated({ |
| required this.library, |
| required this.className, |
| required super.object, |
| }); |
| |
| /// Name of the instrumented library. |
| /// |
| /// The format of this parameter should be a library Uri. |
| /// For example: `'package:flutter/rendering.dart'`. |
| final String library; |
| |
| /// Name of the instrumented class. |
| final String className; |
| |
| @override |
| Map<Object, Map<String, Object>> toMap() { |
| return <Object, Map<String, Object>>{object: <String, Object>{ |
| _FieldNames.libraryName: library, |
| _FieldNames.className: className, |
| _FieldNames.eventType: 'created', |
| }}; |
| } |
| } |
| |
| /// An event that describes disposal of an object. |
| class ObjectDisposed extends ObjectEvent { |
| /// Creates an instance of [ObjectDisposed]. |
| ObjectDisposed({ |
| required super.object, |
| }); |
| |
| @override |
| Map<Object, Map<String, Object>> toMap() { |
| return <Object, Map<String, Object>>{object: <String, Object>{ |
| _FieldNames.eventType: 'disposed', |
| }}; |
| } |
| } |
| |
| /// An interface for listening to object lifecycle events. |
| /// |
| /// If [kFlutterMemoryAllocationsEnabled] is true, |
| /// [MemoryAllocations] listens to creation and disposal events |
| /// for disposable objects in Flutter Framework. |
| /// To dispatch events for other objects, invoke |
| /// [MemoryAllocations.dispatchObjectEvent]. |
| /// |
| /// Use this class with condition `kFlutterMemoryAllocationsEnabled`, |
| /// to make sure not to increase size of the application by the code |
| /// of the class, if memory allocations are disabled. |
| /// |
| /// The class is optimized for massive event flow and small number of |
| /// added or removed listeners. |
| class MemoryAllocations { |
| MemoryAllocations._(); |
| |
| /// The shared instance of [MemoryAllocations]. |
| /// |
| /// Only call this when [kFlutterMemoryAllocationsEnabled] is true. |
| static final MemoryAllocations instance = MemoryAllocations._(); |
| |
| /// List of listeners. |
| /// |
| /// The elements are nullable, because the listeners should be removable |
| /// while iterating through the list. |
| List<ObjectEventListener?>? _listeners; |
| |
| /// Register a listener that is called every time an object event is |
| /// dispatched. |
| /// |
| /// Listeners can be removed with [removeListener]. |
| /// |
| /// Only call this when [kFlutterMemoryAllocationsEnabled] is true. |
| void addListener(ObjectEventListener listener){ |
| if (!kFlutterMemoryAllocationsEnabled) { |
| return; |
| } |
| if (_listeners == null) { |
| _listeners = <ObjectEventListener?>[]; |
| _subscribeToSdkObjects(); |
| } |
| _listeners!.add(listener); |
| } |
| |
| /// Number of active notification loops. |
| /// |
| /// When equal to zero, we can delete listeners from the list, |
| /// otherwise should null them. |
| int _activeDispatchLoops = 0; |
| |
| /// If true, listeners were nulled by [removeListener]. |
| bool _listenersContainNulls = false; |
| |
| /// Stop calling the given listener every time an object event is |
| /// dispatched. |
| /// |
| /// Listeners can be added with [addListener]. |
| /// |
| /// Only call this when [kFlutterMemoryAllocationsEnabled] is true. |
| void removeListener(ObjectEventListener listener){ |
| if (!kFlutterMemoryAllocationsEnabled) { |
| return; |
| } |
| final List<ObjectEventListener?>? listeners = _listeners; |
| if (listeners == null) { |
| return; |
| } |
| |
| if (_activeDispatchLoops > 0) { |
| // If there are active dispatch loops, listeners.remove |
| // should not be invoked, as it will |
| // break the dispatch loops correctness. |
| for (int i = 0; i < listeners.length; i++) { |
| if (listeners[i] == listener) { |
| listeners[i] = null; |
| _listenersContainNulls = true; |
| } |
| } |
| } else { |
| listeners.removeWhere((ObjectEventListener? l) => l == listener); |
| _checkListenersForEmptiness(); |
| } |
| } |
| |
| void _tryDefragmentListeners() { |
| if (_activeDispatchLoops > 0 || !_listenersContainNulls) { |
| return; |
| } |
| _listeners?.removeWhere((ObjectEventListener? e) => e == null); |
| _listenersContainNulls = false; |
| _checkListenersForEmptiness(); |
| } |
| |
| void _checkListenersForEmptiness() { |
| if (_listeners?.isEmpty ?? false) { |
| _listeners = null; |
| _unSubscribeFromSdkObjects(); |
| } |
| } |
| |
| /// Return true if there are listeners. |
| /// |
| /// If there is no listeners, the app can save on creating the event object. |
| /// |
| /// Only call this when [kFlutterMemoryAllocationsEnabled] is true. |
| bool get hasListeners { |
| if (!kFlutterMemoryAllocationsEnabled) { |
| return false; |
| } |
| if (_listenersContainNulls) { |
| return _listeners?.firstWhere((ObjectEventListener? l) => l != null) != null; |
| } |
| return _listeners?.isNotEmpty ?? false; |
| } |
| |
| /// Dispatch a new object event to listeners. |
| /// |
| /// Exceptions thrown by listeners will be caught and reported using |
| /// [FlutterError.reportError]. |
| /// |
| /// Listeners added during an event dispatching, will start being invoked |
| /// for next events, but will be skipped for this event. |
| /// |
| /// Listeners, removed during an event dispatching, will not be invoked |
| /// after the removal. |
| /// |
| /// Only call this when [kFlutterMemoryAllocationsEnabled] is true. |
| void dispatchObjectEvent(ObjectEvent event) { |
| if (!kFlutterMemoryAllocationsEnabled) { |
| return; |
| } |
| final List<ObjectEventListener?>? listeners = _listeners; |
| if (listeners == null || listeners.isEmpty) { |
| return; |
| } |
| |
| _activeDispatchLoops++; |
| final int end = listeners.length; |
| for (int i = 0; i < end; i++) { |
| try { |
| listeners[i]?.call(event); |
| } catch (exception, stack) { |
| final String type = event.object.runtimeType.toString(); |
| FlutterError.reportError(FlutterErrorDetails( |
| exception: exception, |
| stack: stack, |
| library: 'foundation library', |
| context: ErrorDescription('MemoryAllocations while ' |
| 'dispatching notifications for $type'), |
| informationCollector: () => <DiagnosticsNode>[ |
| DiagnosticsProperty<Object>( |
| 'The $type sending notification was', |
| event.object, |
| style: DiagnosticsTreeStyle.errorProperty, |
| ), |
| ], |
| )); |
| } |
| } |
| _activeDispatchLoops--; |
| _tryDefragmentListeners(); |
| } |
| |
| /// Create [ObjectCreated] and invoke [dispatchObjectEvent] if there are listeners. |
| /// |
| /// This method is more efficient than [dispatchObjectEvent] if the event object is not created yet. |
| void dispatchObjectCreated({ |
| required String library, |
| required String className, |
| required Object object, |
| }) { |
| if (!hasListeners) { |
| return; |
| } |
| dispatchObjectEvent(ObjectCreated( |
| library: library, |
| className: className, |
| object: object, |
| )); |
| } |
| |
| /// Create [ObjectDisposed] and invoke [dispatchObjectEvent] if there are listeners. |
| /// |
| /// This method is more efficient than [dispatchObjectEvent] if the event object is not created yet. |
| void dispatchObjectDisposed({required Object object}) { |
| if (!hasListeners) { |
| return; |
| } |
| dispatchObjectEvent(ObjectDisposed(object: object)); |
| } |
| |
| void _subscribeToSdkObjects() { |
| assert(ui.Image.onCreate == null); |
| assert(ui.Image.onDispose == null); |
| assert(ui.Picture.onCreate == null); |
| assert(ui.Picture.onDispose == null); |
| ui.Image.onCreate = _imageOnCreate; |
| ui.Image.onDispose = _imageOnDispose; |
| ui.Picture.onCreate = _pictureOnCreate; |
| ui.Picture.onDispose = _pictureOnDispose; |
| } |
| |
| void _unSubscribeFromSdkObjects() { |
| assert(ui.Image.onCreate == _imageOnCreate); |
| assert(ui.Image.onDispose == _imageOnDispose); |
| assert(ui.Picture.onCreate == _pictureOnCreate); |
| assert(ui.Picture.onDispose == _pictureOnDispose); |
| ui.Image.onCreate = null; |
| ui.Image.onDispose = null; |
| ui.Picture.onCreate = null; |
| ui.Picture.onDispose = null; |
| } |
| |
| void _imageOnCreate(ui.Image image) { |
| dispatchObjectEvent(ObjectCreated( |
| library: _dartUiLibrary, |
| className: '${ui.Image}', |
| object: image, |
| )); |
| } |
| |
| void _pictureOnCreate(ui.Picture picture) { |
| dispatchObjectEvent(ObjectCreated( |
| library: _dartUiLibrary, |
| className: '${ui.Picture}', |
| object: picture, |
| )); |
| } |
| |
| void _imageOnDispose(ui.Image image) { |
| dispatchObjectEvent(ObjectDisposed( |
| object: image, |
| )); |
| } |
| |
| void _pictureOnDispose(ui.Picture picture) { |
| dispatchObjectEvent(ObjectDisposed( |
| object: picture, |
| )); |
| } |
| } |