diff --git a/dev/benchmarks/multiple_flutters/android/.gitignore b/dev/benchmarks/multiple_flutters/android/.gitignore new file mode 100644 index 0000000000..aa724b7707 --- /dev/null +++ b/dev/benchmarks/multiple_flutters/android/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/dev/benchmarks/multiple_flutters/android/app/.gitignore b/dev/benchmarks/multiple_flutters/android/app/.gitignore new file mode 100644 index 0000000000..42afabfd2a --- /dev/null +++ b/dev/benchmarks/multiple_flutters/android/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/dev/benchmarks/multiple_flutters/android/app/build.gradle b/dev/benchmarks/multiple_flutters/android/app/build.gradle new file mode 100644 index 0000000000..c3166e2e2f --- /dev/null +++ b/dev/benchmarks/multiple_flutters/android/app/build.gradle @@ -0,0 +1,54 @@ +// 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. + +plugins { + id 'com.android.application' + id 'kotlin-android' +} + +android { + signingConfigs { + self { + } + } + compileSdkVersion 30 + + defaultConfig { + applicationId "dev.flutter.multipleflutters" + minSdkVersion 24 + targetSdkVersion 30 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + signingConfig debug.signingConfig + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } +} + +dependencies { + + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'androidx.core:core-ktx:1.2.0' + implementation 'androidx.appcompat:appcompat:1.1.0' + implementation 'com.google.android.material:material:1.1.0' + implementation 'androidx.constraintlayout:constraintlayout:1.1.3' + testImplementation 'junit:junit:4.+' + androidTestImplementation 'androidx.test.ext:junit:1.1.1' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.2.0' + implementation project(':flutter') +} \ No newline at end of file diff --git a/dev/benchmarks/multiple_flutters/android/app/proguard-rules.pro b/dev/benchmarks/multiple_flutters/android/app/proguard-rules.pro new file mode 100644 index 0000000000..481bb43481 --- /dev/null +++ b/dev/benchmarks/multiple_flutters/android/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/dev/benchmarks/multiple_flutters/android/app/src/main/AndroidManifest.xml b/dev/benchmarks/multiple_flutters/android/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..ccec715e40 --- /dev/null +++ b/dev/benchmarks/multiple_flutters/android/app/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + \ No newline at end of file diff --git a/dev/benchmarks/multiple_flutters/android/app/src/main/java/dev/flutter/multipleflutters/App.kt b/dev/benchmarks/multiple_flutters/android/app/src/main/java/dev/flutter/multipleflutters/App.kt new file mode 100644 index 0000000000..cd13fc4f2e --- /dev/null +++ b/dev/benchmarks/multiple_flutters/android/app/src/main/java/dev/flutter/multipleflutters/App.kt @@ -0,0 +1,22 @@ +// 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. + +package dev.flutter.multipleflutters + +import android.app.Application +import io.flutter.embedding.engine.FlutterEngineGroup + +/** + * Application class for this app. + * + * This holds onto our engine group. + */ +class App : Application() { + lateinit var engines: FlutterEngineGroup + + override fun onCreate() { + super.onCreate() + engines = FlutterEngineGroup(this) + } +} diff --git a/dev/benchmarks/multiple_flutters/android/app/src/main/java/dev/flutter/multipleflutters/MainActivity.kt b/dev/benchmarks/multiple_flutters/android/app/src/main/java/dev/flutter/multipleflutters/MainActivity.kt new file mode 100644 index 0000000000..374bdf2f94 --- /dev/null +++ b/dev/benchmarks/multiple_flutters/android/app/src/main/java/dev/flutter/multipleflutters/MainActivity.kt @@ -0,0 +1,75 @@ +// 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. + +package dev.flutter.multipleflutters + +import android.content.Intent +import androidx.appcompat.app.AppCompatActivity +import android.os.Bundle +import android.view.View +import android.widget.FrameLayout +import android.widget.LinearLayout +import android.widget.TextView +import androidx.fragment.app.FragmentActivity +import androidx.fragment.app.FragmentManager +import io.flutter.FlutterInjector +import io.flutter.embedding.android.FlutterFragment +import io.flutter.embedding.engine.FlutterEngineCache +import io.flutter.embedding.engine.dart.DartExecutor + +class MainActivity : FragmentActivity() { + private val numberOfFlutters = 2 + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + val root = LinearLayout(this) + root.layoutParams = LinearLayout.LayoutParams( + LinearLayout.LayoutParams.MATCH_PARENT, + LinearLayout.LayoutParams.MATCH_PARENT + ) + root.orientation = LinearLayout.VERTICAL + root.weightSum = numberOfFlutters.toFloat() + + val fragmentManager: FragmentManager = supportFragmentManager + + setContentView(root) + + val app = applicationContext as App + val dartEntrypoint = + DartExecutor.DartEntrypoint( + FlutterInjector.instance().flutterLoader().findAppBundlePath(), "main" + ) + val topEngine = app.engines.createAndRunEngine(this, dartEntrypoint) + val bottomEngine = app.engines.createAndRunEngine(this, dartEntrypoint) + for (i in 0 until numberOfFlutters) { + val flutterContainer = FrameLayout(this) + root.addView(flutterContainer) + flutterContainer.id = 12345 + i + flutterContainer.layoutParams = LinearLayout.LayoutParams( + FrameLayout.LayoutParams.MATCH_PARENT, + FrameLayout.LayoutParams.MATCH_PARENT, + 1.0f + ) + val engine = if (i == 0) topEngine else bottomEngine + FlutterEngineCache.getInstance().put(i.toString(), engine) + val flutterFragment = + FlutterFragment.withCachedEngine(i.toString()).build() + fragmentManager + .beginTransaction() + .add( + 12345 + i, + flutterFragment + ) + .commit() + } + } + + override fun onDestroy() { + for (i in 0 until numberOfFlutters) { + FlutterEngineCache.getInstance().remove(i.toString()) + } + + super.onDestroy() + } +} diff --git a/dev/benchmarks/multiple_flutters/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/dev/benchmarks/multiple_flutters/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000000..39557594a9 --- /dev/null +++ b/dev/benchmarks/multiple_flutters/android/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/dev/benchmarks/multiple_flutters/android/app/src/main/res/drawable/ic_launcher_background.xml b/dev/benchmarks/multiple_flutters/android/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000000..d8d9777331 --- /dev/null +++ b/dev/benchmarks/multiple_flutters/android/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,173 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/dev/benchmarks/multiple_flutters/android/app/src/main/res/values-night/themes.xml b/dev/benchmarks/multiple_flutters/android/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000000..0a36411f72 --- /dev/null +++ b/dev/benchmarks/multiple_flutters/android/app/src/main/res/values-night/themes.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/dev/benchmarks/multiple_flutters/android/app/src/main/res/values/colors.xml b/dev/benchmarks/multiple_flutters/android/app/src/main/res/values/colors.xml new file mode 100644 index 0000000000..5a297dbb61 --- /dev/null +++ b/dev/benchmarks/multiple_flutters/android/app/src/main/res/values/colors.xml @@ -0,0 +1,13 @@ + + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/dev/benchmarks/multiple_flutters/android/app/src/main/res/values/strings.xml b/dev/benchmarks/multiple_flutters/android/app/src/main/res/values/strings.xml new file mode 100644 index 0000000000..4af3827607 --- /dev/null +++ b/dev/benchmarks/multiple_flutters/android/app/src/main/res/values/strings.xml @@ -0,0 +1,7 @@ + + + + Multiple Flutters + \ No newline at end of file diff --git a/dev/benchmarks/multiple_flutters/android/app/src/main/res/values/themes.xml b/dev/benchmarks/multiple_flutters/android/app/src/main/res/values/themes.xml new file mode 100644 index 0000000000..db959bf88d --- /dev/null +++ b/dev/benchmarks/multiple_flutters/android/app/src/main/res/values/themes.xml @@ -0,0 +1,20 @@ + + + + + + \ No newline at end of file diff --git a/dev/benchmarks/multiple_flutters/android/build.gradle b/dev/benchmarks/multiple_flutters/android/build.gradle new file mode 100644 index 0000000000..2bf4a9184f --- /dev/null +++ b/dev/benchmarks/multiple_flutters/android/build.gradle @@ -0,0 +1,30 @@ +// 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. + +// Top-level build file where you can add configuration options common to all sub-projects/modules. +buildscript { + ext.kotlin_version = "1.3.72" + repositories { + google() + jcenter() + } + dependencies { + classpath "com.android.tools.build:gradle:4.1.2" + classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" + + // NOTE: Do not place your application dependencies here; they belong + // in the individual module build.gradle files + } +} + +allprojects { + repositories { + google() + jcenter() + } +} + +task clean(type: Delete) { + delete rootProject.buildDir +} \ No newline at end of file diff --git a/dev/benchmarks/multiple_flutters/android/gradle.properties b/dev/benchmarks/multiple_flutters/android/gradle.properties new file mode 100644 index 0000000000..98bed167dc --- /dev/null +++ b/dev/benchmarks/multiple_flutters/android/gradle.properties @@ -0,0 +1,21 @@ +# Project-wide Gradle settings. +# IDE (e.g. Android Studio) users: +# Gradle settings configured through the IDE *will override* +# any settings specified in this file. +# For more details on how to configure your build environment visit +# http://www.gradle.org/docs/current/userguide/build_environment.html +# Specifies the JVM arguments used for the daemon process. +# The setting is particularly useful for tweaking memory settings. +org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8 +# When configured, Gradle will run in incubating parallel mode. +# This option should only be used with decoupled projects. More details, visit +# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects +# org.gradle.parallel=true +# AndroidX package structure to make it clearer which packages are bundled with the +# Android operating system, and which are packaged with your app"s APK +# https://developer.android.com/topic/libraries/support-library/androidx-rn +android.useAndroidX=true +# Automatically convert third-party libraries to use AndroidX +android.enableJetifier=true +# Kotlin code style for this project: "official" or "obsolete": +kotlin.code.style=official \ No newline at end of file diff --git a/dev/benchmarks/multiple_flutters/android/gradle/wrapper/gradle-wrapper.properties b/dev/benchmarks/multiple_flutters/android/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..b9e13077bc --- /dev/null +++ b/dev/benchmarks/multiple_flutters/android/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Wed Jan 27 11:54:04 PST 2021 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-all.zip diff --git a/dev/benchmarks/multiple_flutters/android/settings.gradle b/dev/benchmarks/multiple_flutters/android/settings.gradle new file mode 100644 index 0000000000..cddcd9ce67 --- /dev/null +++ b/dev/benchmarks/multiple_flutters/android/settings.gradle @@ -0,0 +1,11 @@ +// 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. + +include ':app' +rootProject.name = "Multiple Flutters" +setBinding(new Binding([gradle: this])) // new +evaluate(new File( // new + settingsDir.parentFile, // new + './module/.android/include_flutter.groovy' // new +)) \ No newline at end of file diff --git a/dev/benchmarks/multiple_flutters/module/.gitignore b/dev/benchmarks/multiple_flutters/module/.gitignore new file mode 100644 index 0000000000..ff612b3be4 --- /dev/null +++ b/dev/benchmarks/multiple_flutters/module/.gitignore @@ -0,0 +1,48 @@ +.DS_Store +.dart_tool/ + +.packages +.pub/ + +.idea/ +.vagrant/ +.sconsign.dblite +.svn/ + +*.swp +profile + +DerivedData/ + +.generated/ + +*.pbxuser +*.mode1v3 +*.mode2v3 +*.perspectivev3 + +!default.pbxuser +!default.mode1v3 +!default.mode2v3 +!default.perspectivev3 + +xcuserdata + +*.moved-aside + +*.pyc +*sync/ +Icon? +.tags* + +build/ +.android/ +.ios/ +.flutter-plugins +.flutter-plugins-dependencies + +# Symbolication related +app.*.symbols + +# Obfuscation related +app.*.map.json diff --git a/dev/benchmarks/multiple_flutters/module/.metadata b/dev/benchmarks/multiple_flutters/module/.metadata new file mode 100644 index 0000000000..bdc27cb22d --- /dev/null +++ b/dev/benchmarks/multiple_flutters/module/.metadata @@ -0,0 +1,10 @@ +# This file tracks properties of this Flutter project. +# Used by Flutter tool to assess capabilities and perform upgrades etc. +# +# This file should be version controlled and should not be manually edited. + +version: + revision: 1d3f6971600f6e3fb144a30fab2b889e34af0c22 + channel: master + +project_type: module diff --git a/dev/benchmarks/multiple_flutters/module/lib/main.dart b/dev/benchmarks/multiple_flutters/module/lib/main.dart new file mode 100644 index 0000000000..6b77189f9f --- /dev/null +++ b/dev/benchmarks/multiple_flutters/module/lib/main.dart @@ -0,0 +1,71 @@ +// 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 'package:flutter/material.dart'; + +void main() => runApp(const MyApp(Colors.blue)); + +@pragma('vm:entry-point') +void topMain() => runApp(const MyApp(Colors.green)); + +@pragma('vm:entry-point') +void bottomMain() => runApp(const MyApp(Colors.purple)); + +class MyApp extends StatelessWidget { + const MyApp(this.color); + + final Color color; + + @override + Widget build(BuildContext context) { + return MaterialApp( + title: 'Flutter Demo', + theme: ThemeData( + primarySwatch: color as MaterialColor, + ), + home: const MyHomePage(title: 'Flutter Demo Home Page'), + ); + } +} + +class MyHomePage extends StatefulWidget { + const MyHomePage({Key key, this.title}) : super(key: key); + final String title; + + @override + _MyHomePageState createState() => _MyHomePageState(); +} + +class _MyHomePageState extends State { + @override + Widget build(BuildContext context) { + return Scaffold( + appBar: AppBar( + title: Text(widget.title), + ), + body: Center( + child: Column( + mainAxisAlignment: MainAxisAlignment.center, + children: [ + const Text( + 'You have pushed the button this many times:', + ), + Text( + '0', + style: Theme.of(context).textTheme.headline4, + ), + TextButton( + onPressed: () {}, + child: const Text('Add'), + ), + TextButton( + onPressed: () {}, + child: const Text('Next'), + ), + ], + ), + ), + ); + } +} diff --git a/dev/benchmarks/multiple_flutters/module/pubspec.yaml b/dev/benchmarks/multiple_flutters/module/pubspec.yaml new file mode 100644 index 0000000000..8175300985 --- /dev/null +++ b/dev/benchmarks/multiple_flutters/module/pubspec.yaml @@ -0,0 +1,29 @@ +name: multiple_flutters_module +description: A module that is embedded in the multiple_flutters benchmark test. + +version: 1.0.0+1 + +environment: + sdk: '>=2.8.1 <3.0.0' + +dependencies: + flutter: + sdk: flutter + + cupertino_icons: 1.0.2 + + characters: 1.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + collection: 1.15.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + meta: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + typed_data: 1.3.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + vector_math: 2.1.0 # THIS LINE IS AUTOGENERATED - TO UPDATE USE "flutter update-packages --force-upgrade" + +flutter: + uses-material-design: true + + module: + androidX: true + androidPackage: com.example.multiple_flutters_module + iosBundleIdentifier: com.example.multipleFluttersModule + +# PUBSPEC CHECKSUM: 6f0e diff --git a/dev/devicelab/bin/tasks/flutter_engine_group_performance.dart b/dev/devicelab/bin/tasks/flutter_engine_group_performance.dart new file mode 100644 index 0000000000..a916e1fb6c --- /dev/null +++ b/dev/devicelab/bin/tasks/flutter_engine_group_performance.dart @@ -0,0 +1,87 @@ +// 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:io'; + +import 'package:flutter_devicelab/framework/adb.dart'; +import 'package:flutter_devicelab/framework/framework.dart'; +import 'package:flutter_devicelab/framework/task_result.dart'; +import 'package:flutter_devicelab/framework/utils.dart' as utils; +import 'package:flutter_devicelab/tasks/perf_tests.dart' show ListStatistics; +import 'package:path/path.dart' as path; + +const String _bundleName = 'dev.flutter.multipleflutters'; +const String _activityName = 'MainActivity'; +const int _numberOfIterations = 10; + +Future _withApkInstall( + String apkPath, String bundleName, Function(AndroidDevice) body) async { + final DeviceDiscovery devices = DeviceDiscovery(); + final AndroidDevice device = await devices.workingDevice as AndroidDevice; + await device.unlock(); + await device.adb(['install', '-r', apkPath]); + try { + await body(device); + } finally { + await device.adb(['uninstall', bundleName]); + } +} + +Future _doTest() async { + try { + final String flutterDirectory = utils.flutterDirectory.path; + final String multipleFluttersPath = + path.join(flutterDirectory, 'dev', 'benchmarks', 'multiple_flutters'); + final String modulePath = path.join(multipleFluttersPath, 'module'); + final String androidPath = path.join(multipleFluttersPath, 'android'); + + final String gradlew = Platform.isWindows ? 'gradlew.bat' : 'gradlew'; + final String gradlewExecutable = + Platform.isWindows ? '.\\$gradlew' : './$gradlew'; + final String flutterPath = path.join(flutterDirectory, 'bin', 'flutter'); + await utils.eval(flutterPath, ['pub', 'get'], + workingDirectory: modulePath); + await utils.eval(gradlewExecutable, ['assembleRelease'], + workingDirectory: androidPath); + + final String apkPath = path.join(multipleFluttersPath, 'android', 'app', + 'build', 'outputs', 'apk', 'release', 'app-release.apk'); + + TaskResult result; + await _withApkInstall(apkPath, _bundleName, (AndroidDevice device) async { + final List totalMemorySamples = []; + for (int i = 0; i < _numberOfIterations; ++i) { + await device.adb([ + 'shell', + 'am', + 'start', + '-n', + '$_bundleName/$_bundleName.$_activityName' + ]); + await Future.delayed(const Duration(seconds: 10)); + final Map memoryStats = + await device.getMemoryStats(_bundleName); + final int totalMemory = memoryStats['total_kb'] as int; + totalMemorySamples.add(totalMemory); + await device.stop(_bundleName); + } + final ListStatistics totalMemoryStatistics = + ListStatistics(totalMemorySamples); + + final Map results = { + ...totalMemoryStatistics.asMap('totalMemory') + }; + result = TaskResult.success(results, + benchmarkScoreKeys: results.keys.toList()); + }); + + return result ?? TaskResult.failure('no results found'); + } catch (ex) { + return TaskResult.failure(ex.toString()); + } +} + +Future main() async { + task(_doTest); +}