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:ui' as ui; |
6 | |
7 | import 'assertions.dart'; |
8 | import 'constants.dart'; |
9 | import 'diagnostics.dart'; |
10 | |
11 | const 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`. |
19 | const bool kFlutterMemoryAllocationsEnabled = _kMemoryAllocations || kDebugMode; |
20 | |
21 | const String _dartUiLibrary = 'dart:ui' ; |
22 | |
23 | class _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. |
30 | abstract 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]. |
55 | typedef ObjectEventListener = void Function(ObjectEvent event); |
56 | |
57 | /// An event that describes creation of an object. |
58 | class 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. |
86 | class 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 | ) |
106 | typedef 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. |
122 | class 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 | |