Make FlutterEngineGroup support dart entrypoint args (flutter/engine#29096)

This commit is contained in:
ColdPaleLight 2021-11-19 10:08:03 +08:00 committed by GitHub
parent 5183c56533
commit 9ecd4c1899
42 changed files with 752 additions and 62 deletions

View File

@ -127,6 +127,8 @@ struct Settings {
std::string temp_directory_path;
std::vector<std::string> dart_flags;
// Arguments passed as a List<String> to Dart's entrypoint function.
// TODO(93459): Remove it when it is no longer used.
// https://github.com/flutter/flutter/issues/93459
std::vector<std::string> dart_entrypoint_args;
// Isolate settings

View File

@ -125,6 +125,35 @@ std::vector<std::string> StringArrayToVector(JNIEnv* env, jobjectArray array) {
return out;
}
std::vector<std::string> StringListToVector(JNIEnv* env, jobject list) {
std::vector<std::string> out;
if (env == nullptr || list == nullptr) {
return out;
}
ScopedJavaLocalRef<jclass> list_clazz(env, env->FindClass("java/util/List"));
FML_DCHECK(!list_clazz.is_null());
jmethodID list_get =
env->GetMethodID(list_clazz.obj(), "get", "(I)Ljava/lang/Object;");
jmethodID list_size = env->GetMethodID(list_clazz.obj(), "size", "()I");
jint size = env->CallIntMethod(list, list_size);
if (size == 0) {
return out;
}
out.resize(size);
for (jint i = 0; i < size; ++i) {
ScopedJavaLocalRef<jstring> java_string(
env, static_cast<jstring>(env->CallObjectMethod(list, list_get, i)));
out[i] = JavaStringToString(env, java_string.obj());
}
return out;
}
ScopedJavaLocalRef<jobjectArray> VectorToStringArray(
JNIEnv* env,
const std::vector<std::string>& vector) {

View File

@ -30,6 +30,8 @@ ScopedJavaLocalRef<jstring> StringToJavaString(JNIEnv* env,
std::vector<std::string> StringArrayToVector(JNIEnv* env, jobjectArray jargs);
std::vector<std::string> StringListToVector(JNIEnv* env, jobject list);
ScopedJavaLocalRef<jobjectArray> VectorToStringArray(
JNIEnv* env,
const std::vector<std::string>& vector);

View File

@ -93,6 +93,7 @@ std::weak_ptr<DartIsolate> DartIsolate::SpawnIsolate(
const fml::closure& isolate_shutdown_callback,
std::optional<std::string> dart_entrypoint,
std::optional<std::string> dart_entrypoint_library,
const std::vector<std::string>& dart_entrypoint_args,
std::unique_ptr<IsolateConfiguration> isolate_configuration) const {
return CreateRunningRootIsolate(
settings, //
@ -104,6 +105,7 @@ std::weak_ptr<DartIsolate> DartIsolate::SpawnIsolate(
isolate_shutdown_callback, //
dart_entrypoint, //
dart_entrypoint_library, //
dart_entrypoint_args, //
std::move(isolate_configuration), //
UIDartState::Context{GetTaskRunners(), //
snapshot_delegate, //
@ -128,6 +130,7 @@ std::weak_ptr<DartIsolate> DartIsolate::CreateRunningRootIsolate(
const fml::closure& isolate_shutdown_callback,
std::optional<std::string> dart_entrypoint,
std::optional<std::string> dart_entrypoint_library,
const std::vector<std::string>& dart_entrypoint_args,
std::unique_ptr<IsolateConfiguration> isolate_configuration,
const UIDartState::Context& context,
const DartIsolate* spawning_isolate) {
@ -195,10 +198,14 @@ std::weak_ptr<DartIsolate> DartIsolate::CreateRunningRootIsolate(
root_isolate_create_callback();
}
if (!isolate->RunFromLibrary(dart_entrypoint_library, //
dart_entrypoint, //
settings.dart_entrypoint_args //
)) {
FML_DCHECK(dart_entrypoint_args.empty() ||
settings.dart_entrypoint_args.empty());
const std::vector<std::string>& args = !dart_entrypoint_args.empty()
? dart_entrypoint_args
: settings.dart_entrypoint_args;
if (!isolate->RunFromLibrary(dart_entrypoint_library, //
dart_entrypoint, //
args)) {
FML_LOG(ERROR) << "Could not run the run main Dart entrypoint.";
return {};
}

View File

@ -170,6 +170,8 @@ class DartIsolate : public UIDartState {
/// function to invoke.
/// @param[in] dart_entrypoint_library The name of the dart library
/// containing the entrypoint.
/// @param[in] dart_entrypoint_args Arguments passed as a List<String>
/// to Dart's entrypoint function.
/// @param[in] isolate_configuration The isolate configuration used to
/// configure the isolate before
/// invoking the entrypoint.
@ -215,6 +217,7 @@ class DartIsolate : public UIDartState {
const fml::closure& isolate_shutdown_callback,
std::optional<std::string> dart_entrypoint,
std::optional<std::string> dart_entrypoint_library,
const std::vector<std::string>& dart_entrypoint_args,
std::unique_ptr<IsolateConfiguration> isolate_configuration,
const UIDartState::Context& context,
const DartIsolate* spawning_isolate = nullptr);
@ -245,6 +248,7 @@ class DartIsolate : public UIDartState {
const fml::closure& isolate_shutdown_callback,
std::optional<std::string> dart_entrypoint,
std::optional<std::string> dart_entrypoint_library,
const std::vector<std::string>& dart_entrypoint_args,
std::unique_ptr<IsolateConfiguration> isolate_configuration) const;
// |UIDartState|

View File

@ -64,6 +64,7 @@ TEST_F(DartIsolateTest, RootIsolateCreationAndShutdown) {
settings.isolate_shutdown_callback, // isolate shutdown callback
"main", // dart entrypoint
std::nullopt, // dart entrypoint library
{}, // dart entrypoint arguments
std::move(isolate_configuration), // isolate configuration
std::move(context) // engine context
);
@ -103,6 +104,7 @@ TEST_F(DartIsolateTest, SpawnIsolate) {
settings.isolate_shutdown_callback, // isolate shutdown callback
"main", // dart entrypoint
std::nullopt, // dart entrypoint library
{}, // dart entrypoint arguments
std::move(isolate_configuration), // isolate configuration
std::move(context) // engine context
);
@ -123,6 +125,7 @@ TEST_F(DartIsolateTest, SpawnIsolate) {
/*isolate_shutdown_callback=*/settings.isolate_shutdown_callback,
/*dart_entrypoint=*/"main",
/*dart_entrypoint_library=*/std::nullopt,
/*dart_entrypoint_args=*/{},
/*isolate_configuration=*/std::move(spawn_configuration));
auto spawn = weak_spawn.lock();
ASSERT_TRUE(spawn);
@ -177,6 +180,7 @@ TEST_F(DartIsolateTest, IsolateShutdownCallbackIsInIsolateScope) {
settings.isolate_shutdown_callback, // isolate shutdown callback
"main", // dart entrypoint
std::nullopt, // dart entrypoint library
{}, // dart entrypoint arguments
std::move(isolate_configuration), // isolate configuration
std::move(context) // engine context
);
@ -400,6 +404,7 @@ TEST_F(DartIsolateTest, CanCreateServiceIsolate) {
settings.isolate_shutdown_callback, // isolate shutdown callback
"main", // dart entrypoint
std::nullopt, // dart entrypoint library
{}, // dart entrypoint arguments
std::move(isolate_configuration), // isolate configuration
std::move(context) // engine context
);
@ -499,6 +504,7 @@ TEST_F(DartIsolateTest, InvalidLoadingUnitFails) {
settings.isolate_shutdown_callback, // isolate shutdown callback
"main", // dart entrypoint
std::nullopt, // dart entrypoint library
{}, // dart entrypoint arguments
std::move(isolate_configuration), // isolate configuration
std::move(context) // engine context
);

View File

@ -69,6 +69,7 @@ static std::shared_ptr<DartIsolate> CreateAndRunRootIsolate(
settings.isolate_shutdown_callback, // isolate shutdown callback,
entrypoint, // dart entrypoint
std::nullopt, // dart entrypoint library
{}, // dart entrypoint arguments
std::move(isolate_configuration), // isolate configuration
std::move(context) // engine context
)

View File

@ -344,6 +344,7 @@ bool RuntimeController::LaunchRootIsolate(
fml::closure root_isolate_create_callback,
std::optional<std::string> dart_entrypoint,
std::optional<std::string> dart_entrypoint_library,
const std::vector<std::string>& dart_entrypoint_args,
std::unique_ptr<IsolateConfiguration> isolate_configuration) {
if (root_isolate_.lock()) {
FML_LOG(ERROR) << "Root isolate was already running.";
@ -361,6 +362,7 @@ bool RuntimeController::LaunchRootIsolate(
isolate_shutdown_callback_, //
dart_entrypoint, //
dart_entrypoint_library, //
dart_entrypoint_args, //
std::move(isolate_configuration), //
context_, //
spawning_isolate_.lock().get()) //

View File

@ -132,6 +132,8 @@ class RuntimeController : public PlatformConfigurationClient {
/// @param[in] dart_entrypoint_library The dart entrypoint library. If
/// `std::nullopt` or empty, the core
/// library will be attempted.
/// @param[in] dart_entrypoint_args Arguments passed as a List<String>
/// to Dart's entrypoint function.
/// @param[in] isolate_configuration The isolate configuration
///
/// @return If the isolate could be launched and guided to the
@ -142,6 +144,7 @@ class RuntimeController : public PlatformConfigurationClient {
fml::closure root_isolate_create_callback,
std::optional<std::string> dart_entrypoint,
std::optional<std::string> dart_entrypoint_library,
const std::vector<std::string>& dart_entrypoint_args,
std::unique_ptr<IsolateConfiguration> isolate_configuration);
//----------------------------------------------------------------------------

View File

@ -199,6 +199,10 @@ Engine::RunStatus Engine::Run(RunConfiguration configuration) {
last_entry_point_ = configuration.GetEntrypoint();
last_entry_point_library_ = configuration.GetEntrypointLibrary();
#if (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG)
// This is only used to support restart.
last_entry_point_args_ = configuration.GetEntrypointArgs();
#endif
UpdateAssetManager(configuration.GetAssetManager());
@ -220,6 +224,7 @@ Engine::RunStatus Engine::Run(RunConfiguration configuration) {
root_isolate_create_callback, //
configuration.GetEntrypoint(), //
configuration.GetEntrypointLibrary(), //
configuration.GetEntrypointArgs(), //
configuration.TakeIsolateConfiguration()) //
) {
return RunStatus::Failure;
@ -562,6 +567,10 @@ const std::string& Engine::GetLastEntrypointLibrary() const {
return last_entry_point_library_;
}
const std::vector<std::string>& Engine::GetLastEntrypointArgs() const {
return last_entry_point_args_;
}
// |RuntimeDelegate|
void Engine::RequestDartDeferredLibrary(intptr_t loading_unit_id) {
return delegate_.RequestDartDeferredLibrary(loading_unit_id);

View File

@ -819,6 +819,13 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate {
///
const std::string& GetLastEntrypointLibrary() const;
//----------------------------------------------------------------------------
/// @brief Get the last Entrypoint Arguments that was used in the
/// RunConfiguration when |Engine::Run| was called.This is only
/// valid in debug mode.
///
const std::vector<std::string>& GetLastEntrypointArgs() const;
//----------------------------------------------------------------------------
/// @brief Getter for the initial route. This can be set with a platform
/// message.
@ -955,6 +962,7 @@ class Engine final : public RuntimeDelegate, PointerDataDispatcher::Delegate {
std::string last_entry_point_;
std::string last_entry_point_library_;
std::vector<std::string> last_entry_point_args_;
std::string initial_route_;
ViewportMetrics viewport_metrics_;
std::shared_ptr<AssetManager> asset_manager_;

View File

@ -247,3 +247,17 @@ void canAccessResourceFromAssetDir() async {
},
);
}
void notifyNativeWhenEngineRun(bool success) native 'NotifyNativeWhenEngineRun';
void notifyNativeWhenEngineSpawn(bool success) native 'NotifyNativeWhenEngineSpawn';
@pragma('vm:entry-point')
void canRecieveArgumentsWhenEngineRun(List<String> args) {
notifyNativeWhenEngineRun(args.length == 2 && args[0] == 'foo' && args[1] == 'bar');
}
@pragma('vm:entry-point')
void canRecieveArgumentsWhenEngineSpawn(List<String> args) {
notifyNativeWhenEngineSpawn(args.length == 2 && args[0] == 'arg1' && args[1] == 'arg2');
}

View File

@ -77,6 +77,11 @@ void RunConfiguration::SetEntrypointAndLibrary(std::string entrypoint,
entrypoint_library_ = std::move(library);
}
void RunConfiguration::SetEntrypointArgs(
const std::vector<std::string>& entrypoint_args) {
entrypoint_args_ = entrypoint_args;
}
std::shared_ptr<AssetManager> RunConfiguration::GetAssetManager() const {
return asset_manager_;
}
@ -89,6 +94,10 @@ const std::string& RunConfiguration::GetEntrypointLibrary() const {
return entrypoint_library_;
}
const std::vector<std::string>& RunConfiguration::GetEntrypointArgs() const {
return entrypoint_args_;
}
std::unique_ptr<IsolateConfiguration>
RunConfiguration::TakeIsolateConfiguration() {
return std::move(isolate_configuration_);

View File

@ -151,6 +151,12 @@ class RunConfiguration {
///
void SetEntrypointAndLibrary(std::string entrypoint, std::string library);
//----------------------------------------------------------------------------
/// @brief Updates the main application entrypoint arguments.
///
/// @param[in] entrypoint_args The entrypoint arguments to use.
void SetEntrypointArgs(const std::vector<std::string>& entrypoint_args);
//----------------------------------------------------------------------------
/// @return The asset manager referencing all previously registered asset
/// resolvers.
@ -168,6 +174,12 @@ class RunConfiguration {
///
const std::string& GetEntrypointLibrary() const;
//----------------------------------------------------------------------------
/// @return Arguments passed as a List<String> to Dart's entrypoint
/// function.
///
const std::vector<std::string>& GetEntrypointArgs() const;
//----------------------------------------------------------------------------
/// @brief The engine uses this to take the isolate configuration from
/// the run configuration. The run configuration is no longer
@ -184,6 +196,7 @@ class RunConfiguration {
std::shared_ptr<AssetManager> asset_manager_;
std::string entrypoint_ = "main";
std::string entrypoint_library_ = "";
std::vector<std::string> entrypoint_args_;
FML_DISALLOW_COPY_AND_ASSIGN(RunConfiguration);
};

View File

@ -1556,6 +1556,7 @@ bool Shell::OnServiceProtocolRunInView(
configuration.SetEntrypointAndLibrary(engine_->GetLastEntrypoint(),
engine_->GetLastEntrypointLibrary());
configuration.SetEntrypointArgs(engine_->GetLastEntrypointArgs());
configuration.AddAssetResolver(std::make_unique<DirectoryAssetBundle>(
fml::OpenDirectory(asset_directory_path.c_str(), false,

View File

@ -460,6 +460,39 @@ TEST_F(ShellTest, LastEntrypoint) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}
TEST_F(ShellTest, LastEntrypointArgs) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
auto settings = CreateSettingsForFixture();
auto shell = CreateShell(settings);
ASSERT_TRUE(ValidateShell(shell.get()));
auto configuration = RunConfiguration::InferFromSettings(settings);
ASSERT_TRUE(configuration.IsValid());
std::string entry_point = "fixturesAreFunctionalMain";
std::vector<std::string> entry_point_args = {"arg1"};
configuration.SetEntrypoint(entry_point);
configuration.SetEntrypointArgs(entry_point_args);
fml::AutoResetWaitableEvent main_latch;
std::vector<std::string> last_entry_point_args;
AddNativeCallback(
"SayHiFromFixturesAreFunctionalMain", CREATE_NATIVE_ENTRY([&](auto args) {
last_entry_point_args = shell->GetEngine()->GetLastEntrypointArgs();
main_latch.Signal();
}));
RunEngine(shell.get(), std::move(configuration));
main_latch.Wait();
#if (FLUTTER_RUNTIME_MODE == FLUTTER_RUNTIME_MODE_DEBUG)
EXPECT_EQ(last_entry_point_args, entry_point_args);
#else
ASSERT_TRUE(last_entry_point_args.empty());
#endif
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
DestroyShell(std::move(shell));
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}
TEST_F(ShellTest,
#if defined(WINUWP)
// TODO(cbracken): https://github.com/flutter/flutter/issues/90481
@ -2823,6 +2856,113 @@ TEST_F(ShellTest, Spawn) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}
TEST_F(ShellTest, SpawnWithDartEntrypointArgs) {
auto settings = CreateSettingsForFixture();
auto shell = CreateShell(settings);
ASSERT_TRUE(ValidateShell(shell.get()));
auto configuration = RunConfiguration::InferFromSettings(settings);
ASSERT_TRUE(configuration.IsValid());
configuration.SetEntrypoint("canRecieveArgumentsWhenEngineRun");
const std::vector<std::string> entrypoint_args{"foo", "bar"};
configuration.SetEntrypointArgs(entrypoint_args);
auto second_configuration = RunConfiguration::InferFromSettings(settings);
ASSERT_TRUE(second_configuration.IsValid());
second_configuration.SetEntrypoint("canRecieveArgumentsWhenEngineSpawn");
const std::vector<std::string> second_entrypoint_args{"arg1", "arg2"};
second_configuration.SetEntrypointArgs(second_entrypoint_args);
const std::string initial_route("/foo");
fml::AutoResetWaitableEvent main_latch;
std::string last_entry_point;
// Fulfill native function for the first Shell's entrypoint.
AddNativeCallback("NotifyNativeWhenEngineRun",
CREATE_NATIVE_ENTRY(([&](Dart_NativeArguments args) {
ASSERT_TRUE(tonic::DartConverter<bool>::FromDart(
Dart_GetNativeArgument(args, 0)));
last_entry_point =
shell->GetEngine()->GetLastEntrypoint();
main_latch.Signal();
})));
fml::AutoResetWaitableEvent second_latch;
// Fulfill native function for the second Shell's entrypoint.
AddNativeCallback("NotifyNativeWhenEngineSpawn",
CREATE_NATIVE_ENTRY(([&](Dart_NativeArguments args) {
ASSERT_TRUE(tonic::DartConverter<bool>::FromDart(
Dart_GetNativeArgument(args, 0)));
last_entry_point =
shell->GetEngine()->GetLastEntrypoint();
second_latch.Signal();
})));
RunEngine(shell.get(), std::move(configuration));
main_latch.Wait();
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
// Check first Shell ran the first entrypoint.
ASSERT_EQ("canRecieveArgumentsWhenEngineRun", last_entry_point);
PostSync(
shell->GetTaskRunners().GetPlatformTaskRunner(),
[this, &spawner = shell, &second_configuration, &second_latch,
initial_route]() {
MockPlatformViewDelegate platform_view_delegate;
auto spawn = spawner->Spawn(
std::move(second_configuration), initial_route,
[&platform_view_delegate](Shell& shell) {
auto result = std::make_unique<MockPlatformView>(
platform_view_delegate, shell.GetTaskRunners());
ON_CALL(*result, CreateRenderingSurface())
.WillByDefault(::testing::Invoke(
[] { return std::make_unique<MockSurface>(); }));
return result;
},
[](Shell& shell) { return std::make_unique<Rasterizer>(shell); });
ASSERT_NE(nullptr, spawn.get());
ASSERT_TRUE(ValidateShell(spawn.get()));
PostSync(spawner->GetTaskRunners().GetUITaskRunner(),
[&spawn, &spawner, initial_route] {
// Check second shell ran the second entrypoint.
ASSERT_EQ("canRecieveArgumentsWhenEngineSpawn",
spawn->GetEngine()->GetLastEntrypoint());
ASSERT_EQ(initial_route, spawn->GetEngine()->InitialRoute());
// TODO(74520): Remove conditional once isolate groups are
// supported by JIT.
if (DartVM::IsRunningPrecompiledCode()) {
ASSERT_NE(spawner->GetEngine()
->GetRuntimeController()
->GetRootIsolateGroup(),
0u);
ASSERT_EQ(spawner->GetEngine()
->GetRuntimeController()
->GetRootIsolateGroup(),
spawn->GetEngine()
->GetRuntimeController()
->GetRootIsolateGroup());
}
});
PostSync(
spawner->GetTaskRunners().GetIOTaskRunner(), [&spawner, &spawn] {
ASSERT_EQ(spawner->GetIOManager()->GetResourceContext().get(),
spawn->GetIOManager()->GetResourceContext().get());
});
// Before destroying the shell, wait for expectations of the spawned
// isolate to be met.
second_latch.Wait();
DestroyShell(std::move(spawn));
});
DestroyShell(std::move(shell));
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}
TEST_F(ShellTest, UpdateAssetResolverByTypeReplaces) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
Settings settings = CreateSettingsForFixture();

View File

@ -178,7 +178,8 @@ std::unique_ptr<AndroidShellHolder> AndroidShellHolder::Spawn(
std::shared_ptr<PlatformViewAndroidJNI> jni_facade,
const std::string& entrypoint,
const std::string& libraryUrl,
const std::string& initial_route) const {
const std::string& initial_route,
const std::vector<std::string>& entrypoint_args) const {
FML_DCHECK(shell_ && shell_->IsSetup())
<< "A new Shell can only be spawned "
"if the current Shell is properly constructed";
@ -225,7 +226,8 @@ std::unique_ptr<AndroidShellHolder> AndroidShellHolder::Spawn(
// TODO(xster): could be worth tracing this to investigate whether
// the IsolateConfiguration could be cached somewhere.
auto config = BuildRunConfiguration(asset_manager_, entrypoint, libraryUrl);
auto config = BuildRunConfiguration(asset_manager_, entrypoint, libraryUrl,
entrypoint_args);
if (!config) {
// If the RunConfiguration was null, the kernel blob wasn't readable.
// Fail the whole thing.
@ -241,15 +243,18 @@ std::unique_ptr<AndroidShellHolder> AndroidShellHolder::Spawn(
std::move(shell), weak_platform_view));
}
void AndroidShellHolder::Launch(std::shared_ptr<AssetManager> asset_manager,
const std::string& entrypoint,
const std::string& libraryUrl) {
void AndroidShellHolder::Launch(
std::shared_ptr<AssetManager> asset_manager,
const std::string& entrypoint,
const std::string& libraryUrl,
const std::vector<std::string>& entrypoint_args) {
if (!IsValid()) {
return;
}
asset_manager_ = asset_manager;
auto config = BuildRunConfiguration(asset_manager, entrypoint, libraryUrl);
auto config = BuildRunConfiguration(asset_manager, entrypoint, libraryUrl,
entrypoint_args);
if (!config) {
return;
}
@ -278,7 +283,8 @@ void AndroidShellHolder::NotifyLowMemoryWarning() {
std::optional<RunConfiguration> AndroidShellHolder::BuildRunConfiguration(
std::shared_ptr<flutter::AssetManager> asset_manager,
const std::string& entrypoint,
const std::string& libraryUrl) const {
const std::string& libraryUrl,
const std::vector<std::string>& entrypoint_args) const {
std::unique_ptr<IsolateConfiguration> isolate_configuration;
if (flutter::DartVM::IsRunningPrecompiledCode()) {
isolate_configuration = IsolateConfiguration::CreateForAppSnapshot();
@ -304,6 +310,9 @@ std::optional<RunConfiguration> AndroidShellHolder::BuildRunConfiguration(
} else if (entrypoint.size() > 0) {
config.SetEntrypoint(std::move(entrypoint));
}
if (entrypoint_args.size() > 0) {
config.SetEntrypointArgs(entrypoint_args);
}
}
return config;
}

View File

@ -80,11 +80,13 @@ class AndroidShellHolder {
std::shared_ptr<PlatformViewAndroidJNI> jni_facade,
const std::string& entrypoint,
const std::string& libraryUrl,
const std::string& initial_route) const;
const std::string& initial_route,
const std::vector<std::string>& entrypoint_args) const;
void Launch(std::shared_ptr<AssetManager> asset_manager,
const std::string& entrypoint,
const std::string& libraryUrl);
const std::string& libraryUrl,
const std::vector<std::string>& entrypoint_args);
const flutter::Settings& GetSettings() const;
@ -132,7 +134,8 @@ class AndroidShellHolder {
std::optional<RunConfiguration> BuildRunConfiguration(
std::shared_ptr<flutter::AssetManager> asset_manager,
const std::string& entrypoint,
const std::string& libraryUrl) const;
const std::string& libraryUrl,
const std::vector<std::string>& entrypoint_args) const;
bool IsNDKImageDecoderAvailable();

View File

@ -38,6 +38,7 @@ import io.flutter.embedding.engine.systemchannels.TextInputChannel;
import io.flutter.plugin.localization.LocalizationPlugin;
import io.flutter.plugin.platform.PlatformViewsController;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
/**
@ -380,13 +381,17 @@ public class FlutterEngine {
* @param dartEntrypoint specifies the {@link DartEntrypoint} the new engine should run. It
* doesn't need to be the same entrypoint as the current engine but must be built in the same
* AOT or snapshot.
* @param initialRoute The name of the initial Flutter `Navigator` `Route` to load. If this is
* null, it will default to the "/" route.
* @param dartEntrypointArgs Arguments passed as a list of string to Dart's entrypoint function.
* @return a new {@link io.flutter.embedding.engine.FlutterEngine}.
*/
@NonNull
/*package*/ FlutterEngine spawn(
@NonNull Context context,
@NonNull DartEntrypoint dartEntrypoint,
@Nullable String initialRoute) {
@Nullable String initialRoute,
@Nullable List<String> dartEntrypointArgs) {
if (!isAttachedToJni()) {
throw new IllegalStateException(
"Spawn can only be called on a fully constructed FlutterEngine");
@ -396,7 +401,8 @@ public class FlutterEngine {
flutterJNI.spawn(
dartEntrypoint.dartEntrypointFunctionName,
dartEntrypoint.dartEntrypointLibrary,
initialRoute);
initialRoute,
dartEntrypointArgs);
return new FlutterEngine(
context, // Context.
null, // FlutterLoader. A null value passed here causes the constructor to get it from the

View File

@ -117,8 +117,32 @@ public class FlutterEngineGroup {
@NonNull Context context,
@Nullable DartEntrypoint dartEntrypoint,
@Nullable String initialRoute) {
return createAndRunEngine(
new Options(context).setDartEntrypoint(dartEntrypoint).setInitialRoute(initialRoute));
}
/**
* 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}, the
* specified {@code initialRoute} and the {@code dartEntrypointArgs}.
*
* <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 Options options) {
FlutterEngine engine = null;
Context context = options.getContext();
DartEntrypoint dartEntrypoint = options.getDartEntrypoint();
String initialRoute = options.getInitialRoute();
List<String> dartEntrypointArgs = options.getDartEntrypointArgs();
if (dartEntrypoint == null) {
dartEntrypoint = DartEntrypoint.createDefault();
}
@ -128,9 +152,10 @@ public class FlutterEngineGroup {
if (initialRoute != null) {
engine.getNavigationChannel().setInitialRoute(initialRoute);
}
engine.getDartExecutor().executeDartEntrypoint(dartEntrypoint);
engine.getDartExecutor().executeDartEntrypoint(dartEntrypoint, dartEntrypointArgs);
} else {
engine = activeEngines.get(0).spawn(context, dartEntrypoint, initialRoute);
engine =
activeEngines.get(0).spawn(context, dartEntrypoint, initialRoute, dartEntrypointArgs);
}
activeEngines.add(engine);
@ -156,4 +181,75 @@ public class FlutterEngineGroup {
/* package */ FlutterEngine createEngine(Context context) {
return new FlutterEngine(context);
}
/** Options that control how a FlutterEngine should be created. */
public static class Options {
@NonNull private Context context;
@Nullable private DartEntrypoint dartEntrypoint;
@Nullable private String initialRoute;
@Nullable private List<String> dartEntrypointArgs;
public Options(@NonNull Context context) {
this.context = context;
}
public Context getContext() {
return context;
}
/**
* dartEntrypoint specifies the {@link DartEntrypoint} the new engine should run. It doesn't
* need to be the same entrypoint as the current engine but must be built in the same AOT or
* snapshot.
*/
public DartEntrypoint getDartEntrypoint() {
return dartEntrypoint;
}
/**
* The name of the initial Flutter `Navigator` `Route` to load. If this is null, it will default
* to the "/" route.
*/
public String getInitialRoute() {
return initialRoute;
}
/** Arguments passed as a list of string to Dart's entrypoint function. */
public List<String> getDartEntrypointArgs() {
return dartEntrypointArgs;
}
/**
* Setter for `dartEntrypoint` property.
*
* @param dartEntrypoint specifies the {@link DartEntrypoint} the new engine should run. It
* doesn't need to be the same entrypoint as the current engine but must be built in the
* same AOT or snapshot.
*/
public Options setDartEntrypoint(DartEntrypoint dartEntrypoint) {
this.dartEntrypoint = dartEntrypoint;
return this;
}
/**
* Setter for `initialRoute` property.
*
* @param initialRoute The name of the initial Flutter `Navigator` `Route` to load. If this is
* null, it will default to the "/" route.
*/
public Options setInitialRoute(String initialRoute) {
this.initialRoute = initialRoute;
return this;
}
/**
* Setter for `dartEntrypointArgs` property.
*
* @param dartEntrypointArgs Arguments passed as a list of string to Dart's entrypoint function.
*/
public Options setDartEntrypointArgs(List<String> dartEntrypointArgs) {
this.dartEntrypointArgs = dartEntrypointArgs;
return this;
}
}
}

View File

@ -371,12 +371,17 @@ public class FlutterJNI {
public FlutterJNI spawn(
@Nullable String entrypointFunctionName,
@Nullable String pathToEntrypointFunction,
@Nullable String initialRoute) {
@Nullable String initialRoute,
@Nullable List<String> entrypointArgs) {
ensureRunningOnMainThread();
ensureAttachedToNative();
FlutterJNI spawnedJNI =
nativeSpawn(
nativeShellHolderId, entrypointFunctionName, pathToEntrypointFunction, initialRoute);
nativeShellHolderId,
entrypointFunctionName,
pathToEntrypointFunction,
initialRoute,
entrypointArgs);
Preconditions.checkState(
spawnedJNI.nativeShellHolderId != null && spawnedJNI.nativeShellHolderId != 0,
"Failed to spawn new JNI connected shell from existing shell.");
@ -388,7 +393,8 @@ public class FlutterJNI {
long nativeSpawningShellId,
@Nullable String entrypointFunctionName,
@Nullable String pathToEntrypointFunction,
@Nullable String initialRoute);
@Nullable String initialRoute,
@Nullable List<String> entrypointArgs);
/**
* Detaches this {@code FlutterJNI} instance from Flutter's native engine, which precludes any
@ -397,8 +403,8 @@ public class FlutterJNI {
* <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, String)}, or accumulated
* thereafter.
* up during {@link #attachToNative()} or {@link #spawn(String, String, String, List)}, or
* accumulated thereafter.
*
* <p>It is permissible to re-attach this instance to native after detaching it from native.
*/
@ -850,7 +856,8 @@ public class FlutterJNI {
@NonNull String bundlePath,
@Nullable String entrypointFunctionName,
@Nullable String pathToEntrypointFunction,
@NonNull AssetManager assetManager) {
@NonNull AssetManager assetManager,
@Nullable List<String> entrypointArgs) {
ensureRunningOnMainThread();
ensureAttachedToNative();
nativeRunBundleAndSnapshotFromLibrary(
@ -858,7 +865,8 @@ public class FlutterJNI {
bundlePath,
entrypointFunctionName,
pathToEntrypointFunction,
assetManager);
assetManager,
entrypointArgs);
}
private native void nativeRunBundleAndSnapshotFromLibrary(
@ -866,7 +874,8 @@ public class FlutterJNI {
@NonNull String bundlePath,
@Nullable String entrypointFunctionName,
@Nullable String pathToEntrypointFunction,
@NonNull AssetManager manager);
@NonNull AssetManager manager,
@Nullable List<String> entrypointArgs);
// ------ End Dart Execution Support -------
// --------- Start Platform Message Support ------

View File

@ -17,6 +17,7 @@ import io.flutter.plugin.common.BinaryMessenger;
import io.flutter.plugin.common.StringCodec;
import io.flutter.view.FlutterCallbackInformation;
import java.nio.ByteBuffer;
import java.util.List;
/**
* Configures, bootstraps, and starts executing Dart code.
@ -121,6 +122,20 @@ public class DartExecutor implements BinaryMessenger {
* @param dartEntrypoint specifies which Dart function to run, and where to find it
*/
public void executeDartEntrypoint(@NonNull DartEntrypoint dartEntrypoint) {
executeDartEntrypoint(dartEntrypoint, null);
}
/**
* Starts executing Dart code based on the given {@code dartEntrypoint} and the {@code
* dartEntrypointArgs}.
*
* <p>See {@link DartEntrypoint} for configuration options.
*
* @param dartEntrypoint specifies which Dart function to run, and where to find it
* @param dartEntrypointArgs Arguments passed as a list of string to Dart's entrypoint function.
*/
public void executeDartEntrypoint(
@NonNull DartEntrypoint dartEntrypoint, @Nullable List<String> dartEntrypointArgs) {
if (isApplicationRunning) {
Log.w(TAG, "Attempted to run a DartExecutor that is already running.");
return;
@ -134,7 +149,8 @@ public class DartExecutor implements BinaryMessenger {
dartEntrypoint.pathToBundle,
dartEntrypoint.dartEntrypointFunctionName,
dartEntrypoint.dartEntrypointLibrary,
assetManager);
assetManager,
dartEntrypointArgs);
isApplicationRunning = true;
} finally {
@ -163,7 +179,8 @@ public class DartExecutor implements BinaryMessenger {
dartCallback.pathToBundle,
dartCallback.callbackHandle.callbackName,
dartCallback.callbackHandle.callbackLibraryPath,
dartCallback.androidAssetManager);
dartCallback.androidAssetManager,
null);
isApplicationRunning = true;
} finally {

View File

@ -111,7 +111,11 @@ public class FlutterNativeView implements BinaryMessenger {
if (applicationIsRunning)
throw new AssertionError("This Flutter engine instance is already running an application");
mFlutterJNI.runBundleAndSnapshotFromLibrary(
args.bundlePath, args.entrypoint, args.libraryPath, mContext.getResources().getAssets());
args.bundlePath,
args.entrypoint,
args.libraryPath,
mContext.getResources().getAssets(),
null);
applicationIsRunning = true;
}

View File

@ -155,7 +155,8 @@ static jobject SpawnJNI(JNIEnv* env,
jlong shell_holder,
jstring jEntrypoint,
jstring jLibraryUrl,
jstring jInitialRoute) {
jstring jInitialRoute,
jobject jEntrypointArgs) {
jobject jni = env->NewObject(g_flutter_jni_class->obj(), g_jni_constructor);
if (jni == nullptr) {
FML_LOG(ERROR) << "Could not create a FlutterJNI instance";
@ -169,9 +170,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 entrypoint_args = fml::jni::StringListToVector(env, jEntrypointArgs);
auto spawned_shell_holder = ANDROID_SHELL_HOLDER->Spawn(
jni_facade, entrypoint, libraryUrl, initial_route);
jni_facade, entrypoint, libraryUrl, initial_route, entrypoint_args);
if (spawned_shell_holder == nullptr || !spawned_shell_holder->IsValid()) {
FML_LOG(ERROR) << "Could not spawn Shell";
@ -237,7 +239,8 @@ static void RunBundleAndSnapshotFromLibrary(JNIEnv* env,
jstring jBundlePath,
jstring jEntrypoint,
jstring jLibraryUrl,
jobject jAssetManager) {
jobject jAssetManager,
jobject jEntrypointArgs) {
auto asset_manager = std::make_shared<flutter::AssetManager>();
asset_manager->PushBack(std::make_unique<flutter::APKAssetProvider>(
@ -248,8 +251,10 @@ static void RunBundleAndSnapshotFromLibrary(JNIEnv* env,
auto entrypoint = fml::jni::JavaStringToString(env, jEntrypoint);
auto libraryUrl = fml::jni::JavaStringToString(env, jLibraryUrl);
auto entrypoint_args = fml::jni::StringListToVector(env, jEntrypointArgs);
ANDROID_SHELL_HOLDER->Launch(asset_manager, entrypoint, libraryUrl);
ANDROID_SHELL_HOLDER->Launch(asset_manager, entrypoint, libraryUrl,
entrypoint_args);
}
static jobject LookupCallbackInformation(JNIEnv* env,
@ -629,14 +634,15 @@ bool RegisterApi(JNIEnv* env) {
{
.name = "nativeSpawn",
.signature = "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/"
"String;)Lio/flutter/"
"String;Ljava/util/List;)Lio/flutter/"
"embedding/engine/FlutterJNI;",
.fnPtr = reinterpret_cast<void*>(&SpawnJNI),
},
{
.name = "nativeRunBundleAndSnapshotFromLibrary",
.signature = "(JLjava/lang/String;Ljava/lang/String;"
"Ljava/lang/String;Landroid/content/res/AssetManager;)V",
"Ljava/lang/String;Landroid/content/res/"
"AssetManager;Ljava/util/List;)V",
.fnPtr = reinterpret_cast<void*>(&RunBundleAndSnapshotFromLibrary),
},
{

View File

@ -24,6 +24,8 @@ 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 java.util.ArrayList;
import java.util.List;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
@ -121,7 +123,11 @@ public class FlutterEngineGroupComponentTest {
doReturn(mock(FlutterEngine.class))
.when(firstEngine)
.spawn(any(Context.class), any(DartEntrypoint.class), nullable(String.class));
.spawn(
any(Context.class),
any(DartEntrypoint.class),
nullable(String.class),
nullable(List.class));
FlutterEngine secondEngine =
engineGroupUnderTest.createAndRunEngine(
@ -133,7 +139,11 @@ public class FlutterEngineGroupComponentTest {
// 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), nullable(String.class)))
when(secondEngine.spawn(
any(Context.class),
any(DartEntrypoint.class),
nullable(String.class),
nullable(List.class)))
.thenReturn(mock(FlutterEngine.class));
FlutterEngine thirdEngine =
@ -156,7 +166,8 @@ public class FlutterEngineGroupComponentTest {
eq("some/path/to/flutter_assets"),
eq("other entrypoint"),
isNull(String.class),
any(AssetManager.class));
any(AssetManager.class),
nullable(List.class));
}
@Test
@ -176,7 +187,11 @@ public class FlutterEngineGroupComponentTest {
doAnswer(invocation -> jniAttached = true).when(secondMockflutterJNI).attachToNative();
doReturn(secondMockflutterJNI)
.when(mockflutterJNI)
.spawn(nullable(String.class), nullable(String.class), nullable(String.class));
.spawn(
nullable(String.class),
nullable(String.class),
nullable(String.class),
nullable(List.class));
FlutterEngine secondEngine =
engineGroupUnderTest.createAndRunEngine(
@ -184,6 +199,51 @@ public class FlutterEngineGroupComponentTest {
assertEquals(2, engineGroupUnderTest.activeEngines.size());
verify(mockflutterJNI, times(1))
.spawn(nullable(String.class), nullable(String.class), eq("/bar"));
.spawn(nullable(String.class), nullable(String.class), eq("/bar"), nullable(List.class));
}
@Test
public void canCreateAndRunWithCustomEntrypointArgs() {
List<String> firstDartEntrypointArgs = new ArrayList<String>();
FlutterEngine firstEngine =
engineGroupUnderTest.createAndRunEngine(
new FlutterEngineGroup.Options(RuntimeEnvironment.application)
.setDartEntrypoint(mock(DartEntrypoint.class))
.setDartEntrypointArgs(firstDartEntrypointArgs));
assertEquals(1, engineGroupUnderTest.activeEngines.size());
verify(mockflutterJNI, times(1))
.runBundleAndSnapshotFromLibrary(
nullable(String.class),
nullable(String.class),
isNull(String.class),
any(AssetManager.class),
eq(firstDartEntrypointArgs));
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),
nullable(List.class));
List<String> secondDartEntrypointArgs = new ArrayList<String>();
FlutterEngine secondEngine =
engineGroupUnderTest.createAndRunEngine(
new FlutterEngineGroup.Options(RuntimeEnvironment.application)
.setDartEntrypoint(mock(DartEntrypoint.class))
.setDartEntrypointArgs(secondDartEntrypointArgs));
assertEquals(2, engineGroupUnderTest.activeEngines.size());
verify(mockflutterJNI, times(1))
.spawn(
nullable(String.class),
nullable(String.class),
nullable(String.class),
eq(secondDartEntrypointArgs));
}
}

View File

@ -246,6 +246,29 @@ FLUTTER_DARWIN_EXPORT
libraryURI:(nullable NSString*)libraryURI
initialRoute:(nullable NSString*)initialRoute;
/**
* Runs a Dart program on an Isolate using the specified entrypoint and Dart library,
* which may not be the same as the library containing the Dart program's `main()` function.
*
* The first call to this method will create a new Isolate. Subsequent calls will return
* immediately and have no effect.
*
* @param entrypoint The name of a top-level function from a Dart library. If this is
* FlutterDefaultDartEntrypoint (or nil); this will default to `main()`. If it is not the app's
* main() function, that function must be decorated with `@pragma(vm:entry-point)` to ensure the
* method is not tree-shaken by the Dart compiler.
* @param libraryURI The URI of the Dart library which contains the entrypoint method. IF nil,
* this will default to the same library as the `main()` function in the Dart program.
* @param initialRoute The name of the initial Flutter `Navigator` `Route` to load. If this is
* FlutterDefaultInitialRoute (or nil), it will default to the "/" route.
* @param entrypointArgs Arguments passed as a list of string to Dart's entrypoint function.
* @return YES if the call succeeds in creating and running a Flutter Engine instance; NO otherwise.
*/
- (BOOL)runWithEntrypoint:(nullable NSString*)entrypoint
libraryURI:(nullable NSString*)libraryURI
initialRoute:(nullable NSString*)initialRoute
entrypointArgs:(nullable NSArray<NSString*>*)entrypointArgs;
/**
* Destroy running context for an engine.
*

View File

@ -8,6 +8,36 @@
NS_ASSUME_NONNULL_BEGIN
/** Options that control how a FlutterEngine should be created. */
FLUTTER_DARWIN_EXPORT
@interface FlutterEngineGroupOptions : NSObject
/**
* The name of a top-level function from a Dart library. If this is FlutterDefaultDartEntrypoint
* (or nil); this will default to `main()`. If it is not the app's main() function, that function
* must be decorated with `@pragma(vm:entry-point)` to ensure themethod is not tree-shaken by the
* Dart compiler.
*/
@property(nonatomic, copy, nullable) NSString* entrypoint;
/**
* The URI of the Dart library which contains the entrypoint method. If nil, this will default to
* the same library as the `main()` function in the Dart program.
*/
@property(nonatomic, copy, nullable) NSString* libraryURI;
/**
* The name of the initial Flutter `Navigator` `Route` to load. If this is
* FlutterDefaultInitialRoute (or nil), it will default to the "/" route.
*/
@property(nonatomic, copy, nullable) NSString* initialRoute;
/**
* Arguments passed as a list of string to Dart's entrypoint function.
*/
@property(nonatomic, retain, nullable) NSArray<NSString*>* entrypointArgs;
@end
/**
* Represents a collection of FlutterEngines who share resources which allows
* them to be created with less time const and occupy less memory than just
@ -66,6 +96,15 @@ FLUTTER_DARWIN_EXPORT
- (FlutterEngine*)makeEngineWithEntrypoint:(nullable NSString*)entrypoint
libraryURI:(nullable NSString*)libraryURI
initialRoute:(nullable NSString*)initialRoute;
/**
* Creates a running `FlutterEngine` that shares components with this group.
*
* @param options Options that control how a FlutterEngine should be created.
*
* @see FlutterEngineGroupOptions
*/
- (FlutterEngine*)makeEngineWithOptions:(nullable FlutterEngineGroupOptions*)options;
@end
NS_ASSUME_NONNULL_END

View File

@ -240,6 +240,15 @@ flutter::Settings FLTDefaultSettingsForBundle(NSBundle* bundle) {
- (flutter::RunConfiguration)runConfigurationForEntrypoint:(nullable NSString*)entrypointOrNil
libraryOrNil:(nullable NSString*)dartLibraryOrNil {
return [self runConfigurationForEntrypoint:entrypointOrNil
libraryOrNil:dartLibraryOrNil
entrypointArgs:nil];
}
- (flutter::RunConfiguration)runConfigurationForEntrypoint:(nullable NSString*)entrypointOrNil
libraryOrNil:(nullable NSString*)dartLibraryOrNil
entrypointArgs:
(nullable NSArray<NSString*>*)entrypointArgs {
auto config = flutter::RunConfiguration::InferFromSettings(_settings);
if (dartLibraryOrNil && entrypointOrNil) {
config.SetEntrypointAndLibrary(std::string([entrypointOrNil UTF8String]),
@ -248,6 +257,15 @@ flutter::Settings FLTDefaultSettingsForBundle(NSBundle* bundle) {
} else if (entrypointOrNil) {
config.SetEntrypoint(std::string([entrypointOrNil UTF8String]));
}
if (entrypointArgs.count) {
std::vector<std::string> cppEntrypointArgs;
for (NSString* arg in entrypointArgs) {
cppEntrypointArgs.push_back(std::string([arg UTF8String]));
}
config.SetEntrypointArgs(cppEntrypointArgs);
}
return config;
}

View File

@ -27,6 +27,10 @@ flutter::Settings FLTDefaultSettingsForBundle(NSBundle* bundle = nil);
- (flutter::RunConfiguration)runConfigurationForEntrypoint:(nullable NSString*)entrypointOrNil;
- (flutter::RunConfiguration)runConfigurationForEntrypoint:(nullable NSString*)entrypointOrNil
libraryOrNil:(nullable NSString*)dartLibraryOrNil;
- (flutter::RunConfiguration)runConfigurationForEntrypoint:(nullable NSString*)entrypointOrNil
libraryOrNil:(nullable NSString*)dartLibraryOrNil
entrypointArgs:
(nullable NSArray<NSString*>*)entrypointArgs;
+ (NSString*)flutterAssetsName:(NSBundle*)bundle;
+ (NSString*)domainNetworkPolicy:(NSDictionary*)appTransportSecurity;
@ -35,7 +39,7 @@ flutter::Settings FLTDefaultSettingsForBundle(NSBundle* bundle = nil);
/**
* The embedder can specify data that the isolate can request synchronously on launch. Engines
* launched using this configuration can access the persistent isolate data via the
* `Window.getPersistentIsolateData` accessor.
* `PlatformDispatcher.getPersistentIsolateData` accessor.
*
* @param data The persistent isolate data. This data is persistent for the duration of the Flutter
* application and is available even after isolate restarts. Because of this lifecycle,

View File

@ -569,10 +569,13 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
return self.shell.Screenshot(type, base64Encode);
}
- (void)launchEngine:(NSString*)entrypoint libraryURI:(NSString*)libraryOrNil {
- (void)launchEngine:(NSString*)entrypoint
libraryURI:(NSString*)libraryOrNil
entrypointArgs:(NSArray<NSString*>*)entrypointArgs {
// Launch the Dart application with the inferred run configuration.
self.shell.RunEngine([_dartProject.get() runConfigurationForEntrypoint:entrypoint
libraryOrNil:libraryOrNil]);
libraryOrNil:libraryOrNil
entrypointArgs:entrypointArgs]);
}
- (void)setupShell:(std::unique_ptr<flutter::Shell>)shell
@ -725,8 +728,18 @@ static void SetEntryPoint(flutter::Settings* settings, NSString* entrypoint, NSS
- (BOOL)runWithEntrypoint:(NSString*)entrypoint
libraryURI:(NSString*)libraryURI
initialRoute:(NSString*)initialRoute {
return [self runWithEntrypoint:entrypoint
libraryURI:libraryURI
initialRoute:initialRoute
entrypointArgs:nil];
}
- (BOOL)runWithEntrypoint:(NSString*)entrypoint
libraryURI:(NSString*)libraryURI
initialRoute:(NSString*)initialRoute
entrypointArgs:(NSArray<NSString*>*)entrypointArgs {
if ([self createShell:entrypoint libraryURI:libraryURI initialRoute:initialRoute]) {
[self launchEngine:entrypoint libraryURI:libraryURI];
[self launchEngine:entrypoint libraryURI:libraryURI entrypointArgs:entrypointArgs];
}
return _shell != nullptr;
@ -1073,14 +1086,16 @@ static void SetEntryPoint(flutter::Settings* settings, NSString* entrypoint, NSS
- (FlutterEngine*)spawnWithEntrypoint:(/*nullable*/ NSString*)entrypoint
libraryURI:(/*nullable*/ NSString*)libraryURI
initialRoute:(/*nullable*/ NSString*)initialRoute {
initialRoute:(/*nullable*/ NSString*)initialRoute
entrypointArgs:(/*nullable*/ NSArray<NSString*>*)entrypointArgs {
NSAssert(_shell, @"Spawning from an engine without a shell (possibly not run).");
FlutterEngine* result = [[FlutterEngine alloc] initWithName:_labelPrefix
project:_dartProject.get()
allowHeadlessExecution:_allowHeadlessExecution];
flutter::RunConfiguration configuration =
[_dartProject.get() runConfigurationForEntrypoint:entrypoint libraryOrNil:libraryURI];
[_dartProject.get() runConfigurationForEntrypoint:entrypoint
libraryOrNil:libraryURI
entrypointArgs:entrypointArgs];
fml::WeakPtr<flutter::PlatformView> platform_view = _shell->GetPlatformView();
FML_DCHECK(platform_view);
@ -1115,7 +1130,7 @@ static void SetEntryPoint(flutter::Settings* settings, NSString* entrypoint, NSS
result->_profiler_metrics = _profiler_metrics;
result->_isGpuDisabled = _isGpuDisabled;
[result setupShell:std::move(shell) withObservatoryPublication:NO];
return result;
return [result autorelease];
}
- (const flutter::ThreadHost&)threadHost {

View File

@ -5,6 +5,18 @@
#import "flutter/shell/platform/darwin/ios/framework/Headers/FlutterEngineGroup.h"
#import "flutter/shell/platform/darwin/ios/framework/Source/FlutterEngine_Internal.h"
@implementation FlutterEngineGroupOptions
- (void)dealloc {
[_entrypoint release];
[_libraryURI release];
[_initialRoute release];
[_entrypointArgs release];
[super dealloc];
}
@end
@interface FlutterEngineGroup ()
@property(nonatomic, copy) NSString* name;
@property(nonatomic, retain) NSMutableArray<NSValue*>* engines;
@ -42,16 +54,32 @@
- (FlutterEngine*)makeEngineWithEntrypoint:(nullable NSString*)entrypoint
libraryURI:(nullable NSString*)libraryURI
initialRoute:(nullable NSString*)initialRoute {
NSString* engineName = [NSString stringWithFormat:@"%@.%d", self.name, ++_enginesCreatedCount];
FlutterEngineGroupOptions* options = [[[FlutterEngineGroupOptions alloc] init] autorelease];
options.entrypoint = entrypoint;
options.libraryURI = libraryURI;
options.initialRoute = initialRoute;
return [self makeEngineWithOptions:options];
}
- (FlutterEngine*)makeEngineWithOptions:(nullable FlutterEngineGroupOptions*)options {
NSString* entrypoint = options.entrypoint;
NSString* libraryURI = options.libraryURI;
NSString* initialRoute = options.initialRoute;
NSArray<NSString*>* entrypointArgs = options.entrypointArgs;
FlutterEngine* engine;
if (self.engines.count <= 0) {
engine = [[FlutterEngine alloc] initWithName:engineName project:self.project];
[engine runWithEntrypoint:entrypoint libraryURI:libraryURI initialRoute:initialRoute];
engine = [self makeEngine];
[engine runWithEntrypoint:entrypoint
libraryURI:libraryURI
initialRoute:initialRoute
entrypointArgs:entrypointArgs];
} else {
FlutterEngine* spawner = (FlutterEngine*)[self.engines[0] pointerValue];
engine = [spawner spawnWithEntrypoint:entrypoint
libraryURI:libraryURI
initialRoute:initialRoute];
initialRoute:initialRoute
entrypointArgs:entrypointArgs];
}
[_engines addObject:[NSValue valueWithPointer:engine]];
@ -61,7 +89,13 @@
name:FlutterEngineWillDealloc
object:engine];
return [engine autorelease];
return engine;
}
- (FlutterEngine*)makeEngine {
NSString* engineName = [NSString stringWithFormat:@"%@.%d", self.name, ++_enginesCreatedCount];
FlutterEngine* result = [[FlutterEngine alloc] initWithName:engineName project:self.project];
return [result autorelease];
}
- (void)onEngineWillBeDealloced:(NSNotification*)notification {

View File

@ -10,6 +10,10 @@
FLUTTER_ASSERT_ARC
@interface FlutterEngineGroup ()
- (FlutterEngine*)makeEngine;
@end
@interface FlutterEngineGroupTest : XCTestCase
@end
@ -42,6 +46,86 @@ FLUTTER_ASSERT_ARC
XCTAssertNotNil(spawnee);
}
- (void)testCustomEntrypoint {
FlutterEngineGroup* group = OCMPartialMock([[FlutterEngineGroup alloc] initWithName:@"foo"
project:nil]);
FlutterEngine* mockEngine = OCMClassMock([FlutterEngine class]);
OCMStub([group makeEngine]).andReturn(mockEngine);
OCMStub([mockEngine spawnWithEntrypoint:[OCMArg any]
libraryURI:[OCMArg any]
initialRoute:[OCMArg any]
entrypointArgs:[OCMArg any]])
.andReturn(OCMClassMock([FlutterEngine class]));
FlutterEngine* spawner = [group makeEngineWithEntrypoint:@"firstEntrypoint"
libraryURI:@"firstLibraryURI"];
XCTAssertNotNil(spawner);
OCMVerify([spawner runWithEntrypoint:@"firstEntrypoint"
libraryURI:@"firstLibraryURI"
initialRoute:nil
entrypointArgs:nil]);
FlutterEngine* spawnee = [group makeEngineWithEntrypoint:@"secondEntrypoint"
libraryURI:@"secondLibraryURI"];
XCTAssertNotNil(spawnee);
OCMVerify([spawner spawnWithEntrypoint:@"secondEntrypoint"
libraryURI:@"secondLibraryURI"
initialRoute:nil
entrypointArgs:nil]);
}
- (void)testCustomInitialRoute {
FlutterEngineGroup* group = OCMPartialMock([[FlutterEngineGroup alloc] initWithName:@"foo"
project:nil]);
FlutterEngine* mockEngine = OCMClassMock([FlutterEngine class]);
OCMStub([group makeEngine]).andReturn(mockEngine);
OCMStub([mockEngine spawnWithEntrypoint:[OCMArg any]
libraryURI:[OCMArg any]
initialRoute:[OCMArg any]
entrypointArgs:[OCMArg any]])
.andReturn(OCMClassMock([FlutterEngine class]));
FlutterEngine* spawner = [group makeEngineWithEntrypoint:nil libraryURI:nil initialRoute:@"foo"];
XCTAssertNotNil(spawner);
OCMVerify([spawner runWithEntrypoint:nil libraryURI:nil initialRoute:@"foo" entrypointArgs:nil]);
FlutterEngine* spawnee = [group makeEngineWithEntrypoint:nil libraryURI:nil initialRoute:@"bar"];
XCTAssertNotNil(spawnee);
OCMVerify([spawner spawnWithEntrypoint:nil
libraryURI:nil
initialRoute:@"bar"
entrypointArgs:nil]);
}
- (void)testCustomEntrypointArgs {
FlutterEngineGroup* group = OCMPartialMock([[FlutterEngineGroup alloc] initWithName:@"foo"
project:nil]);
FlutterEngine* mockEngine = OCMClassMock([FlutterEngine class]);
OCMStub([group makeEngine]).andReturn(mockEngine);
OCMStub([mockEngine spawnWithEntrypoint:[OCMArg any]
libraryURI:[OCMArg any]
initialRoute:[OCMArg any]
entrypointArgs:[OCMArg any]])
.andReturn(OCMClassMock([FlutterEngine class]));
FlutterEngineGroupOptions* firstOptions = [[FlutterEngineGroupOptions alloc] init];
NSArray* firstEntrypointArgs = @[ @"foo", @"first" ];
firstOptions.entrypointArgs = firstEntrypointArgs;
FlutterEngine* spawner = [group makeEngineWithOptions:firstOptions];
XCTAssertNotNil(spawner);
OCMVerify([spawner runWithEntrypoint:nil
libraryURI:nil
initialRoute:nil
entrypointArgs:firstEntrypointArgs]);
NSArray* secondEntrypointArgs = @[ @"bar", @"second" ];
FlutterEngineGroupOptions* secondOptions = [[FlutterEngineGroupOptions alloc] init];
secondOptions.entrypointArgs = secondEntrypointArgs;
FlutterEngine* spawnee = [group makeEngineWithOptions:secondOptions];
XCTAssertNotNil(spawnee);
OCMVerify([spawner spawnWithEntrypoint:nil
libraryURI:nil
initialRoute:nil
entrypointArgs:secondEntrypointArgs]);
}
- (void)testReleasesProjectOnDealloc {
__weak FlutterDartProject* weakProject;
@autoreleasepool {

View File

@ -194,7 +194,10 @@ FLUTTER_ASSERT_ARC
- (void)testSpawn {
FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
[engine run];
FlutterEngine* spawn = [engine spawnWithEntrypoint:nil libraryURI:nil initialRoute:nil];
FlutterEngine* spawn = [engine spawnWithEntrypoint:nil
libraryURI:nil
initialRoute:nil
entrypointArgs:nil];
XCTAssertNotNil(spawn);
}

View File

@ -38,7 +38,10 @@ FLUTTER_ASSERT_NOT_ARC
- (void)testSpawnsShareGpuContext {
FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
[engine run];
FlutterEngine* spawn = [engine spawnWithEntrypoint:nil libraryURI:nil initialRoute:nil];
FlutterEngine* spawn = [engine spawnWithEntrypoint:nil
libraryURI:nil
initialRoute:nil
entrypointArgs:nil];
XCTAssertNotNil(spawn);
XCTAssertTrue([engine iosPlatformView] != nullptr);
XCTAssertTrue([spawn iosPlatformView] != nullptr);
@ -50,7 +53,6 @@ FLUTTER_ASSERT_NOT_ARC
XCTAssertTrue(engine_context->GetMainContext() != nullptr);
XCTAssertEqual(engine_context->GetMainContext(), spawn_context->GetMainContext());
[engine release];
[spawn release];
}
- (void)testEnableSemanticsWhenFlutterViewAccessibilityDidCall {

View File

@ -50,7 +50,9 @@ extern NSString* _Nonnull const FlutterEngineWillDealloc;
- (std::shared_ptr<flutter::FlutterPlatformViewsController>&)platformViewsController;
- (nonnull FlutterTextInputPlugin*)textInputPlugin;
- (nonnull FlutterRestorationPlugin*)restorationPlugin;
- (void)launchEngine:(nullable NSString*)entrypoint libraryURI:(nullable NSString*)libraryOrNil;
- (void)launchEngine:(nullable NSString*)entrypoint
libraryURI:(nullable NSString*)libraryOrNil
entrypointArgs:(nullable NSArray<NSString*>*)entrypointArgs;
- (BOOL)createShell:(nullable NSString*)entrypoint
libraryURI:(nullable NSString*)libraryOrNil
initialRoute:(nullable NSString*)initialRoute;
@ -69,7 +71,8 @@ extern NSString* _Nonnull const FlutterEngineWillDealloc;
*/
- (nonnull FlutterEngine*)spawnWithEntrypoint:(nullable NSString*)entrypoint
libraryURI:(nullable NSString*)libraryURI
initialRoute:(nullable NSString*)initialRoute;
initialRoute:(nullable NSString*)initialRoute
entrypointArgs:(nullable NSArray<NSString*>*)entrypointArgs;
/**
* Dispatches the given key event data to the framework through the engine.

View File

@ -20,6 +20,7 @@ class ThreadHost;
- (void)waitForFirstFrame:(NSTimeInterval)timeout callback:(void (^)(BOOL didTimeout))callback;
- (FlutterEngine*)spawnWithEntrypoint:(/*nullable*/ NSString*)entrypoint
libraryURI:(/*nullable*/ NSString*)libraryURI
initialRoute:(/*nullable*/ NSString*)initialRoute;
initialRoute:(/*nullable*/ NSString*)initialRoute
entrypointArgs:(/*nullable*/ NSArray<NSString*>*)entrypointArgs;
- (const flutter::ThreadHost&)threadHost;
@end

View File

@ -648,7 +648,7 @@ static void SendFakeTouchEvent(FlutterEngine* engine,
// Register internal plugins before starting the engine.
[self addInternalPlugins];
[_engine.get() launchEngine:nil libraryURI:nil];
[_engine.get() launchEngine:nil libraryURI:nil entrypointArgs:nil];
[_engine.get() setViewController:self];
_engineNeedsLaunch = NO;
}

View File

@ -135,6 +135,7 @@ std::unique_ptr<AutoIsolateShutdown> RunDartCodeInIsolateOnUITaskRunner(
settings.isolate_shutdown_callback, // isolate shutdown callback
entrypoint, // entrypoint
std::nullopt, // library
{}, // args
std::move(isolate_configuration), // isolate configuration
context // engine context
)

View File

@ -91,7 +91,7 @@
FlutterEngine* spawner = [[FlutterEngine alloc] initWithName:@"FlutterControllerTest"
project:nil];
[spawner run];
return [spawner spawnWithEntrypoint:nil libraryURI:nil initialRoute:nil];
return [spawner spawnWithEntrypoint:nil libraryURI:nil initialRoute:nil entrypointArgs:nil];
} else {
FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"FlutterControllerTest"
project:nil];

View File

@ -10,6 +10,7 @@ NS_ASSUME_NONNULL_BEGIN
withCompletion:(nullable void (^)(void))engineRunCompletion;
- (FlutterEngine*)spawnWithEntrypoint:(nullable NSString*)entrypoint
libraryURI:(nullable NSString*)libraryURI
initialRoute:(nullable NSString*)initialRoute;
initialRoute:(nullable NSString*)initialRoute
entrypointArgs:(nullable NSArray<NSString*>*)entrypointArgs;
@end
NS_ASSUME_NONNULL_END

View File

@ -41,6 +41,7 @@ TEST_F(DartState, IsShuttingDown) {
settings.isolate_shutdown_callback, // isolate shutdown callback
"main", // dart entrypoint
std::nullopt, // dart entrypoint library
{}, // dart entrypoint arguments
std::move(isolate_configuration), // isolate configuration
std::move(context) // engine context
);

View File

@ -60,6 +60,7 @@ FlutterCallbackInformation.html
FlutterDartProject.html
FlutterEngine.html
FlutterEngineGroup.html
FlutterEngineGroupOptions.html
FlutterError.html
FlutterEventChannel.html
FlutterHeadlessDartRunner.html