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
4 changes: 3 additions & 1 deletion shell/common/engine.cc
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,8 @@ std::unique_ptr<Engine> Engine::Spawn(
Delegate& delegate,
const PointerDataDispatcherMaker& dispatcher_maker,
Settings settings,
std::unique_ptr<Animator> animator) const {
std::unique_ptr<Animator> animator,
const std::string& initial_route) const {
auto result = std::make_unique<Engine>(
/*delegate=*/delegate,
/*dispatcher_maker=*/dispatcher_maker,
Expand All @@ -133,6 +134,7 @@ std::unique_ptr<Engine> Engine::Spawn(
settings_.isolate_shutdown_callback, // isolate shutdown callback
settings_.persistent_isolate_data // persistent isolate data
);
result->initial_route_ = initial_route;
return result;
}

Expand Down
3 changes: 2 additions & 1 deletion shell/common/engine.h
Original file line number Diff line number Diff line change
Expand Up @@ -375,7 +375,8 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate {
Delegate& delegate,
const PointerDataDispatcherMaker& dispatcher_maker,
Settings settings,
std::unique_ptr<Animator> animator) const;
std::unique_ptr<Animator> animator,
const std::string& initial_route) const;

//----------------------------------------------------------------------------
/// @brief Destroys the engine engine. Called by the shell on the UI task
Expand Down
30 changes: 28 additions & 2 deletions shell/common/engine_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -268,13 +268,39 @@ TEST_F(EngineTest, SpawnSharesFontLibrary) {
/*font_collection=*/std::make_shared<FontCollection>(),
/*runtime_controller=*/std::move(mock_runtime_controller));

auto spawn =
engine->Spawn(delegate_, dispatcher_maker_, settings_, nullptr);
auto spawn = engine->Spawn(delegate_, dispatcher_maker_, settings_, nullptr,
std::string());
EXPECT_TRUE(spawn != nullptr);
EXPECT_EQ(&engine->GetFontCollection(), &spawn->GetFontCollection());
});
}

TEST_F(EngineTest, SpawnWithCustomInitialRoute) {
PostUITaskSync([this] {
MockRuntimeDelegate client;
auto mock_runtime_controller =
std::make_unique<MockRuntimeController>(client, task_runners_);
auto vm_ref = DartVMRef::Create(settings_);
EXPECT_CALL(*mock_runtime_controller, GetDartVM())
.WillRepeatedly(::testing::Return(vm_ref.get()));
auto engine = std::make_unique<Engine>(
/*delegate=*/delegate_,
/*dispatcher_maker=*/dispatcher_maker_,
/*image_decoder_task_runner=*/image_decoder_task_runner_,
/*task_runners=*/task_runners_,
/*settings=*/settings_,
/*animator=*/std::move(animator_),
/*io_manager=*/io_manager_,
/*font_collection=*/std::make_shared<FontCollection>(),
/*runtime_controller=*/std::move(mock_runtime_controller));

auto spawn =
engine->Spawn(delegate_, dispatcher_maker_, settings_, nullptr, "/foo");
EXPECT_TRUE(spawn != nullptr);
ASSERT_EQ("/foo", spawn->InitialRoute());
});
}

