Skip to content

Commit

Permalink
Clear ImageCache on MemoryPressure (flutter#53959)
Browse files Browse the repository at this point in the history
  • Loading branch information
dnfield authored Apr 4, 2020
1 parent 4b92c16 commit 14f3a36
Show file tree
Hide file tree
Showing 4 changed files with 61 additions and 39 deletions.
6 changes: 6 additions & 0 deletions packages/flutter/lib/src/painting/binding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,12 @@ mixin PaintingBinding on BindingBase, ServicesBinding {
imageCache.clearLiveImages();
}

@override
void handleMemoryPressure() {
super.handleMemoryPressure();
imageCache.clear();
}

/// Listenable that notifies when the available fonts on the system have
/// changed.
///
Expand Down
21 changes: 20 additions & 1 deletion packages/flutter/lib/src/services/binding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -48,13 +48,32 @@ mixin ServicesBinding on BindingBase {
return const _DefaultBinaryMessenger._();
}


/// Called when the operating system notifies the application of a memory
/// pressure situation.
///
/// This method exposes the `memoryPressure` notification from
/// [SystemChannels.system].
@protected
@mustCallSuper
void handleMemoryPressure() { }

/// Handler called for messages received on the [SystemChannels.system]
/// message channel.
///
/// Other bindings may override this to respond to incoming system messages.
@protected
@mustCallSuper
Future<void> handleSystemMessage(Object systemMessage) async { }
Future<void> handleSystemMessage(Object systemMessage) async {
final Map<String, dynamic> message = systemMessage as Map<String, dynamic>;
final String type = message['type'] as String;
switch (type) {
case 'memoryPressure':
handleMemoryPressure();
break;
}
return;
}

/// Adds relevant licenses to the [LicenseRegistry].
///
Expand Down
23 changes: 2 additions & 21 deletions packages/flutter/lib/src/widgets/binding.dart
Original file line number Diff line number Diff line change
Expand Up @@ -655,32 +655,13 @@ mixin WidgetsBinding on BindingBase, ServicesBinding, SchedulerBinding, GestureB
observer.didChangeAppLifecycleState(state);
}

/// Called when the operating system notifies the application of a memory
/// pressure situation.
///
/// Notifies all the observers using
/// [WidgetsBindingObserver.didHaveMemoryPressure].
///
/// This method exposes the `memoryPressure` notification from
/// [SystemChannels.system].
@override
void handleMemoryPressure() {
super.handleMemoryPressure();
for (final WidgetsBindingObserver observer in _observers)
observer.didHaveMemoryPressure();
}

@override
Future<void> handleSystemMessage(Object systemMessage) async {
await super.handleSystemMessage(systemMessage);
final Map<String, dynamic> message = systemMessage as Map<String, dynamic>;
final String type = message['type'] as String;
switch (type) {
case 'memoryPressure':
handleMemoryPressure();
break;
}
return;
}

bool _needToReportFirstFrame = true;

final Completer<void> _firstFrameCompleter = Completer<void>();
Expand Down
50 changes: 33 additions & 17 deletions packages/flutter/test/painting/binding_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,30 +2,30 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'dart:typed_data' show Uint8List;
import 'dart:ui';
import 'dart:ui' as ui;

import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/painting.dart';

import 'image_data.dart';
import 'painting_utils.dart';

void main() {
final PaintingBindingSpy binding = PaintingBindingSpy();

test('instantiateImageCodec used for loading images', () async {
expect(binding.instantiateImageCodecCalledCount, 0);

final Uint8List bytes = Uint8List.fromList(kTransparentImage);
final MemoryImage memoryImage = MemoryImage(bytes);
memoryImage.load(memoryImage, (Uint8List bytes, {int cacheWidth, int cacheHeight}) {
return PaintingBinding.instance.instantiateImageCodec(bytes, cacheWidth: cacheWidth, cacheHeight: cacheHeight);
});
expect(binding.instantiateImageCodecCalledCount, 1);
testWidgets('didHaveMemoryPressure clears imageCache', (WidgetTester tester) async {
imageCache.putIfAbsent(1, () => OneFrameImageStreamCompleter(
Future<ImageInfo>.value(ImageInfo(
image: FakeImage(),
scale: 1.0,
),
)));

await tester.idle();
expect(imageCache.currentSize, 1);
final ByteData message = const JSONMessageCodec().encodeMessage(
<String, dynamic>{'type': 'memoryPressure'});
await ServicesBinding.instance.defaultBinaryMessenger.handlePlatformMessage('flutter/system', message, (_) { });
expect(imageCache.currentSize, 0);
});

test('evict clears live references', () async {
Expand Down Expand Up @@ -84,7 +84,7 @@ class TestBindingBase implements BindingBase {
void unlocked() {}

@override
Window get window => throw UnimplementedError();
ui.Window get window => throw UnimplementedError();
}

class TestPaintingBinding extends TestBindingBase with ServicesBinding, PaintingBinding {
Expand All @@ -111,4 +111,20 @@ class FakeImageCache extends ImageCache {
liveClearCount += 1;
super.clearLiveImages();
}
}
}

class FakeImage implements ui.Image {
@override
void dispose() {}

@override
int get height => 10;

@override
Future<ByteData> toByteData({ui.ImageByteFormat format = ui.ImageByteFormat.rawRgba}) {
throw UnimplementedError();
}

@override
int get width => 10;
}

0 comments on commit 14f3a36

Please sign in to comment.