diff --git a/shell/common/switches.cc b/shell/common/switches.cc index 553e77361c6bc..a2726fea343e8 100644 --- a/shell/common/switches.cc +++ b/shell/common/switches.cc @@ -404,6 +404,12 @@ Settings SettingsFromCommandLine(const fml::CommandLine& command_line) { settings.purge_persistent_cache = command_line.HasOption(FlagForSwitch(Switch::PurgePersistentCache)); + if (command_line.HasOption(FlagForSwitch(Switch::OldGenHeapSize))) { + std::string old_gen_heap_size; + command_line.GetOptionValue(FlagForSwitch(Switch::OldGenHeapSize), + &old_gen_heap_size); + settings.old_gen_heap_size = std::stoi(old_gen_heap_size); + } return settings; } diff --git a/shell/common/switches.h b/shell/common/switches.h index 9a9dea9401d8f..6f6e2a34b8f1c 100644 --- a/shell/common/switches.h +++ b/shell/common/switches.h @@ -199,6 +199,9 @@ DEF_SWITCH( "Uses separate threads for the platform, UI, GPU and IO task runners. " "By default, a single thread is used for all task runners. Only available " "in the flutter_tester.") +DEF_SWITCH(OldGenHeapSize, + "old-gen-heap-size", + "The size limit in megabytes for the Dart VM old gen heap space.") DEF_SWITCHES_END diff --git a/shell/platform/android/io/flutter/FlutterInjector.java b/shell/platform/android/io/flutter/FlutterInjector.java index 7d944785c4f45..0fe80e058e17a 100644 --- a/shell/platform/android/io/flutter/FlutterInjector.java +++ b/shell/platform/android/io/flutter/FlutterInjector.java @@ -62,23 +62,12 @@ public static void reset() { instance = null; } - private FlutterInjector(boolean shouldLoadNative, @NonNull FlutterLoader flutterLoader) { - this.shouldLoadNative = shouldLoadNative; + private FlutterInjector(@NonNull FlutterLoader flutterLoader) { this.flutterLoader = flutterLoader; } - private boolean shouldLoadNative; private FlutterLoader flutterLoader; - /** - * Returns whether the Flutter Android engine embedding should load the native C++ engine. - * - *
Useful for testing since JVM tests via Robolectric can't load native libraries. - */ - public boolean shouldLoadNative() { - return shouldLoadNative; - } - /** Returns the {@link FlutterLoader} instance to use for the Flutter Android engine embedding. */ @NonNull public FlutterLoader flutterLoader() { @@ -92,20 +81,6 @@ public FlutterLoader flutterLoader() { *
Non-overriden values have reasonable defaults. */ public static final class Builder { - - private boolean shouldLoadNative = true; - /** - * Sets whether the Flutter Android engine embedding should load the native C++ engine. - * - *
Useful for testing since JVM tests via Robolectric can't load native libraries. - * - *
Defaults to true. - */ - public Builder setShouldLoadNative(boolean shouldLoadNative) { - this.shouldLoadNative = shouldLoadNative; - return this; - } - private FlutterLoader flutterLoader; /** * Sets a {@link FlutterLoader} override. @@ -130,8 +105,7 @@ private void fillDefaults() { public FlutterInjector build() { fillDefaults(); - System.out.println("should load native is " + shouldLoadNative); - return new FlutterInjector(shouldLoadNative, flutterLoader); + return new FlutterInjector(flutterLoader); } } } diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 5e9864c345bdc..604bb0ebc4e2d 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -97,6 +97,50 @@ public class FlutterJNI { private static final String TAG = "FlutterJNI"; + // BEGIN Methods related to loading for FlutterLoader. + /** + * Loads the libflutter.so C++ library. + * + *
This must be called before any other native methods, and can be overridden by tests to avoid + * loading native libraries. + */ + public void loadLibrary() { + System.loadLibrary("flutter"); + } + + /** + * Prefetch the default font manager provided by SkFontMgr::RefDefault() which is a process-wide + * singleton owned by Skia. Note that, the first call to SkFontMgr::RefDefault() will take + * noticeable time, but later calls will return a reference to the preexisting font manager. + */ + public void prefetchDefaultFontManager() { + FlutterJNI.nativePrefetchDefaultFontManager(); + } + + /** + * Perform one time initialization of the Dart VM and Flutter engine. + * + *
This method must be called only once.
+ *
+ * @param context The application context.
+ * @param args Arguments to the Dart VM/Flutter engine.
+ * @param bundlePath For JIT runtimes, the path to the Dart kernel file for the application.
+ * @param appStoragePath The path to the application data directory.
+ * @param engineCachesPath The path to the application cache directory.
+ * @param initTimeMillis The time, in milliseconds, taken for initialization.
+ */
+ public void init(
+ @NonNull Context context,
+ @NonNull String[] args,
+ @Nullable String bundlePath,
+ @NonNull String appStoragePath,
+ @NonNull String engineCachesPath,
+ long initTimeMillis) {
+ FlutterJNI.nativeInit(
+ context, args, bundlePath, appStoragePath, engineCachesPath, initTimeMillis);
+ }
+ // END methods related to FlutterLoader
+
@Nullable private static AsyncWaitForVsyncDelegate asyncWaitForVsyncDelegate;
// This should also be updated by FlutterView when it is attached to a Display.
// The initial value of 0.0 indicates unknown refresh rate.
@@ -105,7 +149,8 @@ public class FlutterJNI {
// This is set from native code via JNI.
@Nullable private static String observatoryUri;
- // TODO(mattcarroll): add javadocs
+ /** @deprecated Use {@link #init(Context, String[], String, String, String, long)} instead. */
+ @Deprecated
public static native void nativeInit(
@NonNull Context context,
@NonNull String[] args,
@@ -114,11 +159,8 @@ public static native void nativeInit(
@NonNull String engineCachesPath,
long initTimeMillis);
- /**
- * Prefetch the default font manager provided by SkFontMgr::RefDefault() which is a process-wide
- * singleton owned by Skia. Note that, the first call to SkFontMgr::RefDefault() will take
- * noticeable time, but later calls will return a reference to the preexisting font manager.
- */
+ /** @deprecated Use {@link #prefetchDefaultFontManager()} instead. */
+ @Deprecated
public static native void nativePrefetchDefaultFontManager();
private native boolean nativeGetIsSoftwareRenderingEnabled();
diff --git a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java
index e2d97c40ee174..95e96d3473e05 100644
--- a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java
+++ b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java
@@ -4,9 +4,12 @@
package io.flutter.embedding.engine.loader;
+import android.app.ActivityManager;
import android.content.Context;
+import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
+import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.os.SystemClock;
@@ -14,7 +17,6 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import io.flutter.BuildConfig;
-import io.flutter.FlutterInjector;
import io.flutter.Log;
import io.flutter.embedding.engine.FlutterJNI;
import io.flutter.util.PathUtils;
@@ -29,6 +31,9 @@
public class FlutterLoader {
private static final String TAG = "FlutterLoader";
+ private static final String OLD_GEN_HEAP_SIZE_META_DATA_KEY =
+ "io.flutter.embedding.android.OldGenHeapSize";
+
// Must match values in flutter::switches
static final String AOT_SHARED_LIBRARY_NAME = "aot-shared-library-name";
static final String SNAPSHOT_ASSET_PATH_KEY = "snapshot-asset-path";
@@ -60,10 +65,26 @@ public static FlutterLoader getInstance() {
return instance;
}
+ /** Creates a {@code FlutterLoader} that uses a default constructed {@link FlutterJNI}. */
+ public FlutterLoader() {
+ this(new FlutterJNI());
+ }
+
+ /**
+ * Creates a {@code FlutterLoader} with the specified {@link FlutterJNI}.
+ *
+ * @param flutterJNI The {@link FlutterJNI} instance to use for loading the libflutter.so C++
+ * library, setting up the font manager, and calling into C++ initalization.
+ */
+ public FlutterLoader(@NonNull FlutterJNI flutterJNI) {
+ this.flutterJNI = flutterJNI;
+ }
+
private boolean initialized = false;
@Nullable private Settings settings;
private long initStartTimestampMillis;
private FlutterApplicationInfo flutterApplicationInfo;
+ private FlutterJNI flutterJNI;
private static class InitResult {
final String appStoragePath;
@@ -125,9 +146,7 @@ public void startInitialization(@NonNull Context applicationContext, @NonNull Se
public InitResult call() {
ResourceExtractor resourceExtractor = initResources(appContext);
- if (FlutterInjector.instance().shouldLoadNative()) {
- System.loadLibrary("flutter");
- }
+ flutterJNI.loadLibrary();
// Prefetch the default font manager as soon as possible on a background thread.
// It helps to reduce time cost of engine setup that blocks the platform thread.
@@ -136,7 +155,7 @@ public InitResult call() {
new Runnable() {
@Override
public void run() {
- FlutterJNI.nativePrefetchDefaultFontManager();
+ flutterJNI.prefetchDefaultFontManager();
}
});
@@ -225,17 +244,34 @@ public void ensureInitializationComplete(
shellArgs.add("--log-tag=" + settings.getLogTag());
}
+ ApplicationInfo applicationInfo =
+ applicationContext
+ .getPackageManager()
+ .getApplicationInfo(
+ applicationContext.getPackageName(), PackageManager.GET_META_DATA);
+ Bundle metaData = applicationInfo.metaData;
+ int oldGenHeapSizeMegaBytes =
+ metaData != null ? metaData.getInt(OLD_GEN_HEAP_SIZE_META_DATA_KEY) : 0;
+ if (oldGenHeapSizeMegaBytes == 0) {
+ // default to half of total memory.
+ ActivityManager activityManager =
+ (ActivityManager) applicationContext.getSystemService(Context.ACTIVITY_SERVICE);
+ ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
+ activityManager.getMemoryInfo(memInfo);
+ oldGenHeapSizeMegaBytes = (int) (memInfo.totalMem / 1e6 / 2);
+ }
+
+ shellArgs.add("--old-gen-heap-size=" + oldGenHeapSizeMegaBytes);
+
long initTimeMillis = SystemClock.uptimeMillis() - initStartTimestampMillis;
- if (FlutterInjector.instance().shouldLoadNative()) {
- FlutterJNI.nativeInit(
- applicationContext,
- shellArgs.toArray(new String[0]),
- kernelPath,
- result.appStoragePath,
- result.engineCachesPath,
- initTimeMillis);
- }
+ flutterJNI.init(
+ applicationContext,
+ shellArgs.toArray(new String[0]),
+ kernelPath,
+ result.appStoragePath,
+ result.engineCachesPath,
+ initTimeMillis);
initialized = true;
} catch (Exception e) {
diff --git a/shell/platform/android/test/io/flutter/FlutterInjectorTest.java b/shell/platform/android/test/io/flutter/FlutterInjectorTest.java
index 225c24959e136..9809095a0746d 100644
--- a/shell/platform/android/test/io/flutter/FlutterInjectorTest.java
+++ b/shell/platform/android/test/io/flutter/FlutterInjectorTest.java
@@ -7,7 +7,6 @@
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertThrows;
-import static org.junit.Assert.assertTrue;
import io.flutter.embedding.engine.loader.FlutterLoader;
import org.junit.Before;
@@ -35,7 +34,6 @@ public void itHasSomeReasonableDefaults() {
// Implicitly builds when first accessed.
FlutterInjector injector = FlutterInjector.instance();
assertNotNull(injector.flutterLoader());
- assertTrue(injector.shouldLoadNative());
}
@Test
@@ -44,7 +42,6 @@ public void canPartiallyOverride() {
new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build());
FlutterInjector injector = FlutterInjector.instance();
assertEquals(injector.flutterLoader(), mockFlutterLoader);
- assertTrue(injector.shouldLoadNative());
}
@Test()
diff --git a/shell/platform/android/test/io/flutter/embedding/engine/PluginComponentTest.java b/shell/platform/android/test/io/flutter/embedding/engine/PluginComponentTest.java
index 71f47890245bc..138e36c766f0c 100644
--- a/shell/platform/android/test/io/flutter/embedding/engine/PluginComponentTest.java
+++ b/shell/platform/android/test/io/flutter/embedding/engine/PluginComponentTest.java
@@ -32,11 +32,13 @@ public void setUp() {
@Test
public void pluginsCanAccessFlutterAssetPaths() {
// Setup test.
- FlutterInjector.setInstance(new FlutterInjector.Builder().setShouldLoadNative(false).build());
+ FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
+ FlutterInjector.setInstance(
+ new FlutterInjector.Builder().setFlutterLoader(new FlutterLoader(mockFlutterJNI)).build());
FlutterJNI flutterJNI = mock(FlutterJNI.class);
when(flutterJNI.isAttached()).thenReturn(true);
- FlutterLoader flutterLoader = new FlutterLoader();
+ FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI);
// Execute behavior under test.
FlutterEngine flutterEngine =
diff --git a/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java b/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java
index 4ffd848d855b7..c7c6c3afd39ec 100644
--- a/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java
+++ b/shell/platform/android/test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java
@@ -4,13 +4,25 @@
package io.flutter.embedding.engine.loader;
+import static android.os.Looper.getMainLooper;
import static junit.framework.TestCase.assertFalse;
import static junit.framework.TestCase.assertTrue;
+import static org.mockito.Mockito.anyLong;
+import static org.mockito.Mockito.anyString;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.times;
+import static org.mockito.Mockito.verify;
+import static org.robolectric.Shadows.shadowOf;
-import io.flutter.FlutterInjector;
-import org.junit.Before;
+import android.app.ActivityManager;
+import android.content.Context;
+import io.flutter.embedding.engine.FlutterJNI;
+import java.util.Arrays;
+import java.util.List;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;
@@ -18,10 +30,6 @@
@Config(manifest = Config.NONE)
@RunWith(RobolectricTestRunner.class)
public class FlutterLoaderTest {
- @Before
- public void setUp() {
- FlutterInjector.reset();
- }
@Test
public void itReportsUninitializedAfterCreating() {
@@ -31,13 +39,43 @@ public void itReportsUninitializedAfterCreating() {
@Test
public void itReportsInitializedAfterInitializing() {
- FlutterInjector.setInstance(new FlutterInjector.Builder().setShouldLoadNative(false).build());
- FlutterLoader flutterLoader = new FlutterLoader();
+ FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
+ FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI);
assertFalse(flutterLoader.initialized());
flutterLoader.startInitialization(RuntimeEnvironment.application);
flutterLoader.ensureInitializationComplete(RuntimeEnvironment.application, null);
+ shadowOf(getMainLooper()).idle();
assertTrue(flutterLoader.initialized());
- FlutterInjector.reset();
+ verify(mockFlutterJNI, times(1)).loadLibrary();
+ }
+
+ @Test
+ public void itDefaultsTheOldGenHeapSizeAppropriately() {
+ FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
+ FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI);
+
+ assertFalse(flutterLoader.initialized());
+ flutterLoader.startInitialization(RuntimeEnvironment.application);
+ flutterLoader.ensureInitializationComplete(RuntimeEnvironment.application, null);
+ shadowOf(getMainLooper()).idle();
+
+ ActivityManager activityManager =
+ (ActivityManager) RuntimeEnvironment.application.getSystemService(Context.ACTIVITY_SERVICE);
+ ActivityManager.MemoryInfo memInfo = new ActivityManager.MemoryInfo();
+ activityManager.getMemoryInfo(memInfo);
+ int oldGenHeapSizeMegaBytes = (int) (memInfo.totalMem / 1e6 / 2);
+ final String oldGenHeapArg = "--old-gen-heap-size=" + oldGenHeapSizeMegaBytes;
+ ArgumentCaptor