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({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].
53typedef ObjectEventListener = void Function(ObjectEvent event);
54
55/// An event that describes creation of an object.
56class 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.
82class 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)
100typedef 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.
116class 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

Provided by KDAB

Privacy Policy
Learn more about Flutter for embedded and desktop on industrialflutter.com