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
|
||||
// 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/services.dart' show ServicesBinding;
|
||||
|
||||
import 'image_cache.dart';
|
||||
|
||||
const double _kDefaultDecodedCacheRatioCap = 25.0;
|
||||
|
||||
/// Binding for the painting library.
|
||||
///
|
||||
/// Hooks into the cache eviction logic to clear the image cache.
|
||||
@ -44,6 +48,37 @@ abstract class PaintingBinding extends BindingBase with ServicesBinding {
|
||||
@protected
|
||||
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
|
||||
void evict(String asset) {
|
||||
super.evict(asset);
|
||||
|
@ -5,7 +5,7 @@
|
||||
import 'dart:async';
|
||||
import 'dart:io';
|
||||
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 'package:flutter/foundation.dart';
|
||||
@ -427,7 +427,7 @@ abstract class AssetBundleImageProvider extends ImageProvider<AssetBundleImageKe
|
||||
final ByteData data = await key.bundle.load(key.name);
|
||||
if (data == null)
|
||||
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)
|
||||
throw Exception('NetworkImage is an empty file: $resolved');
|
||||
|
||||
return await ui.instantiateImageCodec(bytes);
|
||||
return await PaintingBinding.instance.instantiateImageCodec(bytes);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -555,7 +555,7 @@ class FileImage extends ImageProvider<FileImage> {
|
||||
if (bytes.lengthInBytes == 0)
|
||||
return null;
|
||||
|
||||
return await ui.instantiateImageCodec(bytes);
|
||||
return await PaintingBinding.instance.instantiateImageCodec(bytes);
|
||||
}
|
||||
|
||||
@override
|
||||
@ -616,7 +616,7 @@ class MemoryImage extends ImageProvider<MemoryImage> {
|
||||
Future<ui.Codec> _loadAsync(MemoryImage key) {
|
||||
assert(key == this);
|
||||
|
||||
return ui.instantiateImageCodec(bytes);
|
||||
return PaintingBinding.instance.instantiateImageCodec(bytes);
|
||||
}
|
||||
|
||||
@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