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
5import 'dart:ui' as ui;
6
7import 'assertions.dart';
8import 'constants.dart';
9import 'diagnostics.dart';
10
11const bool _kMemoryAllocations = bool.fromEnvironment('flutter.memory_allocations');
12
13/// If true, Flutter objects dispatch the memory allocation events.
14///
15/// By default, the constant is true for debug mode and false
16/// for profile and release modes.
17/// To enable the dispatching for release mode, pass the compilation flag
18/// `--dart-define=flutter.memory_allocations=true`.
19const bool kFlutterMemoryAllocationsEnabled = _kMemoryAllocations || kDebugMode;
20
21const String _dartUiLibrary = 'dart:ui';
22
23class _FieldNames {
24 static const String eventType = 'eventType';
25 static const String libraryName = 'libraryName';
26 static const String className = 'className';
27}
28
29/// A lifecycle event of an object.
30abstract class ObjectEvent{
31 /// Creates an instance of [ObjectEvent].
32 ObjectEvent({
33 required this.object,
34 });
35
36 /// Reference to the object.
37 ///
38 /// The reference should not be stored in any
39 /// long living place as it will prevent garbage collection.
40 final Object object;
41
42 /// The representation of the event in a form, acceptable by a
43 /// pure dart library, that cannot depend on Flutter.
44 ///
45 /// The method enables code like:
46 /// ```dart
47 /// void myDartMethod(Map<Object, Map<String, Object>> event) {}
48 /// FlutterMemoryAllocations.instance
49 /// .addListener((ObjectEvent event) => myDartMethod(event.toMap()));
50 /// ```
51 Map<Object, Map<String, Object>> toMap();
52}
53
54/// A listener of [ObjectEvent].
55typedef ObjectEventListener = void Function(ObjectEvent event);
56
57/// An event that describes creation of an object.
58class ObjectCreated extends ObjectEvent {
59 /// Creates an instance of [ObjectCreated].
60 ObjectCreated({
61 required this.library,
62 required this.className,
63 required super.object,
64 });
65
66 /// Name of the instrumented library.
67 ///
68 /// The format of this parameter should be a library Uri.
69 /// For example: `'package:flutter/rendering.dart'`.
70 final String library;
71
72 /// Name of the instrumented class.
73 final String className;
74
75 @override
76 Map<Object, Map<String, Object>> toMap() {
77 return <Object, Map<String, Object>>{object: <String, Object>{
78 _FieldNames.libraryName: library,
79 _FieldNames.className: className,
80 _FieldNames.eventType: 'created',
81 }};
82 }
83}
84
85/// An event that describes disposal of an object.
86class ObjectDisposed extends ObjectEvent {
87 /// Creates an instance of [ObjectDisposed].
88 ObjectDisposed({
89 required super.object,
90 });
91
92 @override
93 Map<Object, Map<String, Object>> toMap() {
94 return <Object, Map<String, Object>>{object: <String, Object>{
95 _FieldNames.eventType: 'disposed',
96 }};
97 }
98}
99
100/// An interface for listening to object lifecycle events.
101@Deprecated(
102 'Use `FlutterMemoryAllocations` instead. '
103 'The class `MemoryAllocations` will be introduced in a pure Dart library. '
104 'This feature was deprecated after v3.18.0-18.0.pre.'
105)
106typedef MemoryAllocations = FlutterMemoryAllocations;
107
108/// An interface for listening to object lifecycle events.
109///
110/// If [kFlutterMemoryAllocationsEnabled] is true,
111/// [FlutterMemoryAllocations] listens to creation and disposal events
112/// for disposable objects in Flutter Framework.
113/// To dispatch other events objects, invoke
114/// [FlutterMemoryAllocations.dispatchObjectEvent].
115///
116/// Use this class with condition `kFlutterMemoryAllocationsEnabled`,
117/// to make sure not to increase size of the application by the code
118/// of the class, if memory allocations are disabled.
119///
120/// The class is optimized for massive event flow and small number of
121/// added or removed listeners.
122class FlutterMemoryAllocations {
123 FlutterMemoryAllocations._();
124
125 /// The shared instance of [FlutterMemoryAllocations].
126 ///
127 /// Only call this when [kFlutterMemoryAllocationsEnabled] is true.
128 static final FlutterMemoryAllocations instance = FlutterMemoryAllocations._();
129
130 /// List of listeners.
131 ///
132 /// The elements are nullable, because the listeners should be removable
133 /// while iterating through the list.
134 List<ObjectEventListener?>? _listeners;
135
136 /// Register a listener that is called every time an object event is
137 /// dispatched.
138 ///
139 /// Listeners can be removed with [removeListener].
140 ///
141 /// Only call this when [kFlutterMemoryAllocationsEnabled] is true.
142 void addListener(ObjectEventListener listener){
143 if (!kFlutterMemoryAllocationsEnabled) {
144 return;
145 }
146 if (_listeners == null) {
147 _listeners = <ObjectEventListener?>[];
148 _subscribeToSdkObjects();
149 }
150 _listeners!.add(listener);
151 }
152
153 /// Number of active notification loops.
154 ///
155 /// When equal to zero, we can delete listeners from the list,
156 /// otherwise should null them.
157 int _activeDispatchLoops = 0;
158
159 /// If true, listeners were nulled by [removeListener].
160 bool _listenersContainNulls = false;
161
162 /// Stop calling the given listener every time an object event is
163 /// dispatched.
164 ///
165 /// Listeners can be added with [addListener].
166 ///
167 /// Only call this when [kFlutterMemoryAllocationsEnabled] is true.
168 void removeListener(ObjectEventListener listener){
169 if (!kFlutterMemoryAllocationsEnabled) {
170 return;
171 }
172 final List<ObjectEventListener?>? listeners = _listeners;
173 if (listeners == null) {
174 return;
175 }
176
177 if (_activeDispatchLoops > 0) {
178 // If there are active dispatch loops, listeners.remove
179 // should not be invoked, as it will
180 // break the dispatch loops correctness.
181 for (int i = 0; i < listeners.length; i++) {
182 if (listeners[i] == listener) {
183 listeners[i] = null;
184 _listenersContainNulls = true;
185 }
186 }
187 } else {
188 listeners.removeWhere((ObjectEventListener? l) => l == listener);
189 _checkListenersForEmptiness();
190 }
191 }
192
193 void _tryDefragmentListeners() {
194 if (_activeDispatchLoops > 0 || !_listenersContainNulls) {
195 return;
196 }
197 _listeners?.removeWhere((ObjectEventListener? e) => e == null);
198 _listenersContainNulls = false;
199 _checkListenersForEmptiness();
200 }
201
202 void _checkListenersForEmptiness() {
203 if (_listeners?.isEmpty ?? false) {
204 _listeners = null;
205 _unSubscribeFromSdkObjects();
206 }
207 }
208
209 /// Return true if there are listeners.
210 ///
211 /// If there is no listeners, the app can save on creating the event object.
212 ///
213 /// Only call this when [kFlutterMemoryAllocationsEnabled] is true.
214 bool get hasListeners {
215 if (!kFlutterMemoryAllocationsEnabled) {
216 return false;
217 }
218 if (_listenersContainNulls) {
219 return _listeners?.firstWhere((ObjectEventListener? l) => l != null) != null;
220 }
221 return _listeners?.isNotEmpty ?? false;
222 }
223
224 /// Dispatch a new object event to listeners.
225 ///
226 /// Exceptions thrown by listeners will be caught and reported using
227 /// [FlutterError.reportError].
228 ///
229 /// Listeners added during an event dispatching, will start being invoked
230 /// for next events, but will be skipped for this event.
231 ///
232 /// Listeners, removed during an event dispatching, will not be invoked
233 /// after the removal.
234 ///
235 /// Only call this when [kFlutterMemoryAllocationsEnabled] is true.
236 void dispatchObjectEvent(ObjectEvent event) {
237 if (!kFlutterMemoryAllocationsEnabled) {
238 return;
239 }
240 final List<ObjectEventListener?>? listeners = _listeners;
241 if (listeners == null || listeners.isEmpty) {
242 return;
243 }
244
245 _activeDispatchLoops++;
246 final int end = listeners.length;
247 for (int i = 0; i < end; i++) {
248 try {
249 listeners[i]?.call(event);
250 } catch (exception, stack) {
251 final String type = event.object.runtimeType.toString();
252 FlutterError.reportError(FlutterErrorDetails(
253 exception: exception,
254 stack: stack,
255 library: 'foundation library',
256 context: ErrorDescription('MemoryAllocations while '
257 'dispatching notifications for $type'),
258 informationCollector: () => <DiagnosticsNode>[
259 DiagnosticsProperty<Object>(
260 'The $type sending notification was',
261 event.object,
262 style: DiagnosticsTreeStyle.errorProperty,
263 ),
264 ],
265 ));
266 }
267 }
268 _activeDispatchLoops--;
269 _tryDefragmentListeners();
270 }
271
272 /// Create [ObjectCreated] and invoke [dispatchObjectEvent] if there are listeners.
273 ///
274 /// This method is more efficient than [dispatchObjectEvent] if the event object is not created yet.
275 void dispatchObjectCreated({
276 required String library,
277 required String className,
278 required Object object,
279 }) {
280 if (!hasListeners) {
281 return;
282 }
283 dispatchObjectEvent(ObjectCreated(
284 library: library,
285 className: className,
286 object: object,
287 ));
288 }
289
290 /// Create [ObjectDisposed] and invoke [dispatchObjectEvent] if there are listeners.
291 ///
292 /// This method is more efficient than [dispatchObjectEvent] if the event object is not created yet.
293 void dispatchObjectDisposed({required Object object}) {
294 if (!hasListeners) {
295 return;
296 }
297 dispatchObjectEvent(ObjectDisposed(object: object));
298 }
299
300 void _subscribeToSdkObjects() {
301 assert(ui.Image.onCreate == null);
302 assert(ui.Image.onDispose == null);
303 assert(ui.Picture.onCreate == null);
304 assert(ui.Picture.onDispose == null);
305 ui.Image.onCreate = _imageOnCreate;
306 ui.Image.onDispose = _imageOnDispose;
307 ui.Picture.onCreate = _pictureOnCreate;
308 ui.Picture.onDispose = _pictureOnDispose;
309 }
310
311 void _unSubscribeFromSdkObjects() {
312 assert(ui.Image.onCreate == _imageOnCreate);
313 assert(ui.Image.onDispose == _imageOnDispose);
314 assert(ui.Picture.onCreate == _pictureOnCreate);
315 assert(ui.Picture.onDispose == _pictureOnDispose);
316 ui.Image.onCreate = null;
317 ui.Image.onDispose = null;
318 ui.Picture.onCreate = null;
319 ui.Picture.onDispose = null;
320 }
321
322 void _imageOnCreate(ui.Image image) {
323 dispatchObjectEvent(ObjectCreated(
324 library: _dartUiLibrary,
325 className: '${ui.Image}',
326 object: image,
327 ));
328 }
329
330 void _pictureOnCreate(ui.Picture picture) {
331 dispatchObjectEvent(ObjectCreated(
332 library: _dartUiLibrary,
333 className: '${ui.Picture}',
334 object: picture,
335 ));
336 }
337
338 void _imageOnDispose(ui.Image image) {
339 dispatchObjectEvent(ObjectDisposed(
340 object: image,
341 ));
342 }
343
344 void _pictureOnDispose(ui.Picture picture) {
345 dispatchObjectEvent(ObjectDisposed(
346 object: picture,
347 ));
348 }
349}
350