Memory test on scrolling large images quickly (#46184)
diff --git a/dev/benchmarks/macrobenchmarks/assets/999x1000.png b/dev/benchmarks/macrobenchmarks/assets/999x1000.png new file mode 100644 index 0000000..e9b09f7 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/assets/999x1000.png Binary files differ
diff --git a/dev/benchmarks/macrobenchmarks/lib/common.dart b/dev/benchmarks/macrobenchmarks/lib/common.dart index d6902eb..c8d24dc 100644 --- a/dev/benchmarks/macrobenchmarks/lib/common.dart +++ b/dev/benchmarks/macrobenchmarks/lib/common.dart
@@ -7,3 +7,4 @@ const String kBackdropFilterRouteName = '/backdrop_filter'; const String kSimpleAnimationRouteName = '/simple_animation'; const String kPictureCacheRouteName = '/picture_cache'; +const String kLargeImagesRouteName = '/large_images';
diff --git a/dev/benchmarks/macrobenchmarks/lib/main.dart b/dev/benchmarks/macrobenchmarks/lib/main.dart index eaecea0..5a2160c 100644 --- a/dev/benchmarks/macrobenchmarks/lib/main.dart +++ b/dev/benchmarks/macrobenchmarks/lib/main.dart
@@ -3,6 +3,7 @@ // found in the LICENSE file. import 'package:flutter/material.dart'; +import 'package:macrobenchmarks/src/large_images.dart'; import 'package:macrobenchmarks/src/picture_cache.dart'; import 'common.dart'; @@ -13,24 +14,29 @@ const String kMacrobenchmarks ='Macrobenchmarks'; -void main() => runApp(MacrobenchmarksApp()); +void main() => runApp(const MacrobenchmarksApp()); class MacrobenchmarksApp extends StatelessWidget { + const MacrobenchmarksApp({this.initialRoute = '/'}); + @override Widget build(BuildContext context) { return MaterialApp( title: kMacrobenchmarks, - initialRoute: '/', + initialRoute: initialRoute, routes: <String, WidgetBuilder>{ '/': (BuildContext context) => HomePage(), kCullOpacityRouteName: (BuildContext context) => CullOpacityPage(), kCubicBezierRouteName: (BuildContext context) => CubicBezierPage(), kBackdropFilterRouteName: (BuildContext context) => BackdropFilterPage(), kSimpleAnimationRouteName: (BuildContext conttext) => SimpleAnimationPage(), - kPictureCacheRouteName: (BuildContext conttext) => PictureCachePage(), + kPictureCacheRouteName: (BuildContext context) => PictureCachePage(), + kLargeImagesRouteName: (BuildContext context) => LargeImagesPage(), }, ); } + + final String initialRoute; } class HomePage extends StatelessWidget { @@ -75,6 +81,13 @@ Navigator.pushNamed(context, kPictureCacheRouteName); }, ), + RaisedButton( + key: const Key(kLargeImagesRouteName), + child: const Text('Large Images'), + onPressed: () { + Navigator.pushNamed(context, kLargeImagesRouteName); + }, + ), ], ), );
diff --git a/dev/benchmarks/macrobenchmarks/lib/src/large_images.dart b/dev/benchmarks/macrobenchmarks/lib/src/large_images.dart new file mode 100644 index 0000000..5dd70ff --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/lib/src/large_images.dart
@@ -0,0 +1,48 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// 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/material.dart'; + +class LargeImagesPage extends StatelessWidget { + @override + Widget build(BuildContext context) { + final ImageCache imageCache = PaintingBinding.instance.imageCache; + imageCache.maximumSize = 30; + imageCache.maximumSizeBytes = 50 << 20; + return GridView.builder( + itemCount: 1000, + gridDelegate: const SliverGridDelegateWithFixedCrossAxisCount(crossAxisCount: 3), + itemBuilder: (BuildContext context, int index) => DummyImage(index), + ).build(context); + } +} + +class DummyImage extends StatelessWidget { + DummyImage(this.index) : super(key: ValueKey<int>(index)); + + @override + Widget build(BuildContext context) { + final Future<ByteData> pngData = _getPngData(context); + + return FutureBuilder<ByteData>( + future: pngData, + builder: (BuildContext context, AsyncSnapshot<ByteData> snapshot) { + // Use Image.memory instead of Image.asset to make sure that we're + // creating many copies of the image to trigger the memory issue. + return snapshot.data == null + ? Container() + : Image.memory(snapshot.data.buffer.asUint8List()); + }, + ); + } + + final int index; + + Future<ByteData> _getPngData(BuildContext context) async { + return DefaultAssetBundle.of(context).load('assets/999x1000.png'); + } +}
diff --git a/dev/benchmarks/macrobenchmarks/pubspec.yaml b/dev/benchmarks/macrobenchmarks/pubspec.yaml index 62a69cc..471d22c 100644 --- a/dev/benchmarks/macrobenchmarks/pubspec.yaml +++ b/dev/benchmarks/macrobenchmarks/pubspec.yaml
@@ -90,5 +90,6 @@ assets: - packages/flutter_gallery_assets/food/butternut_squash_soup.png - packages/flutter_gallery_assets/food/cherry_pie.png + - assets/999x1000.png # PUBSPEC CHECKSUM: 4cef
diff --git a/dev/benchmarks/macrobenchmarks/test_memory/large_images.dart b/dev/benchmarks/macrobenchmarks/test_memory/large_images.dart new file mode 100644 index 0000000..62c14d6 --- /dev/null +++ b/dev/benchmarks/macrobenchmarks/test_memory/large_images.dart
@@ -0,0 +1,35 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter/material.dart'; +import 'package:flutter/scheduler.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:macrobenchmarks/common.dart'; +import 'package:macrobenchmarks/main.dart'; + +Future<void> endOfAnimation() async { + do { + await SchedulerBinding.instance.endOfFrame; + } while (SchedulerBinding.instance.hasScheduledFrame); +} + +int iteration = 0; + +class LifecycleObserver extends WidgetsBindingObserver { + @override + void didChangeAppLifecycleState(AppLifecycleState state) { + debugPrint('==== MEMORY BENCHMARK ==== $state ===='); + debugPrint('This was lifecycle event number $iteration in this instance'); + } +} + +Future<void> main() async { + runApp(const MacrobenchmarksApp(initialRoute: kLargeImagesRouteName)); + await endOfAnimation(); + await Future<void>.delayed(const Duration(milliseconds: 50)); + debugPrint('==== MEMORY BENCHMARK ==== READY ===='); + WidgetsBinding.instance.addObserver(LifecycleObserver()); +}
diff --git a/dev/devicelab/bin/tasks/fast_scroll_large_images__memory.dart b/dev/devicelab/bin/tasks/fast_scroll_large_images__memory.dart new file mode 100644 index 0000000..b07f163 --- /dev/null +++ b/dev/devicelab/bin/tasks/fast_scroll_large_images__memory.dart
@@ -0,0 +1,41 @@ +// Copyright 2014 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import 'package:flutter_devicelab/framework/adb.dart'; +import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/framework/utils.dart'; +import 'package:flutter_devicelab/tasks/perf_tests.dart'; + +const String kPackageName = 'com.example.macrobenchmarks'; +const String kActivityName = 'com.example.macrobenchmarks.MainActivity'; + +class FastScrollLargeImagesMemoryTest extends MemoryTest { + FastScrollLargeImagesMemoryTest() + : super( + '${flutterDirectory.path}/dev/benchmarks/macrobenchmarks', + 'test_memory/large_images.dart', kPackageName, + ); + + @override + AndroidDevice get device => super.device; + + @override + int get iterationCount => 5; + + @override + Future<void> useMemory() async { + await launchApp(); + await recordStart(); + await device.shellExec('input', <String>['swipe', '0 1500 0 0 50']); + await Future<void>.delayed(const Duration(milliseconds: 10000)); + await recordEnd(); + } +} + +Future<void> main() async { + deviceOperatingSystem = DeviceOperatingSystem.android; + await task(FastScrollLargeImagesMemoryTest().run); +}
diff --git a/dev/devicelab/manifest.yaml b/dev/devicelab/manifest.yaml index cf6f8cd..7df2641 100644 --- a/dev/devicelab/manifest.yaml +++ b/dev/devicelab/manifest.yaml
@@ -670,6 +670,12 @@ stage: devicelab required_agent_capabilities: ["linux/android"] + fast_scroll_large_images__memory: + description: > + Measures memory usage for scrolling through a list of large images. + stage: devicelab + required_agent_capabilities: ["mac/android"] + analyzer_benchmark: description: > Measures the speed of Dart analyzer.