diff --git a/dev/benchmarks/macrobenchmarks/assets/999x1000.png b/dev/benchmarks/macrobenchmarks/assets/999x1000.png new file mode 100644 index 0000000000..e9b09f7df1 Binary files /dev/null and b/dev/benchmarks/macrobenchmarks/assets/999x1000.png differ diff --git a/dev/benchmarks/macrobenchmarks/lib/common.dart b/dev/benchmarks/macrobenchmarks/lib/common.dart index d6902eb4dc..c8d24dc196 100644 --- a/dev/benchmarks/macrobenchmarks/lib/common.dart +++ b/dev/benchmarks/macrobenchmarks/lib/common.dart @@ -7,3 +7,4 @@ const String kCubicBezierRouteName = '/cubic_bezier'; 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 eaecea02b0..5a2160c6dd 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 @@ import 'src/simple_animation.dart'; 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: { '/': (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 @@ class HomePage extends StatelessWidget { 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 0000000000..5dd70ffc11 --- /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(index)); + + @override + Widget build(BuildContext context) { + final Future pngData = _getPngData(context); + + return FutureBuilder( + future: pngData, + builder: (BuildContext context, AsyncSnapshot 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 _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 62a69cc5ed..471d22ca0e 100644 --- a/dev/benchmarks/macrobenchmarks/pubspec.yaml +++ b/dev/benchmarks/macrobenchmarks/pubspec.yaml @@ -90,5 +90,6 @@ flutter: 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 0000000000..62c14d6b0b --- /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 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 main() async { + runApp(const MacrobenchmarksApp(initialRoute: kLargeImagesRouteName)); + await endOfAnimation(); + await Future.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 0000000000..b07f163bda --- /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 useMemory() async { + await launchApp(); + await recordStart(); + await device.shellExec('input', ['swipe', '0 1500 0 0 50']); + await Future.delayed(const Duration(milliseconds: 10000)); + await recordEnd(); + } +} + +Future main() async { + deviceOperatingSystem = DeviceOperatingSystem.android; + await task(FastScrollLargeImagesMemoryTest().run); +} diff --git a/dev/devicelab/manifest.yaml b/dev/devicelab/manifest.yaml index cf6f8cdeca..7df2641fe4 100644 --- a/dev/devicelab/manifest.yaml +++ b/dev/devicelab/manifest.yaml @@ -670,6 +670,12 @@ tasks: 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.