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