TEST_F(EngineTest, PassesLoadDartDeferredLibraryErrorToRuntime) {
PostUITaskSync([this] {
intptr_t error_id = 123;
Expand Down
6 changes: 4 additions & 2 deletions shell/common/shell.cc
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,7 @@ Shell::~Shell() {

std::unique_ptr<Shell> Shell::Spawn(
RunConfiguration run_configuration,
const std::string& initial_route,
const CreateCallback<PlatformView>& on_create_platform_view,
const CreateCallback<Rasterizer>& on_create_rasterizer) const {
FML_DCHECK(task_runners_.IsValid());
Expand All @@ -488,7 +489,7 @@ std::unique_ptr<Shell> Shell::Spawn(
PlatformData{}, task_runners_, rasterizer_->GetRasterThreadMerger(),
GetSettings(), vm_, vm_->GetVMData()->GetIsolateSnapshot(),
on_create_platform_view, on_create_rasterizer,
[engine = this->engine_.get()](
[engine = this->engine_.get(), initial_route](
Engine::Delegate& delegate,
const PointerDataDispatcherMaker& dispatcher_maker, DartVM& vm,
fml::RefPtr<const DartSnapshot> isolate_snapshot,
Expand All @@ -501,7 +502,8 @@ std::unique_ptr<Shell> Shell::Spawn(
return engine->Spawn(/*delegate=*/delegate,
/*dispatcher_maker=*/dispatcher_maker,
/*settings=*/settings,
/*animator=*/std::move(animator));
/*animator=*/std::move(animator),
/*initial_route=*/initial_route);
},
is_gpu_disabled));
return result;
Expand Down
1 change: 1 addition & 0 deletions shell/common/shell.h
Original file line number Diff line number Diff line change
Expand Up @@ -194,6 +194,7 @@ class Shell final : public PlatformView::Delegate,
/// @see http://flutter.dev/go/multiple-engines
std::unique_ptr<Shell> Spawn(
RunConfiguration run_configuration,
const std::string& initial_route,
const CreateCallback<PlatformView>& on_create_platform_view,
const CreateCallback<Rasterizer>& on_create_rasterizer) const;

Expand Down
10 changes: 7 additions & 3 deletions shell/common/shell_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -2695,6 +2695,8 @@ TEST_F(ShellTest, Spawn) {
ASSERT_TRUE(second_configuration.IsValid());
second_configuration.SetEntrypoint("testCanLaunchSecondaryIsolate");

const std::string initial_route("/foo");

fml::AutoResetWaitableEvent main_latch;
std::string last_entry_point;
// Fulfill native function for the first Shell's entrypoint.
Expand All @@ -2719,10 +2721,11 @@ TEST_F(ShellTest, Spawn) {

PostSync(
shell->GetTaskRunners().GetPlatformTaskRunner(),
[this, &spawner = shell, &second_configuration, &second_latch]() {
[this, &spawner = shell, &second_configuration, &second_latch,
initial_route]() {
MockPlatformViewDelegate platform_view_delegate;
auto spawn = spawner->Spawn(
std::move(second_configuration),
std::move(second_configuration), initial_route,
[&platform_view_delegate](Shell& shell) {
auto result = std::make_unique<MockPlatformView>(
platform_view_delegate, shell.GetTaskRunners());
Expand All @@ -2736,10 +2739,11 @@ TEST_F(ShellTest, Spawn) {
ASSERT_TRUE(ValidateShell(spawn.get()));

PostSync(spawner->GetTaskRunners().GetUITaskRunner(),
[&spawn, &spawner] {
[&spawn, &spawner, initial_route] {
// Check second shell ran the second entrypoint.
ASSERT_EQ("testCanLaunchSecondaryIsolate",
spawn->GetEngine()->GetLastEntrypoint());
ASSERT_EQ(initial_route, spawn->GetEngine()->InitialRoute());

// TODO(74520): Remove conditional once isolate groups are
// supported by JIT.
Expand Down
8 changes: 5 additions & 3 deletions shell/platform/android/android_shell_holder.cc
Original file line number Diff line number Diff line change
Expand Up @@ -174,7 +174,8 @@ const flutter::Settings& AndroidShellHolder::GetSettings() const {
std::unique_ptr<AndroidShellHolder> AndroidShellHolder::Spawn(
std::shared_ptr<PlatformViewAndroidJNI> jni_facade,
const std::string& entrypoint,
const std::string& libraryUrl) const {
const std::string& libraryUrl,
const std::string& initial_route) const {
FML_DCHECK(shell_ && shell_->IsSetup())
<< "A new Shell can only be spawned "
"if the current Shell is properly constructed";
Expand Down Expand Up @@ -226,8 +227,9 @@ std::unique_ptr<AndroidShellHolder> AndroidShellHolder::Spawn(
return nullptr;
}

std::unique_ptr<flutter::Shell> shell = shell_->Spawn(
std::move(config.value()), on_create_platform_view, on_create_rasterizer);
std::unique_ptr<flutter::Shell> shell =
shell_->Spawn(std::move(config.value()), initial_route,
on_create_platform_view, on_create_rasterizer);

return std::unique_ptr<AndroidShellHolder>(
new AndroidShellHolder(GetSettings(), jni_facade, thread_host_,
Expand Down
3 changes: 2 additions & 1 deletion shell/platform/android/android_shell_holder.h
Original file line number Diff line number Diff line change
Expand Up @@ -78,7 +78,8 @@ class AndroidShellHolder {
std::unique_ptr<AndroidShellHolder> Spawn(
std::shared_ptr<PlatformViewAndroidJNI> jni_facade,
const std::string& entrypoint,
const std::string& libraryUrl) const;
const std::string& libraryUrl,
const std::string& initial_route) const;

void Launch(std::shared_ptr<AssetManager> asset_manager,
const std::string& entrypoint,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -384,15 +384,19 @@ private boolean isAttachedToJni() {
*/
@NonNull
/*package*/ FlutterEngine spawn(
@NonNull Context context, @NonNull DartEntrypoint dartEntrypoint) {
@NonNull Context context,
@NonNull DartEntrypoint dartEntrypoint,
@Nullable String initialRoute) {
if (!isAttachedToJni()) {
throw new IllegalStateException(
"Spawn can only be called on a fully constructed FlutterEngine");
}

FlutterJNI newFlutterJNI =
flutterJNI.spawn(
dartEntrypoint.dartEntrypointFunctionName, dartEntrypoint.dartEntrypointLibrary);
dartEntrypoint.dartEntrypointFunctionName,
dartEntrypoint.dartEntrypointLibrary,
initialRoute);
return new FlutterEngine(
context, // Context.
null, // FlutterLoader. A null value passed here causes the constructor to get it from the
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,27 @@ public FlutterEngine createAndRunDefaultEngine(@NonNull Context context) {
*/
public FlutterEngine createAndRunEngine(
@NonNull Context context, @Nullable DartEntrypoint dartEntrypoint) {
return createAndRunEngine(context, dartEntrypoint, null);
}

/**
* Creates a {@link io.flutter.embedding.engine.FlutterEngine} in this group and run its {@link
* io.flutter.embedding.engine.dart.DartExecutor} with the specified {@link DartEntrypoint} and
* the specified {@code initialRoute}.
*
* <p>If no prior {@link io.flutter.embedding.engine.FlutterEngine} were created in this group,
* the initialization cost will be slightly higher than subsequent engines. The very first {@link
* io.flutter.embedding.engine.FlutterEngine} created per program, regardless of
* FlutterEngineGroup, also incurs the Dart VM creation time.
*
* <p>Subsequent engine creations will share resources with existing engines. However, if all
* existing engines were {@link io.flutter.embedding.engine.FlutterEngine#destroy()}ed, the next
* engine created will recreate its dependencies.
*/
public FlutterEngine createAndRunEngine(
@NonNull Context context,
@Nullable DartEntrypoint dartEntrypoint,
@Nullable String initialRoute) {
FlutterEngine engine = null;

if (dartEntrypoint == null) {
Expand All @@ -104,9 +125,12 @@ public FlutterEngine createAndRunEngine(

if (activeEngines.size() == 0) {
engine = createEngine(context);
if (initialRoute != null) {
engine.getNavigationChannel().setInitialRoute(initialRoute);
}
engine.getDartExecutor().executeDartEntrypoint(dartEntrypoint);
} else {
engine = activeEngines.get(0).spawn(context, dartEntrypoint);
engine = activeEngines.get(0).spawn(context, dartEntrypoint, initialRoute);
}

activeEngines.add(engine);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -335,11 +335,14 @@ public long performNativeAttach(@NonNull FlutterJNI flutterJNI) {
@UiThread
@NonNull
public FlutterJNI spawn(
@Nullable String entrypointFunctionName, @Nullable String pathToEntrypointFunction) {
@Nullable String entrypointFunctionName,
@Nullable String pathToEntrypointFunction,
@Nullable String initialRoute) {
ensureRunningOnMainThread();
ensureAttachedToNative();
FlutterJNI spawnedJNI =
nativeSpawn(nativeShellHolderId, entrypointFunctionName, pathToEntrypointFunction);
nativeSpawn(
nativeShellHolderId, entrypointFunctionName, pathToEntrypointFunction, initialRoute);
Preconditions.checkState(
spawnedJNI.nativeShellHolderId != null && spawnedJNI.nativeShellHolderId != 0,
"Failed to spawn new JNI connected shell from existing shell.");
Expand All @@ -350,7 +353,8 @@ public FlutterJNI spawn(
private native FlutterJNI nativeSpawn(
long nativeSpawningShellId,
@Nullable String entrypointFunctionName,
@Nullable String pathToEntrypointFunction);
@Nullable String pathToEntrypointFunction,
@Nullable String initialRoute);

/**
* Detaches this {@code FlutterJNI} instance from Flutter's native engine, which precludes any
Expand All @@ -359,7 +363,7 @@ private native FlutterJNI nativeSpawn(
* <p>This method must not be invoked if {@code FlutterJNI} is not already attached to native.
*
* <p>Invoking this method will result in the release of all native-side resources that were set
* up during {@link #attachToNative()} or {@link #spawn(String, String)}, or accumulated
* up during {@link #attachToNative()} or {@link #spawn(String, String, String)}, or accumulated
* thereafter.
*
* <p>It is permissible to re-attach this instance to native after detaching it from native.
Expand Down
11 changes: 7 additions & 4 deletions shell/platform/android/platform_view_android_jni_impl.cc
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,8 @@ static jobject SpawnJNI(JNIEnv* env,
jobject jcaller,
jlong shell_holder,
jstring jEntrypoint,
jstring jLibraryUrl) {
jstring jLibraryUrl,
jstring jInitialRoute) {
jobject jni = env->NewObject(g_flutter_jni_class->obj(), g_jni_constructor);
if (jni == nullptr) {
FML_LOG(ERROR) << "Could not create a FlutterJNI instance";
Expand All @@ -167,9 +168,10 @@ static jobject SpawnJNI(JNIEnv* env,

auto entrypoint = fml::jni::JavaStringToString(env, jEntrypoint);
auto libraryUrl = fml::jni::JavaStringToString(env, jLibraryUrl);
auto initial_route = fml::jni::JavaStringToString(env, jInitialRoute);

auto spawned_shell_holder =
ANDROID_SHELL_HOLDER->Spawn(jni_facade, entrypoint, libraryUrl);
auto spawned_shell_holder = ANDROID_SHELL_HOLDER->Spawn(
jni_facade, entrypoint, libraryUrl, initial_route);

if (spawned_shell_holder == nullptr || !spawned_shell_holder->IsValid()) {
FML_LOG(ERROR) << "Could not spawn Shell";
Expand Down Expand Up @@ -620,7 +622,8 @@ bool RegisterApi(JNIEnv* env) {
},
{
.name = "nativeSpawn",
.signature = "(JLjava/lang/String;Ljava/lang/String;)Lio/flutter/"
.signature = "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/"
"String;)Lio/flutter/"
"embedding/engine/FlutterJNI;",
.fnPtr = reinterpret_cast<void*>(&SpawnJNI),
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import static org.mockito.Mockito.eq;
import static org.mockito.Mockito.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.nullable;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
Expand All @@ -21,6 +22,7 @@
import io.flutter.FlutterInjector;
import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint;
import io.flutter.embedding.engine.loader.FlutterLoader;
import io.flutter.embedding.engine.systemchannels.NavigationChannel;
import io.flutter.plugins.GeneratedPluginRegistrant;
import org.junit.After;
import org.junit.Before;
Expand Down Expand Up @@ -119,7 +121,7 @@ public void canSpawnMoreEngines() {

doReturn(mock(FlutterEngine.class))
.when(firstEngine)
.spawn(any(Context.class), any(DartEntrypoint.class));
.spawn(any(Context.class), any(DartEntrypoint.class), nullable(String.class));

FlutterEngine secondEngine =
engineGroupUnderTest.createAndRunEngine(
Expand All @@ -131,7 +133,7 @@ public void canSpawnMoreEngines() {

// Now the second spawned engine is the only one left and it will be called to spawn the next
// engine in the chain.
when(secondEngine.spawn(any(Context.class), any(DartEntrypoint.class)))
when(secondEngine.spawn(any(Context.class), any(DartEntrypoint.class), nullable(String.class)))
.thenReturn(mock(FlutterEngine.class));

FlutterEngine thirdEngine =
Expand All @@ -156,4 +158,32 @@ public void canCreateAndRunCustomEntrypoints() {
isNull(String.class),
any(AssetManager.class));
}

@Test
public void canCreateAndRunWithCustomInitialRoute() {
when(firstEngineUnderTest.getNavigationChannel()).thenReturn(mock(NavigationChannel.class));

FlutterEngine firstEngine =
engineGroupUnderTest.createAndRunEngine(
RuntimeEnvironment.application, mock(DartEntrypoint.class), "/foo");
assertEquals(1, engineGroupUnderTest.activeEngines.size());
verify(firstEngine.getNavigationChannel(), times(1)).setInitialRoute("/foo");

when(mockflutterJNI.isAttached()).thenReturn(true);
jniAttached = false;
FlutterJNI secondMockflutterJNI = mock(FlutterJNI.class);
when(secondMockflutterJNI.isAttached()).thenAnswer(invocation -> jniAttached);
doAnswer(invocation -> jniAttached = true).when(secondMockflutterJNI).attachToNative();
doReturn(secondMockflutterJNI)
.when(mockflutterJNI)
.spawn(nullable(String.class), nullable(String.class), nullable(String.class));

FlutterEngine secondEngine =
engineGroupUnderTest.createAndRunEngine(
RuntimeEnvironment.application, mock(DartEntrypoint.class), "/bar");

assertEquals(2, engineGroupUnderTest.activeEngines.size());
verify(mockflutterJNI, times(1))
.spawn(nullable(String.class), nullable(String.class), eq("/bar"));
}
}
Loading