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