Add PlatformDispatcher.engineId (#163476)
Fixes https://github.com/flutter/flutter/issues/163430. This PR adds `engineId` field to `PlatformDispatcher`. When provided by the engine, this can be used to retrieve the engine instance from native code. Dart code: ```dart final identifier = PlatformDispatcher.instance.engineId!; ``` macOS, iOS: ```objc FlutterEngine *engine = [FlutterEngine engineForIdentifier: identifier]; ``` Android: ```java FlutterEngine engine = FlutterEngine.engineForId(identifier); ``` Linux ```cpp FlEngine *engine = fl_engine_for_id(identifier); ``` Windows ```cpp FlutterDesktopEngineRef engine = FlutterDesktopEngineForId(identifier); ``` *If you had to change anything in the [flutter/tests] repo, include a link to the migration guide as per the [breaking change policy].* ## Pre-launch Checklist - [X] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [X] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [X] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [X] I signed the [CLA]. - [X] I listed at least one issue that this PR fixes in the description above. - [X] I updated/added relevant documentation (doc comments with `///`). - [X] I added new tests to check the change I am making, or this PR is [test-exempt]. - [X] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [X] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- Links --> [Contributor Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview [Tree Hygiene]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md [test-exempt]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests [Flutter Style Guide]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md [Features we expect every widget to implement]: https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement [CLA]: https://cla.developers.google.com/ [flutter/tests]: https://github.com/flutter/tests [breaking change policy]: https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes [Discord]: https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md [Data Driven Fixes]: https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
This commit is contained in:
parent
600ee30696
commit
b831b269c5
@ -0,0 +1,23 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:android_driver_extensions/extension.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/material.dart';
|
||||||
|
import 'package:flutter_driver/driver_extension.dart';
|
||||||
|
|
||||||
|
import 'src/allow_list_devices.dart';
|
||||||
|
|
||||||
|
void main() {
|
||||||
|
ensureAndroidDevice();
|
||||||
|
enableFlutterDriverExtension(
|
||||||
|
handler: (String? command) async {
|
||||||
|
return json.encode(<String, Object?>{'engineId': PlatformDispatcher.instance.engineId});
|
||||||
|
},
|
||||||
|
commands: <CommandExtension>[nativeDriverCommands],
|
||||||
|
);
|
||||||
|
runApp(const SizedBox());
|
||||||
|
}
|
@ -0,0 +1,37 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'dart:convert';
|
||||||
|
|
||||||
|
import 'package:android_driver_extensions/native_driver.dart';
|
||||||
|
import 'package:flutter_driver/flutter_driver.dart';
|
||||||
|
import 'package:test/test.dart';
|
||||||
|
|
||||||
|
late final FlutterDriver flutterDriver;
|
||||||
|
late final NativeDriver nativeDriver;
|
||||||
|
|
||||||
|
void main() async {
|
||||||
|
setUpAll(() async {
|
||||||
|
flutterDriver = await FlutterDriver.connect();
|
||||||
|
nativeDriver = await AndroidNativeDriver.connect(flutterDriver);
|
||||||
|
});
|
||||||
|
|
||||||
|
tearDownAll(() async {
|
||||||
|
await nativeDriver.close();
|
||||||
|
await flutterDriver.close();
|
||||||
|
});
|
||||||
|
|
||||||
|
// TODO(matanlurey): Convert to use package:integration_test
|
||||||
|
test('verify that engineId is set and works', () async {
|
||||||
|
final Map<String, Object?> response =
|
||||||
|
json.decode(await flutterDriver.requestData('')) as Map<String, Object?>;
|
||||||
|
expect(
|
||||||
|
response['engineId'],
|
||||||
|
1,
|
||||||
|
// Valid engine ids start at 1 to make detecting uninitialized
|
||||||
|
// values easier.
|
||||||
|
reason: 'engineId of first engine instance should be 1',
|
||||||
|
);
|
||||||
|
}, timeout: Timeout.none);
|
||||||
|
}
|
@ -68,6 +68,11 @@ void _sendViewFocusEvent(int viewId, int viewFocusState, int viewFocusDirection)
|
|||||||
PlatformDispatcher.instance._sendViewFocusEvent(viewFocusEvent);
|
PlatformDispatcher.instance._sendViewFocusEvent(viewFocusEvent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@pragma('vm:entry-point')
|
||||||
|
void _setEngineId(int engineId) {
|
||||||
|
PlatformDispatcher.instance._engineId = engineId;
|
||||||
|
}
|
||||||
|
|
||||||
@pragma('vm:entry-point')
|
@pragma('vm:entry-point')
|
||||||
void _updateDisplays(
|
void _updateDisplays(
|
||||||
List<int> ids,
|
List<int> ids,
|
||||||
|
@ -305,6 +305,13 @@ class PlatformDispatcher {
|
|||||||
_invoke1<ViewFocusEvent>(onViewFocusChange, _onViewFocusChangeZone, event);
|
_invoke1<ViewFocusEvent>(onViewFocusChange, _onViewFocusChangeZone, event);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Opaque engine identifier for the engine running current isolate. Can be used
|
||||||
|
/// in native code to retrieve the engine instance.
|
||||||
|
/// The identifier is valid while the isolate is running.
|
||||||
|
int? get engineId => _engineId;
|
||||||
|
|
||||||
|
int? _engineId;
|
||||||
|
|
||||||
// Called from the engine, via hooks.dart.
|
// Called from the engine, via hooks.dart.
|
||||||
//
|
//
|
||||||
// Updates the available displays.
|
// Updates the available displays.
|
||||||
|
@ -49,6 +49,8 @@ void PlatformConfiguration::DidCreateIsolate() {
|
|||||||
send_view_focus_event_.Set(
|
send_view_focus_event_.Set(
|
||||||
tonic::DartState::Current(),
|
tonic::DartState::Current(),
|
||||||
Dart_GetField(library, tonic::ToDart("_sendViewFocusEvent")));
|
Dart_GetField(library, tonic::ToDart("_sendViewFocusEvent")));
|
||||||
|
set_engine_id_.Set(tonic::DartState::Current(),
|
||||||
|
Dart_GetField(library, tonic::ToDart("_setEngineId")));
|
||||||
update_window_metrics_.Set(
|
update_window_metrics_.Set(
|
||||||
tonic::DartState::Current(),
|
tonic::DartState::Current(),
|
||||||
Dart_GetField(library, tonic::ToDart("_updateWindowMetrics")));
|
Dart_GetField(library, tonic::ToDart("_updateWindowMetrics")));
|
||||||
@ -168,6 +170,20 @@ bool PlatformConfiguration::SendFocusEvent(const ViewFocusEvent& event) {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool PlatformConfiguration::SetEngineId(int64_t engine_id) {
|
||||||
|
std::shared_ptr<tonic::DartState> dart_state =
|
||||||
|
set_engine_id_.dart_state().lock();
|
||||||
|
if (!dart_state) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
tonic::DartState::Scope scope(dart_state);
|
||||||
|
tonic::CheckAndHandleError(
|
||||||
|
tonic::DartInvoke(set_engine_id_.Get(), {
|
||||||
|
tonic::ToDart(engine_id),
|
||||||
|
}));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
bool PlatformConfiguration::UpdateViewMetrics(
|
bool PlatformConfiguration::UpdateViewMetrics(
|
||||||
int64_t view_id,
|
int64_t view_id,
|
||||||
const ViewportMetrics& view_metrics) {
|
const ViewportMetrics& view_metrics) {
|
||||||
|
@ -356,6 +356,14 @@ class PlatformConfiguration final {
|
|||||||
/// @return Whether the focus event was sent.
|
/// @return Whether the focus event was sent.
|
||||||
bool SendFocusEvent(const ViewFocusEvent& event);
|
bool SendFocusEvent(const ViewFocusEvent& event);
|
||||||
|
|
||||||
|
/// @brief Sets the opaque identifier of the engine.
|
||||||
|
///
|
||||||
|
/// The identifier can be passed from Dart to native code to
|
||||||
|
/// retrieve the engine instance.
|
||||||
|
///
|
||||||
|
/// @return Whether the identifier was set.
|
||||||
|
bool SetEngineId(int64_t engine_id);
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
/// @brief Update the view metrics for the specified view.
|
/// @brief Update the view metrics for the specified view.
|
||||||
///
|
///
|
||||||
@ -548,6 +556,7 @@ class PlatformConfiguration final {
|
|||||||
tonic::DartPersistentValue add_view_;
|
tonic::DartPersistentValue add_view_;
|
||||||
tonic::DartPersistentValue remove_view_;
|
tonic::DartPersistentValue remove_view_;
|
||||||
tonic::DartPersistentValue send_view_focus_event_;
|
tonic::DartPersistentValue send_view_focus_event_;
|
||||||
|
tonic::DartPersistentValue set_engine_id_;
|
||||||
tonic::DartPersistentValue update_window_metrics_;
|
tonic::DartPersistentValue update_window_metrics_;
|
||||||
tonic::DartPersistentValue update_displays_;
|
tonic::DartPersistentValue update_displays_;
|
||||||
tonic::DartPersistentValue update_locales_;
|
tonic::DartPersistentValue update_locales_;
|
||||||
|
@ -37,6 +37,8 @@ abstract class PlatformDispatcher {
|
|||||||
|
|
||||||
FlutterView? get implicitView;
|
FlutterView? get implicitView;
|
||||||
|
|
||||||
|
int? get engineId;
|
||||||
|
|
||||||
VoidCallback? get onMetricsChanged;
|
VoidCallback? get onMetricsChanged;
|
||||||
set onMetricsChanged(VoidCallback? callback);
|
set onMetricsChanged(VoidCallback? callback);
|
||||||
|
|
||||||
|
@ -140,6 +140,9 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
|
|||||||
@override
|
@override
|
||||||
EngineFlutterWindow? get implicitView => viewManager[kImplicitViewId] as EngineFlutterWindow?;
|
EngineFlutterWindow? get implicitView => viewManager[kImplicitViewId] as EngineFlutterWindow?;
|
||||||
|
|
||||||
|
@override
|
||||||
|
int? get engineId => null;
|
||||||
|
|
||||||
/// A callback that is invoked whenever the platform's [devicePixelRatio],
|
/// A callback that is invoked whenever the platform's [devicePixelRatio],
|
||||||
/// [physicalSize], [padding], [viewInsets], or [systemGestureInsets]
|
/// [physicalSize], [padding], [viewInsets], or [systemGestureInsets]
|
||||||
/// values change, for example when the device is rotated or when the
|
/// values change, for example when the device is rotated or when the
|
||||||
|
@ -537,7 +537,8 @@ bool RuntimeController::LaunchRootIsolate(
|
|||||||
std::optional<std::string> dart_entrypoint_library,
|
std::optional<std::string> dart_entrypoint_library,
|
||||||
const std::vector<std::string>& dart_entrypoint_args,
|
const std::vector<std::string>& dart_entrypoint_args,
|
||||||
std::unique_ptr<IsolateConfiguration> isolate_configuration,
|
std::unique_ptr<IsolateConfiguration> isolate_configuration,
|
||||||
std::shared_ptr<NativeAssetsManager> native_assets_manager) {
|
std::shared_ptr<NativeAssetsManager> native_assets_manager,
|
||||||
|
std::optional<int64_t> engine_id) {
|
||||||
if (root_isolate_.lock()) {
|
if (root_isolate_.lock()) {
|
||||||
FML_LOG(ERROR) << "Root isolate was already running.";
|
FML_LOG(ERROR) << "Root isolate was already running.";
|
||||||
return false;
|
return false;
|
||||||
@ -586,6 +587,11 @@ bool RuntimeController::LaunchRootIsolate(
|
|||||||
if (!FlushRuntimeStateToIsolate()) {
|
if (!FlushRuntimeStateToIsolate()) {
|
||||||
FML_DLOG(ERROR) << "Could not set up initial isolate state.";
|
FML_DLOG(ERROR) << "Could not set up initial isolate state.";
|
||||||
}
|
}
|
||||||
|
if (engine_id) {
|
||||||
|
if (!platform_configuration->SetEngineId(*engine_id)) {
|
||||||
|
FML_DLOG(ERROR) << "Could not set engine identifier.";
|
||||||
|
}
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
FML_DCHECK(false) << "RuntimeController created without window binding.";
|
FML_DCHECK(false) << "RuntimeController created without window binding.";
|
||||||
}
|
}
|
||||||
|
@ -156,6 +156,8 @@ class RuntimeController : public PlatformConfigurationClient,
|
|||||||
/// @param[in] dart_entrypoint_args Arguments passed as a List<String>
|
/// @param[in] dart_entrypoint_args Arguments passed as a List<String>
|
||||||
/// to Dart's entrypoint function.
|
/// to Dart's entrypoint function.
|
||||||
/// @param[in] isolate_configuration The isolate configuration
|
/// @param[in] isolate_configuration The isolate configuration
|
||||||
|
/// @param[in] engine_id. Engine identifier to be passed to the
|
||||||
|
/// platform dispatcher.
|
||||||
///
|
///
|
||||||
/// @return If the isolate could be launched and guided to the
|
/// @return If the isolate could be launched and guided to the
|
||||||
/// `DartIsolate::Phase::Running` phase.
|
/// `DartIsolate::Phase::Running` phase.
|
||||||
@ -167,7 +169,8 @@ class RuntimeController : public PlatformConfigurationClient,
|
|||||||
std::optional<std::string> dart_entrypoint_library,
|
std::optional<std::string> dart_entrypoint_library,
|
||||||
const std::vector<std::string>& dart_entrypoint_args,
|
const std::vector<std::string>& dart_entrypoint_args,
|
||||||
std::unique_ptr<IsolateConfiguration> isolate_configuration,
|
std::unique_ptr<IsolateConfiguration> isolate_configuration,
|
||||||
std::shared_ptr<NativeAssetsManager> native_assets_manager);
|
std::shared_ptr<NativeAssetsManager> native_assets_manager,
|
||||||
|
std::optional<int64_t> engine_id);
|
||||||
|
|
||||||
//----------------------------------------------------------------------------
|
//----------------------------------------------------------------------------
|
||||||
/// @brief Clone the runtime controller. Launching an isolate with a
|
/// @brief Clone the runtime controller. Launching an isolate with a
|
||||||
|
@ -245,8 +245,9 @@ Engine::RunStatus Engine::Run(RunConfiguration configuration) {
|
|||||||
configuration.GetEntrypointLibrary(), //
|
configuration.GetEntrypointLibrary(), //
|
||||||
configuration.GetEntrypointArgs(), //
|
configuration.GetEntrypointArgs(), //
|
||||||
configuration.TakeIsolateConfiguration(), //
|
configuration.TakeIsolateConfiguration(), //
|
||||||
native_assets_manager_) //
|
native_assets_manager_, //
|
||||||
) {
|
configuration.GetEngineId())) //
|
||||||
|
{
|
||||||
return RunStatus::Failure;
|
return RunStatus::Failure;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -640,3 +640,11 @@ void testSendViewFocusEvent() {
|
|||||||
};
|
};
|
||||||
notifyNative();
|
notifyNative();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@pragma('vm:external-name', 'ReportEngineId')
|
||||||
|
external void _reportEngineId(int? identifier);
|
||||||
|
|
||||||
|
@pragma('vm:entry-point')
|
||||||
|
void providesEngineId() {
|
||||||
|
_reportEngineId(PlatformDispatcher.instance.engineId);
|
||||||
|
}
|
||||||
|
@ -105,6 +105,15 @@ const std::vector<std::string>& RunConfiguration::GetEntrypointArgs() const {
|
|||||||
return entrypoint_args_;
|
return entrypoint_args_;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void RunConfiguration::SetEngineId(int64_t engine_id) {
|
||||||
|
engine_id_ = engine_id;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Engine identifier to be passed to the platform dispatcher.
|
||||||
|
std::optional<int64_t> RunConfiguration::GetEngineId() const {
|
||||||
|
return engine_id_;
|
||||||
|
}
|
||||||
|
|
||||||
std::unique_ptr<IsolateConfiguration>
|
std::unique_ptr<IsolateConfiguration>
|
||||||
RunConfiguration::TakeIsolateConfiguration() {
|
RunConfiguration::TakeIsolateConfiguration() {
|
||||||
return std::move(isolate_configuration_);
|
return std::move(isolate_configuration_);
|
||||||
|
@ -194,12 +194,22 @@ class RunConfiguration {
|
|||||||
///
|
///
|
||||||
std::unique_ptr<IsolateConfiguration> TakeIsolateConfiguration();
|
std::unique_ptr<IsolateConfiguration> TakeIsolateConfiguration();
|
||||||
|
|
||||||
|
//----------------------------------------------------------------------------
|
||||||
|
/// @brief Sets the engine identifier to be passed to the platform
|
||||||
|
/// dispatcher.
|
||||||
|
void SetEngineId(int64_t engine_id);
|
||||||
|
|
||||||
|
///----------------------------------------------------------------------------
|
||||||
|
/// @return Engine identifier to be passed to the platform dispatcher.
|
||||||
|
std::optional<int64_t> GetEngineId() const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
std::unique_ptr<IsolateConfiguration> isolate_configuration_;
|
std::unique_ptr<IsolateConfiguration> isolate_configuration_;
|
||||||
std::shared_ptr<AssetManager> asset_manager_;
|
std::shared_ptr<AssetManager> asset_manager_;
|
||||||
std::string entrypoint_ = "main";
|
std::string entrypoint_ = "main";
|
||||||
std::string entrypoint_library_ = "";
|
std::string entrypoint_library_ = "";
|
||||||
std::vector<std::string> entrypoint_args_;
|
std::vector<std::string> entrypoint_args_;
|
||||||
|
std::optional<int64_t> engine_id_;
|
||||||
|
|
||||||
FML_DISALLOW_COPY_AND_ASSIGN(RunConfiguration);
|
FML_DISALLOW_COPY_AND_ASSIGN(RunConfiguration);
|
||||||
};
|
};
|
||||||
|
@ -71,6 +71,7 @@ class ShellTest : public FixtureTest {
|
|||||||
// Defaults to calling ShellTestPlatformView::Create with the provided
|
// Defaults to calling ShellTestPlatformView::Create with the provided
|
||||||
// arguments.
|
// arguments.
|
||||||
Shell::CreateCallback<PlatformView> platform_view_create_callback;
|
Shell::CreateCallback<PlatformView> platform_view_create_callback;
|
||||||
|
std::optional<int64_t> engine_id;
|
||||||
};
|
};
|
||||||
|
|
||||||
ShellTest();
|
ShellTest();
|
||||||
|
@ -5003,7 +5003,69 @@ TEST_F(ShellTest, SendViewFocusEvent) {
|
|||||||
latch.Wait();
|
latch.Wait();
|
||||||
ASSERT_EQ(last_event,
|
ASSERT_EQ(last_event,
|
||||||
"2 ViewFocusState.unfocused ViewFocusDirection.backward");
|
"2 ViewFocusState.unfocused ViewFocusDirection.backward");
|
||||||
|
DestroyShell(std::move(shell), task_runners);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ShellTest, ProvidesEngineId) {
|
||||||
|
Settings settings = CreateSettingsForFixture();
|
||||||
|
TaskRunners task_runners = GetTaskRunnersForFixture();
|
||||||
|
fml::AutoResetWaitableEvent latch;
|
||||||
|
|
||||||
|
std::optional<int> reported_handle = std::nullopt;
|
||||||
|
|
||||||
|
AddNativeCallback(
|
||||||
|
"ReportEngineId", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
|
||||||
|
Dart_Handle arg = Dart_GetNativeArgument(args, 0);
|
||||||
|
if (Dart_IsNull(arg)) {
|
||||||
|
reported_handle = std::nullopt;
|
||||||
|
} else {
|
||||||
|
reported_handle = tonic::DartConverter<int64_t>::FromDart(arg);
|
||||||
|
}
|
||||||
|
latch.Signal();
|
||||||
|
}));
|
||||||
|
fml::AutoResetWaitableEvent check_latch;
|
||||||
|
|
||||||
|
std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
|
||||||
|
ASSERT_TRUE(shell->IsSetup());
|
||||||
|
|
||||||
|
auto configuration = RunConfiguration::InferFromSettings(settings);
|
||||||
|
configuration.SetEngineId(99);
|
||||||
|
configuration.SetEntrypoint("providesEngineId");
|
||||||
|
RunEngine(shell.get(), std::move(configuration));
|
||||||
|
|
||||||
|
latch.Wait();
|
||||||
|
ASSERT_EQ(reported_handle, 99);
|
||||||
|
DestroyShell(std::move(shell), task_runners);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST_F(ShellTest, ProvidesNullEngineId) {
|
||||||
|
Settings settings = CreateSettingsForFixture();
|
||||||
|
TaskRunners task_runners = GetTaskRunnersForFixture();
|
||||||
|
fml::AutoResetWaitableEvent latch;
|
||||||
|
|
||||||
|
std::optional<int> reported_handle = std::nullopt;
|
||||||
|
|
||||||
|
AddNativeCallback(
|
||||||
|
"ReportEngineId", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
|
||||||
|
Dart_Handle arg = Dart_GetNativeArgument(args, 0);
|
||||||
|
if (Dart_IsNull(arg)) {
|
||||||
|
reported_handle = std::nullopt;
|
||||||
|
} else {
|
||||||
|
reported_handle = tonic::DartConverter<int64_t>::FromDart(arg);
|
||||||
|
}
|
||||||
|
latch.Signal();
|
||||||
|
}));
|
||||||
|
fml::AutoResetWaitableEvent check_latch;
|
||||||
|
|
||||||
|
std::unique_ptr<Shell> shell = CreateShell(settings, task_runners);
|
||||||
|
ASSERT_TRUE(shell->IsSetup());
|
||||||
|
|
||||||
|
auto configuration = RunConfiguration::InferFromSettings(settings);
|
||||||
|
configuration.SetEntrypoint("providesEngineId");
|
||||||
|
RunEngine(shell.get(), std::move(configuration));
|
||||||
|
|
||||||
|
latch.Wait();
|
||||||
|
ASSERT_EQ(reported_handle, std::nullopt);
|
||||||
DestroyShell(std::move(shell), task_runners);
|
DestroyShell(std::move(shell), task_runners);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -222,7 +222,8 @@ std::unique_ptr<AndroidShellHolder> AndroidShellHolder::Spawn(
|
|||||||
const std::string& entrypoint,
|
const std::string& entrypoint,
|
||||||
const std::string& libraryUrl,
|
const std::string& libraryUrl,
|
||||||
const std::string& initial_route,
|
const std::string& initial_route,
|
||||||
const std::vector<std::string>& entrypoint_args) const {
|
const std::vector<std::string>& entrypoint_args,
|
||||||
|
int64_t engine_id) const {
|
||||||
FML_DCHECK(shell_ && shell_->IsSetup())
|
FML_DCHECK(shell_ && shell_->IsSetup())
|
||||||
<< "A new Shell can only be spawned "
|
<< "A new Shell can only be spawned "
|
||||||
"if the current Shell is properly constructed";
|
"if the current Shell is properly constructed";
|
||||||
@ -269,6 +270,7 @@ std::unique_ptr<AndroidShellHolder> AndroidShellHolder::Spawn(
|
|||||||
// Fail the whole thing.
|
// Fail the whole thing.
|
||||||
return nullptr;
|
return nullptr;
|
||||||
}
|
}
|
||||||
|
config->SetEngineId(engine_id);
|
||||||
|
|
||||||
std::unique_ptr<flutter::Shell> shell =
|
std::unique_ptr<flutter::Shell> shell =
|
||||||
shell_->Spawn(std::move(config.value()), initial_route,
|
shell_->Spawn(std::move(config.value()), initial_route,
|
||||||
@ -284,7 +286,8 @@ void AndroidShellHolder::Launch(
|
|||||||
std::unique_ptr<APKAssetProvider> apk_asset_provider,
|
std::unique_ptr<APKAssetProvider> apk_asset_provider,
|
||||||
const std::string& entrypoint,
|
const std::string& entrypoint,
|
||||||
const std::string& libraryUrl,
|
const std::string& libraryUrl,
|
||||||
const std::vector<std::string>& entrypoint_args) {
|
const std::vector<std::string>& entrypoint_args,
|
||||||
|
int64_t engine_id) {
|
||||||
if (!IsValid()) {
|
if (!IsValid()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -294,6 +297,7 @@ void AndroidShellHolder::Launch(
|
|||||||
if (!config) {
|
if (!config) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
config->SetEngineId(engine_id);
|
||||||
UpdateDisplayMetrics();
|
UpdateDisplayMetrics();
|
||||||
shell_->RunEngine(std::move(config.value()));
|
shell_->RunEngine(std::move(config.value()));
|
||||||
}
|
}
|
||||||
|
@ -79,12 +79,14 @@ class AndroidShellHolder {
|
|||||||
const std::string& entrypoint,
|
const std::string& entrypoint,
|
||||||
const std::string& libraryUrl,
|
const std::string& libraryUrl,
|
||||||
const std::string& initial_route,
|
const std::string& initial_route,
|
||||||
const std::vector<std::string>& entrypoint_args) const;
|
const std::vector<std::string>& entrypoint_args,
|
||||||
|
int64_t engine_id) const;
|
||||||
|
|
||||||
void Launch(std::unique_ptr<APKAssetProvider> apk_asset_provider,
|
void Launch(std::unique_ptr<APKAssetProvider> apk_asset_provider,
|
||||||
const std::string& entrypoint,
|
const std::string& entrypoint,
|
||||||
const std::string& libraryUrl,
|
const std::string& libraryUrl,
|
||||||
const std::vector<std::string>& entrypoint_args);
|
const std::vector<std::string>& entrypoint_args,
|
||||||
|
int64_t engine_id);
|
||||||
|
|
||||||
const flutter::Settings& GetSettings() const;
|
const flutter::Settings& GetSettings() const;
|
||||||
|
|
||||||
|
@ -44,8 +44,10 @@ import io.flutter.plugin.platform.PlatformViewsController;
|
|||||||
import io.flutter.plugin.platform.PlatformViewsController2;
|
import io.flutter.plugin.platform.PlatformViewsController2;
|
||||||
import io.flutter.plugin.text.ProcessTextPlugin;
|
import io.flutter.plugin.text.ProcessTextPlugin;
|
||||||
import io.flutter.util.ViewUtils;
|
import io.flutter.util.ViewUtils;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -115,6 +117,20 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
|
|||||||
// Engine Lifecycle.
|
// Engine Lifecycle.
|
||||||
@NonNull private final Set<EngineLifecycleListener> engineLifecycleListeners = new HashSet<>();
|
@NonNull private final Set<EngineLifecycleListener> engineLifecycleListeners = new HashSet<>();
|
||||||
|
|
||||||
|
// Unique handle for this engine.
|
||||||
|
@NonNull private final long engineId;
|
||||||
|
|
||||||
|
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||||
|
public static void resetNextEngineId() {
|
||||||
|
nextEngineId = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle to assign to the next engine created.
|
||||||
|
private static long nextEngineId = 1;
|
||||||
|
|
||||||
|
// Map of engine identifiers to engines.
|
||||||
|
private static final Map<Long, FlutterEngine> idToEngine = new HashMap<>();
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
private final EngineLifecycleListener engineLifecycleListener =
|
private final EngineLifecycleListener engineLifecycleListener =
|
||||||
new EngineLifecycleListener() {
|
new EngineLifecycleListener() {
|
||||||
@ -312,6 +328,10 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
|
|||||||
boolean automaticallyRegisterPlugins,
|
boolean automaticallyRegisterPlugins,
|
||||||
boolean waitForRestorationData,
|
boolean waitForRestorationData,
|
||||||
@Nullable FlutterEngineGroup group) {
|
@Nullable FlutterEngineGroup group) {
|
||||||
|
|
||||||
|
this.engineId = nextEngineId++;
|
||||||
|
idToEngine.put(engineId, this);
|
||||||
|
|
||||||
AssetManager assetManager;
|
AssetManager assetManager;
|
||||||
try {
|
try {
|
||||||
assetManager = context.createPackageContext(context.getPackageName(), 0).getAssets();
|
assetManager = context.createPackageContext(context.getPackageName(), 0).getAssets();
|
||||||
@ -326,7 +346,7 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
|
|||||||
}
|
}
|
||||||
this.flutterJNI = flutterJNI;
|
this.flutterJNI = flutterJNI;
|
||||||
|
|
||||||
this.dartExecutor = new DartExecutor(flutterJNI, assetManager);
|
this.dartExecutor = new DartExecutor(flutterJNI, assetManager, engineId);
|
||||||
this.dartExecutor.onAttachedToJNI();
|
this.dartExecutor.onAttachedToJNI();
|
||||||
|
|
||||||
DeferredComponentManager deferredComponentManager =
|
DeferredComponentManager deferredComponentManager =
|
||||||
@ -451,7 +471,8 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
|
|||||||
dartEntrypoint.dartEntrypointFunctionName,
|
dartEntrypoint.dartEntrypointFunctionName,
|
||||||
dartEntrypoint.dartEntrypointLibrary,
|
dartEntrypoint.dartEntrypointLibrary,
|
||||||
initialRoute,
|
initialRoute,
|
||||||
dartEntrypointArgs);
|
dartEntrypointArgs,
|
||||||
|
nextEngineId);
|
||||||
return new FlutterEngine(
|
return new FlutterEngine(
|
||||||
context, // Context.
|
context, // Context.
|
||||||
null, // FlutterLoader. A null value passed here causes the constructor to get it from the
|
null, // FlutterLoader. A null value passed here causes the constructor to get it from the
|
||||||
@ -486,6 +507,7 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
|
|||||||
FlutterInjector.instance().deferredComponentManager().destroy();
|
FlutterInjector.instance().deferredComponentManager().destroy();
|
||||||
deferredComponentChannel.setDeferredComponentManager(null);
|
deferredComponentChannel.setDeferredComponentManager(null);
|
||||||
}
|
}
|
||||||
|
idToEngine.remove(engineId);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -679,6 +701,25 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
|
|||||||
return pluginRegistry;
|
return pluginRegistry;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns unique identifier for this engine. */
|
||||||
|
public long getEngineId() {
|
||||||
|
return engineId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns engine for the given identifier or null if identifier is not valid. The handle can be
|
||||||
|
* obtained through
|
||||||
|
*
|
||||||
|
* <pre>PlatformDispatcher.instance.engineId</pre>
|
||||||
|
*
|
||||||
|
* <p>Must be called on the UI thread.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
@VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
|
||||||
|
public static FlutterEngine engineForId(long handle) {
|
||||||
|
return idToEngine.get(handle);
|
||||||
|
}
|
||||||
|
|
||||||
/** Lifecycle callbacks for Flutter engine lifecycle events. */
|
/** Lifecycle callbacks for Flutter engine lifecycle events. */
|
||||||
public interface EngineLifecycleListener {
|
public interface EngineLifecycleListener {
|
||||||
/** Lifecycle callback invoked before a hot restart of the Flutter engine. */
|
/** Lifecycle callback invoked before a hot restart of the Flutter engine. */
|
||||||
|
@ -460,7 +460,8 @@ public class FlutterJNI {
|
|||||||
@Nullable String entrypointFunctionName,
|
@Nullable String entrypointFunctionName,
|
||||||
@Nullable String pathToEntrypointFunction,
|
@Nullable String pathToEntrypointFunction,
|
||||||
@Nullable String initialRoute,
|
@Nullable String initialRoute,
|
||||||
@Nullable List<String> entrypointArgs) {
|
@Nullable List<String> entrypointArgs,
|
||||||
|
long engineId) {
|
||||||
ensureRunningOnMainThread();
|
ensureRunningOnMainThread();
|
||||||
ensureAttachedToNative();
|
ensureAttachedToNative();
|
||||||
FlutterJNI spawnedJNI =
|
FlutterJNI spawnedJNI =
|
||||||
@ -469,7 +470,8 @@ public class FlutterJNI {
|
|||||||
entrypointFunctionName,
|
entrypointFunctionName,
|
||||||
pathToEntrypointFunction,
|
pathToEntrypointFunction,
|
||||||
initialRoute,
|
initialRoute,
|
||||||
entrypointArgs);
|
entrypointArgs,
|
||||||
|
engineId);
|
||||||
Preconditions.checkState(
|
Preconditions.checkState(
|
||||||
spawnedJNI.nativeShellHolderId != null && spawnedJNI.nativeShellHolderId != 0,
|
spawnedJNI.nativeShellHolderId != null && spawnedJNI.nativeShellHolderId != 0,
|
||||||
"Failed to spawn new JNI connected shell from existing shell.");
|
"Failed to spawn new JNI connected shell from existing shell.");
|
||||||
@ -482,7 +484,8 @@ public class FlutterJNI {
|
|||||||
@Nullable String entrypointFunctionName,
|
@Nullable String entrypointFunctionName,
|
||||||
@Nullable String pathToEntrypointFunction,
|
@Nullable String pathToEntrypointFunction,
|
||||||
@Nullable String initialRoute,
|
@Nullable String initialRoute,
|
||||||
@Nullable List<String> entrypointArgs);
|
@Nullable List<String> entrypointArgs,
|
||||||
|
long engineId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Detaches this {@code FlutterJNI} instance from Flutter's native engine, which precludes any
|
* Detaches this {@code FlutterJNI} instance from Flutter's native engine, which precludes any
|
||||||
@ -491,7 +494,7 @@ public class FlutterJNI {
|
|||||||
* <p>This method must not be invoked if {@code FlutterJNI} is not already attached to native.
|
* <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
|
* <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, List)}, or
|
* up during {@link #attachToNative()} or {@link #spawn(String, String, String, List, long)}, or
|
||||||
* accumulated thereafter.
|
* accumulated thereafter.
|
||||||
*
|
*
|
||||||
* <p>It is permissible to re-attach this instance to native after detaching it from native.
|
* <p>It is permissible to re-attach this instance to native after detaching it from native.
|
||||||
@ -1002,7 +1005,8 @@ public class FlutterJNI {
|
|||||||
@Nullable String entrypointFunctionName,
|
@Nullable String entrypointFunctionName,
|
||||||
@Nullable String pathToEntrypointFunction,
|
@Nullable String pathToEntrypointFunction,
|
||||||
@NonNull AssetManager assetManager,
|
@NonNull AssetManager assetManager,
|
||||||
@Nullable List<String> entrypointArgs) {
|
@Nullable List<String> entrypointArgs,
|
||||||
|
long engineId) {
|
||||||
ensureRunningOnMainThread();
|
ensureRunningOnMainThread();
|
||||||
ensureAttachedToNative();
|
ensureAttachedToNative();
|
||||||
nativeRunBundleAndSnapshotFromLibrary(
|
nativeRunBundleAndSnapshotFromLibrary(
|
||||||
@ -1011,7 +1015,8 @@ public class FlutterJNI {
|
|||||||
entrypointFunctionName,
|
entrypointFunctionName,
|
||||||
pathToEntrypointFunction,
|
pathToEntrypointFunction,
|
||||||
assetManager,
|
assetManager,
|
||||||
entrypointArgs);
|
entrypointArgs,
|
||||||
|
engineId);
|
||||||
}
|
}
|
||||||
|
|
||||||
private native void nativeRunBundleAndSnapshotFromLibrary(
|
private native void nativeRunBundleAndSnapshotFromLibrary(
|
||||||
@ -1020,7 +1025,8 @@ public class FlutterJNI {
|
|||||||
@Nullable String entrypointFunctionName,
|
@Nullable String entrypointFunctionName,
|
||||||
@Nullable String pathToEntrypointFunction,
|
@Nullable String pathToEntrypointFunction,
|
||||||
@NonNull AssetManager manager,
|
@NonNull AssetManager manager,
|
||||||
@Nullable List<String> entrypointArgs);
|
@Nullable List<String> entrypointArgs,
|
||||||
|
long engineId);
|
||||||
// ------ End Dart Execution Support -------
|
// ------ End Dart Execution Support -------
|
||||||
|
|
||||||
// --------- Start Platform Message Support ------
|
// --------- Start Platform Message Support ------
|
||||||
|
@ -8,6 +8,7 @@ import android.content.res.AssetManager;
|
|||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.UiThread;
|
import androidx.annotation.UiThread;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
import io.flutter.FlutterInjector;
|
import io.flutter.FlutterInjector;
|
||||||
import io.flutter.Log;
|
import io.flutter.Log;
|
||||||
import io.flutter.embedding.engine.FlutterJNI;
|
import io.flutter.embedding.engine.FlutterJNI;
|
||||||
@ -40,6 +41,7 @@ public class DartExecutor implements BinaryMessenger {
|
|||||||
|
|
||||||
@NonNull private final FlutterJNI flutterJNI;
|
@NonNull private final FlutterJNI flutterJNI;
|
||||||
@NonNull private final AssetManager assetManager;
|
@NonNull private final AssetManager assetManager;
|
||||||
|
private final long engineId;
|
||||||
@NonNull private final DartMessenger dartMessenger;
|
@NonNull private final DartMessenger dartMessenger;
|
||||||
@NonNull private final BinaryMessenger binaryMessenger;
|
@NonNull private final BinaryMessenger binaryMessenger;
|
||||||
private boolean isApplicationRunning = false;
|
private boolean isApplicationRunning = false;
|
||||||
@ -57,9 +59,16 @@ public class DartExecutor implements BinaryMessenger {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@VisibleForTesting
|
||||||
public DartExecutor(@NonNull FlutterJNI flutterJNI, @NonNull AssetManager assetManager) {
|
public DartExecutor(@NonNull FlutterJNI flutterJNI, @NonNull AssetManager assetManager) {
|
||||||
|
this(flutterJNI, assetManager, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
public DartExecutor(
|
||||||
|
@NonNull FlutterJNI flutterJNI, @NonNull AssetManager assetManager, long engineId) {
|
||||||
this.flutterJNI = flutterJNI;
|
this.flutterJNI = flutterJNI;
|
||||||
this.assetManager = assetManager;
|
this.assetManager = assetManager;
|
||||||
|
this.engineId = engineId;
|
||||||
this.dartMessenger = new DartMessenger(flutterJNI);
|
this.dartMessenger = new DartMessenger(flutterJNI);
|
||||||
dartMessenger.setMessageHandler("flutter/isolate", isolateChannelMessageHandler);
|
dartMessenger.setMessageHandler("flutter/isolate", isolateChannelMessageHandler);
|
||||||
this.binaryMessenger = new DefaultBinaryMessenger(dartMessenger);
|
this.binaryMessenger = new DefaultBinaryMessenger(dartMessenger);
|
||||||
@ -148,7 +157,8 @@ public class DartExecutor implements BinaryMessenger {
|
|||||||
dartEntrypoint.dartEntrypointFunctionName,
|
dartEntrypoint.dartEntrypointFunctionName,
|
||||||
dartEntrypoint.dartEntrypointLibrary,
|
dartEntrypoint.dartEntrypointLibrary,
|
||||||
assetManager,
|
assetManager,
|
||||||
dartEntrypointArgs);
|
dartEntrypointArgs,
|
||||||
|
engineId);
|
||||||
|
|
||||||
isApplicationRunning = true;
|
isApplicationRunning = true;
|
||||||
}
|
}
|
||||||
@ -174,7 +184,8 @@ public class DartExecutor implements BinaryMessenger {
|
|||||||
dartCallback.callbackHandle.callbackName,
|
dartCallback.callbackHandle.callbackName,
|
||||||
dartCallback.callbackHandle.callbackLibraryPath,
|
dartCallback.callbackHandle.callbackLibraryPath,
|
||||||
dartCallback.androidAssetManager,
|
dartCallback.androidAssetManager,
|
||||||
null);
|
null,
|
||||||
|
engineId);
|
||||||
|
|
||||||
isApplicationRunning = true;
|
isApplicationRunning = true;
|
||||||
}
|
}
|
||||||
|
@ -195,7 +195,8 @@ static jobject SpawnJNI(JNIEnv* env,
|
|||||||
jstring jEntrypoint,
|
jstring jEntrypoint,
|
||||||
jstring jLibraryUrl,
|
jstring jLibraryUrl,
|
||||||
jstring jInitialRoute,
|
jstring jInitialRoute,
|
||||||
jobject jEntrypointArgs) {
|
jobject jEntrypointArgs,
|
||||||
|
jlong engineId) {
|
||||||
jobject jni = env->NewObject(g_flutter_jni_class->obj(), g_jni_constructor);
|
jobject jni = env->NewObject(g_flutter_jni_class->obj(), g_jni_constructor);
|
||||||
if (jni == nullptr) {
|
if (jni == nullptr) {
|
||||||
FML_LOG(ERROR) << "Could not create a FlutterJNI instance";
|
FML_LOG(ERROR) << "Could not create a FlutterJNI instance";
|
||||||
@ -211,8 +212,9 @@ static jobject SpawnJNI(JNIEnv* env,
|
|||||||
auto initial_route = fml::jni::JavaStringToString(env, jInitialRoute);
|
auto initial_route = fml::jni::JavaStringToString(env, jInitialRoute);
|
||||||
auto entrypoint_args = fml::jni::StringListToVector(env, jEntrypointArgs);
|
auto entrypoint_args = fml::jni::StringListToVector(env, jEntrypointArgs);
|
||||||
|
|
||||||
auto spawned_shell_holder = ANDROID_SHELL_HOLDER->Spawn(
|
auto spawned_shell_holder =
|
||||||
jni_facade, entrypoint, libraryUrl, initial_route, entrypoint_args);
|
ANDROID_SHELL_HOLDER->Spawn(jni_facade, entrypoint, libraryUrl,
|
||||||
|
initial_route, entrypoint_args, engineId);
|
||||||
|
|
||||||
if (spawned_shell_holder == nullptr || !spawned_shell_holder->IsValid()) {
|
if (spawned_shell_holder == nullptr || !spawned_shell_holder->IsValid()) {
|
||||||
FML_LOG(ERROR) << "Could not spawn Shell";
|
FML_LOG(ERROR) << "Could not spawn Shell";
|
||||||
@ -279,7 +281,8 @@ static void RunBundleAndSnapshotFromLibrary(JNIEnv* env,
|
|||||||
jstring jEntrypoint,
|
jstring jEntrypoint,
|
||||||
jstring jLibraryUrl,
|
jstring jLibraryUrl,
|
||||||
jobject jAssetManager,
|
jobject jAssetManager,
|
||||||
jobject jEntrypointArgs) {
|
jobject jEntrypointArgs,
|
||||||
|
jlong engineId) {
|
||||||
auto apk_asset_provider = std::make_unique<flutter::APKAssetProvider>(
|
auto apk_asset_provider = std::make_unique<flutter::APKAssetProvider>(
|
||||||
env, // jni environment
|
env, // jni environment
|
||||||
jAssetManager, // asset manager
|
jAssetManager, // asset manager
|
||||||
@ -290,7 +293,7 @@ static void RunBundleAndSnapshotFromLibrary(JNIEnv* env,
|
|||||||
auto entrypoint_args = fml::jni::StringListToVector(env, jEntrypointArgs);
|
auto entrypoint_args = fml::jni::StringListToVector(env, jEntrypointArgs);
|
||||||
|
|
||||||
ANDROID_SHELL_HOLDER->Launch(std::move(apk_asset_provider), entrypoint,
|
ANDROID_SHELL_HOLDER->Launch(std::move(apk_asset_provider), entrypoint,
|
||||||
libraryUrl, entrypoint_args);
|
libraryUrl, entrypoint_args, engineId);
|
||||||
}
|
}
|
||||||
|
|
||||||
static jobject LookupCallbackInformation(JNIEnv* env,
|
static jobject LookupCallbackInformation(JNIEnv* env,
|
||||||
@ -687,7 +690,7 @@ bool RegisterApi(JNIEnv* env) {
|
|||||||
{
|
{
|
||||||
.name = "nativeSpawn",
|
.name = "nativeSpawn",
|
||||||
.signature = "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/"
|
.signature = "(JLjava/lang/String;Ljava/lang/String;Ljava/lang/"
|
||||||
"String;Ljava/util/List;)Lio/flutter/"
|
"String;Ljava/util/List;J)Lio/flutter/"
|
||||||
"embedding/engine/FlutterJNI;",
|
"embedding/engine/FlutterJNI;",
|
||||||
.fnPtr = reinterpret_cast<void*>(&SpawnJNI),
|
.fnPtr = reinterpret_cast<void*>(&SpawnJNI),
|
||||||
},
|
},
|
||||||
@ -695,7 +698,7 @@ bool RegisterApi(JNIEnv* env) {
|
|||||||
.name = "nativeRunBundleAndSnapshotFromLibrary",
|
.name = "nativeRunBundleAndSnapshotFromLibrary",
|
||||||
.signature = "(JLjava/lang/String;Ljava/lang/String;"
|
.signature = "(JLjava/lang/String;Ljava/lang/String;"
|
||||||
"Ljava/lang/String;Landroid/content/res/"
|
"Ljava/lang/String;Landroid/content/res/"
|
||||||
"AssetManager;Ljava/util/List;)V",
|
"AssetManager;Ljava/util/List;J)V",
|
||||||
.fnPtr = reinterpret_cast<void*>(&RunBundleAndSnapshotFromLibrary),
|
.fnPtr = reinterpret_cast<void*>(&RunBundleAndSnapshotFromLibrary),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -59,6 +59,7 @@ public class FlutterEngineGroupComponentTest {
|
|||||||
when(mockFlutterJNI.isAttached()).thenAnswer(invocation -> jniAttached);
|
when(mockFlutterJNI.isAttached()).thenAnswer(invocation -> jniAttached);
|
||||||
doAnswer(invocation -> jniAttached = true).when(mockFlutterJNI).attachToNative();
|
doAnswer(invocation -> jniAttached = true).when(mockFlutterJNI).attachToNative();
|
||||||
GeneratedPluginRegistrant.clearRegisteredEngines();
|
GeneratedPluginRegistrant.clearRegisteredEngines();
|
||||||
|
FlutterEngine.resetNextEngineId();
|
||||||
|
|
||||||
when(mockFlutterLoader.findAppBundlePath()).thenReturn("some/path/to/flutter_assets");
|
when(mockFlutterLoader.findAppBundlePath()).thenReturn("some/path/to/flutter_assets");
|
||||||
|
|
||||||
@ -190,7 +191,8 @@ public class FlutterEngineGroupComponentTest {
|
|||||||
eq("other entrypoint"),
|
eq("other entrypoint"),
|
||||||
isNull(),
|
isNull(),
|
||||||
any(AssetManager.class),
|
any(AssetManager.class),
|
||||||
nullable(List.class));
|
nullable(List.class),
|
||||||
|
eq(1l));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -213,14 +215,20 @@ public class FlutterEngineGroupComponentTest {
|
|||||||
nullable(String.class),
|
nullable(String.class),
|
||||||
nullable(String.class),
|
nullable(String.class),
|
||||||
nullable(String.class),
|
nullable(String.class),
|
||||||
nullable(List.class));
|
nullable(List.class),
|
||||||
|
eq(2l));
|
||||||
|
|
||||||
FlutterEngine secondEngine =
|
FlutterEngine secondEngine =
|
||||||
engineGroupUnderTest.createAndRunEngine(ctx, mock(DartEntrypoint.class), "/bar");
|
engineGroupUnderTest.createAndRunEngine(ctx, mock(DartEntrypoint.class), "/bar");
|
||||||
|
|
||||||
assertEquals(2, engineGroupUnderTest.activeEngines.size());
|
assertEquals(2, engineGroupUnderTest.activeEngines.size());
|
||||||
verify(mockFlutterJNI, times(1))
|
verify(mockFlutterJNI, times(1))
|
||||||
.spawn(nullable(String.class), nullable(String.class), eq("/bar"), nullable(List.class));
|
.spawn(
|
||||||
|
nullable(String.class),
|
||||||
|
nullable(String.class),
|
||||||
|
eq("/bar"),
|
||||||
|
nullable(List.class),
|
||||||
|
eq(2l));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -238,7 +246,8 @@ public class FlutterEngineGroupComponentTest {
|
|||||||
nullable(String.class),
|
nullable(String.class),
|
||||||
isNull(),
|
isNull(),
|
||||||
any(AssetManager.class),
|
any(AssetManager.class),
|
||||||
eq(firstDartEntrypointArgs));
|
eq(firstDartEntrypointArgs),
|
||||||
|
eq(1l));
|
||||||
|
|
||||||
when(mockFlutterJNI.isAttached()).thenReturn(true);
|
when(mockFlutterJNI.isAttached()).thenReturn(true);
|
||||||
jniAttached = false;
|
jniAttached = false;
|
||||||
@ -251,7 +260,8 @@ public class FlutterEngineGroupComponentTest {
|
|||||||
nullable(String.class),
|
nullable(String.class),
|
||||||
nullable(String.class),
|
nullable(String.class),
|
||||||
nullable(String.class),
|
nullable(String.class),
|
||||||
nullable(List.class));
|
nullable(List.class),
|
||||||
|
eq(2l));
|
||||||
List<String> secondDartEntrypointArgs = new ArrayList<String>();
|
List<String> secondDartEntrypointArgs = new ArrayList<String>();
|
||||||
FlutterEngine secondEngine =
|
FlutterEngine secondEngine =
|
||||||
engineGroupUnderTest.createAndRunEngine(
|
engineGroupUnderTest.createAndRunEngine(
|
||||||
@ -265,7 +275,8 @@ public class FlutterEngineGroupComponentTest {
|
|||||||
nullable(String.class),
|
nullable(String.class),
|
||||||
nullable(String.class),
|
nullable(String.class),
|
||||||
nullable(String.class),
|
nullable(String.class),
|
||||||
eq(secondDartEntrypointArgs));
|
eq(secondDartEntrypointArgs),
|
||||||
|
eq(2l));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -309,7 +320,8 @@ public class FlutterEngineGroupComponentTest {
|
|||||||
nullable(String.class),
|
nullable(String.class),
|
||||||
isNull(),
|
isNull(),
|
||||||
any(AssetManager.class),
|
any(AssetManager.class),
|
||||||
nullable(List.class));
|
nullable(List.class),
|
||||||
|
eq(1l));
|
||||||
|
|
||||||
when(mockFlutterJNI.isAttached()).thenReturn(true);
|
when(mockFlutterJNI.isAttached()).thenReturn(true);
|
||||||
jniAttached = false;
|
jniAttached = false;
|
||||||
@ -322,7 +334,8 @@ public class FlutterEngineGroupComponentTest {
|
|||||||
nullable(String.class),
|
nullable(String.class),
|
||||||
nullable(String.class),
|
nullable(String.class),
|
||||||
nullable(String.class),
|
nullable(String.class),
|
||||||
nullable(List.class));
|
nullable(List.class),
|
||||||
|
eq(2l));
|
||||||
|
|
||||||
PlatformViewsController controller = new PlatformViewsController();
|
PlatformViewsController controller = new PlatformViewsController();
|
||||||
boolean waitForRestorationData = false;
|
boolean waitForRestorationData = false;
|
||||||
|
@ -79,6 +79,7 @@ public class FlutterEngineTest {
|
|||||||
.when(flutterJNI)
|
.when(flutterJNI)
|
||||||
.attachToNative();
|
.attachToNative();
|
||||||
GeneratedPluginRegistrant.clearRegisteredEngines();
|
GeneratedPluginRegistrant.clearRegisteredEngines();
|
||||||
|
FlutterEngine.resetNextEngineId();
|
||||||
}
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
@ -130,6 +131,23 @@ public class FlutterEngineTest {
|
|||||||
.dispatchPlatformMessage(eq("flutter/localization"), any(), anyInt(), anyInt());
|
.dispatchPlatformMessage(eq("flutter/localization"), any(), anyInt(), anyInt());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void itCanBeRetrievedByHandle() {
|
||||||
|
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
|
||||||
|
when(mockFlutterJNI.isAttached()).thenReturn(true);
|
||||||
|
FlutterLoader mockFlutterLoader = mock(FlutterLoader.class);
|
||||||
|
when(mockFlutterLoader.automaticallyRegisterPlugins()).thenReturn(true);
|
||||||
|
FlutterEngine flutterEngine1 = new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJNI);
|
||||||
|
FlutterEngine flutterEngine2 = new FlutterEngine(ctx, mockFlutterLoader, mockFlutterJNI);
|
||||||
|
assertEquals(flutterEngine1, FlutterEngine.engineForId(1));
|
||||||
|
assertEquals(flutterEngine2, FlutterEngine.engineForId(2));
|
||||||
|
flutterEngine1.destroy();
|
||||||
|
assertEquals(null, FlutterEngine.engineForId(1));
|
||||||
|
assertEquals(flutterEngine2, FlutterEngine.engineForId(2));
|
||||||
|
flutterEngine2.destroy();
|
||||||
|
assertEquals(null, FlutterEngine.engineForId(2));
|
||||||
|
}
|
||||||
|
|
||||||
// Helps show the root cause of MissingPluginException type errors like
|
// Helps show the root cause of MissingPluginException type errors like
|
||||||
// https://github.com/flutter/flutter/issues/78625.
|
// https://github.com/flutter/flutter/issues/78625.
|
||||||
@Test
|
@Test
|
||||||
|
@ -60,7 +60,7 @@ public class DeferredComponentChannelTest {
|
|||||||
public void deferredComponentChannel_installCompletesResults() {
|
public void deferredComponentChannel_installCompletesResults() {
|
||||||
MethodChannel rawChannel = mock(MethodChannel.class);
|
MethodChannel rawChannel = mock(MethodChannel.class);
|
||||||
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
|
FlutterJNI mockFlutterJNI = mock(FlutterJNI.class);
|
||||||
DartExecutor dartExecutor = new DartExecutor(mockFlutterJNI, mock(AssetManager.class));
|
DartExecutor dartExecutor = new DartExecutor(mockFlutterJNI, mock(AssetManager.class), 0);
|
||||||
TestDeferredComponentManager testDeferredComponentManager = new TestDeferredComponentManager();
|
TestDeferredComponentManager testDeferredComponentManager = new TestDeferredComponentManager();
|
||||||
DeferredComponentChannel fakeDeferredComponentChannel =
|
DeferredComponentChannel fakeDeferredComponentChannel =
|
||||||
new DeferredComponentChannel(dartExecutor);
|
new DeferredComponentChannel(dartExecutor);
|
||||||
|
@ -160,6 +160,10 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
|
|||||||
std::unique_ptr<flutter::ConnectionCollection> _connections;
|
std::unique_ptr<flutter::ConnectionCollection> _connections;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (int64_t)engineIdentifier {
|
||||||
|
return reinterpret_cast<int64_t>((__bridge void*)self);
|
||||||
|
}
|
||||||
|
|
||||||
- (instancetype)init {
|
- (instancetype)init {
|
||||||
return [self initWithName:@"FlutterEngine" project:nil allowHeadlessExecution:YES];
|
return [self initWithName:@"FlutterEngine" project:nil allowHeadlessExecution:YES];
|
||||||
}
|
}
|
||||||
@ -241,6 +245,11 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
|
|||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
+ (FlutterEngine*)engineForIdentifier:(int64_t)identifier {
|
||||||
|
NSAssert([[NSThread currentThread] isMainThread], @"Must be called on the main thread.");
|
||||||
|
return (__bridge FlutterEngine*)reinterpret_cast<void*>(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
- (void)setUpSceneLifecycleNotifications:(NSNotificationCenter*)center API_AVAILABLE(ios(13.0)) {
|
- (void)setUpSceneLifecycleNotifications:(NSNotificationCenter*)center API_AVAILABLE(ios(13.0)) {
|
||||||
[center addObserver:self
|
[center addObserver:self
|
||||||
selector:@selector(sceneWillEnterForeground:)
|
selector:@selector(sceneWillEnterForeground:)
|
||||||
@ -704,9 +713,13 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
|
|||||||
libraryURI:(NSString*)libraryOrNil
|
libraryURI:(NSString*)libraryOrNil
|
||||||
entrypointArgs:(NSArray<NSString*>*)entrypointArgs {
|
entrypointArgs:(NSArray<NSString*>*)entrypointArgs {
|
||||||
// Launch the Dart application with the inferred run configuration.
|
// Launch the Dart application with the inferred run configuration.
|
||||||
self.shell.RunEngine([self.dartProject runConfigurationForEntrypoint:entrypoint
|
flutter::RunConfiguration configuration =
|
||||||
libraryOrNil:libraryOrNil
|
[self.dartProject runConfigurationForEntrypoint:entrypoint
|
||||||
entrypointArgs:entrypointArgs]);
|
libraryOrNil:libraryOrNil
|
||||||
|
entrypointArgs:entrypointArgs];
|
||||||
|
|
||||||
|
configuration.SetEngineId(self.engineIdentifier);
|
||||||
|
self.shell.RunEngine(std::move(configuration));
|
||||||
}
|
}
|
||||||
|
|
||||||
- (void)setUpShell:(std::unique_ptr<flutter::Shell>)shell
|
- (void)setUpShell:(std::unique_ptr<flutter::Shell>)shell
|
||||||
@ -1451,6 +1464,8 @@ static void SetEntryPoint(flutter::Settings* settings, NSString* entrypoint, NSS
|
|||||||
libraryOrNil:libraryURI
|
libraryOrNil:libraryURI
|
||||||
entrypointArgs:entrypointArgs];
|
entrypointArgs:entrypointArgs];
|
||||||
|
|
||||||
|
configuration.SetEngineId(result.engineIdentifier);
|
||||||
|
|
||||||
fml::WeakPtr<flutter::PlatformView> platform_view = _shell->GetPlatformView();
|
fml::WeakPtr<flutter::PlatformView> platform_view = _shell->GetPlatformView();
|
||||||
FML_DCHECK(platform_view);
|
FML_DCHECK(platform_view);
|
||||||
// Static-cast safe since this class always creates PlatformViewIOS instances.
|
// Static-cast safe since this class always creates PlatformViewIOS instances.
|
||||||
|
@ -273,6 +273,20 @@ FLUTTER_ASSERT_ARC
|
|||||||
XCTAssertNotNil(spawn);
|
XCTAssertNotNil(spawn);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
- (void)testEngineId {
|
||||||
|
FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
|
||||||
|
[engine run];
|
||||||
|
int64_t id1 = engine.engineIdentifier;
|
||||||
|
XCTAssertTrue(id1 != 0);
|
||||||
|
FlutterEngine* spawn = [engine spawnWithEntrypoint:nil
|
||||||
|
libraryURI:nil
|
||||||
|
initialRoute:nil
|
||||||
|
entrypointArgs:nil];
|
||||||
|
int64_t id2 = spawn.engineIdentifier;
|
||||||
|
XCTAssertEqual([FlutterEngine engineForIdentifier:id1], engine);
|
||||||
|
XCTAssertEqual([FlutterEngine engineForIdentifier:id2], spawn);
|
||||||
|
}
|
||||||
|
|
||||||
- (void)testSetHandlerAfterRun {
|
- (void)testSetHandlerAfterRun {
|
||||||
FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
|
FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
|
||||||
XCTestExpectation* gotMessage = [self expectationWithDescription:@"gotMessage"];
|
XCTestExpectation* gotMessage = [self expectationWithDescription:@"gotMessage"];
|
||||||
|
@ -87,6 +87,22 @@ NS_ASSUME_NONNULL_BEGIN
|
|||||||
|
|
||||||
@property(nonatomic, readonly) FlutterDartProject* project;
|
@property(nonatomic, readonly) FlutterDartProject* project;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the engine handle. Used in FlutterEngineTest.
|
||||||
|
*/
|
||||||
|
- (int64_t)engineIdentifier;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns engine for the identifier. The identifier must be valid for an engine
|
||||||
|
* that is currently running, otherwise the behavior is undefined.
|
||||||
|
*
|
||||||
|
* The identifier can be obtained in Dart code through
|
||||||
|
* `PlatformDispatcher.instance.engineId`.
|
||||||
|
*
|
||||||
|
* This function must be called on the main thread.
|
||||||
|
*/
|
||||||
|
+ (nullable FlutterEngine*)engineForIdentifier:(int64_t)identifier;
|
||||||
|
|
||||||
@end
|
@end
|
||||||
|
|
||||||
NS_ASSUME_NONNULL_END
|
NS_ASSUME_NONNULL_END
|
||||||
|
@ -642,6 +642,8 @@ static void SetThreadPriority(FlutterThreadPriority priority) {
|
|||||||
std::cout << message << std::endl;
|
std::cout << message << std::endl;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
flutterArguments.engine_id = reinterpret_cast<int64_t>((__bridge void*)self);
|
||||||
|
|
||||||
static size_t sTaskRunnerIdentifiers = 0;
|
static size_t sTaskRunnerIdentifiers = 0;
|
||||||
const FlutterTaskRunnerDescription cocoa_task_runner_description = {
|
const FlutterTaskRunnerDescription cocoa_task_runner_description = {
|
||||||
.struct_size = sizeof(FlutterTaskRunnerDescription),
|
.struct_size = sizeof(FlutterTaskRunnerDescription),
|
||||||
@ -1159,6 +1161,11 @@ static void SetThreadPriority(FlutterThreadPriority priority) {
|
|||||||
_engine = nullptr;
|
_engine = nullptr;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
+ (FlutterEngine*)engineForIdentifier:(int64_t)identifier {
|
||||||
|
NSAssert([[NSThread currentThread] isMainThread], @"Must be called on the main thread.");
|
||||||
|
return (__bridge FlutterEngine*)reinterpret_cast<void*>(identifier);
|
||||||
|
}
|
||||||
|
|
||||||
- (void)setUpPlatformViewChannel {
|
- (void)setUpPlatformViewChannel {
|
||||||
_platformViewsChannel =
|
_platformViewsChannel =
|
||||||
[FlutterMethodChannel methodChannelWithName:@"flutter/platform_views"
|
[FlutterMethodChannel methodChannelWithName:@"flutter/platform_views"
|
||||||
|
@ -863,6 +863,31 @@ TEST_F(FlutterEngineTest, ResponseFromBackgroundThread) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(FlutterEngineTest, CanGetEngineForId) {
|
||||||
|
FlutterEngine* engine = GetFlutterEngine();
|
||||||
|
|
||||||
|
fml::AutoResetWaitableEvent latch;
|
||||||
|
std::optional<int64_t> engineId;
|
||||||
|
AddNativeCallback("NotifyEngineId", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
|
||||||
|
const auto argument = Dart_GetNativeArgument(args, 0);
|
||||||
|
if (!Dart_IsNull(argument)) {
|
||||||
|
const auto id = tonic::DartConverter<int64_t>::FromDart(argument);
|
||||||
|
engineId = id;
|
||||||
|
}
|
||||||
|
latch.Signal();
|
||||||
|
}));
|
||||||
|
|
||||||
|
EXPECT_TRUE([engine runWithEntrypoint:@"testEngineId"]);
|
||||||
|
latch.Wait();
|
||||||
|
|
||||||
|
EXPECT_TRUE(engineId.has_value());
|
||||||
|
if (!engineId.has_value()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
EXPECT_EQ(engine, [FlutterEngine engineForIdentifier:*engineId]);
|
||||||
|
ShutDownEngine();
|
||||||
|
}
|
||||||
|
|
||||||
TEST_F(FlutterEngineTest, ThreadSynchronizerNotBlockingRasterThreadAfterShutdown) {
|
TEST_F(FlutterEngineTest, ThreadSynchronizerNotBlockingRasterThreadAfterShutdown) {
|
||||||
FlutterThreadSynchronizer* threadSynchronizer = [[FlutterThreadSynchronizer alloc] init];
|
FlutterThreadSynchronizer* threadSynchronizer = [[FlutterThreadSynchronizer alloc] init];
|
||||||
[threadSynchronizer shutdown];
|
[threadSynchronizer shutdown];
|
||||||
|
@ -222,6 +222,17 @@ typedef NS_ENUM(NSInteger, FlutterAppExitResponse) {
|
|||||||
* Returns an array of screen objects representing all of the screens available on the system.
|
* Returns an array of screen objects representing all of the screens available on the system.
|
||||||
*/
|
*/
|
||||||
- (NSArray<NSScreen*>*)screens;
|
- (NSArray<NSScreen*>*)screens;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns engine for the identifier. The identifier must be valid for an engine
|
||||||
|
* that is currently running, otherwise the behavior is undefined.
|
||||||
|
*
|
||||||
|
* The identifier can be obtained in Dart code through
|
||||||
|
* `PlatformDispatcher.instance.engineId`.
|
||||||
|
*
|
||||||
|
* This function must be called on the main thread.
|
||||||
|
*/
|
||||||
|
+ (nullable FlutterEngine*)engineForIdentifier:(int64_t)identifier;
|
||||||
@end
|
@end
|
||||||
|
|
||||||
@interface FlutterEngine (Tests)
|
@interface FlutterEngine (Tests)
|
||||||
|
@ -85,3 +85,11 @@ void backgroundTest() {
|
|||||||
void sendFooMessage() {
|
void sendFooMessage() {
|
||||||
PlatformDispatcher.instance.sendPlatformMessage('foo', null, (ByteData? result) {});
|
PlatformDispatcher.instance.sendPlatformMessage('foo', null, (ByteData? result) {});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@pragma('vm:external-name', 'NotifyEngineId')
|
||||||
|
external void notifyEngineId(int? engineId);
|
||||||
|
|
||||||
|
@pragma('vm:entry-point')
|
||||||
|
void testEngineId() {
|
||||||
|
notifyEngineId(PlatformDispatcher.instance.engineId);
|
||||||
|
}
|
||||||
|
@ -2410,6 +2410,10 @@ FlutterEngineResult FlutterEngineInitialize(size_t version,
|
|||||||
run_configuration.SetEntrypointArgs(std::move(arguments));
|
run_configuration.SetEntrypointArgs(std::move(arguments));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (SAFE_ACCESS(args, engine_id, 0) != 0) {
|
||||||
|
run_configuration.SetEngineId(args->engine_id);
|
||||||
|
}
|
||||||
|
|
||||||
if (!run_configuration.IsValid()) {
|
if (!run_configuration.IsValid()) {
|
||||||
return LOG_EMBEDDER_ERROR(
|
return LOG_EMBEDDER_ERROR(
|
||||||
kInvalidArguments,
|
kInvalidArguments,
|
||||||
|
@ -2631,6 +2631,11 @@ typedef struct {
|
|||||||
/// the native view. The callback is invoked from a task posted to the
|
/// the native view. The callback is invoked from a task posted to the
|
||||||
/// platform thread.
|
/// platform thread.
|
||||||
FlutterViewFocusChangeRequestCallback view_focus_change_request_callback;
|
FlutterViewFocusChangeRequestCallback view_focus_change_request_callback;
|
||||||
|
|
||||||
|
/// Opaque identifier provided by the engine. Accessible in Dart code through
|
||||||
|
/// `PlatformDispatcher.instance.engineId`. Can be used in native code to
|
||||||
|
/// retrieve the engine instance that is running the Dart code.
|
||||||
|
int64_t engine_id;
|
||||||
} FlutterProjectArgs;
|
} FlutterProjectArgs;
|
||||||
|
|
||||||
#ifndef FLUTTER_ENGINE_NO_PROTOTYPES
|
#ifndef FLUTTER_ENGINE_NO_PROTOTYPES
|
||||||
|
@ -545,6 +545,7 @@ static FlEngine* fl_engine_new_full(FlDartProject* project,
|
|||||||
g_return_val_if_fail(FL_IS_RENDERER(renderer), nullptr);
|
g_return_val_if_fail(FL_IS_RENDERER(renderer), nullptr);
|
||||||
|
|
||||||
FlEngine* self = FL_ENGINE(g_object_new(fl_engine_get_type(), nullptr));
|
FlEngine* self = FL_ENGINE(g_object_new(fl_engine_get_type(), nullptr));
|
||||||
|
|
||||||
self->project = FL_DART_PROJECT(g_object_ref(project));
|
self->project = FL_DART_PROJECT(g_object_ref(project));
|
||||||
self->renderer = FL_RENDERER(g_object_ref(renderer));
|
self->renderer = FL_RENDERER(g_object_ref(renderer));
|
||||||
if (binary_messenger != nullptr) {
|
if (binary_messenger != nullptr) {
|
||||||
@ -563,6 +564,12 @@ static FlEngine* fl_engine_new_full(FlDartProject* project,
|
|||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FlEngine* fl_engine_for_id(int64_t id) {
|
||||||
|
void* engine = reinterpret_cast<void*>(id);
|
||||||
|
g_return_val_if_fail(FL_IS_ENGINE(engine), nullptr);
|
||||||
|
return FL_ENGINE(engine);
|
||||||
|
}
|
||||||
|
|
||||||
FlEngine* fl_engine_new_with_renderer(FlDartProject* project,
|
FlEngine* fl_engine_new_with_renderer(FlDartProject* project,
|
||||||
FlRenderer* renderer) {
|
FlRenderer* renderer) {
|
||||||
g_return_val_if_fail(FL_IS_DART_PROJECT(project), nullptr);
|
g_return_val_if_fail(FL_IS_DART_PROJECT(project), nullptr);
|
||||||
@ -651,6 +658,7 @@ gboolean fl_engine_start(FlEngine* self, GError** error) {
|
|||||||
dart_entrypoint_args != nullptr ? g_strv_length(dart_entrypoint_args) : 0;
|
dart_entrypoint_args != nullptr ? g_strv_length(dart_entrypoint_args) : 0;
|
||||||
args.dart_entrypoint_argv =
|
args.dart_entrypoint_argv =
|
||||||
reinterpret_cast<const char* const*>(dart_entrypoint_args);
|
reinterpret_cast<const char* const*>(dart_entrypoint_args);
|
||||||
|
args.engine_id = reinterpret_cast<int64_t>(self);
|
||||||
|
|
||||||
FlutterCompositor compositor = {};
|
FlutterCompositor compositor = {};
|
||||||
compositor.struct_size = sizeof(FlutterCompositor);
|
compositor.struct_size = sizeof(FlutterCompositor);
|
||||||
|
@ -588,6 +588,20 @@ FlTextInputHandler* fl_engine_get_text_input_handler(FlEngine* engine);
|
|||||||
*/
|
*/
|
||||||
FlMouseCursorHandler* fl_engine_get_mouse_cursor_handler(FlEngine* engine);
|
FlMouseCursorHandler* fl_engine_get_mouse_cursor_handler(FlEngine* engine);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* fl_engine_for_id:
|
||||||
|
* @handle: an engine identifier obtained through
|
||||||
|
* PlatformDispatcher.instance.engineId.
|
||||||
|
*
|
||||||
|
* Returns Flutter engine associated with the identifier. The identifier
|
||||||
|
* must be valid and for a running engine otherwise the behavior is
|
||||||
|
* undefined.
|
||||||
|
* Must be called from the main thread.
|
||||||
|
*
|
||||||
|
* Returns: a #FlEngine or NULL.
|
||||||
|
*/
|
||||||
|
FlEngine* fl_engine_for_id(int64_t handle);
|
||||||
|
|
||||||
G_END_DECLS
|
G_END_DECLS
|
||||||
|
|
||||||
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_ENGINE_PRIVATE_H_
|
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_ENGINE_PRIVATE_H_
|
||||||
|
@ -416,6 +416,29 @@ TEST(FlEngineTest, DartEntrypointArgs) {
|
|||||||
EXPECT_TRUE(called);
|
EXPECT_TRUE(called);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST(FlEngineTest, EngineId) {
|
||||||
|
g_autoptr(FlDartProject) project = fl_dart_project_new();
|
||||||
|
g_autoptr(FlEngine) engine = fl_engine_new(project);
|
||||||
|
int64_t engine_id;
|
||||||
|
fl_engine_get_embedder_api(engine)->Initialize = MOCK_ENGINE_PROC(
|
||||||
|
Initialize,
|
||||||
|
([&engine_id](size_t version, const FlutterRendererConfig* config,
|
||||||
|
const FlutterProjectArgs* args, void* user_data,
|
||||||
|
FLUTTER_API_SYMBOL(FlutterEngine) * engine_out) {
|
||||||
|
engine_id = args->engine_id;
|
||||||
|
return kSuccess;
|
||||||
|
}));
|
||||||
|
fl_engine_get_embedder_api(engine)->RunInitialized =
|
||||||
|
MOCK_ENGINE_PROC(RunInitialized, ([](auto engine) { return kSuccess; }));
|
||||||
|
|
||||||
|
g_autoptr(GError) error = nullptr;
|
||||||
|
EXPECT_TRUE(fl_engine_start(engine, &error));
|
||||||
|
EXPECT_EQ(error, nullptr);
|
||||||
|
EXPECT_TRUE(engine_id != 0);
|
||||||
|
|
||||||
|
EXPECT_EQ(fl_engine_for_id(engine_id), engine);
|
||||||
|
}
|
||||||
|
|
||||||
TEST(FlEngineTest, Locales) {
|
TEST(FlEngineTest, Locales) {
|
||||||
g_autofree gchar* initial_language = g_strdup(g_getenv("LANGUAGE"));
|
g_autofree gchar* initial_language = g_strdup(g_getenv("LANGUAGE"));
|
||||||
g_setenv("LANGUAGE", "de:en_US", TRUE);
|
g_setenv("LANGUAGE", "de:en_US", TRUE);
|
||||||
|
@ -396,3 +396,11 @@ void onMetricsChangedSignalViewIds() {
|
|||||||
void mergedUIThread() {
|
void mergedUIThread() {
|
||||||
signal();
|
signal();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@pragma('vm:external-name', 'NotifyEngineId')
|
||||||
|
external void notifyEngineId(int? handle);
|
||||||
|
|
||||||
|
@pragma('vm:entry-point')
|
||||||
|
void testEngineId() {
|
||||||
|
notifyEngineId(ui.PlatformDispatcher.instance.engineId);
|
||||||
|
}
|
||||||
|
@ -197,6 +197,12 @@ bool FlutterDesktopEngineDestroy(FlutterDesktopEngineRef engine_ref) {
|
|||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FLUTTER_EXPORT FlutterDesktopEngineRef FlutterDesktopEngineForId(
|
||||||
|
int64_t engine_id) {
|
||||||
|
return HandleForEngine(
|
||||||
|
flutter::FlutterWindowsEngine::GetEngineForId(engine_id));
|
||||||
|
}
|
||||||
|
|
||||||
bool FlutterDesktopEngineRun(FlutterDesktopEngineRef engine,
|
bool FlutterDesktopEngineRun(FlutterDesktopEngineRef engine,
|
||||||
const char* entry_point) {
|
const char* entry_point) {
|
||||||
std::string_view entry_point_view{""};
|
std::string_view entry_point_view{""};
|
||||||
|
@ -233,6 +233,10 @@ FlutterWindowsEngine::~FlutterWindowsEngine() {
|
|||||||
Stop();
|
Stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
FlutterWindowsEngine* FlutterWindowsEngine::GetEngineForId(int64_t engine_id) {
|
||||||
|
return reinterpret_cast<FlutterWindowsEngine*>(engine_id);
|
||||||
|
}
|
||||||
|
|
||||||
void FlutterWindowsEngine::SetSwitches(
|
void FlutterWindowsEngine::SetSwitches(
|
||||||
const std::vector<std::string>& switches) {
|
const std::vector<std::string>& switches) {
|
||||||
project_->SetSwitches(switches);
|
project_->SetSwitches(switches);
|
||||||
@ -309,6 +313,7 @@ bool FlutterWindowsEngine::Run(std::string_view entrypoint) {
|
|||||||
args.icu_data_path = icu_path_string.c_str();
|
args.icu_data_path = icu_path_string.c_str();
|
||||||
args.command_line_argc = static_cast<int>(argv.size());
|
args.command_line_argc = static_cast<int>(argv.size());
|
||||||
args.command_line_argv = argv.empty() ? nullptr : argv.data();
|
args.command_line_argv = argv.empty() ? nullptr : argv.data();
|
||||||
|
args.engine_id = reinterpret_cast<int64_t>(this);
|
||||||
|
|
||||||
// Fail if conflicting non-default entrypoints are specified in the method
|
// Fail if conflicting non-default entrypoints are specified in the method
|
||||||
// argument and the project.
|
// argument and the project.
|
||||||
|
@ -96,6 +96,12 @@ class FlutterWindowsEngine {
|
|||||||
|
|
||||||
virtual ~FlutterWindowsEngine();
|
virtual ~FlutterWindowsEngine();
|
||||||
|
|
||||||
|
// Returns the engine associated with the given identifier.
|
||||||
|
// The engine_id must be valid and for a running engine, otherwise
|
||||||
|
// the behavior is undefined.
|
||||||
|
// Must be called on the platform thread.
|
||||||
|
static FlutterWindowsEngine* GetEngineForId(int64_t engine_id);
|
||||||
|
|
||||||
// Starts running the entrypoint function specifed in the project bundle. If
|
// Starts running the entrypoint function specifed in the project bundle. If
|
||||||
// unspecified, defaults to main().
|
// unspecified, defaults to main().
|
||||||
//
|
//
|
||||||
|
@ -65,6 +65,15 @@ FLUTTER_EXPORT void FlutterDesktopEngineRegisterPlatformViewType(
|
|||||||
const char* view_type_name,
|
const char* view_type_name,
|
||||||
FlutterPlatformViewTypeEntry view_type);
|
FlutterPlatformViewTypeEntry view_type);
|
||||||
|
|
||||||
|
// Returns the engine associated with the given identifier. Engine identifier
|
||||||
|
// must be valid and for a running engine, otherwise the behavior is undefined.
|
||||||
|
//
|
||||||
|
// Identifier can be obtained from PlatformDispatcher.instance.engineId.
|
||||||
|
//
|
||||||
|
// This method must be called from the platform thread.
|
||||||
|
FLUTTER_EXPORT FlutterDesktopEngineRef FlutterDesktopEngineForId(
|
||||||
|
int64_t engine_id);
|
||||||
|
|
||||||
#if defined(__cplusplus)
|
#if defined(__cplusplus)
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
@ -652,5 +652,34 @@ TEST_F(WindowsTest, AddRemoveView) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
TEST_F(WindowsTest, EngineId) {
|
||||||
|
auto& context = GetContext();
|
||||||
|
WindowsConfigBuilder builder(context);
|
||||||
|
builder.SetDartEntrypoint("testEngineId");
|
||||||
|
|
||||||
|
fml::AutoResetWaitableEvent latch;
|
||||||
|
std::optional<int64_t> engineId;
|
||||||
|
context.AddNativeFunction(
|
||||||
|
"NotifyEngineId", CREATE_NATIVE_ENTRY([&](Dart_NativeArguments args) {
|
||||||
|
const auto argument = Dart_GetNativeArgument(args, 0);
|
||||||
|
if (!Dart_IsNull(argument)) {
|
||||||
|
const auto handle = tonic::DartConverter<int64_t>::FromDart(argument);
|
||||||
|
engineId = handle;
|
||||||
|
}
|
||||||
|
latch.Signal();
|
||||||
|
}));
|
||||||
|
// Create the implicit view.
|
||||||
|
ViewControllerPtr first_controller{builder.Run()};
|
||||||
|
ASSERT_NE(first_controller, nullptr);
|
||||||
|
|
||||||
|
latch.Wait();
|
||||||
|
EXPECT_TRUE(engineId.has_value());
|
||||||
|
if (!engineId.has_value()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto engine = FlutterDesktopViewControllerGetEngine(first_controller.get());
|
||||||
|
EXPECT_EQ(engine, FlutterDesktopEngineForId(*engineId));
|
||||||
|
}
|
||||||
|
|
||||||
} // namespace testing
|
} // namespace testing
|
||||||
} // namespace flutter
|
} // namespace flutter
|
||||||
|
Loading…
x
Reference in New Issue
Block a user