Unverified Commit 6fef292f authored by Michael Klimushyn's avatar Michael Klimushyn Committed by GitHub

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
parent 43001a3a
......@@ -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
......
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);
});
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment