Wire decodedCacheRatioCap from PaintingBinding to Codec (#22452)
Users can set `PaintingBinding.decodedCacheRatioCap` to control the max amount of memory used per image to avoid decoding frames each animation loop. This depends on flutter/engine#6310. Fixes #20998, and fixes #14344
This commit is contained in:
parent
43001a3ab6
commit
6fef292f44
@ -2,11 +2,15 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:typed_data' show Uint8List;
|
||||||
|
import 'dart:ui' as ui show instantiateImageCodec, Codec;
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
import 'package:flutter/services.dart' show ServicesBinding;
|
import 'package:flutter/services.dart' show ServicesBinding;
|
||||||
|
|
||||||
import 'image_cache.dart';
|
import 'image_cache.dart';
|
||||||
|
|
||||||
|
const double _kDefaultDecodedCacheRatioCap = 25.0;
|
||||||
|
|
||||||
/// Binding for the painting library.
|
/// Binding for the painting library.
|
||||||
///
|
///
|
||||||
/// Hooks into the cache eviction logic to clear the image cache.
|
/// Hooks into the cache eviction logic to clear the image cache.
|
||||||
@ -44,6 +48,37 @@ abstract class PaintingBinding extends BindingBase with ServicesBinding {
|
|||||||
@protected
|
@protected
|
||||||
ImageCache createImageCache() => ImageCache();
|
ImageCache createImageCache() => ImageCache();
|
||||||
|
|
||||||
|
/// The maximum multiple of the compressed image size used when caching an
|
||||||
|
/// animated image.
|
||||||
|
///
|
||||||
|
/// By default individual frames of animated images are cached into memory to
|
||||||
|
/// avoid using CPU to re-decode them for every loop in the animation. This
|
||||||
|
/// behavior will result in out-of-memory crashes when decoding large
|
||||||
|
/// (or large numbers of) animated images. Set this value to limit how much
|
||||||
|
/// memory each animated image is allowed to use to cache decoded frames
|
||||||
|
/// compared to its compressed size. For example, setting this to `2.0` means
|
||||||
|
/// that a 400KB GIF would be allowed at most to use 800KB of memory caching
|
||||||
|
/// unessential decoded frames. A setting of `1.0` or less disables all caching
|
||||||
|
/// of unessential decoded frames. See [_kDefaultDecodedCacheRatioCap] for the
|
||||||
|
/// default value.
|
||||||
|
double get decodedCacheRatioCap => _kDecodedCacheRatioCap;
|
||||||
|
double _kDecodedCacheRatioCap = _kDefaultDecodedCacheRatioCap;
|
||||||
|
/// Changes the maximum multiple of compressed image size used when caching an
|
||||||
|
/// animated image.
|
||||||
|
///
|
||||||
|
/// Changing this value only affects new images, not images that have already
|
||||||
|
/// been decoded.
|
||||||
|
set decodedCacheRatioCap(double value) {
|
||||||
|
assert (value != null);
|
||||||
|
assert (value >= 0.0);
|
||||||
|
_kDecodedCacheRatioCap = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Calls through to [dart:ui] with [decodedCacheRatioCap] from [ImageCache].
|
||||||
|
Future<ui.Codec> instantiateImageCodec(Uint8List list) {
|
||||||
|
return ui.instantiateImageCodec(list, decodedCacheRatioCap: decodedCacheRatioCap);
|
||||||
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
void evict(String asset) {
|
void evict(String asset) {
|
||||||
super.evict(asset);
|
super.evict(asset);
|
||||||
|
@ -5,7 +5,7 @@
|
|||||||
import 'dart:async';
|
import 'dart:async';
|
||||||
import 'dart:io';
|
import 'dart:io';
|
||||||
import 'dart:typed_data';
|
import 'dart:typed_data';
|
||||||
import 'dart:ui' as ui show instantiateImageCodec, Codec;
|
import 'dart:ui' as ui show Codec;
|
||||||
import 'dart:ui' show Size, Locale, TextDirection, hashValues;
|
import 'dart:ui' show Size, Locale, TextDirection, hashValues;
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
@ -427,7 +427,7 @@ abstract class AssetBundleImageProvider extends ImageProvider<AssetBundleImageKe
|
|||||||
final ByteData data = await key.bundle.load(key.name);
|
final ByteData data = await key.bundle.load(key.name);
|
||||||
if (data == null)
|
if (data == null)
|
||||||
throw 'Unable to read data';
|
throw 'Unable to read data';
|
||||||
return await ui.instantiateImageCodec(data.buffer.asUint8List());
|
return await PaintingBinding.instance.instantiateImageCodec(data.buffer.asUint8List());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -493,7 +493,7 @@ class NetworkImage extends ImageProvider<NetworkImage> {
|
|||||||
if (bytes.lengthInBytes == 0)
|
if (bytes.lengthInBytes == 0)
|
||||||
throw Exception('NetworkImage is an empty file: $resolved');
|
throw Exception('NetworkImage is an empty file: $resolved');
|
||||||
|
|
||||||
return await ui.instantiateImageCodec(bytes);
|
return await PaintingBinding.instance.instantiateImageCodec(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -555,7 +555,7 @@ class FileImage extends ImageProvider<FileImage> {
|
|||||||
if (bytes.lengthInBytes == 0)
|
if (bytes.lengthInBytes == 0)
|
||||||
return null;
|
return null;
|
||||||
|
|
||||||
return await ui.instantiateImageCodec(bytes);
|
return await PaintingBinding.instance.instantiateImageCodec(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
@ -616,7 +616,7 @@ class MemoryImage extends ImageProvider<MemoryImage> {
|
|||||||
Future<ui.Codec> _loadAsync(MemoryImage key) {
|
Future<ui.Codec> _loadAsync(MemoryImage key) {
|
||||||
assert(key == this);
|
assert(key == this);
|
||||||
|
|
||||||
return ui.instantiateImageCodec(bytes);
|
return PaintingBinding.instance.instantiateImageCodec(bytes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
49
packages/flutter/test/painting/binding_test.dart
Normal file
49
packages/flutter/test/painting/binding_test.dart
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import 'dart:typed_data' show Uint8List;
|
||||||
|
import 'dart:ui' as ui show instantiateImageCodec, Codec;
|
||||||
|
|
||||||
|
import 'package:flutter_test/flutter_test.dart';
|
||||||
|
import 'package:flutter/widgets.dart';
|
||||||
|
import 'package:flutter/painting.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
import '../painting/image_data.dart';
|
||||||
|
|
||||||
|
class PaintingBindingSpy extends BindingBase with PaintingBinding {
|
||||||
|
int counter = 0;
|
||||||
|
int get instantiateImageCodecCalledCount => counter;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Future<ui.Codec> instantiateImageCodec(Uint8List list) {
|
||||||
|
counter++;
|
||||||
|
return ui.instantiateImageCodec(list, decodedCacheRatioCap: decodedCacheRatioCap);
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
void initLicenses() {
|
||||||
|
// Do not include any licenses, because we're a test, and the LICENSE file
|
||||||
|
// doesn't get generated for tests.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
final PaintingBindingSpy binding = PaintingBindingSpy();
|
||||||
|
|
||||||
|
test('decodedCacheRatio', () async {
|
||||||
|
// final PaintingBinding binding = PaintingBinding.instance;
|
||||||
|
// Has default value.
|
||||||
|
expect(binding.decodedCacheRatioCap, isNot(null));
|
||||||
|
|
||||||
|
// Can be set.
|
||||||
|
binding.decodedCacheRatioCap = 1.0;
|
||||||
|
expect(binding.decodedCacheRatioCap, 1.0);
|
||||||
|
});
|
||||||
|
|
||||||
|
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);
|
||||||
|
expect(binding.instantiateImageCodecCalledCount, 1);
|
||||||
|
});
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user