Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,9 @@
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import io.flutter.FlutterInjector;
import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint;
import io.flutter.embedding.engine.loader.FlutterLoader;
import java.util.ArrayList;
import java.util.List;

Expand All @@ -25,6 +27,9 @@
* io.flutter.embedding.engine.FlutterEngine}s are created, resources from an existing living {@link
* io.flutter.embedding.engine.FlutterEngine} is re-used.
*
* <p>The shared resources are kept until the last surviving {@link
* io.flutter.embedding.engine.FlutterEngine} is destroyed.
*
* <p>Deleting a FlutterEngineGroup doesn't invalidate its existing {@link
* io.flutter.embedding.engine.FlutterEngine}s, but it eliminates the possibility to create more
* {@link io.flutter.embedding.engine.FlutterEngine}s in that group.
Expand All @@ -33,6 +38,23 @@ public class FlutterEngineGroup {

/* package */ @VisibleForTesting final List<FlutterEngine> activeEngines = new ArrayList<>();

/** Create a FlutterEngineGroup whose child engines will share resources. */
public FlutterEngineGroup(@NonNull Context context) {
this(context, null);
}

/**
* Create a FlutterEngineGroup whose child engines will share resources. Use {@code dartVmArgs} to
* pass flags to the Dart VM during initialization.
*/
public FlutterEngineGroup(@NonNull Context context, @Nullable String[] dartVmArgs) {
FlutterLoader loader = FlutterInjector.instance().flutterLoader();
if (!loader.initialized()) {
loader.startInitialization(context.getApplicationContext());
loader.ensureInitializationComplete(context, dartVmArgs);
}
}

/**
* Creates a {@link io.flutter.embedding.engine.FlutterEngine} in this group and run its {@link
* io.flutter.embedding.engine.dart.DartExecutor} with a default entrypoint of the "main" function
Expand Down Expand Up @@ -67,18 +89,13 @@ public FlutterEngine createAndRunDefaultEngine(@NonNull Context context) {
public FlutterEngine createAndRunEngine(
@NonNull Context context, @Nullable DartEntrypoint dartEntrypoint) {
FlutterEngine engine = null;
// This is done up here because an engine needs to be created first in order to be able to use
// DartEntrypoint.createDefault. The engine creation initializes the FlutterLoader so
// DartEntrypoint known where to find the assets for the AOT or kernel code.
if (activeEngines.size() == 0) {
engine = createEngine(context);
}

if (dartEntrypoint == null) {
dartEntrypoint = DartEntrypoint.createDefault();
}

if (activeEngines.size() == 0) {
engine = createEngine(context);
engine.getDartExecutor().executeDartEntrypoint(dartEntrypoint);
} else {
engine = activeEngines.get(0).spawn(context, dartEntrypoint);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,17 @@
import static org.mockito.Mockito.any;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.content.Context;
import io.flutter.embedding.engine.dart.DartExecutor;
import android.content.res.AssetManager;
import io.flutter.FlutterInjector;
import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint;
import io.flutter.embedding.engine.loader.FlutterLoader;
import io.flutter.plugins.GeneratedPluginRegistrant;
Expand All @@ -27,34 +32,41 @@
import org.robolectric.RuntimeEnvironment;
import org.robolectric.annotation.Config;

// It's a component test because it tests both FlutterEngineGroup and FlutterEngine.
// It's a component test because it tests the FlutterEngineGroup its components such as the
// FlutterEngine and the DartExecutor.
@Config(manifest = Config.NONE)
@RunWith(RobolectricTestRunner.class)
public class FlutterEngineGroupComponentTest {
@Mock FlutterJNI flutterJNI;
@Mock FlutterJNI mockflutterJNI;
@Mock FlutterLoader mockFlutterLoader;
FlutterEngineGroup engineGroupUnderTest;
FlutterEngine firstEngineUnderTest;
boolean jniAttached;

@Before
public void setUp() {
FlutterInjector.reset();

MockitoAnnotations.initMocks(this);
jniAttached = false;
when(flutterJNI.isAttached()).thenAnswer(invocation -> jniAttached);
doAnswer(invocation -> jniAttached = true).when(flutterJNI).attachToNative(false);
when(mockflutterJNI.isAttached()).thenAnswer(invocation -> jniAttached);
doAnswer(invocation -> jniAttached = true).when(mockflutterJNI).attachToNative(false);
GeneratedPluginRegistrant.clearRegisteredEngines();

when(mockFlutterLoader.findAppBundlePath()).thenReturn("some/path/to/flutter_assets");
FlutterInjector.setInstance(
new FlutterInjector.Builder().setFlutterLoader(mockFlutterLoader).build());

firstEngineUnderTest =
spy(
new FlutterEngine(
RuntimeEnvironment.application,
mock(FlutterLoader.class),
flutterJNI,
mockflutterJNI,
/*dartVmArgs=*/ new String[] {},
/*automaticallyRegisterPlugins=*/ false));
when(firstEngineUnderTest.getDartExecutor()).thenReturn(mock(DartExecutor.class));
engineGroupUnderTest =
new FlutterEngineGroup() {
new FlutterEngineGroup(RuntimeEnvironment.application) {
@Override
FlutterEngine createEngine(Context context) {
return firstEngineUnderTest;
Expand Down Expand Up @@ -127,4 +139,21 @@ public void canSpawnMoreEngines() {
RuntimeEnvironment.application, mock(DartEntrypoint.class));
assertEquals(2, engineGroupUnderTest.activeEngines.size());
}

@Test
public void canCreateAndRunCustomEntrypoints() {
FlutterEngine firstEngine =
engineGroupUnderTest.createAndRunEngine(
RuntimeEnvironment.application,
new DartEntrypoint(
FlutterInjector.instance().flutterLoader().findAppBundlePath(),
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is unfortunate, this is impossible to find for people that want to make a DartEntrypoint. Maybe we could add a const for it in the DartEntrypoint class? DartEntrypoint.DEFAULT_APP_BUNDLE_PATH? I couldn't find an example of anyone using the new API on github. It looks like a series of 2 changes have been deprecated and replaced for this.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Totally agree. This API makes no sense lol. But that's a "new feature" and I want to cherry pick this. Left flutter/flutter#74828. Surprised no one complained yet :)

"other entrypoint"));
assertEquals(1, engineGroupUnderTest.activeEngines.size());
verify(mockflutterJNI, times(1))
.runBundleAndSnapshotFromLibrary(
eq("some/path/to/flutter_assets"),
eq("other entrypoint"),
isNull(String.class),
any(AssetManager.class));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ public class SpawnedEngineActivity extends TestActivity {

@Override
public FlutterEngine provideFlutterEngine(@NonNull Context context) {
FlutterEngineGroup engineGroup = new FlutterEngineGroup();
FlutterEngineGroup engineGroup = new FlutterEngineGroup(context);
engineGroup.createAndRunDefaultEngine(context);

FlutterEngine secondEngine = engineGroup.createAndRunDefaultEngine(context);
Expand Down