Add ability to cache images by size (#18135)
This commit is contained in:
parent
dac2ebf0c0
commit
e3d9ecaead
@ -5,6 +5,7 @@
|
||||
import 'image_stream.dart';
|
||||
|
||||
const int _kDefaultSize = 1000;
|
||||
const int _kDefaultSizeBytes = 10485760; // 10 MiB
|
||||
|
||||
/// Class for the [imageCache] object.
|
||||
///
|
||||
@ -25,7 +26,8 @@ const int _kDefaultSize = 1000;
|
||||
/// Generally this class is not used directly. The [ImageProvider] class and its
|
||||
/// subclasses automatically handle the caching of images.
|
||||
class ImageCache {
|
||||
final Map<Object, ImageStreamCompleter> _cache = <Object, ImageStreamCompleter>{};
|
||||
final Map<Object, ImageStreamCompleter> _pendingImages = <Object, ImageStreamCompleter>{};
|
||||
final Map<Object, _CachedImage> _cache = <Object, _CachedImage>{};
|
||||
|
||||
/// Maximum number of entries to store in the cache.
|
||||
///
|
||||
@ -47,20 +49,76 @@ class ImageCache {
|
||||
_maximumSize = value;
|
||||
if (maximumSize == 0) {
|
||||
_cache.clear();
|
||||
_currentSizeBytes = 0;
|
||||
} else {
|
||||
while (_cache.length > maximumSize)
|
||||
_cache.remove(_cache.keys.first);
|
||||
_checkCacheSize();
|
||||
}
|
||||
}
|
||||
|
||||
/// The current number of cached entries.
|
||||
int get currentSize => _cache.length;
|
||||
|
||||
/// Maximum size of entries to store in the cache in bytes.
|
||||
///
|
||||
/// Once more than this amount of bytes have been cached, the
|
||||
/// least-recently-used entry is evicted until there are fewer than the
|
||||
/// maximum bytes.
|
||||
int get maximumSizeBytes => _maximumSizeBytes;
|
||||
int _maximumSizeBytes = _kDefaultSizeBytes;
|
||||
/// Changes the maximum cache bytes.
|
||||
///
|
||||
/// If the new size is smaller than the current size in bytes, the
|
||||
/// extraneous elements are evicted immediately. Setting this to zero and then
|
||||
/// returning it to its original value will therefore immediately clear the
|
||||
/// cache.
|
||||
set maximumSizeBytes(int value) {
|
||||
assert(value != null);
|
||||
assert(value >= 0);
|
||||
if (value == _maximumSizeBytes)
|
||||
return;
|
||||
_maximumSizeBytes = value;
|
||||
if (_maximumSizeBytes == 0) {
|
||||
_cache.clear();
|
||||
_currentSizeBytes = 0;
|
||||
} else {
|
||||
_checkCacheSize();
|
||||
}
|
||||
}
|
||||
|
||||
/// The current size of cached entries in bytes.
|
||||
int get currentSizeBytes => _currentSizeBytes;
|
||||
int _currentSizeBytes = 0;
|
||||
|
||||
/// Evicts all entries from the cache.
|
||||
///
|
||||
/// This is useful if, for instance, the root asset bundle has been updated
|
||||
/// and therefore new images must be obtained.
|
||||
// TODO(ianh): Provide a way to target individual images. This is currently non-trivial
|
||||
// because by the time we get to the imageCache, the keys we're using are opaque.
|
||||
///
|
||||
/// Images which have not finished loading yet will not be removed from the
|
||||
/// cache, and when they complete they will be inserted as normal.
|
||||
void clear() {
|
||||
_cache.clear();
|
||||
_currentSizeBytes = 0;
|
||||
}
|
||||
|
||||
/// Evicts a single entry from the cache, returning true if successful.
|
||||
///
|
||||
/// The [key] must be equal to an object used to cache an image in
|
||||
/// [ImageCache.putIfAbsent].
|
||||
///
|
||||
/// If the key is not immediately available, as is common, consider using
|
||||
/// [ImageProvider.evict] to call this method indirectly instead.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// * [ImageProvider], for providing images to the [Image] widget.
|
||||
bool evict(Object key) {
|
||||
final _CachedImage image = _cache.remove(key);
|
||||
if (image != null) {
|
||||
_currentSizeBytes -= image.sizeBytes;
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/// Returns the previously cached [ImageStream] for the given key, if available;
|
||||
@ -71,21 +129,53 @@ class ImageCache {
|
||||
ImageStreamCompleter putIfAbsent(Object key, ImageStreamCompleter loader()) {
|
||||
assert(key != null);
|
||||
assert(loader != null);
|
||||
ImageStreamCompleter result = _cache[key];
|
||||
if (result != null) {
|
||||
// Remove the provider from the list so that we can put it back in below
|
||||
// and thus move it to the end of the list.
|
||||
_cache.remove(key);
|
||||
} else {
|
||||
if (_cache.length == maximumSize && maximumSize > 0)
|
||||
_cache.remove(_cache.keys.first);
|
||||
result = loader();
|
||||
ImageStreamCompleter result = _pendingImages[key];
|
||||
// Nothing needs to be done because the image hasn't loaded yet.
|
||||
if (result != null)
|
||||
return result;
|
||||
// Remove the provider from the list so that we can move it to the
|
||||
// recently used position below.
|
||||
final _CachedImage image = _cache.remove(key);
|
||||
if (image != null) {
|
||||
_cache[key] = image;
|
||||
return image.completer;
|
||||
}
|
||||
if (maximumSize > 0) {
|
||||
assert(_cache.length < maximumSize);
|
||||
_cache[key] = result;
|
||||
result = loader();
|
||||
void listener(ImageInfo info, bool syncCall) {
|
||||
// Images that fail to load don't contribute to cache size.
|
||||
final int imageSize = info.image == null ? 0 : info.image.height * info.image.width * 4;
|
||||
final _CachedImage image = new _CachedImage(result, imageSize);
|
||||
_currentSizeBytes += imageSize;
|
||||
_pendingImages.remove(key);
|
||||
_cache[key] = image;
|
||||
result.removeListener(listener);
|
||||
_checkCacheSize();
|
||||
}
|
||||
if (maximumSize > 0 && maximumSizeBytes > 0) {
|
||||
_pendingImages[key] = result;
|
||||
result.addListener(listener);
|
||||
}
|
||||
assert(_cache.length <= maximumSize);
|
||||
return result;
|
||||
}
|
||||
|
||||
// Remove images from the cache until both the length and bytes are below
|
||||
// maximum, or the cache is empty.
|
||||
void _checkCacheSize() {
|
||||
while (_currentSizeBytes > _maximumSizeBytes || _cache.length > _maximumSize) {
|
||||
final Object key = _cache.keys.first;
|
||||
final _CachedImage image = _cache[key];
|
||||
_currentSizeBytes -= image.sizeBytes;
|
||||
_cache.remove(key);
|
||||
}
|
||||
assert(_currentSizeBytes >= 0);
|
||||
assert(_cache.length <= maximumSize);
|
||||
assert(_currentSizeBytes <= maximumSizeBytes);
|
||||
}
|
||||
}
|
||||
|
||||
class _CachedImage {
|
||||
_CachedImage(this.completer, this.sizeBytes);
|
||||
|
||||
final ImageStreamCompleter completer;
|
||||
final int sizeBytes;
|
||||
}
|
||||
|
@ -12,6 +12,7 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
|
||||
import 'binding.dart';
|
||||
import 'image_cache.dart';
|
||||
import 'image_stream.dart';
|
||||
|
||||
/// Configuration information passed to the [ImageProvider.resolve] method to
|
||||
@ -284,6 +285,49 @@ abstract class ImageProvider<T> {
|
||||
return stream;
|
||||
}
|
||||
|
||||
/// Evicts an entry from the image cache.
|
||||
///
|
||||
/// Returns a [Future] which indicates whether the value was successfully
|
||||
/// removed.
|
||||
///
|
||||
/// The [ImageProvider] used does not need to be the same instance that was
|
||||
/// passed to an [Image] widget, but it does need to create a key which is
|
||||
/// equal to one.
|
||||
///
|
||||
/// The [cache] is optional and defaults to the global image cache.
|
||||
///
|
||||
/// The [configuration] is optional and defaults to
|
||||
/// [ImageConfiguration.empty].
|
||||
///
|
||||
/// ## Sample code
|
||||
///
|
||||
/// The following sample code shows how an image loaded using the [Image]
|
||||
/// widget can be evicted using a [NetworkImage] with a matching url.
|
||||
///
|
||||
/// ```dart
|
||||
/// class MyWidget extends StatelessWidget {
|
||||
/// final String url = '...';
|
||||
///
|
||||
/// @override
|
||||
/// Widget build(BuildContext context) {
|
||||
/// return new Image.network(url);
|
||||
/// }
|
||||
///
|
||||
/// void evictImage() {
|
||||
/// final NetworkImage provider = new NetworkImage(url);
|
||||
/// provider.evict().then<void>((bool success) {
|
||||
/// if (success)
|
||||
/// debugPrint('removed image!');
|
||||
/// });
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
Future<bool> evict({ImageCache cache, ImageConfiguration configuration = ImageConfiguration.empty}) async {
|
||||
cache ??= imageCache;
|
||||
final T key = await obtainKey(configuration);
|
||||
return cache.evict(key);
|
||||
}
|
||||
|
||||
/// Converts an ImageProvider's settings plus an ImageConfiguration to a key
|
||||
/// that describes the precise image to load.
|
||||
///
|
||||
|
@ -10,35 +10,70 @@ import 'mocks_for_image_cache.dart';
|
||||
|
||||
void main() {
|
||||
new TestRenderingFlutterBinding(); // initializes the imageCache
|
||||
group(ImageCache, () {
|
||||
tearDown(() {
|
||||
imageCache.clear();
|
||||
imageCache.maximumSize = 1000;
|
||||
imageCache.maximumSizeBytes = 10485760;
|
||||
});
|
||||
|
||||
test('Image cache resizing', () async {
|
||||
test('Image cache resizing based on count', () async {
|
||||
imageCache.maximumSize = 2;
|
||||
|
||||
imageCache.maximumSize = 2;
|
||||
final TestImageInfo a = await extractOneFrame(const TestImageProvider(1, 1).resolve(ImageConfiguration.empty));
|
||||
final TestImageInfo b = await extractOneFrame(const TestImageProvider(2, 2).resolve(ImageConfiguration.empty));
|
||||
final TestImageInfo c = await extractOneFrame(const TestImageProvider(3, 3).resolve(ImageConfiguration.empty));
|
||||
final TestImageInfo d = await extractOneFrame(const TestImageProvider(1, 4).resolve(ImageConfiguration.empty));
|
||||
expect(a.value, equals(1));
|
||||
expect(b.value, equals(2));
|
||||
expect(c.value, equals(3));
|
||||
expect(d.value, equals(4));
|
||||
|
||||
final TestImageInfo a = await extractOneFrame(const TestImageProvider(1, 1).resolve(ImageConfiguration.empty));
|
||||
final TestImageInfo b = await extractOneFrame(const TestImageProvider(2, 2).resolve(ImageConfiguration.empty));
|
||||
final TestImageInfo c = await extractOneFrame(const TestImageProvider(3, 3).resolve(ImageConfiguration.empty));
|
||||
final TestImageInfo d = await extractOneFrame(const TestImageProvider(1, 4).resolve(ImageConfiguration.empty));
|
||||
expect(a.value, equals(1));
|
||||
expect(b.value, equals(2));
|
||||
expect(c.value, equals(3));
|
||||
expect(d.value, equals(4));
|
||||
imageCache.maximumSize = 0;
|
||||
|
||||
imageCache.maximumSize = 0;
|
||||
final TestImageInfo e = await extractOneFrame(const TestImageProvider(1, 5).resolve(ImageConfiguration.empty));
|
||||
expect(e.value, equals(5));
|
||||
|
||||
final TestImageInfo e = await extractOneFrame(const TestImageProvider(1, 5).resolve(ImageConfiguration.empty));
|
||||
expect(e.value, equals(5));
|
||||
final TestImageInfo f = await extractOneFrame(const TestImageProvider(1, 6).resolve(ImageConfiguration.empty));
|
||||
expect(f.value, equals(6));
|
||||
|
||||
final TestImageInfo f = await extractOneFrame(const TestImageProvider(1, 6).resolve(ImageConfiguration.empty));
|
||||
expect(f.value, equals(6));
|
||||
imageCache.maximumSize = 3;
|
||||
|
||||
imageCache.maximumSize = 3;
|
||||
final TestImageInfo g = await extractOneFrame(const TestImageProvider(1, 7).resolve(ImageConfiguration.empty));
|
||||
expect(g.value, equals(7));
|
||||
|
||||
final TestImageInfo g = await extractOneFrame(const TestImageProvider(1, 7).resolve(ImageConfiguration.empty));
|
||||
expect(g.value, equals(7));
|
||||
final TestImageInfo h = await extractOneFrame(const TestImageProvider(1, 8).resolve(ImageConfiguration.empty));
|
||||
expect(h.value, equals(7));
|
||||
});
|
||||
|
||||
final TestImageInfo h = await extractOneFrame(const TestImageProvider(1, 8).resolve(ImageConfiguration.empty));
|
||||
expect(h.value, equals(7));
|
||||
test('Image cache resizing based on size', () async {
|
||||
const TestImage testImage = const TestImage(width: 8, height: 8); // 256 B.
|
||||
imageCache.maximumSizeBytes = 256 * 2;
|
||||
|
||||
final TestImageInfo a = await extractOneFrame(const TestImageProvider(1, 1, image: testImage).resolve(ImageConfiguration.empty));
|
||||
final TestImageInfo b = await extractOneFrame(const TestImageProvider(2, 2, image: testImage).resolve(ImageConfiguration.empty));
|
||||
final TestImageInfo c = await extractOneFrame(const TestImageProvider(3, 3, image: testImage).resolve(ImageConfiguration.empty));
|
||||
final TestImageInfo d = await extractOneFrame(const TestImageProvider(1, 4, image: testImage).resolve(ImageConfiguration.empty));
|
||||
expect(a.value, equals(1));
|
||||
expect(b.value, equals(2));
|
||||
expect(c.value, equals(3));
|
||||
expect(d.value, equals(4));
|
||||
|
||||
imageCache.maximumSizeBytes = 0;
|
||||
|
||||
final TestImageInfo e = await extractOneFrame(const TestImageProvider(1, 5, image: testImage).resolve(ImageConfiguration.empty));
|
||||
expect(e.value, equals(5));
|
||||
|
||||
final TestImageInfo f = await extractOneFrame(const TestImageProvider(1, 6, image: testImage).resolve(ImageConfiguration.empty));
|
||||
expect(f.value, equals(6));
|
||||
|
||||
imageCache.maximumSizeBytes = 256 * 3;
|
||||
|
||||
final TestImageInfo g = await extractOneFrame(const TestImageProvider(1, 7, image: testImage).resolve(ImageConfiguration.empty));
|
||||
expect(g.value, equals(7));
|
||||
|
||||
final TestImageInfo h = await extractOneFrame(const TestImageProvider(1, 8, image: testImage).resolve(ImageConfiguration.empty));
|
||||
expect(h.value, equals(7));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -10,77 +10,113 @@ import 'mocks_for_image_cache.dart';
|
||||
|
||||
void main() {
|
||||
new TestRenderingFlutterBinding(); // initializes the imageCache
|
||||
group(ImageCache, () {
|
||||
tearDown(() {
|
||||
imageCache.clear();
|
||||
imageCache.maximumSize = 1000;
|
||||
imageCache.maximumSizeBytes = 10485760;
|
||||
});
|
||||
|
||||
test('Image cache', () async {
|
||||
test('maintains cache size', () async {
|
||||
imageCache.maximumSize = 3;
|
||||
|
||||
imageCache.maximumSize = 3;
|
||||
final TestImageInfo a = await extractOneFrame(const TestImageProvider(1, 1).resolve(ImageConfiguration.empty));
|
||||
expect(a.value, equals(1));
|
||||
final TestImageInfo b = await extractOneFrame(const TestImageProvider(1, 2).resolve(ImageConfiguration.empty));
|
||||
expect(b.value, equals(1));
|
||||
final TestImageInfo c = await extractOneFrame(const TestImageProvider(1, 3).resolve(ImageConfiguration.empty));
|
||||
expect(c.value, equals(1));
|
||||
final TestImageInfo d = await extractOneFrame(const TestImageProvider(1, 4).resolve(ImageConfiguration.empty));
|
||||
expect(d.value, equals(1));
|
||||
final TestImageInfo e = await extractOneFrame(const TestImageProvider(1, 5).resolve(ImageConfiguration.empty));
|
||||
expect(e.value, equals(1));
|
||||
final TestImageInfo f = await extractOneFrame(const TestImageProvider(1, 6).resolve(ImageConfiguration.empty));
|
||||
expect(f.value, equals(1));
|
||||
|
||||
final TestImageInfo a = await extractOneFrame(const TestImageProvider(1, 1).resolve(ImageConfiguration.empty));
|
||||
expect(a.value, equals(1));
|
||||
final TestImageInfo b = await extractOneFrame(const TestImageProvider(1, 2).resolve(ImageConfiguration.empty));
|
||||
expect(b.value, equals(1));
|
||||
final TestImageInfo c = await extractOneFrame(const TestImageProvider(1, 3).resolve(ImageConfiguration.empty));
|
||||
expect(c.value, equals(1));
|
||||
final TestImageInfo d = await extractOneFrame(const TestImageProvider(1, 4).resolve(ImageConfiguration.empty));
|
||||
expect(d.value, equals(1));
|
||||
final TestImageInfo e = await extractOneFrame(const TestImageProvider(1, 5).resolve(ImageConfiguration.empty));
|
||||
expect(e.value, equals(1));
|
||||
final TestImageInfo f = await extractOneFrame(const TestImageProvider(1, 6).resolve(ImageConfiguration.empty));
|
||||
expect(f.value, equals(1));
|
||||
expect(f, equals(a));
|
||||
|
||||
expect(f, equals(a));
|
||||
// cache still only has one entry in it: 1(1)
|
||||
|
||||
// cache still only has one entry in it: 1(1)
|
||||
final TestImageInfo g = await extractOneFrame(const TestImageProvider(2, 7).resolve(ImageConfiguration.empty));
|
||||
expect(g.value, equals(7));
|
||||
|
||||
final TestImageInfo g = await extractOneFrame(const TestImageProvider(2, 7).resolve(ImageConfiguration.empty));
|
||||
expect(g.value, equals(7));
|
||||
// cache has two entries in it: 1(1), 2(7)
|
||||
|
||||
// cache has two entries in it: 1(1), 2(7)
|
||||
final TestImageInfo h = await extractOneFrame(const TestImageProvider(1, 8).resolve(ImageConfiguration.empty));
|
||||
expect(h.value, equals(1));
|
||||
|
||||
final TestImageInfo h = await extractOneFrame(const TestImageProvider(1, 8).resolve(ImageConfiguration.empty));
|
||||
expect(h.value, equals(1));
|
||||
// cache still has two entries in it: 2(7), 1(1)
|
||||
|
||||
// cache still has two entries in it: 2(7), 1(1)
|
||||
final TestImageInfo i = await extractOneFrame(const TestImageProvider(3, 9).resolve(ImageConfiguration.empty));
|
||||
expect(i.value, equals(9));
|
||||
|
||||
final TestImageInfo i = await extractOneFrame(const TestImageProvider(3, 9).resolve(ImageConfiguration.empty));
|
||||
expect(i.value, equals(9));
|
||||
// cache has three entries in it: 2(7), 1(1), 3(9)
|
||||
|
||||
// cache has three entries in it: 2(7), 1(1), 3(9)
|
||||
final TestImageInfo j = await extractOneFrame(const TestImageProvider(1, 10).resolve(ImageConfiguration.empty));
|
||||
expect(j.value, equals(1));
|
||||
|
||||
final TestImageInfo j = await extractOneFrame(const TestImageProvider(1, 10).resolve(ImageConfiguration.empty));
|
||||
expect(j.value, equals(1));
|
||||
// cache still has three entries in it: 2(7), 3(9), 1(1)
|
||||
|
||||
// cache still has three entries in it: 2(7), 3(9), 1(1)
|
||||
final TestImageInfo k = await extractOneFrame(const TestImageProvider(4, 11).resolve(ImageConfiguration.empty));
|
||||
expect(k.value, equals(11));
|
||||
|
||||
final TestImageInfo k = await extractOneFrame(const TestImageProvider(4, 11).resolve(ImageConfiguration.empty));
|
||||
expect(k.value, equals(11));
|
||||
// cache has three entries: 3(9), 1(1), 4(11)
|
||||
|
||||
// cache has three entries: 3(9), 1(1), 4(11)
|
||||
final TestImageInfo l = await extractOneFrame(const TestImageProvider(1, 12).resolve(ImageConfiguration.empty));
|
||||
expect(l.value, equals(1));
|
||||
|
||||
final TestImageInfo l = await extractOneFrame(const TestImageProvider(1, 12).resolve(ImageConfiguration.empty));
|
||||
expect(l.value, equals(1));
|
||||
// cache has three entries: 3(9), 4(11), 1(1)
|
||||
|
||||
// cache has three entries: 3(9), 4(11), 1(1)
|
||||
final TestImageInfo m = await extractOneFrame(const TestImageProvider(2, 13).resolve(ImageConfiguration.empty));
|
||||
expect(m.value, equals(13));
|
||||
|
||||
final TestImageInfo m = await extractOneFrame(const TestImageProvider(2, 13).resolve(ImageConfiguration.empty));
|
||||
expect(m.value, equals(13));
|
||||
// cache has three entries: 4(11), 1(1), 2(13)
|
||||
|
||||
// cache has three entries: 4(11), 1(1), 2(13)
|
||||
final TestImageInfo n = await extractOneFrame(const TestImageProvider(3, 14).resolve(ImageConfiguration.empty));
|
||||
expect(n.value, equals(14));
|
||||
|
||||
final TestImageInfo n = await extractOneFrame(const TestImageProvider(3, 14).resolve(ImageConfiguration.empty));
|
||||
expect(n.value, equals(14));
|
||||
// cache has three entries: 1(1), 2(13), 3(14)
|
||||
|
||||
// cache has three entries: 1(1), 2(13), 3(14)
|
||||
final TestImageInfo o = await extractOneFrame(const TestImageProvider(4, 15).resolve(ImageConfiguration.empty));
|
||||
expect(o.value, equals(15));
|
||||
|
||||
final TestImageInfo o = await extractOneFrame(const TestImageProvider(4, 15).resolve(ImageConfiguration.empty));
|
||||
expect(o.value, equals(15));
|
||||
// cache has three entries: 2(13), 3(14), 4(15)
|
||||
|
||||
// cache has three entries: 2(13), 3(14), 4(15)
|
||||
final TestImageInfo p = await extractOneFrame(const TestImageProvider(1, 16).resolve(ImageConfiguration.empty));
|
||||
expect(p.value, equals(16));
|
||||
|
||||
final TestImageInfo p = await extractOneFrame(const TestImageProvider(1, 16).resolve(ImageConfiguration.empty));
|
||||
expect(p.value, equals(16));
|
||||
// cache has three entries: 3(14), 4(15), 1(16)
|
||||
|
||||
// cache has three entries: 3(14), 4(15), 1(16)
|
||||
});
|
||||
|
||||
test('clear removes all images and resets cache size', () async {
|
||||
const TestImage testImage = const TestImage(width: 8, height: 8);
|
||||
|
||||
expect(imageCache.currentSize, 0);
|
||||
expect(imageCache.currentSizeBytes, 0);
|
||||
|
||||
await extractOneFrame(const TestImageProvider(1, 1, image: testImage).resolve(ImageConfiguration.empty));
|
||||
await extractOneFrame(const TestImageProvider(2, 2, image: testImage).resolve(ImageConfiguration.empty));
|
||||
|
||||
expect(imageCache.currentSize, 2);
|
||||
expect(imageCache.currentSizeBytes, 256 * 2);
|
||||
|
||||
imageCache.clear();
|
||||
|
||||
expect(imageCache.currentSize, 0);
|
||||
expect(imageCache.currentSizeBytes, 0);
|
||||
});
|
||||
|
||||
test('evicts individual images', () async {
|
||||
const TestImage testImage = const TestImage(width: 8, height: 8);
|
||||
await extractOneFrame(const TestImageProvider(1, 1, image: testImage).resolve(ImageConfiguration.empty));
|
||||
await extractOneFrame(const TestImageProvider(2, 2, image: testImage).resolve(ImageConfiguration.empty));
|
||||
|
||||
expect(imageCache.currentSize, 2);
|
||||
expect(imageCache.currentSizeBytes, 256 * 2);
|
||||
expect(imageCache.evict(1), true);
|
||||
expect(imageCache.currentSize, 1);
|
||||
expect(imageCache.currentSizeBytes, 256);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -2,13 +2,56 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter/painting.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../rendering/rendering_tester.dart';
|
||||
import 'image_data.dart';
|
||||
|
||||
void main() {
|
||||
test('NetworkImage non-null url test', () {
|
||||
expect(() {
|
||||
new NetworkImage(nonconst(null));
|
||||
}, throwsAssertionError);
|
||||
new TestRenderingFlutterBinding(); // initializes the imageCache
|
||||
group(ImageProvider, () {
|
||||
tearDown(() {
|
||||
imageCache.clear();
|
||||
});
|
||||
|
||||
test('NetworkImage non-null url test', () {
|
||||
expect(() {
|
||||
new NetworkImage(nonconst(null));
|
||||
}, throwsAssertionError);
|
||||
});
|
||||
|
||||
test('ImageProvider can evict images', () async {
|
||||
final Uint8List bytes = new Uint8List.fromList(kTransparentImage);
|
||||
final MemoryImage imageProvider = new MemoryImage(bytes);
|
||||
final ImageStream stream = imageProvider.resolve(ImageConfiguration.empty);
|
||||
final Completer<void> completer = new Completer<void>();
|
||||
stream.addListener((ImageInfo info, bool syncCall) => completer.complete());
|
||||
await completer.future;
|
||||
|
||||
expect(imageCache.currentSize, 1);
|
||||
expect(await new MemoryImage(bytes).evict(), true);
|
||||
expect(imageCache.currentSize, 0);
|
||||
});
|
||||
|
||||
test('ImageProvider.evict respects the provided ImageCache', () async {
|
||||
final ImageCache otherCache = new ImageCache();
|
||||
final Uint8List bytes = new Uint8List.fromList(kTransparentImage);
|
||||
final MemoryImage imageProvider = new MemoryImage(bytes);
|
||||
otherCache.putIfAbsent(imageProvider, () => imageProvider.load(imageProvider));
|
||||
final ImageStream stream = imageProvider.resolve(ImageConfiguration.empty);
|
||||
final Completer<void> completer = new Completer<void>();
|
||||
stream.addListener((ImageInfo info, bool syncCall) => completer.complete());
|
||||
await completer.future;
|
||||
|
||||
expect(otherCache.currentSize, 1);
|
||||
expect(imageCache.currentSize, 1);
|
||||
expect(await imageProvider.evict(cache: otherCache), true);
|
||||
expect(otherCache.currentSize, 0);
|
||||
expect(imageCache.currentSize, 1);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -3,7 +3,9 @@
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:typed_data';
|
||||
import 'dart:ui' as ui show Image;
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/painting.dart';
|
||||
@ -54,3 +56,19 @@ Future<ImageInfo> extractOneFrame(ImageStream stream) {
|
||||
stream.addListener(listener);
|
||||
return completer.future;
|
||||
}
|
||||
|
||||
class TestImage implements ui.Image {
|
||||
const TestImage({this.height = 0, this.width = 0});
|
||||
@override
|
||||
final int height;
|
||||
@override
|
||||
final int width;
|
||||
|
||||
@override
|
||||
void dispose() {}
|
||||
|
||||
@override
|
||||
Future<ByteData> toByteData({ImageByteFormat format = ImageByteFormat.rawRgba}) {
|
||||
throw new UnimplementedError();
|
||||
}
|
||||
}
|
||||
|
@ -295,7 +295,7 @@ void main() {
|
||||
final TestImageProvider imageProvider = new TestImageProvider();
|
||||
await tester.pumpWidget(new Image(image: imageProvider));
|
||||
final State<Image> image = tester.state/*State<Image>*/(find.byType(Image));
|
||||
expect(image.toString(), equalsIgnoringHashCodes('_ImageState#00000(stream: ImageStream#00000(OneFrameImageStreamCompleter#00000, unresolved, 1 listener), pixels: null)'));
|
||||
expect(image.toString(), equalsIgnoringHashCodes('_ImageState#00000(stream: ImageStream#00000(OneFrameImageStreamCompleter#00000, unresolved, 2 listeners), pixels: null)'));
|
||||
imageProvider.complete();
|
||||
await tester.pump();
|
||||
expect(image.toString(), equalsIgnoringHashCodes('_ImageState#00000(stream: ImageStream#00000(OneFrameImageStreamCompleter#00000, [100×100] @ 1.0x, 1 listener), pixels: [100×100] @ 1.0x)'));
|
||||
@ -353,14 +353,14 @@ void main() {
|
||||
child: image,
|
||||
),
|
||||
);
|
||||
expect(imageStreamCompleter.listeners.length, 1);
|
||||
expect(imageStreamCompleter.listeners.length, 2);
|
||||
await tester.pumpWidget(
|
||||
new TickerMode(
|
||||
enabled: false,
|
||||
child: image,
|
||||
),
|
||||
);
|
||||
expect(imageStreamCompleter.listeners.length, 0);
|
||||
expect(imageStreamCompleter.listeners.length, 1);
|
||||
});
|
||||
|
||||
testWidgets('Verify Image shows correct RenderImage when changing to an already completed provider', (WidgetTester tester) async {
|
||||
|
Loading…
x
Reference in New Issue
Block a user