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 /// MemoryAllocations.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);
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///
102/// If [kFlutterMemoryAllocationsEnabled] is true,
103/// [MemoryAllocations] listens to creation and disposal events
104/// for disposable objects in Flutter Framework.
105/// To dispatch events for other objects, invoke
106/// [MemoryAllocations.dispatchObjectEvent].
107///
108/// Use this class with condition `kFlutterMemoryAllocationsEnabled`,
109/// to make sure not to increase size of the application by the code
110/// of the class, if memory allocations are disabled.
111///
112/// The class is optimized for massive event flow and small number of
113/// added or removed listeners.
114class MemoryAllocations {
115 MemoryAllocations._();
116
117 /// The shared instance of [MemoryAllocations].
118 ///
119 /// Only call this when [kFlutterMemoryAllocationsEnabled] is true.
120 static final MemoryAllocations instance = MemoryAllocations._();
121
122 /// List of listeners.
123 ///
124 /// The elements are nullable, because the listeners should be removable
125 /// while iterating through the list.
126 List<ObjectEventListener?>? _listeners;
127
128 /// Register a listener that is called every time an object event is
129 /// dispatched.
130 ///
131 /// Listeners can be removed with [removeListener].
132 ///
133 /// Only call this when [kFlutterMemoryAllocationsEnabled] is true.
134 void addListener(ObjectEventListener listener){
135 if (!kFlutterMemoryAllocationsEnabled) {
136 return;
137 }
138 if (_listeners == null) {
139 _listeners = <ObjectEventListener?>[];
140 _subscribeToSdkObjects();
141 }
142 _listeners!.add(listener);
143 }
144
145 /// Number of active notification loops.
146 ///
147 /// When equal to zero, we can delete listeners from the list,
148 /// otherwise should null them.
149 int _activeDispatchLoops = 0;
150
151 /// If true, listeners were nulled by [removeListener].
152 bool _listenersContainNulls = false;
153
154 /// Stop calling the given listener every time an object event is
155 /// dispatched.
156 ///
157 /// Listeners can be added with [addListener].
158 ///
159 /// Only call this when [kFlutterMemoryAllocationsEnabled] is true.
160 void removeListener(ObjectEventListener listener){
161 if (!kFlutterMemoryAllocationsEnabled) {
162 return;
163 }
164 final List<ObjectEventListener?>? listeners = _listeners;
165 if (listeners == null) {
166 return;
167 }
168
169 if (_activeDispatchLoops > 0) {
170 // If there are active dispatch loops, listeners.remove
171 // should not be invoked, as it will
172 // break the dispatch loops correctness.
173 for (int i = 0; i < listeners.length; i++) {
174 if (listeners[i] == listener) {
175 listeners[i] = null;
176 _listenersContainNulls = true;
177 }
178 }
179 } else {
180 listeners.removeWhere((ObjectEventListener? l) => l == listener);
181 _checkListenersForEmptiness();
182 }
183 }
184
185 void _tryDefragmentListeners() {
186 if (_activeDispatchLoops > 0 || !_listenersContainNulls) {
187 return;
188 }
189 _listeners?.removeWhere((ObjectEventListener? e) => e == null);
190 _listenersContainNulls = false;
191 _checkListenersForEmptiness();
192 }
193
194 void _checkListenersForEmptiness() {
195 if (_listeners?.isEmpty ?? false) {
196 _listeners = null;
197 _unSubscribeFromSdkObjects();
198 }
199 }
200
201 /// Return true if there are listeners.
202 ///
203 /// If there is no listeners, the app can save on creating the event object.
204 ///
205 /// Only call this when [kFlutterMemoryAllocationsEnabled] is true.
206 bool get hasListeners {
207 if (!kFlutterMemoryAllocationsEnabled) {
208 return false;
209 }
210 if (_listenersContainNulls) {
211 return _listeners?.firstWhere((ObjectEventListener? l) => l != null) != null;
212 }
213 return _listeners?.isNotEmpty ?? false;
214 }
215
216 /// Dispatch a new object event to listeners.
217 ///
218 /// Exceptions thrown by listeners will be caught and reported using
219 /// [FlutterError.reportError].
220 ///
221 /// Listeners added during an event dispatching, will start being invoked
222 /// for next events, but will be skipped for this event.
223 ///
224 /// Listeners, removed during an event dispatching, will not be invoked
225 /// after the removal.
226 ///
227 /// Only call this when [kFlutterMemoryAllocationsEnabled] is true.
228 void dispatchObjectEvent(ObjectEvent event) {
229 if (!kFlutterMemoryAllocationsEnabled) {
230 return;
231 }
232 final List<ObjectEventListener?>? listeners = _listeners;
233 if (listeners == null || listeners.isEmpty) {
234 return;
235 }
236
237 _activeDispatchLoops++;
238 final int end = listeners.length;
239 for (int i = 0; i < end; i++) {
240 try {
241 listeners[i]?.call(event);
242 } catch (exception, stack) {
243 final String type = event.object.runtimeType.toString();
244 FlutterError.reportError(FlutterErrorDetails(
245 exception: exception,
246 stack: stack,
247 library: 'foundation library',
248 context: ErrorDescription('MemoryAllocations while '
249 'dispatching notifications for $type'),
250 informationCollector: () => <DiagnosticsNode>[
251 DiagnosticsProperty<Object>(
252 'The $type sending notification was',
253 event.object,
254 style: DiagnosticsTreeStyle.errorProperty,
255 ),
256 ],
257 ));
258 }
259 }
260 _activeDispatchLoops--;
261 _tryDefragmentListeners();
262 }
263
264 /// Create [ObjectCreated] and invoke [dispatchObjectEvent] if there are listeners.
265 ///
266 /// This method is more efficient than [dispatchObjectEvent] if the event object is not created yet.
267 void dispatchObjectCreated({
268 required String library,
269 required String className,
270 required Object object,
271 }) {
272 if (!hasListeners) {
273 return;
274 }
275 dispatchObjectEvent(ObjectCreated(
276 library: library,
277 className: className,
278 object: object,
279 ));
280 }
281
282 /// Create [ObjectDisposed] and invoke [dispatchObjectEvent] if there are listeners.
283 ///
284 /// This method is more efficient than [dispatchObjectEvent] if the event object is not created yet.
285 void dispatchObjectDisposed({required Object object}) {
286 if (!hasListeners) {
287 return;
288 }
289 dispatchObjectEvent(ObjectDisposed(object: object));
290 }
291
292 void _subscribeToSdkObjects() {
293 assert(ui.Image.onCreate == null);
294 assert(ui.Image.onDispose == null);
295 assert(ui.Picture.onCreate == null);
296 assert(ui.Picture.onDispose == null);
297 ui.Image.onCreate = _imageOnCreate;
298 ui.Image.onDispose = _imageOnDispose;
299 ui.Picture.onCreate = _pictureOnCreate;
300 ui.Picture.onDispose = _pictureOnDispose;
301 }
302
303 void _unSubscribeFromSdkObjects() {
304 assert(ui.Image.onCreate == _imageOnCreate);
305 assert(ui.Image.onDispose == _imageOnDispose);
306 assert(ui.Picture.onCreate == _pictureOnCreate);
307 assert(ui.Picture.onDispose == _pictureOnDispose);
308 ui.Image.onCreate = null;
309 ui.Image.onDispose = null;
310 ui.Picture.onCreate = null;
311 ui.Picture.onDispose = null;
312 }
313
314 void _imageOnCreate(ui.Image image) {
315 dispatchObjectEvent(ObjectCreated(
316 library: _dartUiLibrary,
317 className: '${ui.Image}',
318 object: image,
319 ));
320 }
321
322 void _pictureOnCreate(ui.Picture picture) {
323 dispatchObjectEvent(ObjectCreated(
324 library: _dartUiLibrary,
325 className: '${ui.Picture}',
326 object: picture,
327 ));
328 }
329
330 void _imageOnDispose(ui.Image image) {
331 dispatchObjectEvent(ObjectDisposed(
332 object: image,
333 ));
334 }
335
336 void _pictureOnDispose(ui.Picture picture) {
337 dispatchObjectEvent(ObjectDisposed(
338 object: picture,
339 ));
340 }
341}
342