mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Make FlutterEngineGroup support dart entrypoint args (flutter/engine#29096)
This commit is contained in:
parent
5183c56533
commit
9ecd4c1899
@ -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
|
||||
|
||||
@ -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) {
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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 {};
|
||||
}
|
||||
|
||||
@ -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|
|
||||
|
||||
@ -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
|
||||
);
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
@ -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()) //
|
||||
|
||||
@ -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);
|
||||
|
||||
//----------------------------------------------------------------------------
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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_;
|
||||
|
||||
@ -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');
|
||||
}
|
||||
|
||||
@ -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_);
|
||||
|
||||
@ -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);
|
||||
};
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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();
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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();
|
||||
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -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 ------
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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),
|
||||
},
|
||||
{
|
||||
|
||||
@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
@ -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.
|
||||
*
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
|
||||
@ -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,
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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);
|
||||
}
|
||||
|
||||
|
||||
@ -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 {
|
||||
|
||||
@ -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.
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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;
|
||||
}
|
||||
|
||||
@ -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
|
||||
)
|
||||
|
||||
@ -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];
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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
|
||||
);
|
||||
|
||||
@ -60,6 +60,7 @@ FlutterCallbackInformation.html
|
||||
FlutterDartProject.html
|
||||
FlutterEngine.html
|
||||
FlutterEngineGroup.html
|
||||
FlutterEngineGroupOptions.html
|
||||
FlutterError.html
|
||||
FlutterEventChannel.html
|
||||
FlutterHeadlessDartRunner.html
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user