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);
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void _setEngineId(int engineId) {
|
||||
PlatformDispatcher.instance._engineId = engineId;
|
||||
}
|
||||
|
||||
@pragma('vm:entry-point')
|
||||
void _updateDisplays(
|
||||
List<int> ids,
|
||||
|
@ -305,6 +305,13 @@ class PlatformDispatcher {
|
||||
_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.
|
||||
//
|
||||
// Updates the available displays.
|
||||
|
@ -49,6 +49,8 @@ void PlatformConfiguration::DidCreateIsolate() {
|
||||
send_view_focus_event_.Set(
|
||||
tonic::DartState::Current(),
|
||||
Dart_GetField(library, tonic::ToDart("_sendViewFocusEvent")));
|
||||
set_engine_id_.Set(tonic::DartState::Current(),
|
||||
Dart_GetField(library, tonic::ToDart("_setEngineId")));
|
||||
update_window_metrics_.Set(
|
||||
tonic::DartState::Current(),
|
||||
Dart_GetField(library, tonic::ToDart("_updateWindowMetrics")));
|
||||
@ -168,6 +170,20 @@ bool PlatformConfiguration::SendFocusEvent(const ViewFocusEvent& event) {
|
||||
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(
|
||||
int64_t view_id,
|
||||
const ViewportMetrics& view_metrics) {
|
||||
|
@ -356,6 +356,14 @@ class PlatformConfiguration final {
|
||||
/// @return Whether the focus event was sent.
|
||||
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.
|
||||
///
|
||||
@ -548,6 +556,7 @@ class PlatformConfiguration final {
|
||||
tonic::DartPersistentValue add_view_;
|
||||
tonic::DartPersistentValue remove_view_;
|
||||
tonic::DartPersistentValue send_view_focus_event_;
|
||||
tonic::DartPersistentValue set_engine_id_;
|
||||
tonic::DartPersistentValue update_window_metrics_;
|
||||
tonic::DartPersistentValue update_displays_;
|
||||
tonic::DartPersistentValue update_locales_;
|
||||
|
@ -37,6 +37,8 @@ abstract class PlatformDispatcher {
|
||||
|
||||
FlutterView? get implicitView;
|
||||
|
||||
int? get engineId;
|
||||
|
||||
VoidCallback? get onMetricsChanged;
|
||||
set onMetricsChanged(VoidCallback? callback);
|
||||
|
||||
|
@ -140,6 +140,9 @@ class EnginePlatformDispatcher extends ui.PlatformDispatcher {
|
||||
@override
|
||||
EngineFlutterWindow? get implicitView => viewManager[kImplicitViewId] as EngineFlutterWindow?;
|
||||
|
||||
@override
|
||||
int? get engineId => null;
|
||||
|
||||
/// A callback that is invoked whenever the platform's [devicePixelRatio],
|
||||
/// [physicalSize], [padding], [viewInsets], or [systemGestureInsets]
|
||||
/// 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,
|
||||
const std::vector<std::string>& dart_entrypoint_args,
|
||||
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()) {
|
||||
FML_LOG(ERROR) << "Root isolate was already running.";
|
||||
return false;
|
||||
@ -586,6 +587,11 @@ bool RuntimeController::LaunchRootIsolate(
|
||||
if (!FlushRuntimeStateToIsolate()) {
|
||||
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 {
|
||||
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>
|
||||
/// to Dart's entrypoint function.
|
||||
/// @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
|
||||
/// `DartIsolate::Phase::Running` phase.
|
||||
@ -167,7 +169,8 @@ class RuntimeController : public PlatformConfigurationClient,
|
||||
std::optional<std::string> dart_entrypoint_library,
|
||||
const std::vector<std::string>& dart_entrypoint_args,
|
||||
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
|
||||
|
@ -245,8 +245,9 @@ Engine::RunStatus Engine::Run(RunConfiguration configuration) {
|
||||
configuration.GetEntrypointLibrary(), //
|
||||
configuration.GetEntrypointArgs(), //
|
||||
configuration.TakeIsolateConfiguration(), //
|
||||
native_assets_manager_) //
|
||||
) {
|
||||
native_assets_manager_, //
|
||||
configuration.GetEngineId())) //
|
||||
{
|
||||
return RunStatus::Failure;
|
||||
}
|
||||
|
||||
|
@ -640,3 +640,11 @@ void testSendViewFocusEvent() {
|
||||
};
|
||||
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_;
|
||||
}
|
||||
|
||||
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>
|
||||
RunConfiguration::TakeIsolateConfiguration() {
|
||||
return std::move(isolate_configuration_);
|
||||
|
@ -194,12 +194,22 @@ class RunConfiguration {
|
||||
///
|
||||
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:
|
||||
std::unique_ptr<IsolateConfiguration> isolate_configuration_;
|
||||
std::shared_ptr<AssetManager> asset_manager_;
|
||||
std::string entrypoint_ = "main";
|
||||
std::string entrypoint_library_ = "";
|
||||
std::vector<std::string> entrypoint_args_;
|
||||
std::optional<int64_t> engine_id_;
|
||||
|
||||
FML_DISALLOW_COPY_AND_ASSIGN(RunConfiguration);
|
||||
};
|
||||
|
@ -71,6 +71,7 @@ class ShellTest : public FixtureTest {
|
||||
// Defaults to calling ShellTestPlatformView::Create with the provided
|
||||
// arguments.
|
||||
Shell::CreateCallback<PlatformView> platform_view_create_callback;
|
||||
std::optional<int64_t> engine_id;
|
||||
};
|
||||
|
||||
ShellTest();
|
||||
|
@ -5003,7 +5003,69 @@ TEST_F(ShellTest, SendViewFocusEvent) {
|
||||
latch.Wait();
|
||||
ASSERT_EQ(last_event,
|
||||
"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);
|
||||
}
|
||||
|
||||
|
@ -222,7 +222,8 @@ std::unique_ptr<AndroidShellHolder> AndroidShellHolder::Spawn(
|
||||
const std::string& entrypoint,
|
||||
const std::string& libraryUrl,
|
||||
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())
|
||||
<< "A new Shell can only be spawned "
|
||||
"if the current Shell is properly constructed";
|
||||
@ -269,6 +270,7 @@ std::unique_ptr<AndroidShellHolder> AndroidShellHolder::Spawn(
|
||||
// Fail the whole thing.
|
||||
return nullptr;
|
||||
}
|
||||
config->SetEngineId(engine_id);
|
||||
|
||||
std::unique_ptr<flutter::Shell> shell =
|
||||
shell_->Spawn(std::move(config.value()), initial_route,
|
||||
@ -284,7 +286,8 @@ void AndroidShellHolder::Launch(
|
||||
std::unique_ptr<APKAssetProvider> apk_asset_provider,
|
||||
const std::string& entrypoint,
|
||||
const std::string& libraryUrl,
|
||||
const std::vector<std::string>& entrypoint_args) {
|
||||
const std::vector<std::string>& entrypoint_args,
|
||||
int64_t engine_id) {
|
||||
if (!IsValid()) {
|
||||
return;
|
||||
}
|
||||
@ -294,6 +297,7 @@ void AndroidShellHolder::Launch(
|
||||
if (!config) {
|
||||
return;
|
||||
}
|
||||
config->SetEngineId(engine_id);
|
||||
UpdateDisplayMetrics();
|
||||
shell_->RunEngine(std::move(config.value()));
|
||||
}
|
||||
|
@ -79,12 +79,14 @@ class AndroidShellHolder {
|
||||
const std::string& entrypoint,
|
||||
const std::string& libraryUrl,
|
||||
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,
|
||||
const std::string& entrypoint,
|
||||
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;
|
||||
|
||||
|
@ -44,8 +44,10 @@ import io.flutter.plugin.platform.PlatformViewsController;
|
||||
import io.flutter.plugin.platform.PlatformViewsController2;
|
||||
import io.flutter.plugin.text.ProcessTextPlugin;
|
||||
import io.flutter.util.ViewUtils;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@ -115,6 +117,20 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
|
||||
// Engine Lifecycle.
|
||||
@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
|
||||
private final EngineLifecycleListener engineLifecycleListener =
|
||||
new EngineLifecycleListener() {
|
||||
@ -312,6 +328,10 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
|
||||
boolean automaticallyRegisterPlugins,
|
||||
boolean waitForRestorationData,
|
||||
@Nullable FlutterEngineGroup group) {
|
||||
|
||||
this.engineId = nextEngineId++;
|
||||
idToEngine.put(engineId, this);
|
||||
|
||||
AssetManager assetManager;
|
||||
try {
|
||||
assetManager = context.createPackageContext(context.getPackageName(), 0).getAssets();
|
||||
@ -326,7 +346,7 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
|
||||
}
|
||||
this.flutterJNI = flutterJNI;
|
||||
|
||||
this.dartExecutor = new DartExecutor(flutterJNI, assetManager);
|
||||
this.dartExecutor = new DartExecutor(flutterJNI, assetManager, engineId);
|
||||
this.dartExecutor.onAttachedToJNI();
|
||||
|
||||
DeferredComponentManager deferredComponentManager =
|
||||
@ -451,7 +471,8 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
|
||||
dartEntrypoint.dartEntrypointFunctionName,
|
||||
dartEntrypoint.dartEntrypointLibrary,
|
||||
initialRoute,
|
||||
dartEntrypointArgs);
|
||||
dartEntrypointArgs,
|
||||
nextEngineId);
|
||||
return new FlutterEngine(
|
||||
context, // Context.
|
||||
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();
|
||||
deferredComponentChannel.setDeferredComponentManager(null);
|
||||
}
|
||||
idToEngine.remove(engineId);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -679,6 +701,25 @@ public class FlutterEngine implements ViewUtils.DisplayUpdater {
|
||||
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. */
|
||||
public interface EngineLifecycleListener {
|
||||
/** Lifecycle callback invoked before a hot restart of the Flutter engine. */
|
||||
|
@ -460,7 +460,8 @@ public class FlutterJNI {
|
||||
@Nullable String entrypointFunctionName,
|
||||
@Nullable String pathToEntrypointFunction,
|
||||
@Nullable String initialRoute,
|
||||
@Nullable List<String> entrypointArgs) {
|
||||
@Nullable List<String> entrypointArgs,
|
||||
long engineId) {
|
||||
ensureRunningOnMainThread();
|
||||
ensureAttachedToNative();
|
||||
FlutterJNI spawnedJNI =
|
||||
@ -469,7 +470,8 @@ public class FlutterJNI {
|
||||
entrypointFunctionName,
|
||||
pathToEntrypointFunction,
|
||||
initialRoute,
|
||||
entrypointArgs);
|
||||
entrypointArgs,
|
||||
engineId);
|
||||
Preconditions.checkState(
|
||||
spawnedJNI.nativeShellHolderId != null && spawnedJNI.nativeShellHolderId != 0,
|
||||
"Failed to spawn new JNI connected shell from existing shell.");
|
||||
@ -482,7 +484,8 @@ public class FlutterJNI {
|
||||
@Nullable String entrypointFunctionName,
|
||||
@Nullable String pathToEntrypointFunction,
|
||||
@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
|
||||
@ -491,7 +494,7 @@ 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, List)}, or
|
||||
* up during {@link #attachToNative()} or {@link #spawn(String, String, String, List, long)}, or
|
||||
* accumulated thereafter.
|
||||
*
|
||||
* <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 pathToEntrypointFunction,
|
||||
@NonNull AssetManager assetManager,
|
||||
@Nullable List<String> entrypointArgs) {
|
||||
@Nullable List<String> entrypointArgs,
|
||||
long engineId) {
|
||||
ensureRunningOnMainThread();
|
||||
ensureAttachedToNative();
|
||||
nativeRunBundleAndSnapshotFromLibrary(
|
||||
@ -1011,7 +1015,8 @@ public class FlutterJNI {
|
||||
entrypointFunctionName,
|
||||
pathToEntrypointFunction,
|
||||
assetManager,
|
||||
entrypointArgs);
|
||||
entrypointArgs,
|
||||
engineId);
|
||||
}
|
||||
|
||||
private native void nativeRunBundleAndSnapshotFromLibrary(
|
||||
@ -1020,7 +1025,8 @@ public class FlutterJNI {
|
||||
@Nullable String entrypointFunctionName,
|
||||
@Nullable String pathToEntrypointFunction,
|
||||
@NonNull AssetManager manager,
|
||||
@Nullable List<String> entrypointArgs);
|
||||
@Nullable List<String> entrypointArgs,
|
||||
long engineId);
|
||||
// ------ End Dart Execution Support -------
|
||||
|
||||
// --------- Start Platform Message Support ------
|
||||
|
@ -8,6 +8,7 @@ import android.content.res.AssetManager;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.UiThread;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import io.flutter.FlutterInjector;
|
||||
import io.flutter.Log;
|
||||
import io.flutter.embedding.engine.FlutterJNI;
|
||||
@ -40,6 +41,7 @@ public class DartExecutor implements BinaryMessenger {
|
||||
|
||||
@NonNull private final FlutterJNI flutterJNI;
|
||||
@NonNull private final AssetManager assetManager;
|
||||
private final long engineId;
|
||||
@NonNull private final DartMessenger dartMessenger;
|
||||
@NonNull private final BinaryMessenger binaryMessenger;
|
||||
private boolean isApplicationRunning = false;
|
||||
@ -57,9 +59,16 @@ public class DartExecutor implements BinaryMessenger {
|
||||
}
|
||||
};
|
||||
|
||||
@VisibleForTesting
|
||||
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.assetManager = assetManager;
|
||||
this.engineId = engineId;
|
||||
this.dartMessenger = new DartMessenger(flutterJNI);
|
||||
dartMessenger.setMessageHandler("flutter/isolate", isolateChannelMessageHandler);
|
||||
this.binaryMessenger = new DefaultBinaryMessenger(dartMessenger);
|
||||
@ -148,7 +157,8 @@ public class DartExecutor implements BinaryMessenger {
|
||||
dartEntrypoint.dartEntrypointFunctionName,
|
||||
dartEntrypoint.dartEntrypointLibrary,
|
||||
assetManager,
|
||||
dartEntrypointArgs);
|
||||
dartEntrypointArgs,
|
||||
engineId);
|
||||
|
||||
isApplicationRunning = true;
|
||||
}
|
||||
@ -174,7 +184,8 @@ public class DartExecutor implements BinaryMessenger {
|
||||
dartCallback.callbackHandle.callbackName,
|
||||
dartCallback.callbackHandle.callbackLibraryPath,
|
||||
dartCallback.androidAssetManager,
|
||||
null);
|
||||
null,
|
||||
engineId);
|
||||
|
||||
isApplicationRunning = true;
|
||||
}
|
||||
|
@ -195,7 +195,8 @@ static jobject SpawnJNI(JNIEnv* env,
|
||||
jstring jEntrypoint,
|
||||
jstring jLibraryUrl,
|
||||
jstring jInitialRoute,
|
||||
jobject jEntrypointArgs) {
|
||||
jobject jEntrypointArgs,
|
||||
jlong engineId) {
|
||||
jobject jni = env->NewObject(g_flutter_jni_class->obj(), g_jni_constructor);
|
||||
if (jni == nullptr) {
|
||||
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 entrypoint_args = fml::jni::StringListToVector(env, jEntrypointArgs);
|
||||
|
||||
auto spawned_shell_holder = ANDROID_SHELL_HOLDER->Spawn(
|
||||
jni_facade, entrypoint, libraryUrl, initial_route, entrypoint_args);
|
||||
auto spawned_shell_holder =
|
||||
ANDROID_SHELL_HOLDER->Spawn(jni_facade, entrypoint, libraryUrl,
|
||||
initial_route, entrypoint_args, engineId);
|
||||
|
||||
if (spawned_shell_holder == nullptr || !spawned_shell_holder->IsValid()) {
|
||||
FML_LOG(ERROR) << "Could not spawn Shell";
|
||||
@ -279,7 +281,8 @@ static void RunBundleAndSnapshotFromLibrary(JNIEnv* env,
|
||||
jstring jEntrypoint,
|
||||
jstring jLibraryUrl,
|
||||
jobject jAssetManager,
|
||||
jobject jEntrypointArgs) {
|
||||
jobject jEntrypointArgs,
|
||||
jlong engineId) {
|
||||
auto apk_asset_provider = std::make_unique<flutter::APKAssetProvider>(
|
||||
env, // jni environment
|
||||
jAssetManager, // asset manager
|
||||
@ -290,7 +293,7 @@ static void RunBundleAndSnapshotFromLibrary(JNIEnv* env,
|
||||
auto entrypoint_args = fml::jni::StringListToVector(env, jEntrypointArgs);
|
||||
|
||||
ANDROID_SHELL_HOLDER->Launch(std::move(apk_asset_provider), entrypoint,
|
||||
libraryUrl, entrypoint_args);
|
||||
libraryUrl, entrypoint_args, engineId);
|
||||
}
|
||||
|
||||
static jobject LookupCallbackInformation(JNIEnv* env,
|
||||
@ -687,7 +690,7 @@ bool RegisterApi(JNIEnv* env) {
|
||||
{
|
||||
.name = "nativeSpawn",
|
||||
.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;",
|
||||
.fnPtr = reinterpret_cast<void*>(&SpawnJNI),
|
||||
},
|
||||
@ -695,7 +698,7 @@ bool RegisterApi(JNIEnv* env) {
|
||||
.name = "nativeRunBundleAndSnapshotFromLibrary",
|
||||
.signature = "(JLjava/lang/String;Ljava/lang/String;"
|
||||
"Ljava/lang/String;Landroid/content/res/"
|
||||
"AssetManager;Ljava/util/List;)V",
|
||||
"AssetManager;Ljava/util/List;J)V",
|
||||
.fnPtr = reinterpret_cast<void*>(&RunBundleAndSnapshotFromLibrary),
|
||||
},
|
||||
{
|
||||
|
@ -59,6 +59,7 @@ public class FlutterEngineGroupComponentTest {
|
||||
when(mockFlutterJNI.isAttached()).thenAnswer(invocation -> jniAttached);
|
||||
doAnswer(invocation -> jniAttached = true).when(mockFlutterJNI).attachToNative();
|
||||
GeneratedPluginRegistrant.clearRegisteredEngines();
|
||||
FlutterEngine.resetNextEngineId();
|
||||
|
||||
when(mockFlutterLoader.findAppBundlePath()).thenReturn("some/path/to/flutter_assets");
|
||||
|
||||
@ -190,7 +191,8 @@ public class FlutterEngineGroupComponentTest {
|
||||
eq("other entrypoint"),
|
||||
isNull(),
|
||||
any(AssetManager.class),
|
||||
nullable(List.class));
|
||||
nullable(List.class),
|
||||
eq(1l));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -213,14 +215,20 @@ public class FlutterEngineGroupComponentTest {
|
||||
nullable(String.class),
|
||||
nullable(String.class),
|
||||
nullable(String.class),
|
||||
nullable(List.class));
|
||||
nullable(List.class),
|
||||
eq(2l));
|
||||
|
||||
FlutterEngine secondEngine =
|
||||
engineGroupUnderTest.createAndRunEngine(ctx, mock(DartEntrypoint.class), "/bar");
|
||||
|
||||
assertEquals(2, engineGroupUnderTest.activeEngines.size());
|
||||
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
|
||||
@ -238,7 +246,8 @@ public class FlutterEngineGroupComponentTest {
|
||||
nullable(String.class),
|
||||
isNull(),
|
||||
any(AssetManager.class),
|
||||
eq(firstDartEntrypointArgs));
|
||||
eq(firstDartEntrypointArgs),
|
||||
eq(1l));
|
||||
|
||||
when(mockFlutterJNI.isAttached()).thenReturn(true);
|
||||
jniAttached = false;
|
||||
@ -251,7 +260,8 @@ public class FlutterEngineGroupComponentTest {
|
||||
nullable(String.class),
|
||||
nullable(String.class),
|
||||
nullable(String.class),
|
||||
nullable(List.class));
|
||||
nullable(List.class),
|
||||
eq(2l));
|
||||
List<String> secondDartEntrypointArgs = new ArrayList<String>();
|
||||
FlutterEngine secondEngine =
|
||||
engineGroupUnderTest.createAndRunEngine(
|
||||
@ -265,7 +275,8 @@ public class FlutterEngineGroupComponentTest {
|
||||
nullable(String.class),
|
||||
nullable(String.class),
|
||||
nullable(String.class),
|
||||
eq(secondDartEntrypointArgs));
|
||||
eq(secondDartEntrypointArgs),
|
||||
eq(2l));
|
||||
}
|
||||
|
||||
@Test
|
||||
@ -309,7 +320,8 @@ public class FlutterEngineGroupComponentTest {
|
||||
nullable(String.class),
|
||||
isNull(),
|
||||
any(AssetManager.class),
|
||||
nullable(List.class));
|
||||
nullable(List.class),
|
||||
eq(1l));
|
||||
|
||||
when(mockFlutterJNI.isAttached()).thenReturn(true);
|
||||
jniAttached = false;
|
||||
@ -322,7 +334,8 @@ public class FlutterEngineGroupComponentTest {
|
||||
nullable(String.class),
|
||||
nullable(String.class),
|
||||
nullable(String.class),
|
||||
nullable(List.class));
|
||||
nullable(List.class),
|
||||
eq(2l));
|
||||
|
||||
PlatformViewsController controller = new PlatformViewsController();
|
||||
boolean waitForRestorationData = false;
|
||||
|
@ -79,6 +79,7 @@ public class FlutterEngineTest {
|
||||
.when(flutterJNI)
|
||||
.attachToNative();
|
||||
GeneratedPluginRegistrant.clearRegisteredEngines();
|
||||
FlutterEngine.resetNextEngineId();
|
||||
}
|
||||
|
||||
@After
|
||||
@ -130,6 +131,23 @@ public class FlutterEngineTest {
|
||||
.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
|
||||
// https://github.com/flutter/flutter/issues/78625.
|
||||
@Test
|
||||
|
@ -60,7 +60,7 @@ public class DeferredComponentChannelTest {
|
||||
public void deferredComponentChannel_installCompletesResults() {
|
||||
MethodChannel rawChannel = mock(MethodChannel.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();
|
||||
DeferredComponentChannel fakeDeferredComponentChannel =
|
||||
new DeferredComponentChannel(dartExecutor);
|
||||
|
@ -160,6 +160,10 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
|
||||
std::unique_ptr<flutter::ConnectionCollection> _connections;
|
||||
}
|
||||
|
||||
- (int64_t)engineIdentifier {
|
||||
return reinterpret_cast<int64_t>((__bridge void*)self);
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
return [self initWithName:@"FlutterEngine" project:nil allowHeadlessExecution:YES];
|
||||
}
|
||||
@ -241,6 +245,11 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
|
||||
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)) {
|
||||
[center addObserver:self
|
||||
selector:@selector(sceneWillEnterForeground:)
|
||||
@ -704,9 +713,13 @@ static constexpr int kNumProfilerSamplesPerSec = 5;
|
||||
libraryURI:(NSString*)libraryOrNil
|
||||
entrypointArgs:(NSArray<NSString*>*)entrypointArgs {
|
||||
// Launch the Dart application with the inferred run configuration.
|
||||
self.shell.RunEngine([self.dartProject runConfigurationForEntrypoint:entrypoint
|
||||
flutter::RunConfiguration configuration =
|
||||
[self.dartProject runConfigurationForEntrypoint:entrypoint
|
||||
libraryOrNil:libraryOrNil
|
||||
entrypointArgs:entrypointArgs]);
|
||||
entrypointArgs:entrypointArgs];
|
||||
|
||||
configuration.SetEngineId(self.engineIdentifier);
|
||||
self.shell.RunEngine(std::move(configuration));
|
||||
}
|
||||
|
||||
- (void)setUpShell:(std::unique_ptr<flutter::Shell>)shell
|
||||
@ -1451,6 +1464,8 @@ static void SetEntryPoint(flutter::Settings* settings, NSString* entrypoint, NSS
|
||||
libraryOrNil:libraryURI
|
||||
entrypointArgs:entrypointArgs];
|
||||
|
||||
configuration.SetEngineId(result.engineIdentifier);
|
||||
|
||||
fml::WeakPtr<flutter::PlatformView> platform_view = _shell->GetPlatformView();
|
||||
FML_DCHECK(platform_view);
|
||||
// Static-cast safe since this class always creates PlatformViewIOS instances.
|
||||
|
@ -273,6 +273,20 @@ FLUTTER_ASSERT_ARC
|
||||
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 {
|
||||
FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"foobar"];
|
||||
XCTestExpectation* gotMessage = [self expectationWithDescription:@"gotMessage"];
|
||||
|
@ -87,6 +87,22 @@ NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@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
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
@ -642,6 +642,8 @@ static void SetThreadPriority(FlutterThreadPriority priority) {
|
||||
std::cout << message << std::endl;
|
||||
};
|
||||
|
||||
flutterArguments.engine_id = reinterpret_cast<int64_t>((__bridge void*)self);
|
||||
|
||||
static size_t sTaskRunnerIdentifiers = 0;
|
||||
const FlutterTaskRunnerDescription cocoa_task_runner_description = {
|
||||
.struct_size = sizeof(FlutterTaskRunnerDescription),
|
||||
@ -1159,6 +1161,11 @@ static void SetThreadPriority(FlutterThreadPriority priority) {
|
||||
_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 {
|
||||
_platformViewsChannel =
|
||||
[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) {
|
||||
FlutterThreadSynchronizer* threadSynchronizer = [[FlutterThreadSynchronizer alloc] init];
|
||||
[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.
|
||||
*/
|
||||
- (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
|
||||
|
||||
@interface FlutterEngine (Tests)
|
||||
|
@ -85,3 +85,11 @@ void backgroundTest() {
|
||||
void sendFooMessage() {
|
||||
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));
|
||||
}
|
||||
|
||||
if (SAFE_ACCESS(args, engine_id, 0) != 0) {
|
||||
run_configuration.SetEngineId(args->engine_id);
|
||||
}
|
||||
|
||||
if (!run_configuration.IsValid()) {
|
||||
return LOG_EMBEDDER_ERROR(
|
||||
kInvalidArguments,
|
||||
|
@ -2631,6 +2631,11 @@ typedef struct {
|
||||
/// the native view. The callback is invoked from a task posted to the
|
||||
/// platform thread.
|
||||
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;
|
||||
|
||||
#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);
|
||||
|
||||
FlEngine* self = FL_ENGINE(g_object_new(fl_engine_get_type(), nullptr));
|
||||
|
||||
self->project = FL_DART_PROJECT(g_object_ref(project));
|
||||
self->renderer = FL_RENDERER(g_object_ref(renderer));
|
||||
if (binary_messenger != nullptr) {
|
||||
@ -563,6 +564,12 @@ static FlEngine* fl_engine_new_full(FlDartProject* project,
|
||||
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,
|
||||
FlRenderer* renderer) {
|
||||
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;
|
||||
args.dart_entrypoint_argv =
|
||||
reinterpret_cast<const char* const*>(dart_entrypoint_args);
|
||||
args.engine_id = reinterpret_cast<int64_t>(self);
|
||||
|
||||
FlutterCompositor compositor = {};
|
||||
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);
|
||||
|
||||
/**
|
||||
* 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
|
||||
|
||||
#endif // FLUTTER_SHELL_PLATFORM_LINUX_FL_ENGINE_PRIVATE_H_
|
||||
|
@ -416,6 +416,29 @@ TEST(FlEngineTest, DartEntrypointArgs) {
|
||||
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) {
|
||||
g_autofree gchar* initial_language = g_strdup(g_getenv("LANGUAGE"));
|
||||
g_setenv("LANGUAGE", "de:en_US", TRUE);
|
||||
|
@ -396,3 +396,11 @@ void onMetricsChangedSignalViewIds() {
|
||||
void mergedUIThread() {
|
||||
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;
|
||||
}
|
||||
|
||||
FLUTTER_EXPORT FlutterDesktopEngineRef FlutterDesktopEngineForId(
|
||||
int64_t engine_id) {
|
||||
return HandleForEngine(
|
||||
flutter::FlutterWindowsEngine::GetEngineForId(engine_id));
|
||||
}
|
||||
|
||||
bool FlutterDesktopEngineRun(FlutterDesktopEngineRef engine,
|
||||
const char* entry_point) {
|
||||
std::string_view entry_point_view{""};
|
||||
|
@ -233,6 +233,10 @@ FlutterWindowsEngine::~FlutterWindowsEngine() {
|
||||
Stop();
|
||||
}
|
||||
|
||||
FlutterWindowsEngine* FlutterWindowsEngine::GetEngineForId(int64_t engine_id) {
|
||||
return reinterpret_cast<FlutterWindowsEngine*>(engine_id);
|
||||
}
|
||||
|
||||
void FlutterWindowsEngine::SetSwitches(
|
||||
const std::vector<std::string>& 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.command_line_argc = static_cast<int>(argv.size());
|
||||
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
|
||||
// argument and the project.
|
||||
|
@ -96,6 +96,12 @@ class 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
|
||||
// unspecified, defaults to main().
|
||||
//
|
||||
|
@ -65,6 +65,15 @@ FLUTTER_EXPORT void FlutterDesktopEngineRegisterPlatformViewType(
|
||||
const char* view_type_name,
|
||||
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)
|
||||
}
|
||||
#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 flutter
|
||||
|
Loading…
x
Reference in New Issue
Block a user