From 64a17788832ddacacf773f509c1adbff88108ef9 Mon Sep 17 00:00:00 2001 From: Xiao Yu Date: Wed, 13 Jan 2021 02:16:28 -0800 Subject: [PATCH 01/19] half of the plumbing --- .../platform/android/android_shell_holder.cc | 93 +++++++++++++++++-- shell/platform/android/android_shell_holder.h | 50 +++++++++- .../embedding/engine/FlutterEngine.java | 15 +++ .../embedding/engine/FlutterEngineGroup.java | 30 ++++++ .../flutter/embedding/engine/FlutterJNI.java | 77 ++++++++++++++- .../io/flutter/util/Preconditions.java | 29 ++++++ .../io/flutter/view/FlutterNativeView.java | 5 + .../platform/android/platform_view_android.cc | 4 +- .../platform/android/platform_view_android.h | 4 +- shell/platform/darwin/ios/platform_view_ios.h | 2 +- 10 files changed, 292 insertions(+), 17 deletions(-) create mode 100644 shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java diff --git a/shell/platform/android/android_shell_holder.cc b/shell/platform/android/android_shell_holder.cc index 00aed2b25b876..a052d856b5688 100644 --- a/shell/platform/android/android_shell_holder.cc +++ b/shell/platform/android/android_shell_holder.cc @@ -6,6 +6,7 @@ #include "flutter/shell/platform/android/android_shell_holder.h" +#include #include #include #include @@ -14,11 +15,14 @@ #include #include +#include "flutter/fml/logging.h" #include "flutter/fml/make_copyable.h" #include "flutter/fml/message_loop.h" #include "flutter/fml/platform/android/jni_util.h" #include "flutter/shell/common/rasterizer.h" +#include "flutter/shell/common/thread_host.h" #include "flutter/shell/platform/android/platform_view_android.h" +#include "flutter/shell/platform/android/context/android_context.h" namespace flutter { @@ -33,13 +37,14 @@ AndroidShellHolder::AndroidShellHolder( std::shared_ptr jni_facade, bool is_background_view) : settings_(std::move(settings)), jni_facade_(jni_facade) { - static size_t shell_count = 1; - auto thread_label = std::to_string(shell_count++); + static size_t thread_host_count = 1; + auto thread_label = std::to_string(thread_host_count++); + thread_host_ = std::make_shared(); if (is_background_view) { - thread_host_ = {thread_label, ThreadHost::Type::UI}; + *thread_host_ = {thread_label, ThreadHost::Type::UI}; } else { - thread_host_ = {thread_label, ThreadHost::Type::UI | + *thread_host_ = {thread_label, ThreadHost::Type::UI | ThreadHost::Type::RASTER | ThreadHost::Type::IO}; } @@ -82,14 +87,14 @@ AndroidShellHolder::AndroidShellHolder( fml::RefPtr platform_runner = fml::MessageLoop::GetCurrent().GetTaskRunner(); if (is_background_view) { - auto single_task_runner = thread_host_.ui_thread->GetTaskRunner(); + auto single_task_runner = thread_host_->ui_thread->GetTaskRunner(); raster_runner = single_task_runner; ui_runner = single_task_runner; io_runner = single_task_runner; } else { - raster_runner = thread_host_.raster_thread->GetTaskRunner(); - ui_runner = thread_host_.ui_thread->GetTaskRunner(); - io_runner = thread_host_.io_thread->GetTaskRunner(); + raster_runner = thread_host_->raster_thread->GetTaskRunner(); + ui_runner = thread_host_->ui_thread->GetTaskRunner(); + io_runner = thread_host_->io_thread->GetTaskRunner(); } flutter::TaskRunners task_runners(thread_label, // label @@ -129,9 +134,25 @@ AndroidShellHolder::AndroidShellHolder( is_valid_ = shell_ != nullptr; } +AndroidShellHolder::AndroidShellHolder(Settings settings, + std::shared_ptr jni_facade, + std::shared_ptr thread_host, + std::unique_ptr shell, + fml::WeakPtr platform_view) + : settings_(std::move(settings)), + jni_facade_(jni_facade), + platform_view_(platform_view), + thread_host_(thread_host), + shell_(std::move(shell)) { + FML_DCHECK(jni_facade); + FML_DCHECK(shell_); + FML_DCHECK(platform_view_); + is_valid_ = shell_ != nullptr; +} + AndroidShellHolder::~AndroidShellHolder() { shell_.reset(); - thread_host_.Reset(); + thread_host_->Reset(); } bool AndroidShellHolder::IsValid() const { @@ -142,6 +163,60 @@ const flutter::Settings& AndroidShellHolder::GetSettings() const { return settings_; } +std::unique_ptr AndroidShellHolder::Spawn( + std::shared_ptr jni_facade, + RunConfiguration configuration) const { + FML_DCHECK(shell_) << "A new Shell can only be spawned if the current Shell " + "is properly constructed"; + + // Take the old Settings but overwrite the entrypoint components with the + // new parameter's. + Settings newSettings = GetSettings(); + newSettings.advisory_script_uri = configuration.GetEntrypointLibrary(); + newSettings.advisory_script_entrypoint = configuration.GetEntrypoint(); + + fml::WeakPtr weak_platform_view; + PlatformViewAndroid* android_platform_view = platform_view_.get(); + // There's some indirection with platform_view_ being a weak pointer but + // we just checked that the shell_ exists above and a valid shell is the + // owner of the platform view so this weak pointer always exists. + FML_DCHECK(android_platform_view); + std::shared_ptr android_context = + android_platform_view->GetAndroidContext(); + FML_DCHECK(android_context); + + // This is a synchronous call, so the captures don't have race checks. + Shell::CreateCallback on_create_platform_view = + [&jni_facade, android_context, &weak_platform_view](Shell& shell) { + std::unique_ptr platform_view_android; + platform_view_android = std::make_unique( + shell, // delegate + shell.GetTaskRunners(), // task runners + jni_facade, // JNI interop + shell.GetSettings() + .enable_software_rendering // use software rendering + ); + weak_platform_view = platform_view_android->GetWeakPtr(); + shell.OnDisplayUpdates(DisplayUpdateType::kStartup, + {Display(jni_facade->GetDisplayRefreshRate())}); + return platform_view_android; + }; + + Shell::CreateCallback on_create_rasterizer = [](Shell& shell) { + return std::make_unique(shell); + }; + + std::unique_ptr shell = + shell_->Spawn(std::move(newSettings), on_create_platform_view, on_create_rasterizer); + + return AndroidShellHolder( + newSettings, + jni_facade, + thread_host_, + shell, + weak_platform_view); +} + void AndroidShellHolder::Launch(RunConfiguration config) { if (!IsValid()) { return; diff --git a/shell/platform/android/android_shell_holder.h b/shell/platform/android/android_shell_holder.h index d888893f77c27..d1c4f67d74cca 100644 --- a/shell/platform/android/android_shell_holder.h +++ b/shell/platform/android/android_shell_holder.h @@ -19,16 +19,64 @@ namespace flutter { + //---------------------------------------------------------------------------- + /// @brief This is the Android owner of the core engine Shell. + /// + /// This is the top orchestrator class on the C++ side for the + /// Android embedding. It corresponds to a FlutterEngine on the + /// Java side. This class is in C++ because the core Shell is in + /// C++ and an Android specific orchestrator needs to exist to + /// compose it with other Android specific components such as + /// the PlatformViewAndroid. This composition of many to one + /// C++ components would be difficult to do through JNI whereas + /// a FlutterEngine and AndroidShellHolder has a 1:1 relationship. + /// + /// Technically, the FlutterJNI class owns this AndroidShellHolder + /// class instance, but the FlutterJNI class is meant to be mostly + /// static and has minimal state to perform the C++ pointer <-> + /// Java class instance translation. + /// class AndroidShellHolder { public: AndroidShellHolder(flutter::Settings settings, std::shared_ptr jni_facade, bool is_background_view); + //---------------------------------------------------------------------------- + /// @brief Constructor with its components injected. + /// + /// This is similar to the standard constructor, except its + /// members were constructed elsewhere and injected. + /// + AndroidShellHolder(flutter::Settings settings, + std::shared_ptr jni_facade, + std::shared_ptr thread_host, + std::unique_ptr shell, + fml::WeakPtr); + ~AndroidShellHolder(); bool IsValid() const; + //---------------------------------------------------------------------------- + /// @brief This is a factory for a derived AndroidShellHolder from an + /// existing AndroidShellHolder. + /// + /// The new and existing AndroidShellHolder and underlying + /// Shells Creates one Shell from another Shell where the created Shell + /// takes the opportunity to share any internal components it can. + /// This results is a Shell that has a smaller startup time cost + /// and a smaller memory footprint than an Shell created with a + /// Create function. + /// + /// The new Shell's flutter::Settings cannot be changed from that + /// of the initial Shell. The RunConfiguration subcomponent can + /// be changed however in the spawned Shell to run a different + /// entrypoint than the existing shell. + /// + std::unique_ptr Spawn(std::shared_ptr jni_facade, + RunConfiguration configuration) const; + void Launch(RunConfiguration configuration); const flutter::Settings& GetSettings() const; @@ -46,7 +94,7 @@ class AndroidShellHolder { const flutter::Settings settings_; const std::shared_ptr jni_facade_; fml::WeakPtr platform_view_; - ThreadHost thread_host_; + std::shared_ptr thread_host_; std::unique_ptr shell_; bool is_valid_ = false; uint64_t next_pointer_flow_id_ = 0; diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 16007fa1e23c8..93437563b41c7 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -114,6 +114,12 @@ public void onPreEngineRestart() { platformViewsController.onPreEngineRestart(); restorationChannel.clearData(); } + + @Override + public void onEngineDestroy() { + // This inner implementation doesn't do anything since FlutterEngine sent this + // notification in the first place. It's meant for external listeners. + } }; /** @@ -382,6 +388,9 @@ private void registerPlugins() { */ public void destroy() { Log.v(TAG, "Destroying."); + for (EngineLifecycleListener listener : engineLifecycleListeners) { + listener.onEngineDestroy(); + } // The order that these things are destroyed is important. pluginRegistry.destroy(); platformViewsController.onDetachedFromJNI(); @@ -567,5 +576,11 @@ public ContentProviderControlSurface getContentProviderControlSurface() { public interface EngineLifecycleListener { /** Lifecycle callback invoked before a hot restart of the Flutter engine. */ void onPreEngineRestart(); + /** + * Lifecycle callback invoked before the Flutter engine is destroyed. + * + * For the duration of the call, the Flutter engine is still valid. + */ + void onEngineDestroy(); } } diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java new file mode 100644 index 0000000000000..41dba09d24663 --- /dev/null +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java @@ -0,0 +1,30 @@ +// Copyright 2013 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. + +package io.flutter.embedding.engine; + +import java.util.List; + +/** + * This class is experimental. Please do not ship production code using it. + * + * Represents a collection of {@link FlutterEngine}s who share resources among + * each other to allow them to be created faster and with less memory than + * calling the {@link FlutterEngine}'s constructor multiple times. + * + * When creating or recreating the first {@link FlutterEngine} in the + * FlutterEngineGroup, the behavior is the same as creating a + * {@link FlutterEngine} via its constructor. When subsequent + * {@link FlutterEngine}s are created, resources from an existing living + * {@link FlutterEngine} is re-used. + * + * Deleting a FlutterEngineGroup doesn't invalidate its existing + * {@link FlutterEngine}s, but it eliminates the possibility to create more + * {@link FlutterEngine}s in that group. + */ +public class FlutterEngineGroup { + + private List activeEngines = ArrayList<>(); + +} diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index e122e2528a936..238183b60d534 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -28,6 +28,7 @@ import io.flutter.plugin.common.StandardMessageCodec; import io.flutter.plugin.localization.LocalizationPlugin; import io.flutter.plugin.platform.PlatformViewsController; +import io.flutter.util.Preconditions; import io.flutter.view.AccessibilityBridge; import io.flutter.view.FlutterCallbackInformation; import java.nio.ByteBuffer; @@ -104,19 +105,35 @@ public class FlutterJNI { * *

This must be called before any other native methods, and can be overridden by tests to avoid * loading native libraries. + * + *

This method should only be called once. */ public void loadLibrary() { + if (loadLibraryCalled = true) { + Log.w(TAG, "FlutterJNI.loadLibrary called more than once"); + } + System.loadLibrary("flutter"); + loadLibraryCalled = true; } + private static boolean loadLibraryCalled = false; /** * Prefetch the default font manager provided by SkFontMgr::RefDefault() which is a process-wide * singleton owned by Skia. Note that, the first call to SkFontMgr::RefDefault() will take * noticeable time, but later calls will return a reference to the preexisting font manager. + * + *

This method should only be called once. */ public void prefetchDefaultFontManager() { + if (prefetchDefaultFontManagerCalled = true) { + Log.w(TAG, "FlutterJNI.prefetchDefaultFontManager called more than once"); + } + FlutterJNI.nativePrefetchDefaultFontManager(); + prefetchDefaultFontManagerCalled = true; } + private static boolean prefetchDefaultFontManagerCalled = false; /** * Perform one time initialization of the Dart VM and Flutter engine. @@ -137,9 +154,15 @@ public void init( @NonNull String appStoragePath, @NonNull String engineCachesPath, long initTimeMillis) { + if (initCalled = true) { + Log.w(TAG, "FlutterJNI.init called more than once"); + } + FlutterJNI.nativeInit( - context, args, bundlePath, appStoragePath, engineCachesPath, initTimeMillis); + context, args, bundlePath, appStoragePath, engineCachesPath, initTimeMillis); + initCalled = true; } + private static boolean initCalled = false; // END methods related to FlutterLoader @Nullable private static AsyncWaitForVsyncDelegate asyncWaitForVsyncDelegate; @@ -167,20 +190,35 @@ public static native void nativeInit( private native boolean nativeGetIsSoftwareRenderingEnabled(); @UiThread - // TODO(mattcarroll): add javadocs + /** + * Checks launch settings for whether software rendering is requested. + * + * The value is the same per program. + */ public boolean getIsSoftwareRenderingEnabled() { return nativeGetIsSoftwareRenderingEnabled(); } @Nullable - // TODO(mattcarroll): add javadocs + /** + * Observatory URI for the VM instance. + * + * Its value is set by the native engine once + * {@link #init(Context, String[], String, String, String, long)} is run. + */ public static String getObservatoryUri() { return observatoryUri; } public static void setRefreshRateFPS(float refreshRateFPS) { + if (setRefreshRateFPSCalled = true) { + Log.w(TAG, "FlutterJNI.setRefreshRateFPS called more than once"); + } + FlutterJNI.refreshRateFPS = refreshRateFPS; + setRefreshRateFPSCalled = true; } + private static boolean setRefreshRateFPSCalled = false; // TODO(mattcarroll): add javadocs public static void setAsyncWaitForVsyncDelegate(@Nullable AsyncWaitForVsyncDelegate delegate) { @@ -220,6 +258,9 @@ public static native void nativeOnVsync( // ----- End Engine FlutterTextUtils Methods ---- + // Below represents the stateful part of the FlutterJNI instances that aren't static per program. + // Conceptually, it represents a native shell instance. + @Nullable private Long nativePlatformViewId; @Nullable private AccessibilityDelegate accessibilityDelegate; @Nullable private PlatformMessageHandler platformMessageHandler; @@ -272,6 +313,36 @@ public long performNativeAttach(@NonNull FlutterJNI flutterJNI, boolean isBackgr private native long nativeAttach(@NonNull FlutterJNI flutterJNI, boolean isBackgroundView); + /** + * Spawns a new FlutterJNI instance from the current instance. + * + * This creates another native shell from the current shell. This causes the 2 shells to re-use + * some of the shared resources, reducing the total memory consumption versus creating a + * new FlutterJNI by calling its standard constructor. + * + * This can only be called once the current FlutterJNI instance is attached by calling + * {@link #attachToNative(boolean)}. + * + * Static methods that should be only called once such as + * {@link #init(Context, String[], String, String, String, long)} or + * {@link #setRefreshRateFPS(float)} shouldn't be called again on the spawned FlutterJNI instance. + */ + @UiThread + public FlutterJNI spawn() { + ensureRunningOnMainThread(); + ensureAttachedToNative(); + FlutterJNI spawnedJNI = new FlutterJNI(); + spawnedJNI.nativePlatformViewId = nativeSpawn(this); + Preconditions.checkState( + spawnedJNI.nativePlatformViewId != null && + spawnedJNI.nativePlatformViewId > 0, + "Failed to spawn new JNI connected shell from existing shell."); + + return spawnedJNI; + } + + private native long nativeSpawn(@NonNull FlutterJNI flutterJNI); + /** * Detaches this {@code FlutterJNI} instance from Flutter's native engine, which precludes any * further communication between Android code and Flutter's platform agnostic engine. diff --git a/shell/platform/android/io/flutter/util/Preconditions.java b/shell/platform/android/io/flutter/util/Preconditions.java index 3a089c1b1ea80..c9a63fb56eae7 100644 --- a/shell/platform/android/io/flutter/util/Preconditions.java +++ b/shell/platform/android/io/flutter/util/Preconditions.java @@ -4,6 +4,8 @@ package io.flutter.util; +import androidx.annotation.Nullable; + /** * Static convenience methods that help a method or constructor check whether it was invoked * correctly (that is, whether its preconditions were met). @@ -24,4 +26,31 @@ public static T checkNotNull(T reference) { } return reference; } + + /** + * Ensures the truth of an expression involving the state of the calling instance. + * + * @param expression a boolean expression that must be checked to be true + * @throws IllegalStateException if {@code expression} is false + */ + public static void checkState(boolean expression) { + if (!expression) { + throw new IllegalStateException(); + } + } + + + /** + * Ensures the truth of an expression involving the state of the calling instance. + * + * @param expression a boolean expression that must be checked to be true + * @param errorMessage the exception message to use if the check fails; will be converted to a + * string using {@link String#valueOf(Object)} + * @throws IllegalStateException if {@code expression} is false + */ + public static void checkState(boolean expression, @Nullable Object errorMessage) { + if (!expression) { + throw new IllegalStateException(String.valueOf(errorMessage)); + } + } } diff --git a/shell/platform/android/io/flutter/view/FlutterNativeView.java b/shell/platform/android/io/flutter/view/FlutterNativeView.java index 2d5ee8c40ad56..1ea6d5ef29377 100644 --- a/shell/platform/android/io/flutter/view/FlutterNativeView.java +++ b/shell/platform/android/io/flutter/view/FlutterNativeView.java @@ -165,5 +165,10 @@ public void onPreEngineRestart() { } mPluginRegistry.onPreEngineRestart(); } + + public void onEngineDestroy() { + // The old embedding doesn't actually have a FlutterEngine. It interacts with the JNI + // directly. + } } } diff --git a/shell/platform/android/platform_view_android.cc b/shell/platform/android/platform_view_android.cc index 68b2b8fdb1a3f..2434dcc78bfac 100644 --- a/shell/platform/android/platform_view_android.cc +++ b/shell/platform/android/platform_view_android.cc @@ -63,11 +63,11 @@ PlatformViewAndroid::PlatformViewAndroid( platform_view_android_delegate_(jni_facade) { if (use_software_rendering) { android_context_ = - std::make_unique(AndroidRenderingAPI::kSoftware); + std::make_shared(AndroidRenderingAPI::kSoftware); } else { #if SHELL_ENABLE_VULKAN android_context_ = - std::make_unique(AndroidRenderingAPI::kVulkan); + std::make_shared(AndroidRenderingAPI::kVulkan); #else // SHELL_ENABLE_VULKAN android_context_ = std::make_unique( AndroidRenderingAPI::kOpenGLES, diff --git a/shell/platform/android/platform_view_android.h b/shell/platform/android/platform_view_android.h index 49e3c15c13929..4d22500f00654 100644 --- a/shell/platform/android/platform_view_android.h +++ b/shell/platform/android/platform_view_android.h @@ -108,9 +108,11 @@ class PlatformViewAndroid final : public PlatformView { std::unique_ptr updated_asset_resolver, AssetResolver::AssetResolverType type) override; + const std::shared_ptr& GetAndroidContext() { return android_context_; } + private: const std::shared_ptr jni_facade_; - std::unique_ptr android_context_; + std::shared_ptr android_context_; std::shared_ptr surface_factory_; PlatformViewAndroidDelegate platform_view_android_delegate_; diff --git a/shell/platform/darwin/ios/platform_view_ios.h b/shell/platform/darwin/ios/platform_view_ios.h index 46cb3e027c448..2425fea5bae9a 100644 --- a/shell/platform/darwin/ios/platform_view_ios.h +++ b/shell/platform/darwin/ios/platform_view_ios.h @@ -93,7 +93,7 @@ class PlatformViewIOS final : public PlatformView { // |PlatformView| void SetSemanticsEnabled(bool enabled) override; - /** Acessor for the `IOSContext` associated with the platform view. */ + /** Accessor for the `IOSContext` associated with the platform view. */ const std::shared_ptr& GetIosContext() { return ios_context_; } private: From 37acd45559daf4723c5450b3a1702ff5780d53ac Mon Sep 17 00:00:00 2001 From: Xiao Yu Date: Thu, 14 Jan 2021 01:13:09 -0800 Subject: [PATCH 02/19] piped through, start manual testing --- ci/licenses_golden/licenses_flutter | 1 + shell/platform/android/BUILD.gn | 1 + .../platform/android/android_shell_holder.cc | 12 +- shell/platform/android/android_shell_holder.h | 7 +- .../embedding/engine/FlutterEngine.java | 40 +++++- .../embedding/engine/FlutterEngineGroup.java | 53 +++++++- .../flutter/embedding/engine/FlutterJNI.java | 122 ++++++++++-------- .../embedding/engine/dart/DartExecutor.java | 14 +- .../android/platform_view_android_jni_impl.cc | 31 +++++ tools/android_lint/project.xml | 4 +- 10 files changed, 215 insertions(+), 70 deletions(-) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 9c211f9916270..341f925b3bf1b 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -747,6 +747,7 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/Splas FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/TransparencyMode.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineCache.java +FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterOverlaySurface.java diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 0a2189a8e3afd..5094c05b4d62c 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -150,6 +150,7 @@ android_java_sources = [ "io/flutter/embedding/android/TransparencyMode.java", "io/flutter/embedding/engine/FlutterEngine.java", "io/flutter/embedding/engine/FlutterEngineCache.java", + "io/flutter/embedding/engine/FlutterEngineGroup.java", "io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java", "io/flutter/embedding/engine/FlutterJNI.java", "io/flutter/embedding/engine/FlutterOverlaySurface.java", diff --git a/shell/platform/android/android_shell_holder.cc b/shell/platform/android/android_shell_holder.cc index a052d856b5688..d29c0074f08ff 100644 --- a/shell/platform/android/android_shell_holder.cc +++ b/shell/platform/android/android_shell_holder.cc @@ -164,16 +164,16 @@ const flutter::Settings& AndroidShellHolder::GetSettings() const { } std::unique_ptr AndroidShellHolder::Spawn( - std::shared_ptr jni_facade, - RunConfiguration configuration) const { + std::shared_ptr jni_facade, + std::string entrypoint, std::string libraryUr) const { FML_DCHECK(shell_) << "A new Shell can only be spawned if the current Shell " "is properly constructed"; // Take the old Settings but overwrite the entrypoint components with the // new parameter's. Settings newSettings = GetSettings(); - newSettings.advisory_script_uri = configuration.GetEntrypointLibrary(); - newSettings.advisory_script_entrypoint = configuration.GetEntrypoint(); + newSettings.advisory_script_entrypoint = entrypoint; + newSettings.advisory_script_uri = libraryUr; fml::WeakPtr weak_platform_view; PlatformViewAndroid* android_platform_view = platform_view_.get(); @@ -209,11 +209,11 @@ std::unique_ptr AndroidShellHolder::Spawn( std::unique_ptr shell = shell_->Spawn(std::move(newSettings), on_create_platform_view, on_create_rasterizer); - return AndroidShellHolder( + return std::make_unique( newSettings, jni_facade, thread_host_, - shell, + std::move(shell), weak_platform_view); } diff --git a/shell/platform/android/android_shell_holder.h b/shell/platform/android/android_shell_holder.h index d1c4f67d74cca..8f2cc133bcc33 100644 --- a/shell/platform/android/android_shell_holder.h +++ b/shell/platform/android/android_shell_holder.h @@ -74,8 +74,13 @@ class AndroidShellHolder { /// be changed however in the spawned Shell to run a different /// entrypoint than the existing shell. /// + /// Since the AndroidShellHolder both binds downwards to a Shell + /// and also upwards to JNI callbacks that the PlatformViewAndroid + /// makes, the JNI instance holding this AndroidShellHolder should + /// be created first to supply the jni_facade callback. + /// std::unique_ptr Spawn(std::shared_ptr jni_facade, - RunConfiguration configuration) const; + std::string entrypoint, std::string libraryUr) const; void Launch(RunConfiguration configuration); diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 93437563b41c7..867f059b89bce 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -12,6 +12,7 @@ import io.flutter.FlutterInjector; import io.flutter.Log; import io.flutter.embedding.engine.dart.DartExecutor; +import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint; import io.flutter.embedding.engine.deferredcomponents.DeferredComponentManager; import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.embedding.engine.plugins.PluginRegistry; @@ -75,6 +76,7 @@ public class FlutterEngine { private static final String TAG = "FlutterEngine"; + @NonNull private final Context context; @NonNull private final FlutterJNI flutterJNI; @NonNull private final FlutterRenderer renderer; @NonNull private final DartExecutor dartExecutor; @@ -275,6 +277,7 @@ public FlutterEngine( @Nullable String[] dartVmArgs, boolean automaticallyRegisterPlugins, boolean waitForRestorationData) { + this.context = context; AssetManager assetManager; try { assetManager = context.createPackageContext(context.getPackageName(), 0).getAssets(); @@ -310,15 +313,23 @@ public FlutterEngine( if (flutterLoader == null) { flutterLoader = FlutterInjector.instance().flutterLoader(); } - flutterLoader.startInitialization(context.getApplicationContext()); - flutterLoader.ensureInitializationComplete(context, dartVmArgs); + + if (!flutterJNI.isAttached()) { + flutterLoader.startInitialization(context.getApplicationContext()); + flutterLoader.ensureInitializationComplete(context, dartVmArgs); + } flutterJNI.addEngineLifecycleListener(engineLifecycleListener); flutterJNI.setPlatformViewsController(platformViewsController); flutterJNI.setLocalizationPlugin(localizationPlugin); flutterJNI.setDeferredComponentManager(FlutterInjector.instance().deferredComponentManager()); - attachToJni(); + // It should typically be a fresh, unattached JNI. But on a spawned engine, the JNI instance + // is already attached to a native shell. In that case, the Java FlutterEngine is created around + // an existing shell. + if (!flutterJNI.isAttached()) { + attachToJni(); + } // TODO(mattcarroll): FlutterRenderer is temporally coupled to attach(). Remove that coupling if // possible. @@ -350,6 +361,29 @@ private boolean isAttachedToJni() { return flutterJNI.isAttached(); } + /** + * Create a second {@link FlutterEngine} based on this current one by sharing as much resources + * together as possible to minimize startup latency and memory cost. + * + * + * @param dartEntrypoint specifies the {@link DartEntrypoint} the new engine should run. It + * doesn't need to be the same entrypoint as the current engine but must be built in the + * same AOT or snapshot. + * @return a new {@link FlutterEngine}. + */ + // This method is package private because it's non-ideal. The method on FlutterEngine should + // ideally just create a second engine and a call to its DartExecutor should then run a + // DartEntrypoint. + /*package*/ FlutterEngine spawn(@NonNull DartEntrypoint dartEntrypoint) { + if (!isAttachedToJni()) { + throw new IllegalStateException("Spawn can only be called on a fully constructed FlutterEngine"); + } + + FlutterJNI newFlutterJNI = flutterJNI.spawn( + dartEntrypoint.dartEntrypointFunctionName, dartEntrypoint.dartEntrypointLibrary); + return new FlutterEngine(context, null, newFlutterJNI); + } + /** * Registers all plugins that an app lists in its pubspec.yaml. * diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java index 41dba09d24663..32e6916f4a6b4 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java @@ -4,8 +4,15 @@ package io.flutter.embedding.engine; +import java.util.ArrayList; import java.util.List; +import android.content.Context; +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint; + /** * This class is experimental. Please do not ship production code using it. * @@ -25,6 +32,50 @@ */ public class FlutterEngineGroup { - private List activeEngines = ArrayList<>(); + public FlutterEngineGroup(@NonNull Context context) { + this.context = context; + } + + private final Context context; + private final List activeEngines = new ArrayList<>(); + + public FlutterEngine createAndRunDefaultEngine() { + return createAndRunEngine(null); + } + + public FlutterEngine createAndRunEngine(@Nullable DartEntrypoint dartEntrypoint) { + FlutterEngine engine = null; + if (activeEngines.size() == 0) { + engine = new FlutterEngine(context); + } + + if (dartEntrypoint == null) { + dartEntrypoint = DartEntrypoint.createDefault(); + } + + if (activeEngines.size() == 0) { + engine.getDartExecutor().executeDartEntrypoint(dartEntrypoint); + } else { + engine = activeEngines.get(0).spawn(dartEntrypoint); + } + + activeEngines.add(engine); + + final FlutterEngine engineToCleanUpOnDestroy = engine; + engine.addEngineLifecycleListener(new FlutterEngine.EngineLifecycleListener(){ + + @Override + public void onPreEngineRestart() { + // No-op. Not interested. + } + + @Override + public void onEngineDestroy() { + activeEngines.remove(engineToCleanUpOnDestroy); + } + + }); + return engine; + } } diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 238183b60d534..77beaf3a70147 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -261,7 +261,7 @@ public static native void nativeOnVsync( // Below represents the stateful part of the FlutterJNI instances that aren't static per program. // Conceptually, it represents a native shell instance. - @Nullable private Long nativePlatformViewId; + @Nullable private Long nativeShellId; @Nullable private AccessibilityDelegate accessibilityDelegate; @Nullable private PlatformMessageHandler platformMessageHandler; @Nullable private LocalizationPlugin localizationPlugin; @@ -290,7 +290,7 @@ public FlutterJNI() { * a Java Native Interface (JNI). */ public boolean isAttached() { - return nativePlatformViewId != null; + return nativeShellId != null; } /** @@ -303,7 +303,7 @@ public boolean isAttached() { public void attachToNative(boolean isBackgroundView) { ensureRunningOnMainThread(); ensureNotAttachedToNative(); - nativePlatformViewId = performNativeAttach(this, isBackgroundView); + nativeShellId = performNativeAttach(this, isBackgroundView); } @VisibleForTesting @@ -328,20 +328,27 @@ public long performNativeAttach(@NonNull FlutterJNI flutterJNI, boolean isBackgr * {@link #setRefreshRateFPS(float)} shouldn't be called again on the spawned FlutterJNI instance. */ @UiThread - public FlutterJNI spawn() { + public FlutterJNI spawn( + @Nullable String entrypointFunctionName, + @Nullable String pathToEntrypointFunction) { ensureRunningOnMainThread(); ensureAttachedToNative(); FlutterJNI spawnedJNI = new FlutterJNI(); - spawnedJNI.nativePlatformViewId = nativeSpawn(this); + // Call the native function with the current Shell ID. It creates a new Shell. Feed the new + // Shell ID into the new FlutterJNI instance. + spawnedJNI.nativeShellId = + nativeSpawn(spawnedJNI, nativeShellId, entrypointFunctionName, pathToEntrypointFunction); Preconditions.checkState( - spawnedJNI.nativePlatformViewId != null && - spawnedJNI.nativePlatformViewId > 0, - "Failed to spawn new JNI connected shell from existing shell."); + spawnedJNI.nativeShellId != null && + spawnedJNI.nativeShellId > 0, + "Failed to spawn new JNI connected shell from existing shell."); return spawnedJNI; } - private native long nativeSpawn(@NonNull FlutterJNI flutterJNI); + private native long nativeSpawn(@NonNull FlutterJNI flutterJNI, long nativeShellId, + @Nullable String entrypointFunctionName, + @Nullable String pathToEntrypointFunction); /** * Detaches this {@code FlutterJNI} instance from Flutter's native engine, which precludes any @@ -350,7 +357,8 @@ public FlutterJNI spawn() { *

This method must not be invoked if {@code FlutterJNI} is not already attached to native. * *

Invoking this method will result in the release of all native-side resources that were setup - * during {@link #attachToNative(boolean)}, or accumulated thereafter. + * during {@link #attachToNative(boolean)} or {@link #spawn(String, String)}, or accumulated + * thereafter. * *

It is permissable to re-attach this instance to native after detaching it from native. */ @@ -358,21 +366,21 @@ public FlutterJNI spawn() { public void detachFromNativeAndReleaseResources() { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeDestroy(nativePlatformViewId); - nativePlatformViewId = null; + nativeDestroy(nativeShellId); + nativeShellId = null; } - private native void nativeDestroy(long nativePlatformViewId); + private native void nativeDestroy(long nativeShellId); private void ensureNotAttachedToNative() { - if (nativePlatformViewId != null) { + if (nativeShellId != null) { throw new RuntimeException( "Cannot execute operation because FlutterJNI is attached to native."); } } private void ensureAttachedToNative() { - if (nativePlatformViewId == null) { + if (nativeShellId == null) { throw new RuntimeException( "Cannot execute operation because FlutterJNI is not attached to native."); } @@ -435,10 +443,10 @@ void onRenderingStopped() { public void onSurfaceCreated(@NonNull Surface surface) { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeSurfaceCreated(nativePlatformViewId, surface); + nativeSurfaceCreated(nativeShellId, surface); } - private native void nativeSurfaceCreated(long nativePlatformViewId, @NonNull Surface surface); + private native void nativeSurfaceCreated(long nativeShellId, @NonNull Surface surface); /** * In hybrid composition, call this method when the {@link Surface} has changed. @@ -451,11 +459,11 @@ public void onSurfaceCreated(@NonNull Surface surface) { public void onSurfaceWindowChanged(@NonNull Surface surface) { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeSurfaceWindowChanged(nativePlatformViewId, surface); + nativeSurfaceWindowChanged(nativeShellId, surface); } private native void nativeSurfaceWindowChanged( - long nativePlatformViewId, @NonNull Surface surface); + long nativeShellId, @NonNull Surface surface); /** * Call this method when the {@link Surface} changes that was previously registered with {@link @@ -468,10 +476,10 @@ private native void nativeSurfaceWindowChanged( public void onSurfaceChanged(int width, int height) { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeSurfaceChanged(nativePlatformViewId, width, height); + nativeSurfaceChanged(nativeShellId, width, height); } - private native void nativeSurfaceChanged(long nativePlatformViewId, int width, int height); + private native void nativeSurfaceChanged(long nativeShellId, int width, int height); /** * Call this method when the {@link Surface} is destroyed that was previously registered with @@ -485,10 +493,10 @@ public void onSurfaceDestroyed() { ensureRunningOnMainThread(); ensureAttachedToNative(); onRenderingStopped(); - nativeSurfaceDestroyed(nativePlatformViewId); + nativeSurfaceDestroyed(nativeShellId); } - private native void nativeSurfaceDestroyed(long nativePlatformViewId); + private native void nativeSurfaceDestroyed(long nativeShellId); /** * Call this method to notify Flutter of the current device viewport metrics that are applies to @@ -517,7 +525,7 @@ public void setViewportMetrics( ensureRunningOnMainThread(); ensureAttachedToNative(); nativeSetViewportMetrics( - nativePlatformViewId, + nativeShellId, devicePixelRatio, physicalWidth, physicalHeight, @@ -536,7 +544,7 @@ public void setViewportMetrics( } private native void nativeSetViewportMetrics( - long nativePlatformViewId, + long nativeShellId, float devicePixelRatio, int physicalWidth, int physicalHeight, @@ -560,11 +568,11 @@ private native void nativeSetViewportMetrics( public void dispatchPointerDataPacket(@NonNull ByteBuffer buffer, int position) { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeDispatchPointerDataPacket(nativePlatformViewId, buffer, position); + nativeDispatchPointerDataPacket(nativeShellId, buffer, position); } private native void nativeDispatchPointerDataPacket( - long nativePlatformViewId, @NonNull ByteBuffer buffer, int position); + long nativeShellId, @NonNull ByteBuffer buffer, int position); // ------ End Touch Interaction Support --- @UiThread @@ -661,11 +669,11 @@ public void dispatchSemanticsAction( int id, int action, @Nullable ByteBuffer args, int argsPosition) { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeDispatchSemanticsAction(nativePlatformViewId, id, action, args, argsPosition); + nativeDispatchSemanticsAction(nativeShellId, id, action, args, argsPosition); } private native void nativeDispatchSemanticsAction( - long nativePlatformViewId, int id, int action, @Nullable ByteBuffer args, int argsPosition); + long nativeShellId, int id, int action, @Nullable ByteBuffer args, int argsPosition); /** * Instructs Flutter to enable/disable its semantics tree, which is used by Flutter to support @@ -675,10 +683,10 @@ private native void nativeDispatchSemanticsAction( public void setSemanticsEnabled(boolean enabled) { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeSetSemanticsEnabled(nativePlatformViewId, enabled); + nativeSetSemanticsEnabled(nativeShellId, enabled); } - private native void nativeSetSemanticsEnabled(long nativePlatformViewId, boolean enabled); + private native void nativeSetSemanticsEnabled(long nativeShellId, boolean enabled); // TODO(mattcarroll): figure out what flags are supported and add javadoc about when/why/where to // use this. @@ -686,10 +694,10 @@ public void setSemanticsEnabled(boolean enabled) { public void setAccessibilityFeatures(int flags) { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeSetAccessibilityFeatures(nativePlatformViewId, flags); + nativeSetAccessibilityFeatures(nativeShellId, flags); } - private native void nativeSetAccessibilityFeatures(long nativePlatformViewId, int flags); + private native void nativeSetAccessibilityFeatures(long nativeShellId, int flags); // ------ End Accessibility Support ---- // ------ Start Texture Registration Support ----- @@ -701,11 +709,11 @@ public void setAccessibilityFeatures(int flags) { public void registerTexture(long textureId, @NonNull SurfaceTextureWrapper textureWrapper) { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeRegisterTexture(nativePlatformViewId, textureId, textureWrapper); + nativeRegisterTexture(nativeShellId, textureId, textureWrapper); } private native void nativeRegisterTexture( - long nativePlatformViewId, long textureId, @NonNull SurfaceTextureWrapper textureWrapper); + long nativeShellId, long textureId, @NonNull SurfaceTextureWrapper textureWrapper); /** * Call this method to inform Flutter that a texture previously registered with {@link @@ -718,10 +726,10 @@ private native void nativeRegisterTexture( public void markTextureFrameAvailable(long textureId) { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeMarkTextureFrameAvailable(nativePlatformViewId, textureId); + nativeMarkTextureFrameAvailable(nativeShellId, textureId); } - private native void nativeMarkTextureFrameAvailable(long nativePlatformViewId, long textureId); + private native void nativeMarkTextureFrameAvailable(long nativeShellId, long textureId); /** * Unregisters a texture that was registered with {@link #registerTexture(long, SurfaceTexture)}. @@ -730,10 +738,10 @@ public void markTextureFrameAvailable(long textureId) { public void unregisterTexture(long textureId) { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeUnregisterTexture(nativePlatformViewId, textureId); + nativeUnregisterTexture(nativeShellId, textureId); } - private native void nativeUnregisterTexture(long nativePlatformViewId, long textureId); + private native void nativeUnregisterTexture(long nativeShellId, long textureId); // ------ Start Texture Registration Support ----- // ------ Start Dart Execution Support ------- @@ -752,7 +760,7 @@ public void runBundleAndSnapshotFromLibrary( ensureRunningOnMainThread(); ensureAttachedToNative(); nativeRunBundleAndSnapshotFromLibrary( - nativePlatformViewId, + nativeShellId, bundlePath, entrypointFunctionName, pathToEntrypointFunction, @@ -760,7 +768,7 @@ public void runBundleAndSnapshotFromLibrary( } private native void nativeRunBundleAndSnapshotFromLibrary( - long nativePlatformViewId, + long nativeShellId, @NonNull String bundlePath, @Nullable String entrypointFunctionName, @Nullable String pathToEntrypointFunction, @@ -831,7 +839,7 @@ private void handlePlatformMessageResponse(int replyId, byte[] reply) { public void dispatchEmptyPlatformMessage(@NonNull String channel, int responseId) { ensureRunningOnMainThread(); if (isAttached()) { - nativeDispatchEmptyPlatformMessage(nativePlatformViewId, channel, responseId); + nativeDispatchEmptyPlatformMessage(nativeShellId, channel, responseId); } else { Log.w( TAG, @@ -844,7 +852,7 @@ public void dispatchEmptyPlatformMessage(@NonNull String channel, int responseId // Send an empty platform message to Dart. private native void nativeDispatchEmptyPlatformMessage( - long nativePlatformViewId, @NonNull String channel, int responseId); + long nativeShellId, @NonNull String channel, int responseId); /** Sends a reply {@code message} from Android to Flutter over the given {@code channel}. */ @UiThread @@ -852,7 +860,7 @@ public void dispatchPlatformMessage( @NonNull String channel, @Nullable ByteBuffer message, int position, int responseId) { ensureRunningOnMainThread(); if (isAttached()) { - nativeDispatchPlatformMessage(nativePlatformViewId, channel, message, position, responseId); + nativeDispatchPlatformMessage(nativeShellId, channel, message, position, responseId); } else { Log.w( TAG, @@ -865,7 +873,7 @@ public void dispatchPlatformMessage( // Send a data-carrying platform message to Dart. private native void nativeDispatchPlatformMessage( - long nativePlatformViewId, + long nativeShellId, @NonNull String channel, @Nullable ByteBuffer message, int position, @@ -876,7 +884,7 @@ private native void nativeDispatchPlatformMessage( public void invokePlatformMessageEmptyResponseCallback(int responseId) { ensureRunningOnMainThread(); if (isAttached()) { - nativeInvokePlatformMessageEmptyResponseCallback(nativePlatformViewId, responseId); + nativeInvokePlatformMessageEmptyResponseCallback(nativeShellId, responseId); } else { Log.w( TAG, @@ -887,7 +895,7 @@ public void invokePlatformMessageEmptyResponseCallback(int responseId) { // Send an empty response to a platform message received from Dart. private native void nativeInvokePlatformMessageEmptyResponseCallback( - long nativePlatformViewId, int responseId); + long nativeShellId, int responseId); // TODO(mattcarroll): differentiate between channel responses and platform responses. @UiThread @@ -896,7 +904,7 @@ public void invokePlatformMessageResponseCallback( ensureRunningOnMainThread(); if (isAttached()) { nativeInvokePlatformMessageResponseCallback( - nativePlatformViewId, responseId, message, position); + nativeShellId, responseId, message, position); } else { Log.w( TAG, @@ -907,7 +915,7 @@ public void invokePlatformMessageResponseCallback( // Send a data-carrying response to a platform message received from Dart. private native void nativeInvokePlatformMessageResponseCallback( - long nativePlatformViewId, int responseId, @Nullable ByteBuffer message, int position); + long nativeShellId, int responseId, @Nullable ByteBuffer message, int position); // ------- End Platform Message Support ---- // ----- Start Engine Lifecycle Support ---- @@ -1111,11 +1119,11 @@ public void requestDartDeferredLibrary(int loadingUnitId) { public void loadDartDeferredLibrary(int loadingUnitId, @NonNull String[] searchPaths) { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeLoadDartDeferredLibrary(nativePlatformViewId, loadingUnitId, searchPaths); + nativeLoadDartDeferredLibrary(nativeShellId, loadingUnitId, searchPaths); } private native void nativeLoadDartDeferredLibrary( - long nativePlatformViewId, int loadingUnitId, @NonNull String[] searchPaths); + long nativeShellId, int loadingUnitId, @NonNull String[] searchPaths); /** * Adds the specified AssetManager as an APKAssetResolver in the Flutter Engine's AssetManager. @@ -1132,11 +1140,11 @@ public void updateJavaAssetManager( @NonNull AssetManager assetManager, @NonNull String assetBundlePath) { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeUpdateJavaAssetManager(nativePlatformViewId, assetManager, assetBundlePath); + nativeUpdateJavaAssetManager(nativeShellId, assetManager, assetBundlePath); } private native void nativeUpdateJavaAssetManager( - long nativePlatformViewId, + long nativeShellId, @NonNull AssetManager assetManager, @NonNull String assetBundlePath); @@ -1192,11 +1200,11 @@ public void onDisplayPlatformView( public Bitmap getBitmap() { ensureRunningOnMainThread(); ensureAttachedToNative(); - return nativeGetBitmap(nativePlatformViewId); + return nativeGetBitmap(nativeShellId); } // TODO(mattcarroll): determine if this is nonull or nullable - private native Bitmap nativeGetBitmap(long nativePlatformViewId); + private native Bitmap nativeGetBitmap(long nativeShellId); /** * Notifies the Dart VM of a low memory event, or that the application is in a state such that now @@ -1209,10 +1217,10 @@ public Bitmap getBitmap() { public void notifyLowMemoryWarning() { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeNotifyLowMemoryWarning(nativePlatformViewId); + nativeNotifyLowMemoryWarning(nativeShellId); } - private native void nativeNotifyLowMemoryWarning(long nativePlatformViewId); + private native void nativeNotifyLowMemoryWarning(long nativeShellId); private void ensureRunningOnMainThread() { if (Looper.myLooper() != mainLooper) { diff --git a/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java b/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java index 3d6cb8416e21d..340af7fa3d479 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java +++ b/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java @@ -121,7 +121,8 @@ public void executeDartEntrypoint(@NonNull DartEntrypoint dartEntrypoint) { Log.v(TAG, "Executing Dart entrypoint: " + dartEntrypoint); flutterJNI.runBundleAndSnapshotFromLibrary( - dartEntrypoint.pathToBundle, dartEntrypoint.dartEntrypointFunctionName, null, assetManager); + dartEntrypoint.pathToBundle, dartEntrypoint.dartEntrypointFunctionName, + dartEntrypoint.dartEntrypointLibrary, assetManager); isApplicationRunning = true; } @@ -269,12 +270,23 @@ public static DartEntrypoint createDefault() { /** The path within the AssetManager where the app will look for assets. */ @NonNull public final String pathToBundle; + /** The library or file location that contains the Dart entrypoint function. */ + @Nullable public final String dartEntrypointLibrary; + /** The name of a Dart function to execute. */ @NonNull public final String dartEntrypointFunctionName; public DartEntrypoint( @NonNull String pathToBundle, @NonNull String dartEntrypointFunctionName) { this.pathToBundle = pathToBundle; + dartEntrypointLibrary = null; + this.dartEntrypointFunctionName = dartEntrypointFunctionName; + } + + public DartEntrypoint( + @NonNull String pathToBundle, @NonNull String dartEntrypointLibrary, @NonNull String dartEntrypointFunctionName) { + this.pathToBundle = pathToBundle; + this.dartEntrypointLibrary = dartEntrypointLibrary; this.dartEntrypointFunctionName = dartEntrypointFunctionName; } diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index 1b1efa0d2efe9..feb5e68377c1d 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -7,6 +7,7 @@ #include #include #include +#include #include #include @@ -145,6 +146,30 @@ static void DestroyJNI(JNIEnv* env, jobject jcaller, jlong shell_holder) { delete ANDROID_SHELL_HOLDER; } +// Signature is similar to RunBundleAndSnapshotFromLibrary but it can't change +// the bundle path or asset manager since we can only spawn with the same +// AOT. +static jlong SpawnJNI(JNIEnv* env, + jobject jcaller, + jobject flutterJNI, + jlong shell_holder, + jstring jEntrypoint, + jstring jLibraryUrl) { + fml::jni::JavaObjectWeakGlobalRef java_jni(env, flutterJNI); + std::shared_ptr jni_facade = + std::make_shared(java_jni); + + auto entrypoint = fml::jni::JavaStringToString(env, jEntrypoint); + auto libraryUrl = fml::jni::JavaStringToString(env, jLibraryUrl); + + auto spawned_shell_holder = ANDROID_SHELL_HOLDER->Spawn(jni_facade, entrypoint ,libraryUrl); + if (spawned_shell_holder->IsValid()) { + return reinterpret_cast(spawned_shell_holder.release()); + } else { + return 0; + } +} + static void SurfaceCreated(JNIEnv* env, jobject jcaller, jlong shell_holder, @@ -599,6 +624,12 @@ bool RegisterApi(JNIEnv* env) { .signature = "(J)V", .fnPtr = reinterpret_cast(&DestroyJNI), }, + { + .name = "nativeSpawn", + .signature = "(Lio/flutter/embedding/engine/FlutterJNI;" + "JLjava/lang/String;Ljava/lang/String;)J", + .fnPtr = reinterpret_cast(&SpawnJNI), + }, { .name = "nativeRunBundleAndSnapshotFromLibrary", .signature = "(JLjava/lang/String;Ljava/lang/String;" diff --git a/tools/android_lint/project.xml b/tools/android_lint/project.xml index f557fb58e130c..29b512015262b 100644 --- a/tools/android_lint/project.xml +++ b/tools/android_lint/project.xml @@ -25,6 +25,9 @@ + + + @@ -36,7 +39,6 @@ - From 78dd4d446238f87354c41bf32a22a311db08324e Mon Sep 17 00:00:00 2001 From: Xiao Yu Date: Thu, 14 Jan 2021 01:14:56 -0800 Subject: [PATCH 03/19] autoformat --- .../platform/android/android_shell_holder.cc | 45 ++++++++------- shell/platform/android/android_shell_holder.h | 50 +++++++++-------- .../embedding/engine/FlutterEngine.java | 15 ++--- .../embedding/engine/FlutterEngineGroup.java | 55 +++++++++---------- .../flutter/embedding/engine/FlutterJNI.java | 54 +++++++++--------- .../embedding/engine/dart/DartExecutor.java | 10 +++- .../io/flutter/util/Preconditions.java | 1 - .../platform/android/platform_view_android.h | 4 +- .../android/platform_view_android_jni_impl.cc | 3 +- 9 files changed, 119 insertions(+), 118 deletions(-) diff --git a/shell/platform/android/android_shell_holder.cc b/shell/platform/android/android_shell_holder.cc index d29c0074f08ff..9fefa3aba7689 100644 --- a/shell/platform/android/android_shell_holder.cc +++ b/shell/platform/android/android_shell_holder.cc @@ -6,10 +6,10 @@ #include "flutter/shell/platform/android/android_shell_holder.h" -#include #include #include #include +#include #include #include @@ -21,8 +21,8 @@ #include "flutter/fml/platform/android/jni_util.h" #include "flutter/shell/common/rasterizer.h" #include "flutter/shell/common/thread_host.h" -#include "flutter/shell/platform/android/platform_view_android.h" #include "flutter/shell/platform/android/context/android_context.h" +#include "flutter/shell/platform/android/platform_view_android.h" namespace flutter { @@ -45,8 +45,8 @@ AndroidShellHolder::AndroidShellHolder( *thread_host_ = {thread_label, ThreadHost::Type::UI}; } else { *thread_host_ = {thread_label, ThreadHost::Type::UI | - ThreadHost::Type::RASTER | - ThreadHost::Type::IO}; + ThreadHost::Type::RASTER | + ThreadHost::Type::IO}; } fml::WeakPtr weak_platform_view; @@ -134,16 +134,17 @@ AndroidShellHolder::AndroidShellHolder( is_valid_ = shell_ != nullptr; } -AndroidShellHolder::AndroidShellHolder(Settings settings, - std::shared_ptr jni_facade, - std::shared_ptr thread_host, - std::unique_ptr shell, - fml::WeakPtr platform_view) - : settings_(std::move(settings)), - jni_facade_(jni_facade), - platform_view_(platform_view), - thread_host_(thread_host), - shell_(std::move(shell)) { +AndroidShellHolder::AndroidShellHolder( + Settings settings, + std::shared_ptr jni_facade, + std::shared_ptr thread_host, + std::unique_ptr shell, + fml::WeakPtr platform_view) + : settings_(std::move(settings)), + jni_facade_(jni_facade), + platform_view_(platform_view), + thread_host_(thread_host), + shell_(std::move(shell)) { FML_DCHECK(jni_facade); FML_DCHECK(shell_); FML_DCHECK(platform_view_); @@ -165,7 +166,8 @@ const flutter::Settings& AndroidShellHolder::GetSettings() const { std::unique_ptr AndroidShellHolder::Spawn( std::shared_ptr jni_facade, - std::string entrypoint, std::string libraryUr) const { + std::string entrypoint, + std::string libraryUr) const { FML_DCHECK(shell_) << "A new Shell can only be spawned if the current Shell " "is properly constructed"; @@ -206,15 +208,12 @@ std::unique_ptr AndroidShellHolder::Spawn( return std::make_unique(shell); }; - std::unique_ptr shell = - shell_->Spawn(std::move(newSettings), on_create_platform_view, on_create_rasterizer); + std::unique_ptr shell = shell_->Spawn( + std::move(newSettings), on_create_platform_view, on_create_rasterizer); - return std::make_unique( - newSettings, - jni_facade, - thread_host_, - std::move(shell), - weak_platform_view); + return std::make_unique(newSettings, jni_facade, + thread_host_, std::move(shell), + weak_platform_view); } void AndroidShellHolder::Launch(RunConfiguration config) { diff --git a/shell/platform/android/android_shell_holder.h b/shell/platform/android/android_shell_holder.h index 8f2cc133bcc33..bf2a118f3d5cf 100644 --- a/shell/platform/android/android_shell_holder.h +++ b/shell/platform/android/android_shell_holder.h @@ -19,23 +19,23 @@ namespace flutter { - //---------------------------------------------------------------------------- - /// @brief This is the Android owner of the core engine Shell. - /// - /// This is the top orchestrator class on the C++ side for the - /// Android embedding. It corresponds to a FlutterEngine on the - /// Java side. This class is in C++ because the core Shell is in - /// C++ and an Android specific orchestrator needs to exist to - /// compose it with other Android specific components such as - /// the PlatformViewAndroid. This composition of many to one - /// C++ components would be difficult to do through JNI whereas - /// a FlutterEngine and AndroidShellHolder has a 1:1 relationship. - /// - /// Technically, the FlutterJNI class owns this AndroidShellHolder - /// class instance, but the FlutterJNI class is meant to be mostly - /// static and has minimal state to perform the C++ pointer <-> - /// Java class instance translation. - /// +//---------------------------------------------------------------------------- +/// @brief This is the Android owner of the core engine Shell. +/// +/// This is the top orchestrator class on the C++ side for the +/// Android embedding. It corresponds to a FlutterEngine on the +/// Java side. This class is in C++ because the core Shell is in +/// C++ and an Android specific orchestrator needs to exist to +/// compose it with other Android specific components such as +/// the PlatformViewAndroid. This composition of many to one +/// C++ components would be difficult to do through JNI whereas +/// a FlutterEngine and AndroidShellHolder has a 1:1 relationship. +/// +/// Technically, the FlutterJNI class owns this AndroidShellHolder +/// class instance, but the FlutterJNI class is meant to be mostly +/// static and has minimal state to perform the C++ pointer <-> +/// Java class instance translation. +/// class AndroidShellHolder { public: AndroidShellHolder(flutter::Settings settings, @@ -63,11 +63,11 @@ class AndroidShellHolder { /// existing AndroidShellHolder. /// /// The new and existing AndroidShellHolder and underlying - /// Shells Creates one Shell from another Shell where the created Shell - /// takes the opportunity to share any internal components it can. - /// This results is a Shell that has a smaller startup time cost - /// and a smaller memory footprint than an Shell created with a - /// Create function. + /// Shells Creates one Shell from another Shell where the created + /// Shell takes the opportunity to share any internal components + /// it can. This results is a Shell that has a smaller startup + /// time cost and a smaller memory footprint than an Shell created + /// with a Create function. /// /// The new Shell's flutter::Settings cannot be changed from that /// of the initial Shell. The RunConfiguration subcomponent can @@ -79,8 +79,10 @@ class AndroidShellHolder { /// makes, the JNI instance holding this AndroidShellHolder should /// be created first to supply the jni_facade callback. /// - std::unique_ptr Spawn(std::shared_ptr jni_facade, - std::string entrypoint, std::string libraryUr) const; + std::unique_ptr Spawn( + std::shared_ptr jni_facade, + std::string entrypoint, + std::string libraryUr) const; void Launch(RunConfiguration configuration); diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 867f059b89bce..c6401664791e2 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -365,10 +365,9 @@ private boolean isAttachedToJni() { * Create a second {@link FlutterEngine} based on this current one by sharing as much resources * together as possible to minimize startup latency and memory cost. * - * * @param dartEntrypoint specifies the {@link DartEntrypoint} the new engine should run. It - * doesn't need to be the same entrypoint as the current engine but must be built in the - * same AOT or snapshot. + * doesn't need to be the same entrypoint as the current engine but must be built in the same + * AOT or snapshot. * @return a new {@link FlutterEngine}. */ // This method is package private because it's non-ideal. The method on FlutterEngine should @@ -376,11 +375,13 @@ private boolean isAttachedToJni() { // DartEntrypoint. /*package*/ FlutterEngine spawn(@NonNull DartEntrypoint dartEntrypoint) { if (!isAttachedToJni()) { - throw new IllegalStateException("Spawn can only be called on a fully constructed FlutterEngine"); + throw new IllegalStateException( + "Spawn can only be called on a fully constructed FlutterEngine"); } - FlutterJNI newFlutterJNI = flutterJNI.spawn( - dartEntrypoint.dartEntrypointFunctionName, dartEntrypoint.dartEntrypointLibrary); + FlutterJNI newFlutterJNI = + flutterJNI.spawn( + dartEntrypoint.dartEntrypointFunctionName, dartEntrypoint.dartEntrypointLibrary); return new FlutterEngine(context, null, newFlutterJNI); } @@ -613,7 +614,7 @@ public interface EngineLifecycleListener { /** * Lifecycle callback invoked before the Flutter engine is destroyed. * - * For the duration of the call, the Flutter engine is still valid. + *

For the duration of the call, the Flutter engine is still valid. */ void onEngineDestroy(); } diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java index 32e6916f4a6b4..785d95e54fa7b 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java @@ -4,31 +4,29 @@ package io.flutter.embedding.engine; -import java.util.ArrayList; -import java.util.List; - import android.content.Context; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import io.flutter.embedding.engine.FlutterEngine; import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint; +import java.util.ArrayList; +import java.util.List; /** * This class is experimental. Please do not ship production code using it. * - * Represents a collection of {@link FlutterEngine}s who share resources among - * each other to allow them to be created faster and with less memory than - * calling the {@link FlutterEngine}'s constructor multiple times. + *

Represents a collection of {@link io.flutter.embedding.engine.FlutterEngine}s who share + * resources among each other to allow them to be created faster and with less memory than calling + * the {@link io.flutter.embedding.engine.FlutterEngine}'s constructor multiple times. * - * When creating or recreating the first {@link FlutterEngine} in the - * FlutterEngineGroup, the behavior is the same as creating a - * {@link FlutterEngine} via its constructor. When subsequent - * {@link FlutterEngine}s are created, resources from an existing living - * {@link FlutterEngine} is re-used. + *

When creating or recreating the first {@link io.flutter.embedding.engine.FlutterEngine} in the + * FlutterEngineGroup, the behavior is the same as creating a {@link + * io.flutter.embedding.engine.FlutterEngine} via its constructor. When subsequent {@link + * io.flutter.embedding.engine.FlutterEngine}s are created, resources from an existing living {@link + * io.flutter.embedding.engine.FlutterEngine} is re-used. * - * Deleting a FlutterEngineGroup doesn't invalidate its existing - * {@link FlutterEngine}s, but it eliminates the possibility to create more - * {@link FlutterEngine}s in that group. + *

Deleting a FlutterEngineGroup doesn't invalidate its existing {@link + * io.flutter.embedding.engine.FlutterEngine}s, but it eliminates the possibility to create more + * {@link io.flutter.embedding.engine.FlutterEngine}s in that group. */ public class FlutterEngineGroup { @@ -62,20 +60,19 @@ public FlutterEngine createAndRunEngine(@Nullable DartEntrypoint dartEntrypoint) activeEngines.add(engine); final FlutterEngine engineToCleanUpOnDestroy = engine; - engine.addEngineLifecycleListener(new FlutterEngine.EngineLifecycleListener(){ - - @Override - public void onPreEngineRestart() { - // No-op. Not interested. - } - - @Override - public void onEngineDestroy() { - activeEngines.remove(engineToCleanUpOnDestroy); - } - - }); + engine.addEngineLifecycleListener( + new FlutterEngine.EngineLifecycleListener() { + + @Override + public void onPreEngineRestart() { + // No-op. Not interested. + } + + @Override + public void onEngineDestroy() { + activeEngines.remove(engineToCleanUpOnDestroy); + } + }); return engine; } - } diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 77beaf3a70147..e895689315554 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -116,6 +116,7 @@ public void loadLibrary() { System.loadLibrary("flutter"); loadLibraryCalled = true; } + private static boolean loadLibraryCalled = false; /** @@ -133,6 +134,7 @@ public void prefetchDefaultFontManager() { FlutterJNI.nativePrefetchDefaultFontManager(); prefetchDefaultFontManagerCalled = true; } + private static boolean prefetchDefaultFontManagerCalled = false; /** @@ -159,9 +161,10 @@ public void init( } FlutterJNI.nativeInit( - context, args, bundlePath, appStoragePath, engineCachesPath, initTimeMillis); + context, args, bundlePath, appStoragePath, engineCachesPath, initTimeMillis); initCalled = true; } + private static boolean initCalled = false; // END methods related to FlutterLoader @@ -193,7 +196,7 @@ public static native void nativeInit( /** * Checks launch settings for whether software rendering is requested. * - * The value is the same per program. + *

The value is the same per program. */ public boolean getIsSoftwareRenderingEnabled() { return nativeGetIsSoftwareRenderingEnabled(); @@ -203,8 +206,8 @@ public boolean getIsSoftwareRenderingEnabled() { /** * Observatory URI for the VM instance. * - * Its value is set by the native engine once - * {@link #init(Context, String[], String, String, String, long)} is run. + *

Its value is set by the native engine once {@link #init(Context, String[], String, String, + * String, long)} is run. */ public static String getObservatoryUri() { return observatoryUri; @@ -218,6 +221,7 @@ public static void setRefreshRateFPS(float refreshRateFPS) { FlutterJNI.refreshRateFPS = refreshRateFPS; setRefreshRateFPSCalled = true; } + private static boolean setRefreshRateFPSCalled = false; // TODO(mattcarroll): add javadocs @@ -316,21 +320,20 @@ public long performNativeAttach(@NonNull FlutterJNI flutterJNI, boolean isBackgr /** * Spawns a new FlutterJNI instance from the current instance. * - * This creates another native shell from the current shell. This causes the 2 shells to re-use - * some of the shared resources, reducing the total memory consumption versus creating a - * new FlutterJNI by calling its standard constructor. + *

This creates another native shell from the current shell. This causes the 2 shells to re-use + * some of the shared resources, reducing the total memory consumption versus creating a new + * FlutterJNI by calling its standard constructor. * - * This can only be called once the current FlutterJNI instance is attached by calling - * {@link #attachToNative(boolean)}. + *

This can only be called once the current FlutterJNI instance is attached by calling {@link + * #attachToNative(boolean)}. * - * Static methods that should be only called once such as - * {@link #init(Context, String[], String, String, String, long)} or - * {@link #setRefreshRateFPS(float)} shouldn't be called again on the spawned FlutterJNI instance. + *

Static methods that should be only called once such as {@link #init(Context, String[], + * String, String, String, long)} or {@link #setRefreshRateFPS(float)} shouldn't be called again + * on the spawned FlutterJNI instance. */ @UiThread public FlutterJNI spawn( - @Nullable String entrypointFunctionName, - @Nullable String pathToEntrypointFunction) { + @Nullable String entrypointFunctionName, @Nullable String pathToEntrypointFunction) { ensureRunningOnMainThread(); ensureAttachedToNative(); FlutterJNI spawnedJNI = new FlutterJNI(); @@ -339,14 +342,15 @@ public FlutterJNI spawn( spawnedJNI.nativeShellId = nativeSpawn(spawnedJNI, nativeShellId, entrypointFunctionName, pathToEntrypointFunction); Preconditions.checkState( - spawnedJNI.nativeShellId != null && - spawnedJNI.nativeShellId > 0, + spawnedJNI.nativeShellId != null && spawnedJNI.nativeShellId > 0, "Failed to spawn new JNI connected shell from existing shell."); return spawnedJNI; } - private native long nativeSpawn(@NonNull FlutterJNI flutterJNI, long nativeShellId, + private native long nativeSpawn( + @NonNull FlutterJNI flutterJNI, + long nativeShellId, @Nullable String entrypointFunctionName, @Nullable String pathToEntrypointFunction); @@ -462,8 +466,7 @@ public void onSurfaceWindowChanged(@NonNull Surface surface) { nativeSurfaceWindowChanged(nativeShellId, surface); } - private native void nativeSurfaceWindowChanged( - long nativeShellId, @NonNull Surface surface); + private native void nativeSurfaceWindowChanged(long nativeShellId, @NonNull Surface surface); /** * Call this method when the {@link Surface} changes that was previously registered with {@link @@ -760,11 +763,7 @@ public void runBundleAndSnapshotFromLibrary( ensureRunningOnMainThread(); ensureAttachedToNative(); nativeRunBundleAndSnapshotFromLibrary( - nativeShellId, - bundlePath, - entrypointFunctionName, - pathToEntrypointFunction, - assetManager); + nativeShellId, bundlePath, entrypointFunctionName, pathToEntrypointFunction, assetManager); } private native void nativeRunBundleAndSnapshotFromLibrary( @@ -903,8 +902,7 @@ public void invokePlatformMessageResponseCallback( int responseId, @Nullable ByteBuffer message, int position) { ensureRunningOnMainThread(); if (isAttached()) { - nativeInvokePlatformMessageResponseCallback( - nativeShellId, responseId, message, position); + nativeInvokePlatformMessageResponseCallback(nativeShellId, responseId, message, position); } else { Log.w( TAG, @@ -1144,9 +1142,7 @@ public void updateJavaAssetManager( } private native void nativeUpdateJavaAssetManager( - long nativeShellId, - @NonNull AssetManager assetManager, - @NonNull String assetBundlePath); + long nativeShellId, @NonNull AssetManager assetManager, @NonNull String assetBundlePath); /** * Indicates that a failure was encountered during the Android portion of downloading a dynamic diff --git a/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java b/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java index 340af7fa3d479..ccb8c3b7ebcea 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java +++ b/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java @@ -121,8 +121,10 @@ public void executeDartEntrypoint(@NonNull DartEntrypoint dartEntrypoint) { Log.v(TAG, "Executing Dart entrypoint: " + dartEntrypoint); flutterJNI.runBundleAndSnapshotFromLibrary( - dartEntrypoint.pathToBundle, dartEntrypoint.dartEntrypointFunctionName, - dartEntrypoint.dartEntrypointLibrary, assetManager); + dartEntrypoint.pathToBundle, + dartEntrypoint.dartEntrypointFunctionName, + dartEntrypoint.dartEntrypointLibrary, + assetManager); isApplicationRunning = true; } @@ -284,7 +286,9 @@ public DartEntrypoint( } public DartEntrypoint( - @NonNull String pathToBundle, @NonNull String dartEntrypointLibrary, @NonNull String dartEntrypointFunctionName) { + @NonNull String pathToBundle, + @NonNull String dartEntrypointLibrary, + @NonNull String dartEntrypointFunctionName) { this.pathToBundle = pathToBundle; this.dartEntrypointLibrary = dartEntrypointLibrary; this.dartEntrypointFunctionName = dartEntrypointFunctionName; diff --git a/shell/platform/android/io/flutter/util/Preconditions.java b/shell/platform/android/io/flutter/util/Preconditions.java index c9a63fb56eae7..83c58f202fd4d 100644 --- a/shell/platform/android/io/flutter/util/Preconditions.java +++ b/shell/platform/android/io/flutter/util/Preconditions.java @@ -39,7 +39,6 @@ public static void checkState(boolean expression) { } } - /** * Ensures the truth of an expression involving the state of the calling instance. * diff --git a/shell/platform/android/platform_view_android.h b/shell/platform/android/platform_view_android.h index 4d22500f00654..b590fd5a3fea3 100644 --- a/shell/platform/android/platform_view_android.h +++ b/shell/platform/android/platform_view_android.h @@ -108,7 +108,9 @@ class PlatformViewAndroid final : public PlatformView { std::unique_ptr updated_asset_resolver, AssetResolver::AssetResolverType type) override; - const std::shared_ptr& GetAndroidContext() { return android_context_; } + const std::shared_ptr& GetAndroidContext() { + return android_context_; + } private: const std::shared_ptr jni_facade_; diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index feb5e68377c1d..cc329ed3e05a2 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -162,7 +162,8 @@ static jlong SpawnJNI(JNIEnv* env, auto entrypoint = fml::jni::JavaStringToString(env, jEntrypoint); auto libraryUrl = fml::jni::JavaStringToString(env, jLibraryUrl); - auto spawned_shell_holder = ANDROID_SHELL_HOLDER->Spawn(jni_facade, entrypoint ,libraryUrl); + auto spawned_shell_holder = + ANDROID_SHELL_HOLDER->Spawn(jni_facade, entrypoint, libraryUrl); if (spawned_shell_holder->IsValid()) { return reinterpret_cast(spawned_shell_holder.release()); } else { From 32416798ae569e382fe73452cf15b71d8f60ede0 Mon Sep 17 00:00:00 2001 From: Xiao Yu Date: Thu, 14 Jan 2021 03:44:44 -0800 Subject: [PATCH 04/19] patched Android together --- runtime/isolate_configuration.h | 2 +- shell/common/shell.cc | 5 +- shell/common/shell.h | 1 + .../platform/android/android_shell_holder.cc | 65 +++++++++++++++---- shell/platform/android/android_shell_holder.h | 12 +++- .../android/platform_view_android_jni_impl.cc | 33 +--------- .../ios/framework/Source/FlutterEngine.mm | 4 +- 7 files changed, 74 insertions(+), 48 deletions(-) diff --git a/runtime/isolate_configuration.h b/runtime/isolate_configuration.h index 1fc3efef51323..f1a018704d6c0 100644 --- a/runtime/isolate_configuration.h +++ b/runtime/isolate_configuration.h @@ -54,7 +54,7 @@ class IsolateConfiguration { /// using the legacy settings fields that specify /// the asset by name instead of a mappings /// callback. - /// @param[in] io_worker An optional IO worker. Specify `nullptr` is a + /// @param[in] io_worker An optional IO worker. Specify `nullptr` if a /// worker should not be used or one is not /// available. /// diff --git a/shell/common/shell.cc b/shell/common/shell.cc index c135a76c6c3a3..a897545c29686 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -476,6 +476,7 @@ Shell::~Shell() { std::unique_ptr Shell::Spawn( Settings settings, + RunConfiguration run_configuration, const CreateCallback& on_create_platform_view, const CreateCallback& on_create_rasterizer) const { FML_DCHECK(task_runners_.IsValid()); @@ -498,10 +499,8 @@ std::unique_ptr Shell::Spawn( /*settings=*/settings, /*animator=*/std::move(animator)); })); - RunConfiguration configuration = - RunConfiguration::InferFromSettings(settings); result->shared_resource_context_ = io_manager_->GetSharedResourceContext(); - result->RunEngine(std::move(configuration)); + result->RunEngine(std::move(run_configuration)); return result; } diff --git a/shell/common/shell.h b/shell/common/shell.h index c0ae17a929148..3f30071ce62d2 100644 --- a/shell/common/shell.h +++ b/shell/common/shell.h @@ -222,6 +222,7 @@ class Shell final : public PlatformView::Delegate, /// @see http://flutter.dev/go/multiple-engines std::unique_ptr Spawn( Settings settings, + RunConfiguration run_configuration, const CreateCallback& on_create_platform_view, const CreateCallback& on_create_rasterizer) const; diff --git a/shell/platform/android/android_shell_holder.cc b/shell/platform/android/android_shell_holder.cc index 9fefa3aba7689..c4605e5b9b337 100644 --- a/shell/platform/android/android_shell_holder.cc +++ b/shell/platform/android/android_shell_holder.cc @@ -2,6 +2,9 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +#include +#include +#include "shell/common/run_configuration.h" #define FML_USED_ON_EMBEDDER #include "flutter/shell/platform/android/android_shell_holder.h" @@ -167,16 +170,10 @@ const flutter::Settings& AndroidShellHolder::GetSettings() const { std::unique_ptr AndroidShellHolder::Spawn( std::shared_ptr jni_facade, std::string entrypoint, - std::string libraryUr) const { + std::string libraryUrl) const { FML_DCHECK(shell_) << "A new Shell can only be spawned if the current Shell " "is properly constructed"; - // Take the old Settings but overwrite the entrypoint components with the - // new parameter's. - Settings newSettings = GetSettings(); - newSettings.advisory_script_entrypoint = entrypoint; - newSettings.advisory_script_uri = libraryUr; - fml::WeakPtr weak_platform_view; PlatformViewAndroid* android_platform_view = platform_view_.get(); // There's some indirection with platform_view_ being a weak pointer but @@ -208,20 +205,33 @@ std::unique_ptr AndroidShellHolder::Spawn( return std::make_unique(shell); }; + FML_DLOG(ERROR) << "Spawned run"; + auto config = BuildRunConfiguration(asset_manager_, entrypoint, libraryUrl); + if (!config) { + return nullptr; + } + std::unique_ptr shell = shell_->Spawn( - std::move(newSettings), on_create_platform_view, on_create_rasterizer); + std::move(GetSettings()), std::move(config.value()), on_create_platform_view, on_create_rasterizer); - return std::make_unique(newSettings, jni_facade, + return std::make_unique(GetSettings(), jni_facade, thread_host_, std::move(shell), weak_platform_view); } -void AndroidShellHolder::Launch(RunConfiguration config) { +void AndroidShellHolder::Launch(std::shared_ptr asset_manager, + std::string entrypoint, + std::string libraryUrl) { if (!IsValid()) { return; } - shell_->RunEngine(std::move(config)); + asset_manager_ = asset_manager; + auto config = BuildRunConfiguration(asset_manager, entrypoint, libraryUrl); + if (!config) { + return; + } + shell_->RunEngine(std::move(config.value())); } Rasterizer::Screenshot AndroidShellHolder::Screenshot( @@ -242,4 +252,37 @@ void AndroidShellHolder::NotifyLowMemoryWarning() { FML_DCHECK(shell_); shell_->NotifyLowMemoryWarning(); } + +std::optional AndroidShellHolder::BuildRunConfiguration( + std::shared_ptr asset_manager, + std::string entrypoint, + std::string libraryUrl) const { + std::unique_ptr isolate_configuration; + if (flutter::DartVM::IsRunningPrecompiledCode()) { + isolate_configuration = IsolateConfiguration::CreateForAppSnapshot(); + } else { + std::unique_ptr kernel_blob = + fml::FileMapping::CreateReadOnly( + GetSettings().application_kernel_asset); + if (!kernel_blob) { + FML_DLOG(ERROR) << "Unable to load the kernel blob asset."; + return std::nullopt; + } + isolate_configuration = + IsolateConfiguration::CreateForKernel(std::move(kernel_blob)); + } + + RunConfiguration config(std::move(isolate_configuration), + std::move(asset_manager)); + + { + if ((entrypoint.size() > 0) && (libraryUrl.size() > 0)) { + config.SetEntrypointAndLibrary(std::move(entrypoint), + std::move(libraryUrl)); + } else if (entrypoint.size() > 0) { + config.SetEntrypoint(std::move(entrypoint)); + } + } + return config; +} } // namespace flutter diff --git a/shell/platform/android/android_shell_holder.h b/shell/platform/android/android_shell_holder.h index bf2a118f3d5cf..5086b5df9e697 100644 --- a/shell/platform/android/android_shell_holder.h +++ b/shell/platform/android/android_shell_holder.h @@ -7,6 +7,7 @@ #include +#include "assets/asset_manager.h" #include "flutter/fml/macros.h" #include "flutter/fml/unique_fd.h" #include "flutter/lib/ui/window/viewport_metrics.h" @@ -82,9 +83,11 @@ class AndroidShellHolder { std::unique_ptr Spawn( std::shared_ptr jni_facade, std::string entrypoint, - std::string libraryUr) const; + std::string libraryUrl) const; - void Launch(RunConfiguration configuration); + void Launch(std::shared_ptr asset_manager, + std::string entrypoint, + std::string libraryUrl); const flutter::Settings& GetSettings() const; @@ -105,8 +108,13 @@ class AndroidShellHolder { std::unique_ptr shell_; bool is_valid_ = false; uint64_t next_pointer_flow_id_ = 0; + std::shared_ptr asset_manager_; static void ThreadDestructCallback(void* value); + std::optional BuildRunConfiguration( + std::shared_ptr asset_manager, + std::string entrypoint, + std::string libraryUrl) const; FML_DISALLOW_COPY_AND_ASSIGN(AndroidShellHolder); }; diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index cc329ed3e05a2..89fc009f8f6f3 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -226,37 +226,10 @@ static void RunBundleAndSnapshotFromLibrary(JNIEnv* env, fml::jni::JavaStringToString(env, jBundlePath)) // apk asset dir ); - std::unique_ptr isolate_configuration; - if (flutter::DartVM::IsRunningPrecompiledCode()) { - isolate_configuration = IsolateConfiguration::CreateForAppSnapshot(); - } else { - std::unique_ptr kernel_blob = - fml::FileMapping::CreateReadOnly( - ANDROID_SHELL_HOLDER->GetSettings().application_kernel_asset); - if (!kernel_blob) { - FML_DLOG(ERROR) << "Unable to load the kernel blob asset."; - return; - } - isolate_configuration = - IsolateConfiguration::CreateForKernel(std::move(kernel_blob)); - } - - RunConfiguration config(std::move(isolate_configuration), - std::move(asset_manager)); - - { - auto entrypoint = fml::jni::JavaStringToString(env, jEntrypoint); - auto libraryUrl = fml::jni::JavaStringToString(env, jLibraryUrl); - - if ((entrypoint.size() > 0) && (libraryUrl.size() > 0)) { - config.SetEntrypointAndLibrary(std::move(entrypoint), - std::move(libraryUrl)); - } else if (entrypoint.size() > 0) { - config.SetEntrypoint(std::move(entrypoint)); - } - } + auto entrypoint = fml::jni::JavaStringToString(env, jEntrypoint); + auto libraryUrl = fml::jni::JavaStringToString(env, jLibraryUrl); - ANDROID_SHELL_HOLDER->Launch(std::move(config)); + ANDROID_SHELL_HOLDER->Launch(asset_manager, entrypoint, libraryUrl); } static jobject LookupCallbackInformation(JNIEnv* env, diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index ccad2bb7f7582..b1e8a7fbdef51 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -991,8 +991,10 @@ - (FlutterEngine*)spawnWithEntrypoint:(/*nullable*/ NSString*)entrypoint flutter::Shell::CreateCallback on_create_rasterizer = [](flutter::Shell& shell) { return std::make_unique(shell); }; + RunConfiguration configuration = RunConfiguration::InferFromSettings(settings); + std::unique_ptr shell = - _shell->Spawn(std::move(settings), on_create_platform_view, on_create_rasterizer); + _shell->Spawn(std::move(settings), std::move(configuration), on_create_platform_view, on_create_rasterizer); result->_threadHost = _threadHost; result->_profiler = _profiler; From 3a0d864a8c5c5bc84905b7600ccbd05fd0bdfc52 Mon Sep 17 00:00:00 2001 From: Xiao Yu Date: Thu, 14 Jan 2021 17:49:04 -0800 Subject: [PATCH 05/19] Simplified shell spawn down to just take run configuration --- shell/common/shell.cc | 3 +- shell/common/shell.h | 5 +- shell/common/shell_unittests.cc | 70 ++++++++++++------- .../platform/android/android_shell_holder.cc | 12 ++-- shell/platform/android/android_shell_holder.h | 10 +-- .../ios/framework/Source/FlutterEngine.mm | 37 +++++----- 6 files changed, 76 insertions(+), 61 deletions(-) diff --git a/shell/common/shell.cc b/shell/common/shell.cc index a897545c29686..fe3e687f1333d 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -475,13 +475,12 @@ Shell::~Shell() { } std::unique_ptr Shell::Spawn( - Settings settings, RunConfiguration run_configuration, const CreateCallback& on_create_platform_view, const CreateCallback& on_create_rasterizer) const { FML_DCHECK(task_runners_.IsValid()); std::unique_ptr result(Shell::Create( - task_runners_, PlatformData{}, settings, + task_runners_, PlatformData{}, GetSettings(), vm_->GetVMData()->GetIsolateSnapshot(), on_create_platform_view, on_create_rasterizer, vm_, [engine = this->engine_.get()]( diff --git a/shell/common/shell.h b/shell/common/shell.h index 3f30071ce62d2..2bd48b5d20abc 100644 --- a/shell/common/shell.h +++ b/shell/common/shell.h @@ -218,10 +218,13 @@ class Shell final : public PlatformView::Delegate, /// This results is a Shell that has a smaller startup time cost /// and a smaller memory footprint than an Shell created with a /// Create function. + /// @param[in] run_configuration A RunConfiguration used to run the Isolate + /// associated with this new Shell. It doesn't have to be the same + /// configuration as the current Shell but it needs to be in the + /// same snapshot or AOT. /// /// @see http://flutter.dev/go/multiple-engines std::unique_ptr Spawn( - Settings settings, RunConfiguration run_configuration, const CreateCallback& on_create_platform_view, const CreateCallback& on_create_rasterizer) const; diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc index f5adef7425ed5..1855e54371a3d 100644 --- a/shell/common/shell_unittests.cc +++ b/shell/common/shell_unittests.cc @@ -2438,39 +2438,57 @@ TEST_F(ShellTest, Spawn) { ASSERT_TRUE(configuration.IsValid()); configuration.SetEntrypoint("fixturesAreFunctionalMain"); + auto second_configuration = RunConfiguration::InferFromSettings(settings); + ASSERT_TRUE(second_configuration.IsValid()); + second_configuration.SetEntrypoint("testCanLaunchSecondaryIsolate"); + fml::AutoResetWaitableEvent main_latch; + std::string last_entry_point; AddNativeCallback( - "SayHiFromFixturesAreFunctionalMain", - CREATE_NATIVE_ENTRY([&main_latch](auto args) { main_latch.Signal(); })); + "SayHiFromFixturesAreFunctionalMain", CREATE_NATIVE_ENTRY([&](auto args) { + last_entry_point = shell->GetEngine()->GetLastEntrypoint(); + main_latch.Signal(); + })); + AddNativeCallback( + // The Dart native function names aren't very consistent but this is just + // the native function name of the second vm entrypoint in the fixture. + "NotifyNative", CREATE_NATIVE_ENTRY([&](auto args) {})); RunEngine(shell.get(), std::move(configuration)); main_latch.Wait(); ASSERT_TRUE(DartVMRef::IsInstanceRunning()); + ASSERT_EQ("fixturesAreFunctionalMain", last_entry_point); - PostSync(shell->GetTaskRunners().GetPlatformTaskRunner(), [this, - &spawner = shell, - settings]() { - MockPlatformViewDelegate platform_view_delegate; - auto spawn = spawner->Spawn( - settings, - [&platform_view_delegate](Shell& shell) { - auto result = std::make_unique( - platform_view_delegate, shell.GetTaskRunners()); - ON_CALL(*result, CreateRenderingSurface()) - .WillByDefault(::testing::Invoke( - [] { return std::make_unique(); })); - return result; - }, - [](Shell& shell) { return std::make_unique(shell); }); - ASSERT_NE(nullptr, spawn.get()); - ASSERT_TRUE(ValidateShell(spawn.get())); - - PostSync(spawner->GetTaskRunners().GetIOTaskRunner(), [&spawner, &spawn] { - ASSERT_EQ(spawner->GetIOManager()->GetResourceContext().get(), - spawn->GetIOManager()->GetResourceContext().get()); - }); - DestroyShell(std::move(spawn)); - }); + PostSync( + shell->GetTaskRunners().GetPlatformTaskRunner(), + [this, &spawner = shell, &second_configuration]() { + MockPlatformViewDelegate platform_view_delegate; + auto spawn = spawner->Spawn( + std::move(second_configuration), + [&platform_view_delegate](Shell& shell) { + auto result = std::make_unique( + platform_view_delegate, shell.GetTaskRunners()); + ON_CALL(*result, CreateRenderingSurface()) + .WillByDefault(::testing::Invoke( + [] { return std::make_unique(); })); + return result; + }, + [](Shell& shell) { return std::make_unique(shell); }); + ASSERT_NE(nullptr, spawn.get()); + ASSERT_TRUE(ValidateShell(spawn.get())); + + PostSync(spawner->GetTaskRunners().GetUITaskRunner(), [&spawn] { + ASSERT_EQ("testCanLaunchSecondaryIsolate", + spawn->GetEngine()->GetLastEntrypoint()); + }); + + PostSync( + spawner->GetTaskRunners().GetIOTaskRunner(), [&spawner, &spawn] { + ASSERT_EQ(spawner->GetIOManager()->GetResourceContext().get(), + spawn->GetIOManager()->GetResourceContext().get()); + }); + DestroyShell(std::move(spawn)); + }); DestroyShell(std::move(shell)); ASSERT_FALSE(DartVMRef::IsInstanceRunning()); diff --git a/shell/platform/android/android_shell_holder.cc b/shell/platform/android/android_shell_holder.cc index c4605e5b9b337..5638c5ff0b17d 100644 --- a/shell/platform/android/android_shell_holder.cc +++ b/shell/platform/android/android_shell_holder.cc @@ -2,9 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -#include -#include -#include "shell/common/run_configuration.h" #define FML_USED_ON_EMBEDDER #include "flutter/shell/platform/android/android_shell_holder.h" @@ -12,7 +9,9 @@ #include #include #include +#include #include +#include #include #include @@ -23,6 +22,7 @@ #include "flutter/fml/message_loop.h" #include "flutter/fml/platform/android/jni_util.h" #include "flutter/shell/common/rasterizer.h" +#include "flutter/shell/common/run_configuration.h" #include "flutter/shell/common/thread_host.h" #include "flutter/shell/platform/android/context/android_context.h" #include "flutter/shell/platform/android/platform_view_android.h" @@ -212,7 +212,7 @@ std::unique_ptr AndroidShellHolder::Spawn( } std::unique_ptr shell = shell_->Spawn( - std::move(GetSettings()), std::move(config.value()), on_create_platform_view, on_create_rasterizer); + std::move(config.value()), on_create_platform_view, on_create_rasterizer); return std::make_unique(GetSettings(), jni_facade, thread_host_, std::move(shell), @@ -220,8 +220,8 @@ std::unique_ptr AndroidShellHolder::Spawn( } void AndroidShellHolder::Launch(std::shared_ptr asset_manager, - std::string entrypoint, - std::string libraryUrl) { + std::string entrypoint, + std::string libraryUrl) { if (!IsValid()) { return; } diff --git a/shell/platform/android/android_shell_holder.h b/shell/platform/android/android_shell_holder.h index 5086b5df9e697..0905d67229c79 100644 --- a/shell/platform/android/android_shell_holder.h +++ b/shell/platform/android/android_shell_holder.h @@ -86,8 +86,8 @@ class AndroidShellHolder { std::string libraryUrl) const; void Launch(std::shared_ptr asset_manager, - std::string entrypoint, - std::string libraryUrl); + std::string entrypoint, + std::string libraryUrl); const flutter::Settings& GetSettings() const; @@ -112,9 +112,9 @@ class AndroidShellHolder { static void ThreadDestructCallback(void* value); std::optional BuildRunConfiguration( - std::shared_ptr asset_manager, - std::string entrypoint, - std::string libraryUrl) const; + std::shared_ptr asset_manager, + std::string entrypoint, + std::string libraryUrl) const; FML_DISALLOW_COPY_AND_ASSIGN(AndroidShellHolder); }; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index b1e8a7fbdef51..9d4aaf74e1746 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -557,20 +557,6 @@ + (NSString*)generateThreadLabel:(NSString*)labelPrefix { threadHostType}; } -static void SetEntryPoint(flutter::Settings* settings, NSString* entrypoint, NSString* libraryURI) { - if (libraryURI) { - FML_DCHECK(entrypoint) << "Must specify entrypoint if specifying library"; - settings->advisory_script_entrypoint = entrypoint.UTF8String; - settings->advisory_script_uri = libraryURI.UTF8String; - } else if (entrypoint) { - settings->advisory_script_entrypoint = entrypoint.UTF8String; - settings->advisory_script_uri = std::string("main.dart"); - } else { - settings->advisory_script_entrypoint = std::string("main"); - settings->advisory_script_uri = std::string("main.dart"); - } -} - - (BOOL)createShell:(NSString*)entrypoint libraryURI:(NSString*)libraryURI initialRoute:(NSString*)initialRoute { @@ -586,7 +572,17 @@ - (BOOL)createShell:(NSString*)entrypoint auto platformData = [_dartProject.get() defaultPlatformData]; - SetEntryPoint(&settings, entrypoint, libraryURI); + if (libraryURI) { + FML_DCHECK(entrypoint) << "Must specify entrypoint if specifying library"; + settings->advisory_script_entrypoint = entrypoint.UTF8String; + settings->advisory_script_uri = libraryURI.UTF8String; + } else if (entrypoint) { + settings->advisory_script_entrypoint = entrypoint.UTF8String; + settings->advisory_script_uri = std::string("main.dart"); + } else { + settings->advisory_script_entrypoint = std::string("main"); + settings->advisory_script_uri = std::string("main.dart"); + } NSString* threadLabel = [FlutterEngine generateThreadLabel:_labelPrefix]; _threadHost = std::make_shared(); @@ -968,10 +964,11 @@ - (FlutterEngine*)spawnWithEntrypoint:(/*nullable*/ NSString*)entrypoint project:_dartProject.get() allowHeadlessExecution:_allowHeadlessExecution]; - flutter::Settings settings = _shell->GetSettings(); - SetEntryPoint(&settings, entrypoint, libraryURI); + RunConfiguration configuration = + [_dartProject.get() runConfigurationForEntrypoint:entrypoint libraryOrNil:libraryOrNil] - fml::WeakPtr platform_view = _shell->GetPlatformView(); + fml::WeakPtr + platform_view = _shell->GetPlatformView(); FML_DCHECK(platform_view); // Static-cast safe since this class always creates PlatformViewIOS instances. flutter::PlatformViewIOS* ios_platform_view = @@ -991,10 +988,8 @@ - (FlutterEngine*)spawnWithEntrypoint:(/*nullable*/ NSString*)entrypoint flutter::Shell::CreateCallback on_create_rasterizer = [](flutter::Shell& shell) { return std::make_unique(shell); }; - RunConfiguration configuration = RunConfiguration::InferFromSettings(settings); - std::unique_ptr shell = - _shell->Spawn(std::move(settings), std::move(configuration), on_create_platform_view, on_create_rasterizer); + _shell->Spawn(std::move(configuration), on_create_platform_view, on_create_rasterizer); result->_threadHost = _threadHost; result->_profiler = _profiler; From e2b91ff106e0b00f0bae745499982975ca694a98 Mon Sep 17 00:00:00 2001 From: Xiao Yu Date: Thu, 14 Jan 2021 21:53:16 -0800 Subject: [PATCH 06/19] a round of comments and docs --- shell/common/shell.h | 6 ++++ shell/common/shell_unittests.cc | 4 +++ .../platform/android/android_shell_holder.cc | 21 ++++++++---- shell/platform/android/android_shell_holder.h | 13 +++++-- .../embedding/engine/FlutterEngine.java | 4 +-- .../flutter/embedding/engine/FlutterJNI.java | 5 +-- .../platform/android/platform_view_android.cc | 34 ++++++++++++++----- .../platform/android/platform_view_android.h | 12 +++++++ 8 files changed, 77 insertions(+), 22 deletions(-) diff --git a/shell/common/shell.h b/shell/common/shell.h index 2bd48b5d20abc..3749d4727bca7 100644 --- a/shell/common/shell.h +++ b/shell/common/shell.h @@ -218,6 +218,12 @@ class Shell final : public PlatformView::Delegate, /// This results is a Shell that has a smaller startup time cost /// and a smaller memory footprint than an Shell created with a /// Create function. + /// + /// The new Shell is returned in a running state so RunEngine + /// shouldn't be called again on the Shell. Once running, the + /// second Shell is mostly independent from the original Shell + /// and the original Shell doesn't need to keep running for the + /// spawned Shell to keep functioning. /// @param[in] run_configuration A RunConfiguration used to run the Isolate /// associated with this new Shell. It doesn't have to be the same /// configuration as the current Shell but it needs to be in the diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc index 1855e54371a3d..aca67acd5c081 100644 --- a/shell/common/shell_unittests.cc +++ b/shell/common/shell_unittests.cc @@ -2444,11 +2444,13 @@ TEST_F(ShellTest, Spawn) { fml::AutoResetWaitableEvent main_latch; std::string last_entry_point; + // Fulfill native function for the first Shell's entrypoint. AddNativeCallback( "SayHiFromFixturesAreFunctionalMain", CREATE_NATIVE_ENTRY([&](auto args) { last_entry_point = shell->GetEngine()->GetLastEntrypoint(); main_latch.Signal(); })); + // Fulfill native function for the second Shell's entrypoint. AddNativeCallback( // The Dart native function names aren't very consistent but this is just // the native function name of the second vm entrypoint in the fixture. @@ -2457,6 +2459,7 @@ TEST_F(ShellTest, Spawn) { RunEngine(shell.get(), std::move(configuration)); main_latch.Wait(); ASSERT_TRUE(DartVMRef::IsInstanceRunning()); + // Check first Shell ran the first entrypoint. ASSERT_EQ("fixturesAreFunctionalMain", last_entry_point); PostSync( @@ -2478,6 +2481,7 @@ TEST_F(ShellTest, Spawn) { ASSERT_TRUE(ValidateShell(spawn.get())); PostSync(spawner->GetTaskRunners().GetUITaskRunner(), [&spawn] { + // Check second shell ran the second entrypoint. ASSERT_EQ("testCanLaunchSecondaryIsolate", spawn->GetEngine()->GetLastEntrypoint()); }); diff --git a/shell/platform/android/android_shell_holder.cc b/shell/platform/android/android_shell_holder.cc index 5638c5ff0b17d..c36ec2b3bb7d9 100644 --- a/shell/platform/android/android_shell_holder.cc +++ b/shell/platform/android/android_shell_holder.cc @@ -9,7 +9,6 @@ #include #include #include -#include #include #include @@ -150,7 +149,9 @@ AndroidShellHolder::AndroidShellHolder( shell_(std::move(shell)) { FML_DCHECK(jni_facade); FML_DCHECK(shell_); + FML_DCHECK(shell_->IsSetup()); FML_DCHECK(platform_view_); + FML_DCHECK(thread_host_); is_valid_ = shell_ != nullptr; } @@ -171,10 +172,16 @@ std::unique_ptr AndroidShellHolder::Spawn( std::shared_ptr jni_facade, std::string entrypoint, std::string libraryUrl) const { - FML_DCHECK(shell_) << "A new Shell can only be spawned if the current Shell " - "is properly constructed"; + FML_DCHECK(shell_ && shell_->IsSetup()) + << "A new Shell can only be spawned " + "if the current Shell is properly constructed"; + // Pull out the new PlatformViewAndroid from the new Shell to feed to it to + // the new AndroidShellHolder. fml::WeakPtr weak_platform_view; + + // Take out the old AndroidContext to reuse inside the PlatformViewAndroid + // of the new Shell. PlatformViewAndroid* android_platform_view = platform_view_.get(); // There's some indirection with platform_view_ being a weak pointer but // we just checked that the shell_ exists above and a valid shell is the @@ -192,8 +199,7 @@ std::unique_ptr AndroidShellHolder::Spawn( shell, // delegate shell.GetTaskRunners(), // task runners jni_facade, // JNI interop - shell.GetSettings() - .enable_software_rendering // use software rendering + android_context // Android context ); weak_platform_view = platform_view_android->GetWeakPtr(); shell.OnDisplayUpdates(DisplayUpdateType::kStartup, @@ -205,9 +211,12 @@ std::unique_ptr AndroidShellHolder::Spawn( return std::make_unique(shell); }; - FML_DLOG(ERROR) << "Spawned run"; + // TODO(xster): could be worth tracing this to investigate whether + // the IsolateConfiguration could be cached somewhere. auto config = BuildRunConfiguration(asset_manager_, entrypoint, libraryUrl); if (!config) { + // If the RunConfiguration was null, the kernel blob wasn't readable. + // Fail the whole thing. return nullptr; } diff --git a/shell/platform/android/android_shell_holder.h b/shell/platform/android/android_shell_holder.h index 0905d67229c79..78e0d5376f4b5 100644 --- a/shell/platform/android/android_shell_holder.h +++ b/shell/platform/android/android_shell_holder.h @@ -7,7 +7,7 @@ #include -#include "assets/asset_manager.h" +#include "flutter/assets/asset_manager.h" #include "flutter/fml/macros.h" #include "flutter/fml/unique_fd.h" #include "flutter/lib/ui/window/viewport_metrics.h" @@ -49,11 +49,16 @@ class AndroidShellHolder { /// This is similar to the standard constructor, except its /// members were constructed elsewhere and injected. /// + /// All injected components must be non-null and valid. + /// + /// Used when constructing the Shell from the inside out when + /// spawning from an existing Shell. + /// AndroidShellHolder(flutter::Settings settings, std::shared_ptr jni_facade, std::shared_ptr thread_host, std::unique_ptr shell, - fml::WeakPtr); + fml::WeakPtr platform_view); ~AndroidShellHolder(); @@ -80,6 +85,9 @@ class AndroidShellHolder { /// makes, the JNI instance holding this AndroidShellHolder should /// be created first to supply the jni_facade callback. /// + /// @param[in] jni_facade this argument should be the JNI callback facade of + /// a new JNI instance meant to hold this AndroidShellHolder. + /// std::unique_ptr Spawn( std::shared_ptr jni_facade, std::string entrypoint, @@ -99,6 +107,7 @@ class AndroidShellHolder { void UpdateAssetManager(fml::RefPtr asset_manager); void NotifyLowMemoryWarning(); + ; private: const flutter::Settings settings_; diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index c6401664791e2..d448c042a0b3b 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -370,9 +370,7 @@ private boolean isAttachedToJni() { * AOT or snapshot. * @return a new {@link FlutterEngine}. */ - // This method is package private because it's non-ideal. The method on FlutterEngine should - // ideally just create a second engine and a call to its DartExecutor should then run a - // DartEntrypoint. + @NonNull /*package*/ FlutterEngine spawn(@NonNull DartEntrypoint dartEntrypoint) { if (!isAttachedToJni()) { throw new IllegalStateException( diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index e895689315554..c7096e6769776 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -106,7 +106,7 @@ public class FlutterJNI { *

This must be called before any other native methods, and can be overridden by tests to avoid * loading native libraries. * - *

This method should only be called once. + *

This method should only be called once across all FlutterJNI instances. */ public void loadLibrary() { if (loadLibraryCalled = true) { @@ -124,7 +124,7 @@ public void loadLibrary() { * singleton owned by Skia. Note that, the first call to SkFontMgr::RefDefault() will take * noticeable time, but later calls will return a reference to the preexisting font manager. * - *

This method should only be called once. + *

This method should only be called once across all FlutterJNI instances. */ public void prefetchDefaultFontManager() { if (prefetchDefaultFontManagerCalled = true) { @@ -332,6 +332,7 @@ public long performNativeAttach(@NonNull FlutterJNI flutterJNI, boolean isBackgr * on the spawned FlutterJNI instance. */ @UiThread + @NonNull public FlutterJNI spawn( @Nullable String entrypointFunctionName, @Nullable String pathToEntrypointFunction) { ensureRunningOnMainThread(); diff --git a/shell/platform/android/platform_view_android.cc b/shell/platform/android/platform_view_android.cc index 2434dcc78bfac..d498d4dfbd97b 100644 --- a/shell/platform/android/platform_view_android.cc +++ b/shell/platform/android/platform_view_android.cc @@ -74,16 +74,19 @@ PlatformViewAndroid::PlatformViewAndroid( fml::MakeRefCounted()); #endif // SHELL_ENABLE_VULKAN } - FML_CHECK(android_context_ && android_context_->IsValid()) - << "Could not create an Android context."; - - surface_factory_ = std::make_shared( - *android_context_, jni_facade); + InitSurface(); +} - android_surface_ = surface_factory_->CreateSurface(); - FML_CHECK(android_surface_ && android_surface_->IsValid()) - << "Could not create an OpenGL, Vulkan or Software surface to setup " - "rendering."; +PlatformViewAndroid::PlatformViewAndroid( + PlatformView::Delegate& delegate, + flutter::TaskRunners task_runners, + std::shared_ptr jni_facade, + std::shared_ptr android_context) + : PlatformView(delegate, std::move(task_runners)), + jni_facade_(jni_facade), + android_context_(android_context), + platform_view_android_delegate_(jni_facade) { + InitSurface(); } PlatformViewAndroid::PlatformViewAndroid( @@ -96,6 +99,19 @@ PlatformViewAndroid::PlatformViewAndroid( PlatformViewAndroid::~PlatformViewAndroid() = default; +void PlatformViewAndroid::InitSurface() { + FML_CHECK(android_context_ && android_context_->IsValid()) + << "Could not create an Android context."; + + surface_factory_ = std::make_shared( + *android_context_, jni_facade_); + + android_surface_ = surface_factory_->CreateSurface(); + FML_CHECK(android_surface_ && android_surface_->IsValid()) + << "Could not create an OpenGL, Vulkan or Software surface to setup " + "rendering."; +} + void PlatformViewAndroid::NotifyCreated( fml::RefPtr native_window) { if (android_surface_) { diff --git a/shell/platform/android/platform_view_android.h b/shell/platform/android/platform_view_android.h index b590fd5a3fea3..c570f7ee973a3 100644 --- a/shell/platform/android/platform_view_android.h +++ b/shell/platform/android/platform_view_android.h @@ -53,6 +53,16 @@ class PlatformViewAndroid final : public PlatformView { std::shared_ptr jni_facade, bool use_software_rendering); + //---------------------------------------------------------------------------- + /// @brief Creates a new PlatformViewAndroid but using an existing + /// Android GPU context to create new surfaces. This maximizes + /// resource sharing between 2 PlatformViewAndroids of 2 Shells. + /// + PlatformViewAndroid(PlatformView::Delegate& delegate, + flutter::TaskRunners task_runners, + std::shared_ptr jni_facade, + std::shared_ptr android_context); + ~PlatformViewAndroid() override; void NotifyCreated(fml::RefPtr native_window); @@ -159,6 +169,8 @@ class PlatformViewAndroid final : public PlatformView { // |PlatformView| void RequestDartDeferredLibrary(intptr_t loading_unit_id) override; + void InitSurface(); + void InstallFirstFrameCallback(); void FireFirstFrameCallback(); From 9767a6a1f8e3fa5490b91d3ac77e2a337147cb93 Mon Sep 17 00:00:00 2001 From: Xiao Yu Date: Fri, 15 Jan 2021 00:53:59 -0800 Subject: [PATCH 07/19] add tests --- .gitignore | 1 + shell/platform/android/BUILD.gn | 1 + .../embedding/engine/FlutterEngineGroup.java | 10 +- .../flutter/embedding/engine/FlutterJNI.java | 8 +- .../test/io/flutter/FlutterTestSuite.java | 2 + .../FlutterEngineGroupComponentTest.java | 140 ++++++++++++++++ .../embedding/engine/FlutterEngineTest.java | 64 +++++++- testing/scenario_app/android/.gitignore | 1 + .../.cache/daemon/5.6.4/registry.bin | Bin 73 -> 0 bytes .../flutter/scenariosui/SpawnEngineTests.java | 37 +++++ .../android/app/src/main/AndroidManifest.xml | 17 ++ .../scenarios/SpawnedEngineActivity.java | 29 ++++ .../dev/flutter/scenarios/TestActivity.java | 152 ++++++++++++++++++ .../scenarios/TextPlatformViewActivity.java | 144 +---------------- 14 files changed, 452 insertions(+), 154 deletions(-) create mode 100644 shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java delete mode 100644 testing/scenario_app/android/android/gradle-home/.cache/daemon/5.6.4/registry.bin create mode 100644 testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/SpawnEngineTests.java create mode 100644 testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SpawnedEngineActivity.java create mode 100644 testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TestActivity.java diff --git a/.gitignore b/.gitignore index d46d282df64d9..dfae6ab8dc57b 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,7 @@ .*.sw? .DS_Store .ccls-cache +.cache .classpath .clangd/ .cproject diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 5094c05b4d62c..7cd09258552a4 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -466,6 +466,7 @@ action("robolectric_tests") { "test/io/flutter/embedding/android/RobolectricFlutterActivity.java", "test/io/flutter/embedding/engine/FlutterEngineCacheTest.java", "test/io/flutter/embedding/engine/FlutterEngineConnectionRegistryTest.java", + "test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java", "test/io/flutter/embedding/engine/FlutterEngineTest.java", "test/io/flutter/embedding/engine/FlutterJNITest.java", "test/io/flutter/embedding/engine/FlutterShellArgsTest.java", diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java index 785d95e54fa7b..b9054f17ae219 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java @@ -7,6 +7,7 @@ import android.content.Context; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.annotation.VisibleForTesting; import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint; import java.util.ArrayList; import java.util.List; @@ -35,7 +36,7 @@ public FlutterEngineGroup(@NonNull Context context) { } private final Context context; - private final List activeEngines = new ArrayList<>(); + /* package */ @VisibleForTesting final List activeEngines = new ArrayList<>(); public FlutterEngine createAndRunDefaultEngine() { return createAndRunEngine(null); @@ -44,7 +45,7 @@ public FlutterEngine createAndRunDefaultEngine() { public FlutterEngine createAndRunEngine(@Nullable DartEntrypoint dartEntrypoint) { FlutterEngine engine = null; if (activeEngines.size() == 0) { - engine = new FlutterEngine(context); + engine = createEngine(context); } if (dartEntrypoint == null) { @@ -75,4 +76,9 @@ public void onEngineDestroy() { }); return engine; } + + @VisibleForTesting + /* package */ FlutterEngine createEngine(Context context) { + return new FlutterEngine(context); + } } diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index c7096e6769776..55197f604a073 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -109,7 +109,7 @@ public class FlutterJNI { *

This method should only be called once across all FlutterJNI instances. */ public void loadLibrary() { - if (loadLibraryCalled = true) { + if (loadLibraryCalled) { Log.w(TAG, "FlutterJNI.loadLibrary called more than once"); } @@ -127,7 +127,7 @@ public void loadLibrary() { *

This method should only be called once across all FlutterJNI instances. */ public void prefetchDefaultFontManager() { - if (prefetchDefaultFontManagerCalled = true) { + if (prefetchDefaultFontManagerCalled) { Log.w(TAG, "FlutterJNI.prefetchDefaultFontManager called more than once"); } @@ -156,7 +156,7 @@ public void init( @NonNull String appStoragePath, @NonNull String engineCachesPath, long initTimeMillis) { - if (initCalled = true) { + if (initCalled) { Log.w(TAG, "FlutterJNI.init called more than once"); } @@ -214,7 +214,7 @@ public static String getObservatoryUri() { } public static void setRefreshRateFPS(float refreshRateFPS) { - if (setRefreshRateFPSCalled = true) { + if (setRefreshRateFPSCalled) { Log.w(TAG, "FlutterJNI.setRefreshRateFPS called more than once"); } diff --git a/shell/platform/android/test/io/flutter/FlutterTestSuite.java b/shell/platform/android/test/io/flutter/FlutterTestSuite.java index 5ea424c1413cb..b4adff91c685e 100644 --- a/shell/platform/android/test/io/flutter/FlutterTestSuite.java +++ b/shell/platform/android/test/io/flutter/FlutterTestSuite.java @@ -13,6 +13,7 @@ import io.flutter.embedding.android.FlutterViewTest; import io.flutter.embedding.engine.FlutterEngineCacheTest; import io.flutter.embedding.engine.FlutterEngineConnectionRegistryTest; +import io.flutter.embedding.engine.FlutterEngineGroupComponentTest; import io.flutter.embedding.engine.FlutterJNITest; import io.flutter.embedding.engine.LocalizationPluginTest; import io.flutter.embedding.engine.RenderingComponentTest; @@ -59,6 +60,7 @@ FlutterAndroidComponentTest.class, FlutterEngineCacheTest.class, FlutterEngineConnectionRegistryTest.class, + FlutterEngineGroupComponentTest.class, FlutterEngineTest.class, FlutterFragmentActivityTest.class, FlutterFragmentTest.class, diff --git a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java new file mode 100644 index 0000000000000..36d830d377d9c --- /dev/null +++ b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java @@ -0,0 +1,140 @@ +// Copyright 2013 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. + +package io.flutter.embedding.engine; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import android.content.Context; +import android.content.pm.PackageManager.NameNotFoundException; +import io.flutter.FlutterInjector; +import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.embedding.engine.FlutterEngine.EngineLifecycleListener; +import io.flutter.embedding.engine.dart.DartExecutor; +import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint;; +import io.flutter.embedding.engine.FlutterEngineGroup; +import io.flutter.embedding.engine.FlutterJNI; +import io.flutter.embedding.engine.loader.FlutterLoader; +import io.flutter.plugin.platform.PlatformViewsController; +import io.flutter.plugins.GeneratedPluginRegistrant; +import java.util.List; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +// It's a component test because it tests both FlutterEngineGroup and FlutterEngine. +@Config(manifest = Config.NONE) +@RunWith(RobolectricTestRunner.class) +public class FlutterEngineGroupComponentTest { + @Mock FlutterJNI flutterJNI; + FlutterEngineGroup engineGroupUnderTest; + FlutterEngine firstEngineUnderTest; + boolean jniAttached; + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + jniAttached = false; + when(flutterJNI.isAttached()).thenAnswer(invocation -> jniAttached); + doAnswer( + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + jniAttached = true; + return null; + } + }) + .when(flutterJNI) + .attachToNative(false); + GeneratedPluginRegistrant.clearRegisteredEngines(); + + firstEngineUnderTest = spy(new FlutterEngine( + RuntimeEnvironment.application, + mock(FlutterLoader.class), + flutterJNI, + /*dartVmArgs=*/ new String[] {}, + /*automaticallyRegisterPlugins=*/ false)); + when(firstEngineUnderTest.getDartExecutor()).thenReturn(mock(DartExecutor.class)); + engineGroupUnderTest = new FlutterEngineGroup(RuntimeEnvironment.application) { + @Override + FlutterEngine createEngine(Context context) { + return firstEngineUnderTest; + } + }; + } + + @After + public void tearDown() { + GeneratedPluginRegistrant.clearRegisteredEngines(); + engineGroupUnderTest = null; + firstEngineUnderTest = null; + } + + @Test + public void listensToEngineDestruction() { + FlutterEngine firstEngine = engineGroupUnderTest.createAndRunEngine(mock(DartEntrypoint.class)); + assertEquals(1, engineGroupUnderTest.activeEngines.size()); + + firstEngine.destroy(); + assertEquals(0, engineGroupUnderTest.activeEngines.size()); + } + + @Test + public void canRecreateEngines() { + FlutterEngine firstEngine = engineGroupUnderTest.createAndRunEngine(mock(DartEntrypoint.class)); + assertEquals(1, engineGroupUnderTest.activeEngines.size()); + + firstEngine.destroy(); + assertEquals(0, engineGroupUnderTest.activeEngines.size()); + + FlutterEngine secondEngine = engineGroupUnderTest.createAndRunEngine(mock(DartEntrypoint.class)); + assertEquals(1, engineGroupUnderTest.activeEngines.size()); + // They happen to be equal in our test since we mocked it to be so. + assertEquals(firstEngine, secondEngine); + } + + @Test + public void canSpawnMoreEngines() { + FlutterEngine firstEngine = engineGroupUnderTest.createAndRunEngine(mock(DartEntrypoint.class)); + assertEquals(1, engineGroupUnderTest.activeEngines.size()); + + doReturn(mock(FlutterEngine.class)).when(firstEngine).spawn(any(DartEntrypoint.class)); + + FlutterEngine secondEngine = engineGroupUnderTest.createAndRunEngine(mock(DartEntrypoint.class)); + assertEquals(2, engineGroupUnderTest.activeEngines.size()); + + firstEngine.destroy(); + assertEquals(1, engineGroupUnderTest.activeEngines.size()); + + // Now the second spawned engine is the only one left and it will be called to spawn the next + // engine in the chain. + when(secondEngine.spawn(any(DartEntrypoint.class))).thenReturn(mock(FlutterEngine.class)); + + FlutterEngine thirdEngine = engineGroupUnderTest.createAndRunEngine(mock(DartEntrypoint.class)); + assertEquals(2, engineGroupUnderTest.activeEngines.size()); + } + +} diff --git a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java index be9057d97ed8e..93a0a320e4231 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java @@ -6,7 +6,9 @@ import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.times; import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; @@ -15,6 +17,7 @@ import android.content.pm.PackageManager.NameNotFoundException; import io.flutter.FlutterInjector; import io.flutter.embedding.engine.FlutterEngine; +import io.flutter.embedding.engine.FlutterEngine.EngineLifecycleListener; import io.flutter.embedding.engine.FlutterJNI; import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.plugin.platform.PlatformViewsController; @@ -27,6 +30,8 @@ import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; +import org.mockito.invocation.InvocationOnMock; +import org.mockito.stubbing.Answer; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; @@ -35,11 +40,23 @@ @RunWith(RobolectricTestRunner.class) public class FlutterEngineTest { @Mock FlutterJNI flutterJNI; + boolean jniAttached; @Before public void setUp() { MockitoAnnotations.initMocks(this); - when(flutterJNI.isAttached()).thenReturn(true); + jniAttached = false; + when(flutterJNI.isAttached()).thenAnswer(invocation -> jniAttached); + doAnswer( + new Answer() { + @Override + public Object answer(InvocationOnMock invocation) throws Throwable { + jniAttached = true; + return null; + } + }) + .when(flutterJNI) + .attachToNative(false); GeneratedPluginRegistrant.clearRegisteredEngines(); } @@ -108,9 +125,6 @@ public void itNotifiesPlatformViewsControllerWhenDevHotRestart() { @Test public void itNotifiesPlatformViewsControllerAboutJNILifecycle() { - FlutterJNI mockFlutterJNI = mock(FlutterJNI.class); - when(mockFlutterJNI.isAttached()).thenReturn(true); - PlatformViewsController platformViewsController = mock(PlatformViewsController.class); // Execute behavior under test. @@ -118,7 +132,7 @@ public void itNotifiesPlatformViewsControllerAboutJNILifecycle() { new FlutterEngine( RuntimeEnvironment.application, mock(FlutterLoader.class), - mockFlutterJNI, + flutterJNI, platformViewsController, /*dartVmArgs=*/ new String[] {}, /*automaticallyRegisterPlugins=*/ false); @@ -178,4 +192,44 @@ public void itCanUseFlutterLoaderInjectionViaFlutterInjector() throws NameNotFou verify(mockFlutterLoader, times(1)).startInitialization(any()); verify(mockFlutterLoader, times(1)).ensureInitializationComplete(any(), any()); } + + @Test + public void itNotifiesListenersForDestruction() throws NameNotFoundException { + Context context = mock(Context.class); + Context packageContext = mock(Context.class); + + when(context.createPackageContext(any(), anyInt())).thenReturn(packageContext); + + FlutterEngine engineUnderTest = + new FlutterEngine( + context, + mock(FlutterLoader.class), + flutterJNI, + /*dartVmArgs=*/ new String[] {}, + /*automaticallyRegisterPlugins=*/ false); + + EngineLifecycleListener listener = mock(EngineLifecycleListener.class); + engineUnderTest.addEngineLifecycleListener(listener); + engineUnderTest.destroy(); + verify(listener, times(1)).onEngineDestroy(); + } + + @Test + public void itDoesNotAttachAgainWhenBuiltWithAnAttachedJNI() throws NameNotFoundException { + Context context = mock(Context.class); + Context packageContext = mock(Context.class); + + when(context.createPackageContext(any(), anyInt())).thenReturn(packageContext); + when(flutterJNI.isAttached()).thenReturn(true); + + FlutterEngine engineUnderTest = + new FlutterEngine( + context, + mock(FlutterLoader.class), + flutterJNI, + /*dartVmArgs=*/ new String[] {}, + /*automaticallyRegisterPlugins=*/ false); + + verify(flutterJNI, never()).attachToNative(false); + } } diff --git a/testing/scenario_app/android/.gitignore b/testing/scenario_app/android/.gitignore index b32f6f298eae0..88b214e75574c 100644 --- a/testing/scenario_app/android/.gitignore +++ b/testing/scenario_app/android/.gitignore @@ -12,3 +12,4 @@ /build /captures .externalNativeBuild +.cache diff --git a/testing/scenario_app/android/android/gradle-home/.cache/daemon/5.6.4/registry.bin b/testing/scenario_app/android/android/gradle-home/.cache/daemon/5.6.4/registry.bin deleted file mode 100644 index 0f3905597f5bd4e03874f3612dabc22511cc807f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 73 zcmZQ%fB;4Y2FB7${x_N-T%kxtCPoJB#I%ysB88HSRE3np)ZF|$g`(8-%;J)wN`<7< T activityRule = + new ActivityTestRule<>( + SpawnedEngineActivity.class, /*initialTouchMode=*/ false, /*launchActivity=*/ false); + + @Before + public void setUp() { + intent = new Intent(Intent.ACTION_MAIN); + } + + @Test + public void testSpawnedEngine() throws Exception { + intent.putExtra("scenario", "poppable_screen"); + ScreenshotUtil.capture(activityRule.launchActivity(intent)); + } +} diff --git a/testing/scenario_app/android/app/src/main/AndroidManifest.xml b/testing/scenario_app/android/app/src/main/AndroidManifest.xml index d5a088de8c355..642450cd3bc27 100644 --- a/testing/scenario_app/android/app/src/main/AndroidManifest.xml +++ b/testing/scenario_app/android/app/src/main/AndroidManifest.xml @@ -26,6 +26,23 @@ + + + + + + + + + + + notifyFlutterRendered()); + + return secondEngine; + } + +} diff --git a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TestActivity.java b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TestActivity.java new file mode 100644 index 0000000000000..ce0a89e5c39d1 --- /dev/null +++ b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TestActivity.java @@ -0,0 +1,152 @@ +// Copyright 2013 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. + +package dev.flutter.scenarios; + +import android.Manifest; +import android.content.Context; +import android.content.Intent; +import android.net.Uri; +import android.os.Build; +import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; +import androidx.annotation.NonNull; +import io.flutter.Log; +import io.flutter.embedding.engine.FlutterShellArgs; +import io.flutter.embedding.engine.loader.FlutterLoader; +import io.flutter.plugin.common.BasicMessageChannel; +import io.flutter.plugin.common.BinaryCodec; +import io.flutter.plugin.common.JSONMethodCodec; +import io.flutter.plugin.common.MethodChannel; +import java.io.FileDescriptor; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.atomic.AtomicBoolean; + +public class TestActivity extends TestableFlutterActivity { + static final String TAG = "Scenarios"; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + final Intent launchIntent = getIntent(); + if ("com.google.intent.action.TEST_LOOP".equals(launchIntent.getAction())) { + if (Build.VERSION.SDK_INT > 22) { + requestPermissions(new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1); + } + // Run for one minute, get the timeline data, write it, and finish. + final Uri logFileUri = launchIntent.getData(); + new Handler() + .postDelayed( + new Runnable() { + @Override + public void run() { + writeTimelineData(logFileUri); + + testFlutterLoaderCallbackWhenInitializedTwice(); + } + }, + 20000); + } else { + testFlutterLoaderCallbackWhenInitializedTwice(); + } + } + + @Override + @NonNull + public FlutterShellArgs getFlutterShellArgs() { + FlutterShellArgs args = FlutterShellArgs.fromIntent(getIntent()); + args.add(FlutterShellArgs.ARG_TRACE_STARTUP); + args.add(FlutterShellArgs.ARG_ENABLE_DART_PROFILING); + args.add(FlutterShellArgs.ARG_VERBOSE_LOGGING); + return args; + } + + @Override + public void onFlutterUiDisplayed() { + final Intent launchIntent = getIntent(); + if (!launchIntent.hasExtra("scenario")) { + return; + } + MethodChannel channel = + new MethodChannel(getFlutterEngine().getDartExecutor(), "driver", JSONMethodCodec.INSTANCE); + Map test = new HashMap<>(2); + test.put("name", launchIntent.getStringExtra("scenario")); + test.put("use_android_view", launchIntent.getBooleanExtra("use_android_view", false)); + channel.invokeMethod("set_scenario", test); + } + + private void writeTimelineData(Uri logFile) { + if (logFile == null) { + throw new IllegalArgumentException(); + } + if (getFlutterEngine() == null) { + Log.e(TAG, "Could not write timeline data - no engine."); + return; + } + final BasicMessageChannel channel = + new BasicMessageChannel<>( + getFlutterEngine().getDartExecutor(), "write_timeline", BinaryCodec.INSTANCE); + channel.send( + null, + (ByteBuffer reply) -> { + try { + final FileDescriptor fd = + getContentResolver().openAssetFileDescriptor(logFile, "w").getFileDescriptor(); + final FileOutputStream outputStream = new FileOutputStream(fd); + outputStream.write(reply.array()); + outputStream.close(); + } catch (IOException ex) { + Log.e(TAG, "Could not write timeline file: " + ex.toString()); + } + finish(); + }); + } + + /** + * This method verifies that {@link FlutterLoader#ensureInitializationCompleteAsync(Context, + * String[], Handler, Runnable)} invokes its callback when called after initialization. + */ + private void testFlutterLoaderCallbackWhenInitializedTwice() { + FlutterLoader flutterLoader = new FlutterLoader(); + + // Flutter is probably already loaded in this app based on + // code that ran before this method. Nonetheless, invoke the + // blocking initialization here to ensure it's initialized. + flutterLoader.startInitialization(getApplicationContext()); + flutterLoader.ensureInitializationComplete(getApplication(), new String[] {}); + + // Now that Flutter is loaded, invoke ensureInitializationCompleteAsync with + // a callback and verify that the callback is invoked. + Handler mainHandler = new Handler(Looper.getMainLooper()); + + final AtomicBoolean didInvokeCallback = new AtomicBoolean(false); + + flutterLoader.ensureInitializationCompleteAsync( + getApplication(), + new String[] {}, + mainHandler, + new Runnable() { + @Override + public void run() { + didInvokeCallback.set(true); + } + }); + + mainHandler.post( + new Runnable() { + @Override + public void run() { + if (!didInvokeCallback.get()) { + throw new RuntimeException( + "Failed test: FlutterLoader#ensureInitializationCompleteAsync() did not invoke its callback."); + } + } + }); + } +} diff --git a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TextPlatformViewActivity.java b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TextPlatformViewActivity.java index 1fd0609df3bc1..abd3d19ff3f86 100644 --- a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TextPlatformViewActivity.java +++ b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/TextPlatformViewActivity.java @@ -4,70 +4,11 @@ package dev.flutter.scenarios; -import android.Manifest; -import android.content.Context; -import android.content.Intent; -import android.net.Uri; -import android.os.Build; -import android.os.Bundle; -import android.os.Handler; -import android.os.Looper; -import androidx.annotation.NonNull; -import io.flutter.Log; import io.flutter.embedding.engine.FlutterEngine; -import io.flutter.embedding.engine.FlutterShellArgs; -import io.flutter.embedding.engine.loader.FlutterLoader; -import io.flutter.plugin.common.BasicMessageChannel; -import io.flutter.plugin.common.BinaryCodec; -import io.flutter.plugin.common.JSONMethodCodec; -import io.flutter.plugin.common.MethodChannel; -import java.io.FileDescriptor; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import java.util.HashMap; -import java.util.Map; -import java.util.concurrent.atomic.AtomicBoolean; -public class TextPlatformViewActivity extends TestableFlutterActivity { +public class TextPlatformViewActivity extends TestActivity { static final String TAG = "Scenarios"; - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - final Intent launchIntent = getIntent(); - if ("com.google.intent.action.TEST_LOOP".equals(launchIntent.getAction())) { - if (Build.VERSION.SDK_INT > 22) { - requestPermissions(new String[] {Manifest.permission.WRITE_EXTERNAL_STORAGE}, 1); - } - // Run for one minute, get the timeline data, write it, and finish. - final Uri logFileUri = launchIntent.getData(); - new Handler() - .postDelayed( - new Runnable() { - @Override - public void run() { - writeTimelineData(logFileUri); - - testFlutterLoaderCallbackWhenInitializedTwice(); - } - }, - 20000); - } else { - testFlutterLoaderCallbackWhenInitializedTwice(); - } - } - - @Override - @NonNull - public FlutterShellArgs getFlutterShellArgs() { - FlutterShellArgs args = FlutterShellArgs.fromIntent(getIntent()); - args.add(FlutterShellArgs.ARG_TRACE_STARTUP); - args.add(FlutterShellArgs.ARG_ENABLE_DART_PROFILING); - args.add(FlutterShellArgs.ARG_VERBOSE_LOGGING); - return args; - } - @Override public void configureFlutterEngine(FlutterEngine flutterEngine) { super.configureFlutterEngine(flutterEngine); @@ -76,87 +17,4 @@ public void configureFlutterEngine(FlutterEngine flutterEngine) { .getRegistry() .registerViewFactory("scenarios/textPlatformView", new TextPlatformViewFactory()); } - - @Override - public void onFlutterUiDisplayed() { - final Intent launchIntent = getIntent(); - if (!launchIntent.hasExtra("scenario")) { - return; - } - MethodChannel channel = - new MethodChannel(getFlutterEngine().getDartExecutor(), "driver", JSONMethodCodec.INSTANCE); - Map test = new HashMap<>(2); - test.put("name", launchIntent.getStringExtra("scenario")); - test.put("use_android_view", launchIntent.getBooleanExtra("use_android_view", false)); - channel.invokeMethod("set_scenario", test); - } - - private void writeTimelineData(Uri logFile) { - if (logFile == null) { - throw new IllegalArgumentException(); - } - if (getFlutterEngine() == null) { - Log.e(TAG, "Could not write timeline data - no engine."); - return; - } - final BasicMessageChannel channel = - new BasicMessageChannel<>( - getFlutterEngine().getDartExecutor(), "write_timeline", BinaryCodec.INSTANCE); - channel.send( - null, - (ByteBuffer reply) -> { - try { - final FileDescriptor fd = - getContentResolver().openAssetFileDescriptor(logFile, "w").getFileDescriptor(); - final FileOutputStream outputStream = new FileOutputStream(fd); - outputStream.write(reply.array()); - outputStream.close(); - } catch (IOException ex) { - Log.e(TAG, "Could not write timeline file: " + ex.toString()); - } - finish(); - }); - } - - /** - * This method verifies that {@link FlutterLoader#ensureInitializationCompleteAsync(Context, - * String[], Handler, Runnable)} invokes its callback when called after initialization. - */ - private void testFlutterLoaderCallbackWhenInitializedTwice() { - FlutterLoader flutterLoader = new FlutterLoader(); - - // Flutter is probably already loaded in this app based on - // code that ran before this method. Nonetheless, invoke the - // blocking initialization here to ensure it's initialized. - flutterLoader.startInitialization(getApplicationContext()); - flutterLoader.ensureInitializationComplete(getApplication(), new String[] {}); - - // Now that Flutter is loaded, invoke ensureInitializationCompleteAsync with - // a callback and verify that the callback is invoked. - Handler mainHandler = new Handler(Looper.getMainLooper()); - - final AtomicBoolean didInvokeCallback = new AtomicBoolean(false); - - flutterLoader.ensureInitializationCompleteAsync( - getApplication(), - new String[] {}, - mainHandler, - new Runnable() { - @Override - public void run() { - didInvokeCallback.set(true); - } - }); - - mainHandler.post( - new Runnable() { - @Override - public void run() { - if (!didInvokeCallback.get()) { - throw new RuntimeException( - "Failed test: FlutterLoader#ensureInitializationCompleteAsync() did not invoke its callback."); - } - } - }); - } } From 08654a51ab67fab133a333d0bea2103667748fa5 Mon Sep 17 00:00:00 2001 From: Xiao Yu Date: Fri, 15 Jan 2021 01:05:57 -0800 Subject: [PATCH 08/19] autoformat --- shell/platform/android/android_shell_holder.h | 1 - .../FlutterEngineGroupComponentTest.java | 52 +++++++------------ .../scenarios/SpawnedEngineActivity.java | 1 - 3 files changed, 20 insertions(+), 34 deletions(-) diff --git a/shell/platform/android/android_shell_holder.h b/shell/platform/android/android_shell_holder.h index 78e0d5376f4b5..0ae3db4901e9d 100644 --- a/shell/platform/android/android_shell_holder.h +++ b/shell/platform/android/android_shell_holder.h @@ -107,7 +107,6 @@ class AndroidShellHolder { void UpdateAssetManager(fml::RefPtr asset_manager); void NotifyLowMemoryWarning(); - ; private: const flutter::Settings settings_; diff --git a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java index 36d830d377d9c..9db9df3a61e65 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java @@ -5,38 +5,22 @@ package io.flutter.embedding.engine; import static org.junit.Assert.assertEquals; -import static org.junit.Assert.assertNotNull; -import static org.junit.Assert.assertTrue; import static org.mockito.Mockito.any; -import static org.mockito.Mockito.anyInt; -import static org.mockito.Mockito.atLeast; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; -import static org.mockito.Mockito.never; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.Context; -import android.content.pm.PackageManager.NameNotFoundException; -import io.flutter.FlutterInjector; -import io.flutter.embedding.engine.FlutterEngine; -import io.flutter.embedding.engine.FlutterEngine.EngineLifecycleListener; import io.flutter.embedding.engine.dart.DartExecutor; -import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint;; -import io.flutter.embedding.engine.FlutterEngineGroup; -import io.flutter.embedding.engine.FlutterJNI; +import io.flutter.embedding.engine.dart.DartExecutor.DartEntrypoint; import io.flutter.embedding.engine.loader.FlutterLoader; -import io.flutter.plugin.platform.PlatformViewsController; import io.flutter.plugins.GeneratedPluginRegistrant; -import java.util.List; import org.junit.After; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; -import org.mockito.ArgumentCaptor; import org.mockito.Mock; import org.mockito.MockitoAnnotations; import org.mockito.invocation.InvocationOnMock; @@ -71,19 +55,22 @@ public Object answer(InvocationOnMock invocation) throws Throwable { .attachToNative(false); GeneratedPluginRegistrant.clearRegisteredEngines(); - firstEngineUnderTest = spy(new FlutterEngine( - RuntimeEnvironment.application, - mock(FlutterLoader.class), - flutterJNI, - /*dartVmArgs=*/ new String[] {}, - /*automaticallyRegisterPlugins=*/ false)); + firstEngineUnderTest = + spy( + new FlutterEngine( + RuntimeEnvironment.application, + mock(FlutterLoader.class), + flutterJNI, + /*dartVmArgs=*/ new String[] {}, + /*automaticallyRegisterPlugins=*/ false)); when(firstEngineUnderTest.getDartExecutor()).thenReturn(mock(DartExecutor.class)); - engineGroupUnderTest = new FlutterEngineGroup(RuntimeEnvironment.application) { - @Override - FlutterEngine createEngine(Context context) { - return firstEngineUnderTest; - } - }; + engineGroupUnderTest = + new FlutterEngineGroup(RuntimeEnvironment.application) { + @Override + FlutterEngine createEngine(Context context) { + return firstEngineUnderTest; + } + }; } @After @@ -110,7 +97,8 @@ public void canRecreateEngines() { firstEngine.destroy(); assertEquals(0, engineGroupUnderTest.activeEngines.size()); - FlutterEngine secondEngine = engineGroupUnderTest.createAndRunEngine(mock(DartEntrypoint.class)); + FlutterEngine secondEngine = + engineGroupUnderTest.createAndRunEngine(mock(DartEntrypoint.class)); assertEquals(1, engineGroupUnderTest.activeEngines.size()); // They happen to be equal in our test since we mocked it to be so. assertEquals(firstEngine, secondEngine); @@ -123,7 +111,8 @@ public void canSpawnMoreEngines() { doReturn(mock(FlutterEngine.class)).when(firstEngine).spawn(any(DartEntrypoint.class)); - FlutterEngine secondEngine = engineGroupUnderTest.createAndRunEngine(mock(DartEntrypoint.class)); + FlutterEngine secondEngine = + engineGroupUnderTest.createAndRunEngine(mock(DartEntrypoint.class)); assertEquals(2, engineGroupUnderTest.activeEngines.size()); firstEngine.destroy(); @@ -136,5 +125,4 @@ public void canSpawnMoreEngines() { FlutterEngine thirdEngine = engineGroupUnderTest.createAndRunEngine(mock(DartEntrypoint.class)); assertEquals(2, engineGroupUnderTest.activeEngines.size()); } - } diff --git a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SpawnedEngineActivity.java b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SpawnedEngineActivity.java index dd63651a9e769..bfca91031ef2e 100644 --- a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SpawnedEngineActivity.java +++ b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SpawnedEngineActivity.java @@ -25,5 +25,4 @@ public FlutterEngine provideFlutterEngine(@NonNull Context context) { return secondEngine; } - } From bb201cf95be3438acd670fd94535ab33b0f92748 Mon Sep 17 00:00:00 2001 From: Xiao Yu Date: Fri, 15 Jan 2021 09:27:06 -0800 Subject: [PATCH 09/19] fix --- shell/platform/android/BUILD.gn | 2 +- .../ios/framework/Source/FlutterEngine.mm | 19 +++++++++---------- 2 files changed, 10 insertions(+), 11 deletions(-) diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 7cd09258552a4..69ab05fc049b5 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -150,8 +150,8 @@ android_java_sources = [ "io/flutter/embedding/android/TransparencyMode.java", "io/flutter/embedding/engine/FlutterEngine.java", "io/flutter/embedding/engine/FlutterEngineCache.java", - "io/flutter/embedding/engine/FlutterEngineGroup.java", "io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java", + "io/flutter/embedding/engine/FlutterEngineGroup.java", "io/flutter/embedding/engine/FlutterJNI.java", "io/flutter/embedding/engine/FlutterOverlaySurface.java", "io/flutter/embedding/engine/FlutterShellArgs.java", diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index 9d4aaf74e1746..ea493d4bc97ad 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -574,14 +574,14 @@ - (BOOL)createShell:(NSString*)entrypoint if (libraryURI) { FML_DCHECK(entrypoint) << "Must specify entrypoint if specifying library"; - settings->advisory_script_entrypoint = entrypoint.UTF8String; - settings->advisory_script_uri = libraryURI.UTF8String; + settings.advisory_script_entrypoint = entrypoint.UTF8String; + settings.advisory_script_uri = libraryURI.UTF8String; } else if (entrypoint) { - settings->advisory_script_entrypoint = entrypoint.UTF8String; - settings->advisory_script_uri = std::string("main.dart"); + settings.advisory_script_entrypoint = entrypoint.UTF8String; + settings.advisory_script_uri = std::string("main.dart"); } else { - settings->advisory_script_entrypoint = std::string("main"); - settings->advisory_script_uri = std::string("main.dart"); + settings.advisory_script_entrypoint = std::string("main"); + settings.advisory_script_uri = std::string("main.dart"); } NSString* threadLabel = [FlutterEngine generateThreadLabel:_labelPrefix]; @@ -964,11 +964,10 @@ - (FlutterEngine*)spawnWithEntrypoint:(/*nullable*/ NSString*)entrypoint project:_dartProject.get() allowHeadlessExecution:_allowHeadlessExecution]; - RunConfiguration configuration = - [_dartProject.get() runConfigurationForEntrypoint:entrypoint libraryOrNil:libraryOrNil] + flutter::RunConfiguration configuration = + [_dartProject.get() runConfigurationForEntrypoint:entrypoint libraryOrNil:libraryURI]; - fml::WeakPtr - platform_view = _shell->GetPlatformView(); + fml::WeakPtr platform_view = _shell->GetPlatformView(); FML_DCHECK(platform_view); // Static-cast safe since this class always creates PlatformViewIOS instances. flutter::PlatformViewIOS* ios_platform_view = From 25520ade8739f661477ca6948cdde8dd8f65d36a Mon Sep 17 00:00:00 2001 From: Xiao Yu Date: Fri, 15 Jan 2021 14:01:48 -0800 Subject: [PATCH 10/19] reviews --- .../platform/android/android_shell_holder.cc | 29 ++-- shell/platform/android/android_shell_holder.h | 64 ++++---- .../embedding/engine/FlutterEngine.java | 20 ++- .../embedding/engine/FlutterEngineGroup.java | 23 ++- .../flutter/embedding/engine/FlutterJNI.java | 137 ++++++++++-------- .../io/flutter/view/FlutterNativeView.java | 2 +- .../android/platform_view_android_jni_impl.cc | 13 +- .../FlutterEngineGroupComponentTest.java | 44 +++--- .../embedding/engine/FlutterEngineTest.java | 2 +- .../embedding/engine/PluginComponentTest.java | 7 +- .../ios/framework/Source/FlutterEngine.mm | 26 ++-- 11 files changed, 204 insertions(+), 163 deletions(-) diff --git a/shell/platform/android/android_shell_holder.cc b/shell/platform/android/android_shell_holder.cc index c36ec2b3bb7d9..9bba31afee782 100644 --- a/shell/platform/android/android_shell_holder.cc +++ b/shell/platform/android/android_shell_holder.cc @@ -71,8 +71,8 @@ AndroidShellHolder::AndroidShellHolder( ); } weak_platform_view = platform_view_android->GetWeakPtr(); - shell.OnDisplayUpdates(DisplayUpdateType::kStartup, - {Display(jni_facade->GetDisplayRefreshRate())}); + auto display = Display(jni_facade->GetDisplayRefreshRate()); + shell.OnDisplayUpdates(DisplayUpdateType::kStartup, {display}); return platform_view_android; }; @@ -170,14 +170,17 @@ const flutter::Settings& AndroidShellHolder::GetSettings() const { std::unique_ptr AndroidShellHolder::Spawn( std::shared_ptr jni_facade, - std::string entrypoint, - std::string libraryUrl) const { + const std::string& entrypoint, + const std::string& libraryUrl) const { FML_DCHECK(shell_ && shell_->IsSetup()) << "A new Shell can only be spawned " "if the current Shell is properly constructed"; // Pull out the new PlatformViewAndroid from the new Shell to feed to it to // the new AndroidShellHolder. + // + // It's a weak pointer because it's owned by the Shell (which we're also) + // making below. And the AndroidShellHolder then owns the Shell. fml::WeakPtr weak_platform_view; // Take out the old AndroidContext to reuse inside the PlatformViewAndroid @@ -202,8 +205,8 @@ std::unique_ptr AndroidShellHolder::Spawn( android_context // Android context ); weak_platform_view = platform_view_android->GetWeakPtr(); - shell.OnDisplayUpdates(DisplayUpdateType::kStartup, - {Display(jni_facade->GetDisplayRefreshRate())}); + auto display = Display(jni_facade->GetDisplayRefreshRate()); + shell.OnDisplayUpdates(DisplayUpdateType::kStartup, {display}); return platform_view_android; }; @@ -223,14 +226,14 @@ std::unique_ptr AndroidShellHolder::Spawn( std::unique_ptr shell = shell_->Spawn( std::move(config.value()), on_create_platform_view, on_create_rasterizer); - return std::make_unique(GetSettings(), jni_facade, - thread_host_, std::move(shell), - weak_platform_view); + return std::unique_ptr( + new AndroidShellHolder(GetSettings(), jni_facade, thread_host_, + std::move(shell), weak_platform_view)); } void AndroidShellHolder::Launch(std::shared_ptr asset_manager, - std::string entrypoint, - std::string libraryUrl) { + const std::string& entrypoint, + const std::string& libraryUrl) { if (!IsValid()) { return; } @@ -264,8 +267,8 @@ void AndroidShellHolder::NotifyLowMemoryWarning() { std::optional AndroidShellHolder::BuildRunConfiguration( std::shared_ptr asset_manager, - std::string entrypoint, - std::string libraryUrl) const { + const std::string& entrypoint, + const std::string& libraryUrl) const { std::unique_ptr isolate_configuration; if (flutter::DartVM::IsRunningPrecompiledCode()) { isolate_configuration = IsolateConfiguration::CreateForAppSnapshot(); diff --git a/shell/platform/android/android_shell_holder.h b/shell/platform/android/android_shell_holder.h index 0ae3db4901e9d..dbae653cf4c78 100644 --- a/shell/platform/android/android_shell_holder.h +++ b/shell/platform/android/android_shell_holder.h @@ -23,12 +23,12 @@ namespace flutter { //---------------------------------------------------------------------------- /// @brief This is the Android owner of the core engine Shell. /// -/// This is the top orchestrator class on the C++ side for the +/// @details This is the top orchestrator class on the C++ side for the /// Android embedding. It corresponds to a FlutterEngine on the -/// Java side. This class is in C++ because the core Shell is in -/// C++ and an Android specific orchestrator needs to exist to -/// compose it with other Android specific components such as -/// the PlatformViewAndroid. This composition of many to one +/// Java side. This class is in C++ because the Shell is in +/// C++ and an Android orchestrator needs to exist to +/// compose it with other Android specific C++ components such as +/// the PlatformViewAndroid. This composition of many-to-one /// C++ components would be difficult to do through JNI whereas /// a FlutterEngine and AndroidShellHolder has a 1:1 relationship. /// @@ -43,23 +43,6 @@ class AndroidShellHolder { std::shared_ptr jni_facade, bool is_background_view); - //---------------------------------------------------------------------------- - /// @brief Constructor with its components injected. - /// - /// This is similar to the standard constructor, except its - /// members were constructed elsewhere and injected. - /// - /// All injected components must be non-null and valid. - /// - /// Used when constructing the Shell from the inside out when - /// spawning from an existing Shell. - /// - AndroidShellHolder(flutter::Settings settings, - std::shared_ptr jni_facade, - std::shared_ptr thread_host, - std::unique_ptr shell, - fml::WeakPtr platform_view); - ~AndroidShellHolder(); bool IsValid() const; @@ -68,13 +51,15 @@ class AndroidShellHolder { /// @brief This is a factory for a derived AndroidShellHolder from an /// existing AndroidShellHolder. /// - /// The new and existing AndroidShellHolder and underlying - /// Shells Creates one Shell from another Shell where the created + /// @details Creates one Shell from another Shell where the created /// Shell takes the opportunity to share any internal components /// it can. This results is a Shell that has a smaller startup /// time cost and a smaller memory footprint than an Shell created /// with a Create function. /// + /// The new Shell is returned in a new AndroidShellHolder + /// instance. + /// /// The new Shell's flutter::Settings cannot be changed from that /// of the initial Shell. The RunConfiguration subcomponent can /// be changed however in the spawned Shell to run a different @@ -88,14 +73,17 @@ class AndroidShellHolder { /// @param[in] jni_facade this argument should be the JNI callback facade of /// a new JNI instance meant to hold this AndroidShellHolder. /// + /// @returns A new AndroidShellHolder containing a new Shell. Returns + /// nullptr when a new Shell can't be created. + /// std::unique_ptr Spawn( std::shared_ptr jni_facade, - std::string entrypoint, - std::string libraryUrl) const; + const std::string& entrypoint, + const std::string& libraryUrl) const; void Launch(std::shared_ptr asset_manager, - std::string entrypoint, - std::string libraryUrl); + const std::string& entrypoint, + const std::string& libraryUrl); const flutter::Settings& GetSettings() const; @@ -118,11 +106,27 @@ class AndroidShellHolder { uint64_t next_pointer_flow_id_ = 0; std::shared_ptr asset_manager_; + //---------------------------------------------------------------------------- + /// @brief Constructor with its components injected. + /// + /// @details This is similar to the standard constructor, except its + /// members were constructed elsewhere and injected. + /// + /// All injected components must be non-null and valid. + /// + /// Used when constructing the Shell from the inside out when + /// spawning from an existing Shell. + /// + AndroidShellHolder(flutter::Settings settings, + std::shared_ptr jni_facade, + std::shared_ptr thread_host, + std::unique_ptr shell, + fml::WeakPtr platform_view); static void ThreadDestructCallback(void* value); std::optional BuildRunConfiguration( std::shared_ptr asset_manager, - std::string entrypoint, - std::string libraryUrl) const; + const std::string& entrypoint, + const std::string& libraryUrl) const; FML_DISALLOW_COPY_AND_ASSIGN(AndroidShellHolder); }; diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index d448c042a0b3b..52de50afd0db0 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -76,7 +76,6 @@ public class FlutterEngine { private static final String TAG = "FlutterEngine"; - @NonNull private final Context context; @NonNull private final FlutterJNI flutterJNI; @NonNull private final FlutterRenderer renderer; @NonNull private final DartExecutor dartExecutor; @@ -118,7 +117,7 @@ public void onPreEngineRestart() { } @Override - public void onEngineDestroy() { + public void onEngineWillDestroy() { // This inner implementation doesn't do anything since FlutterEngine sent this // notification in the first place. It's meant for external listeners. } @@ -277,7 +276,6 @@ public FlutterEngine( @Nullable String[] dartVmArgs, boolean automaticallyRegisterPlugins, boolean waitForRestorationData) { - this.context = context; AssetManager assetManager; try { assetManager = context.createPackageContext(context.getPackageName(), 0).getAssets(); @@ -365,13 +363,17 @@ private boolean isAttachedToJni() { * Create a second {@link FlutterEngine} based on this current one by sharing as much resources * together as possible to minimize startup latency and memory cost. * + * @param context is a Context used to create the {@link FlutterEngine}. Could be the same Context + * as the current engine or a different one. Generally, only an application Context is needed + * for the {@link FlutterEngine} and its dependencies. * @param dartEntrypoint specifies the {@link DartEntrypoint} the new engine should run. It * doesn't need to be the same entrypoint as the current engine but must be built in the same * AOT or snapshot. * @return a new {@link FlutterEngine}. */ @NonNull - /*package*/ FlutterEngine spawn(@NonNull DartEntrypoint dartEntrypoint) { + /*package*/ FlutterEngine spawn( + @NonNull Context context, @NonNull DartEntrypoint dartEntrypoint) { if (!isAttachedToJni()) { throw new IllegalStateException( "Spawn can only be called on a fully constructed FlutterEngine"); @@ -380,7 +382,11 @@ private boolean isAttachedToJni() { FlutterJNI newFlutterJNI = flutterJNI.spawn( dartEntrypoint.dartEntrypointFunctionName, dartEntrypoint.dartEntrypointLibrary); - return new FlutterEngine(context, null, newFlutterJNI); + return new FlutterEngine( + context, // Context. + null, // FlutterLoader. A null value passed here causes the constructor to get it from the + // FlutterInjector. + newFlutterJNI); // FlutterJNI. } /** @@ -422,7 +428,7 @@ private void registerPlugins() { public void destroy() { Log.v(TAG, "Destroying."); for (EngineLifecycleListener listener : engineLifecycleListeners) { - listener.onEngineDestroy(); + listener.onEngineWillDestroy(); } // The order that these things are destroyed is important. pluginRegistry.destroy(); @@ -614,6 +620,6 @@ public interface EngineLifecycleListener { * *

For the duration of the call, the Flutter engine is still valid. */ - void onEngineDestroy(); + void onEngineWillDestroy(); } } diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java index b9054f17ae219..b934a8ea1fd33 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java @@ -16,8 +16,8 @@ * This class is experimental. Please do not ship production code using it. * *

Represents a collection of {@link io.flutter.embedding.engine.FlutterEngine}s who share - * resources among each other to allow them to be created faster and with less memory than calling - * the {@link io.flutter.embedding.engine.FlutterEngine}'s constructor multiple times. + * resources to allow them to be created faster and with less memory than calling the {@link + * io.flutter.embedding.engine.FlutterEngine}'s constructor multiple times. * *

When creating or recreating the first {@link io.flutter.embedding.engine.FlutterEngine} in the * FlutterEngineGroup, the behavior is the same as creating a {@link @@ -31,19 +31,18 @@ */ public class FlutterEngineGroup { - public FlutterEngineGroup(@NonNull Context context) { - this.context = context; - } - - private final Context context; /* package */ @VisibleForTesting final List activeEngines = new ArrayList<>(); - public FlutterEngine createAndRunDefaultEngine() { - return createAndRunEngine(null); + public FlutterEngine createAndRunDefaultEngine(@NonNull Context context) { + return createAndRunEngine(context, null); } - public FlutterEngine createAndRunEngine(@Nullable DartEntrypoint dartEntrypoint) { + public FlutterEngine createAndRunEngine( + @NonNull Context context, @Nullable DartEntrypoint dartEntrypoint) { FlutterEngine engine = null; + // This is done up here because an engine needs to be created first in order to be able to use + // DartEntrypoint.createDefault. The engine creation initializes the FlutterLoader so + // DartEntrypoint known where to find the assets for the AOT or kernel code. if (activeEngines.size() == 0) { engine = createEngine(context); } @@ -55,7 +54,7 @@ public FlutterEngine createAndRunEngine(@Nullable DartEntrypoint dartEntrypoint) if (activeEngines.size() == 0) { engine.getDartExecutor().executeDartEntrypoint(dartEntrypoint); } else { - engine = activeEngines.get(0).spawn(dartEntrypoint); + engine = activeEngines.get(0).spawn(context, dartEntrypoint); } activeEngines.add(engine); @@ -70,7 +69,7 @@ public void onPreEngineRestart() { } @Override - public void onEngineDestroy() { + public void onEngineWillDestroy() { activeEngines.remove(engineToCleanUpOnDestroy); } }); diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 55197f604a073..fccd223597f8b 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -109,12 +109,12 @@ public class FlutterJNI { *

This method should only be called once across all FlutterJNI instances. */ public void loadLibrary() { - if (loadLibraryCalled) { + if (FlutterJNI.loadLibraryCalled) { Log.w(TAG, "FlutterJNI.loadLibrary called more than once"); } System.loadLibrary("flutter"); - loadLibraryCalled = true; + FlutterJNI.loadLibraryCalled = true; } private static boolean loadLibraryCalled = false; @@ -127,12 +127,12 @@ public void loadLibrary() { *

This method should only be called once across all FlutterJNI instances. */ public void prefetchDefaultFontManager() { - if (prefetchDefaultFontManagerCalled) { + if (FlutterJNI.prefetchDefaultFontManagerCalled) { Log.w(TAG, "FlutterJNI.prefetchDefaultFontManager called more than once"); } FlutterJNI.nativePrefetchDefaultFontManager(); - prefetchDefaultFontManagerCalled = true; + FlutterJNI.prefetchDefaultFontManagerCalled = true; } private static boolean prefetchDefaultFontManagerCalled = false; @@ -140,7 +140,7 @@ public void prefetchDefaultFontManager() { /** * Perform one time initialization of the Dart VM and Flutter engine. * - *

This method must be called only once. + *

This method must be called only once. Calling more than once will cause an exception. * * @param context The application context. * @param args Arguments to the Dart VM/Flutter engine. @@ -156,13 +156,15 @@ public void init( @NonNull String appStoragePath, @NonNull String engineCachesPath, long initTimeMillis) { - if (initCalled) { + if (FlutterJNI.initCalled) { Log.w(TAG, "FlutterJNI.init called more than once"); + throw new IllegalStateException( + "FlutterJNI.init cannot be called more than once per application across all FlutterJNI instances"); } FlutterJNI.nativeInit( context, args, bundlePath, appStoragePath, engineCachesPath, initTimeMillis); - initCalled = true; + FlutterJNI.initCalled = true; } private static boolean initCalled = false; @@ -214,12 +216,12 @@ public static String getObservatoryUri() { } public static void setRefreshRateFPS(float refreshRateFPS) { - if (setRefreshRateFPSCalled) { + if (FlutterJNI.setRefreshRateFPSCalled) { Log.w(TAG, "FlutterJNI.setRefreshRateFPS called more than once"); } FlutterJNI.refreshRateFPS = refreshRateFPS; - setRefreshRateFPSCalled = true; + FlutterJNI.setRefreshRateFPSCalled = true; } private static boolean setRefreshRateFPSCalled = false; @@ -265,7 +267,7 @@ public static native void nativeOnVsync( // Below represents the stateful part of the FlutterJNI instances that aren't static per program. // Conceptually, it represents a native shell instance. - @Nullable private Long nativeShellId; + @Nullable private Long nativeShellHolderId; @Nullable private AccessibilityDelegate accessibilityDelegate; @Nullable private PlatformMessageHandler platformMessageHandler; @Nullable private LocalizationPlugin localizationPlugin; @@ -294,7 +296,7 @@ public FlutterJNI() { * a Java Native Interface (JNI). */ public boolean isAttached() { - return nativeShellId != null; + return nativeShellHolderId != null; } /** @@ -307,7 +309,7 @@ public boolean isAttached() { public void attachToNative(boolean isBackgroundView) { ensureRunningOnMainThread(); ensureNotAttachedToNative(); - nativeShellId = performNativeAttach(this, isBackgroundView); + nativeShellHolderId = performNativeAttach(this, isBackgroundView); } @VisibleForTesting @@ -340,10 +342,11 @@ public FlutterJNI spawn( FlutterJNI spawnedJNI = new FlutterJNI(); // Call the native function with the current Shell ID. It creates a new Shell. Feed the new // Shell ID into the new FlutterJNI instance. - spawnedJNI.nativeShellId = - nativeSpawn(spawnedJNI, nativeShellId, entrypointFunctionName, pathToEntrypointFunction); + spawnedJNI.nativeShellHolderId = + nativeSpawn( + spawnedJNI, nativeShellHolderId, entrypointFunctionName, pathToEntrypointFunction); Preconditions.checkState( - spawnedJNI.nativeShellId != null && spawnedJNI.nativeShellId > 0, + spawnedJNI.nativeShellHolderId != null && spawnedJNI.nativeShellHolderId > 0, "Failed to spawn new JNI connected shell from existing shell."); return spawnedJNI; @@ -351,7 +354,7 @@ public FlutterJNI spawn( private native long nativeSpawn( @NonNull FlutterJNI flutterJNI, - long nativeShellId, + long nativeSpawningShellId, @Nullable String entrypointFunctionName, @Nullable String pathToEntrypointFunction); @@ -371,21 +374,21 @@ private native long nativeSpawn( public void detachFromNativeAndReleaseResources() { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeDestroy(nativeShellId); - nativeShellId = null; + nativeDestroy(nativeShellHolderId); + nativeShellHolderId = null; } - private native void nativeDestroy(long nativeShellId); + private native void nativeDestroy(long nativeShellHolderId); private void ensureNotAttachedToNative() { - if (nativeShellId != null) { + if (nativeShellHolderId != null) { throw new RuntimeException( "Cannot execute operation because FlutterJNI is attached to native."); } } private void ensureAttachedToNative() { - if (nativeShellId == null) { + if (nativeShellHolderId == null) { throw new RuntimeException( "Cannot execute operation because FlutterJNI is not attached to native."); } @@ -448,10 +451,10 @@ void onRenderingStopped() { public void onSurfaceCreated(@NonNull Surface surface) { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeSurfaceCreated(nativeShellId, surface); + nativeSurfaceCreated(nativeShellHolderId, surface); } - private native void nativeSurfaceCreated(long nativeShellId, @NonNull Surface surface); + private native void nativeSurfaceCreated(long nativeShellHolderId, @NonNull Surface surface); /** * In hybrid composition, call this method when the {@link Surface} has changed. @@ -464,10 +467,11 @@ public void onSurfaceCreated(@NonNull Surface surface) { public void onSurfaceWindowChanged(@NonNull Surface surface) { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeSurfaceWindowChanged(nativeShellId, surface); + nativeSurfaceWindowChanged(nativeShellHolderId, surface); } - private native void nativeSurfaceWindowChanged(long nativeShellId, @NonNull Surface surface); + private native void nativeSurfaceWindowChanged( + long nativeShellHolderId, @NonNull Surface surface); /** * Call this method when the {@link Surface} changes that was previously registered with {@link @@ -480,10 +484,10 @@ public void onSurfaceWindowChanged(@NonNull Surface surface) { public void onSurfaceChanged(int width, int height) { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeSurfaceChanged(nativeShellId, width, height); + nativeSurfaceChanged(nativeShellHolderId, width, height); } - private native void nativeSurfaceChanged(long nativeShellId, int width, int height); + private native void nativeSurfaceChanged(long nativeShellHolderId, int width, int height); /** * Call this method when the {@link Surface} is destroyed that was previously registered with @@ -497,10 +501,10 @@ public void onSurfaceDestroyed() { ensureRunningOnMainThread(); ensureAttachedToNative(); onRenderingStopped(); - nativeSurfaceDestroyed(nativeShellId); + nativeSurfaceDestroyed(nativeShellHolderId); } - private native void nativeSurfaceDestroyed(long nativeShellId); + private native void nativeSurfaceDestroyed(long nativeShellHolderId); /** * Call this method to notify Flutter of the current device viewport metrics that are applies to @@ -529,7 +533,7 @@ public void setViewportMetrics( ensureRunningOnMainThread(); ensureAttachedToNative(); nativeSetViewportMetrics( - nativeShellId, + nativeShellHolderId, devicePixelRatio, physicalWidth, physicalHeight, @@ -548,7 +552,7 @@ public void setViewportMetrics( } private native void nativeSetViewportMetrics( - long nativeShellId, + long nativeShellHolderId, float devicePixelRatio, int physicalWidth, int physicalHeight, @@ -572,11 +576,11 @@ private native void nativeSetViewportMetrics( public void dispatchPointerDataPacket(@NonNull ByteBuffer buffer, int position) { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeDispatchPointerDataPacket(nativeShellId, buffer, position); + nativeDispatchPointerDataPacket(nativeShellHolderId, buffer, position); } private native void nativeDispatchPointerDataPacket( - long nativeShellId, @NonNull ByteBuffer buffer, int position); + long nativeShellHolderId, @NonNull ByteBuffer buffer, int position); // ------ End Touch Interaction Support --- @UiThread @@ -673,11 +677,11 @@ public void dispatchSemanticsAction( int id, int action, @Nullable ByteBuffer args, int argsPosition) { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeDispatchSemanticsAction(nativeShellId, id, action, args, argsPosition); + nativeDispatchSemanticsAction(nativeShellHolderId, id, action, args, argsPosition); } private native void nativeDispatchSemanticsAction( - long nativeShellId, int id, int action, @Nullable ByteBuffer args, int argsPosition); + long nativeShellHolderId, int id, int action, @Nullable ByteBuffer args, int argsPosition); /** * Instructs Flutter to enable/disable its semantics tree, which is used by Flutter to support @@ -687,10 +691,10 @@ private native void nativeDispatchSemanticsAction( public void setSemanticsEnabled(boolean enabled) { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeSetSemanticsEnabled(nativeShellId, enabled); + nativeSetSemanticsEnabled(nativeShellHolderId, enabled); } - private native void nativeSetSemanticsEnabled(long nativeShellId, boolean enabled); + private native void nativeSetSemanticsEnabled(long nativeShellHolderId, boolean enabled); // TODO(mattcarroll): figure out what flags are supported and add javadoc about when/why/where to // use this. @@ -698,10 +702,10 @@ public void setSemanticsEnabled(boolean enabled) { public void setAccessibilityFeatures(int flags) { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeSetAccessibilityFeatures(nativeShellId, flags); + nativeSetAccessibilityFeatures(nativeShellHolderId, flags); } - private native void nativeSetAccessibilityFeatures(long nativeShellId, int flags); + private native void nativeSetAccessibilityFeatures(long nativeShellHolderId, int flags); // ------ End Accessibility Support ---- // ------ Start Texture Registration Support ----- @@ -713,11 +717,11 @@ public void setAccessibilityFeatures(int flags) { public void registerTexture(long textureId, @NonNull SurfaceTextureWrapper textureWrapper) { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeRegisterTexture(nativeShellId, textureId, textureWrapper); + nativeRegisterTexture(nativeShellHolderId, textureId, textureWrapper); } private native void nativeRegisterTexture( - long nativeShellId, long textureId, @NonNull SurfaceTextureWrapper textureWrapper); + long nativeShellHolderId, long textureId, @NonNull SurfaceTextureWrapper textureWrapper); /** * Call this method to inform Flutter that a texture previously registered with {@link @@ -730,10 +734,10 @@ private native void nativeRegisterTexture( public void markTextureFrameAvailable(long textureId) { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeMarkTextureFrameAvailable(nativeShellId, textureId); + nativeMarkTextureFrameAvailable(nativeShellHolderId, textureId); } - private native void nativeMarkTextureFrameAvailable(long nativeShellId, long textureId); + private native void nativeMarkTextureFrameAvailable(long nativeShellHolderId, long textureId); /** * Unregisters a texture that was registered with {@link #registerTexture(long, SurfaceTexture)}. @@ -742,10 +746,10 @@ public void markTextureFrameAvailable(long textureId) { public void unregisterTexture(long textureId) { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeUnregisterTexture(nativeShellId, textureId); + nativeUnregisterTexture(nativeShellHolderId, textureId); } - private native void nativeUnregisterTexture(long nativeShellId, long textureId); + private native void nativeUnregisterTexture(long nativeShellHolderId, long textureId); // ------ Start Texture Registration Support ----- // ------ Start Dart Execution Support ------- @@ -764,11 +768,15 @@ public void runBundleAndSnapshotFromLibrary( ensureRunningOnMainThread(); ensureAttachedToNative(); nativeRunBundleAndSnapshotFromLibrary( - nativeShellId, bundlePath, entrypointFunctionName, pathToEntrypointFunction, assetManager); + nativeShellHolderId, + bundlePath, + entrypointFunctionName, + pathToEntrypointFunction, + assetManager); } private native void nativeRunBundleAndSnapshotFromLibrary( - long nativeShellId, + long nativeShellHolderId, @NonNull String bundlePath, @Nullable String entrypointFunctionName, @Nullable String pathToEntrypointFunction, @@ -839,7 +847,7 @@ private void handlePlatformMessageResponse(int replyId, byte[] reply) { public void dispatchEmptyPlatformMessage(@NonNull String channel, int responseId) { ensureRunningOnMainThread(); if (isAttached()) { - nativeDispatchEmptyPlatformMessage(nativeShellId, channel, responseId); + nativeDispatchEmptyPlatformMessage(nativeShellHolderId, channel, responseId); } else { Log.w( TAG, @@ -852,7 +860,7 @@ public void dispatchEmptyPlatformMessage(@NonNull String channel, int responseId // Send an empty platform message to Dart. private native void nativeDispatchEmptyPlatformMessage( - long nativeShellId, @NonNull String channel, int responseId); + long nativeShellHolderId, @NonNull String channel, int responseId); /** Sends a reply {@code message} from Android to Flutter over the given {@code channel}. */ @UiThread @@ -860,7 +868,7 @@ public void dispatchPlatformMessage( @NonNull String channel, @Nullable ByteBuffer message, int position, int responseId) { ensureRunningOnMainThread(); if (isAttached()) { - nativeDispatchPlatformMessage(nativeShellId, channel, message, position, responseId); + nativeDispatchPlatformMessage(nativeShellHolderId, channel, message, position, responseId); } else { Log.w( TAG, @@ -873,7 +881,7 @@ public void dispatchPlatformMessage( // Send a data-carrying platform message to Dart. private native void nativeDispatchPlatformMessage( - long nativeShellId, + long nativeShellHolderId, @NonNull String channel, @Nullable ByteBuffer message, int position, @@ -884,7 +892,7 @@ private native void nativeDispatchPlatformMessage( public void invokePlatformMessageEmptyResponseCallback(int responseId) { ensureRunningOnMainThread(); if (isAttached()) { - nativeInvokePlatformMessageEmptyResponseCallback(nativeShellId, responseId); + nativeInvokePlatformMessageEmptyResponseCallback(nativeShellHolderId, responseId); } else { Log.w( TAG, @@ -895,7 +903,7 @@ public void invokePlatformMessageEmptyResponseCallback(int responseId) { // Send an empty response to a platform message received from Dart. private native void nativeInvokePlatformMessageEmptyResponseCallback( - long nativeShellId, int responseId); + long nativeShellHolderId, int responseId); // TODO(mattcarroll): differentiate between channel responses and platform responses. @UiThread @@ -903,7 +911,8 @@ public void invokePlatformMessageResponseCallback( int responseId, @Nullable ByteBuffer message, int position) { ensureRunningOnMainThread(); if (isAttached()) { - nativeInvokePlatformMessageResponseCallback(nativeShellId, responseId, message, position); + nativeInvokePlatformMessageResponseCallback( + nativeShellHolderId, responseId, message, position); } else { Log.w( TAG, @@ -914,7 +923,7 @@ public void invokePlatformMessageResponseCallback( // Send a data-carrying response to a platform message received from Dart. private native void nativeInvokePlatformMessageResponseCallback( - long nativeShellId, int responseId, @Nullable ByteBuffer message, int position); + long nativeShellHolderId, int responseId, @Nullable ByteBuffer message, int position); // ------- End Platform Message Support ---- // ----- Start Engine Lifecycle Support ---- @@ -1118,11 +1127,11 @@ public void requestDartDeferredLibrary(int loadingUnitId) { public void loadDartDeferredLibrary(int loadingUnitId, @NonNull String[] searchPaths) { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeLoadDartDeferredLibrary(nativeShellId, loadingUnitId, searchPaths); + nativeLoadDartDeferredLibrary(nativeShellHolderId, loadingUnitId, searchPaths); } private native void nativeLoadDartDeferredLibrary( - long nativeShellId, int loadingUnitId, @NonNull String[] searchPaths); + long nativeShellHolderId, int loadingUnitId, @NonNull String[] searchPaths); /** * Adds the specified AssetManager as an APKAssetResolver in the Flutter Engine's AssetManager. @@ -1139,11 +1148,13 @@ public void updateJavaAssetManager( @NonNull AssetManager assetManager, @NonNull String assetBundlePath) { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeUpdateJavaAssetManager(nativeShellId, assetManager, assetBundlePath); + nativeUpdateJavaAssetManager(nativeShellHolderId, assetManager, assetBundlePath); } private native void nativeUpdateJavaAssetManager( - long nativeShellId, @NonNull AssetManager assetManager, @NonNull String assetBundlePath); + long nativeShellHolderId, + @NonNull AssetManager assetManager, + @NonNull String assetBundlePath); /** * Indicates that a failure was encountered during the Android portion of downloading a dynamic @@ -1197,11 +1208,11 @@ public void onDisplayPlatformView( public Bitmap getBitmap() { ensureRunningOnMainThread(); ensureAttachedToNative(); - return nativeGetBitmap(nativeShellId); + return nativeGetBitmap(nativeShellHolderId); } // TODO(mattcarroll): determine if this is nonull or nullable - private native Bitmap nativeGetBitmap(long nativeShellId); + private native Bitmap nativeGetBitmap(long nativeShellHolderId); /** * Notifies the Dart VM of a low memory event, or that the application is in a state such that now @@ -1214,10 +1225,10 @@ public Bitmap getBitmap() { public void notifyLowMemoryWarning() { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeNotifyLowMemoryWarning(nativeShellId); + nativeNotifyLowMemoryWarning(nativeShellHolderId); } - private native void nativeNotifyLowMemoryWarning(long nativeShellId); + private native void nativeNotifyLowMemoryWarning(long nativeShellHolderId); private void ensureRunningOnMainThread() { if (Looper.myLooper() != mainLooper) { diff --git a/shell/platform/android/io/flutter/view/FlutterNativeView.java b/shell/platform/android/io/flutter/view/FlutterNativeView.java index 1ea6d5ef29377..2b7afda518c1f 100644 --- a/shell/platform/android/io/flutter/view/FlutterNativeView.java +++ b/shell/platform/android/io/flutter/view/FlutterNativeView.java @@ -166,7 +166,7 @@ public void onPreEngineRestart() { mPluginRegistry.onPreEngineRestart(); } - public void onEngineDestroy() { + public void onEngineWillDestroy() { // The old embedding doesn't actually have a FlutterEngine. It interacts with the JNI // directly. } diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index 89fc009f8f6f3..52ef4724cc982 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -149,13 +149,20 @@ static void DestroyJNI(JNIEnv* env, jobject jcaller, jlong shell_holder) { // Signature is similar to RunBundleAndSnapshotFromLibrary but it can't change // the bundle path or asset manager since we can only spawn with the same // AOT. +// +// The newFlutterJNI instance must be a new FlutterJNI instance that will +// hold the reference to this new returned AndroidShellHolder (and receive +// callbacks from this new AndroidShellHolder). +// +// The shell_holder instance must be a pointer address to the current +// AndroidShellHolder whose Shell will be used to spawn a new Shell. static jlong SpawnJNI(JNIEnv* env, jobject jcaller, - jobject flutterJNI, + jobject newFlutterJNI, jlong shell_holder, jstring jEntrypoint, jstring jLibraryUrl) { - fml::jni::JavaObjectWeakGlobalRef java_jni(env, flutterJNI); + fml::jni::JavaObjectWeakGlobalRef java_jni(env, newFlutterJNI); std::shared_ptr jni_facade = std::make_shared(java_jni); @@ -164,7 +171,7 @@ static jlong SpawnJNI(JNIEnv* env, auto spawned_shell_holder = ANDROID_SHELL_HOLDER->Spawn(jni_facade, entrypoint, libraryUrl); - if (spawned_shell_holder->IsValid()) { + if (spawned_shell_holder != nullptr && spawned_shell_holder->IsValid()) { return reinterpret_cast(spawned_shell_holder.release()); } else { return 0; diff --git a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java index 9db9df3a61e65..8e709d86c43c1 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineGroupComponentTest.java @@ -23,8 +23,6 @@ import org.junit.runner.RunWith; import org.mockito.Mock; import org.mockito.MockitoAnnotations; -import org.mockito.invocation.InvocationOnMock; -import org.mockito.stubbing.Answer; import org.robolectric.RobolectricTestRunner; import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; @@ -43,16 +41,7 @@ public void setUp() { MockitoAnnotations.initMocks(this); jniAttached = false; when(flutterJNI.isAttached()).thenAnswer(invocation -> jniAttached); - doAnswer( - new Answer() { - @Override - public Object answer(InvocationOnMock invocation) throws Throwable { - jniAttached = true; - return null; - } - }) - .when(flutterJNI) - .attachToNative(false); + doAnswer(invocation -> jniAttached = true).when(flutterJNI).attachToNative(false); GeneratedPluginRegistrant.clearRegisteredEngines(); firstEngineUnderTest = @@ -65,7 +54,7 @@ public Object answer(InvocationOnMock invocation) throws Throwable { /*automaticallyRegisterPlugins=*/ false)); when(firstEngineUnderTest.getDartExecutor()).thenReturn(mock(DartExecutor.class)); engineGroupUnderTest = - new FlutterEngineGroup(RuntimeEnvironment.application) { + new FlutterEngineGroup() { @Override FlutterEngine createEngine(Context context) { return firstEngineUnderTest; @@ -82,7 +71,9 @@ public void tearDown() { @Test public void listensToEngineDestruction() { - FlutterEngine firstEngine = engineGroupUnderTest.createAndRunEngine(mock(DartEntrypoint.class)); + FlutterEngine firstEngine = + engineGroupUnderTest.createAndRunEngine( + RuntimeEnvironment.application, mock(DartEntrypoint.class)); assertEquals(1, engineGroupUnderTest.activeEngines.size()); firstEngine.destroy(); @@ -91,14 +82,17 @@ public void listensToEngineDestruction() { @Test public void canRecreateEngines() { - FlutterEngine firstEngine = engineGroupUnderTest.createAndRunEngine(mock(DartEntrypoint.class)); + FlutterEngine firstEngine = + engineGroupUnderTest.createAndRunEngine( + RuntimeEnvironment.application, mock(DartEntrypoint.class)); assertEquals(1, engineGroupUnderTest.activeEngines.size()); firstEngine.destroy(); assertEquals(0, engineGroupUnderTest.activeEngines.size()); FlutterEngine secondEngine = - engineGroupUnderTest.createAndRunEngine(mock(DartEntrypoint.class)); + engineGroupUnderTest.createAndRunEngine( + RuntimeEnvironment.application, mock(DartEntrypoint.class)); assertEquals(1, engineGroupUnderTest.activeEngines.size()); // They happen to be equal in our test since we mocked it to be so. assertEquals(firstEngine, secondEngine); @@ -106,13 +100,18 @@ public void canRecreateEngines() { @Test public void canSpawnMoreEngines() { - FlutterEngine firstEngine = engineGroupUnderTest.createAndRunEngine(mock(DartEntrypoint.class)); + FlutterEngine firstEngine = + engineGroupUnderTest.createAndRunEngine( + RuntimeEnvironment.application, mock(DartEntrypoint.class)); assertEquals(1, engineGroupUnderTest.activeEngines.size()); - doReturn(mock(FlutterEngine.class)).when(firstEngine).spawn(any(DartEntrypoint.class)); + doReturn(mock(FlutterEngine.class)) + .when(firstEngine) + .spawn(any(Context.class), any(DartEntrypoint.class)); FlutterEngine secondEngine = - engineGroupUnderTest.createAndRunEngine(mock(DartEntrypoint.class)); + engineGroupUnderTest.createAndRunEngine( + RuntimeEnvironment.application, mock(DartEntrypoint.class)); assertEquals(2, engineGroupUnderTest.activeEngines.size()); firstEngine.destroy(); @@ -120,9 +119,12 @@ public void canSpawnMoreEngines() { // Now the second spawned engine is the only one left and it will be called to spawn the next // engine in the chain. - when(secondEngine.spawn(any(DartEntrypoint.class))).thenReturn(mock(FlutterEngine.class)); + when(secondEngine.spawn(any(Context.class), any(DartEntrypoint.class))) + .thenReturn(mock(FlutterEngine.class)); - FlutterEngine thirdEngine = engineGroupUnderTest.createAndRunEngine(mock(DartEntrypoint.class)); + FlutterEngine thirdEngine = + engineGroupUnderTest.createAndRunEngine( + RuntimeEnvironment.application, mock(DartEntrypoint.class)); assertEquals(2, engineGroupUnderTest.activeEngines.size()); } } diff --git a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java index 93a0a320e4231..d57ab939048b2 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/FlutterEngineTest.java @@ -211,7 +211,7 @@ public void itNotifiesListenersForDestruction() throws NameNotFoundException { EngineLifecycleListener listener = mock(EngineLifecycleListener.class); engineUnderTest.addEngineLifecycleListener(listener); engineUnderTest.destroy(); - verify(listener, times(1)).onEngineDestroy(); + verify(listener, times(1)).onEngineWillDestroy(); } @Test diff --git a/shell/platform/android/test/io/flutter/embedding/engine/PluginComponentTest.java b/shell/platform/android/test/io/flutter/embedding/engine/PluginComponentTest.java index 138e36c766f0c..7c2fb5b8069be 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/PluginComponentTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/PluginComponentTest.java @@ -5,6 +5,7 @@ package test.io.flutter.embedding.engine; import static junit.framework.TestCase.assertEquals; +import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.when; @@ -24,6 +25,8 @@ @Config(manifest = Config.NONE) @RunWith(RobolectricTestRunner.class) public class PluginComponentTest { + boolean jniAttached; + @Before public void setUp() { FlutterInjector.reset(); @@ -36,7 +39,9 @@ public void pluginsCanAccessFlutterAssetPaths() { FlutterInjector.setInstance( new FlutterInjector.Builder().setFlutterLoader(new FlutterLoader(mockFlutterJNI)).build()); FlutterJNI flutterJNI = mock(FlutterJNI.class); - when(flutterJNI.isAttached()).thenReturn(true); + jniAttached = false; + when(flutterJNI.isAttached()).thenAnswer(invocation -> jniAttached); + doAnswer(invocation -> jniAttached = true).when(flutterJNI).attachToNative(false); FlutterLoader flutterLoader = new FlutterLoader(mockFlutterJNI); diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index ea493d4bc97ad..859f77d405120 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -557,6 +557,20 @@ + (NSString*)generateThreadLabel:(NSString*)labelPrefix { threadHostType}; } +static void SetEntryPoint(flutter::Settings* settings, NSString* entrypoint, NSString* libraryURI) { + if (libraryURI) { + FML_DCHECK(entrypoint) << "Must specify entrypoint if specifying library"; + settings->advisory_script_entrypoint = entrypoint.UTF8String; + settings->advisory_script_uri = libraryURI.UTF8String; + } else if (entrypoint) { + settings->advisory_script_entrypoint = entrypoint.UTF8String; + settings->advisory_script_uri = std::string("main.dart"); + } else { + settings->advisory_script_entrypoint = std::string("main"); + settings->advisory_script_uri = std::string("main.dart"); + } +} + - (BOOL)createShell:(NSString*)entrypoint libraryURI:(NSString*)libraryURI initialRoute:(NSString*)initialRoute { @@ -572,17 +586,7 @@ - (BOOL)createShell:(NSString*)entrypoint auto platformData = [_dartProject.get() defaultPlatformData]; - if (libraryURI) { - FML_DCHECK(entrypoint) << "Must specify entrypoint if specifying library"; - settings.advisory_script_entrypoint = entrypoint.UTF8String; - settings.advisory_script_uri = libraryURI.UTF8String; - } else if (entrypoint) { - settings.advisory_script_entrypoint = entrypoint.UTF8String; - settings.advisory_script_uri = std::string("main.dart"); - } else { - settings.advisory_script_entrypoint = std::string("main"); - settings.advisory_script_uri = std::string("main.dart"); - } + SetEntryPoint(&settings, entrypoint, libraryURI); NSString* threadLabel = [FlutterEngine generateThreadLabel:_labelPrefix]; _threadHost = std::make_shared(); From 1b4b7c6e815a4595d591ff936771925f47e69d81 Mon Sep 17 00:00:00 2001 From: Xiao Yu Date: Fri, 15 Jan 2021 14:33:49 -0800 Subject: [PATCH 11/19] fix licenses + forgot to document some methods on FlutterEngineGroup --- ci/licenses_golden/licenses_flutter | 2 +- .../embedding/engine/FlutterEngineGroup.java | 27 +++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index 341f925b3bf1b..542f394e4f68d 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -747,8 +747,8 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/Splas FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/android/TransparencyMode.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineCache.java -FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineConnectionRegistry.java +FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterOverlaySurface.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/FlutterShellArgs.java diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java index b934a8ea1fd33..0e9d76350d00f 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngineGroup.java @@ -33,10 +33,37 @@ public class FlutterEngineGroup { /* package */ @VisibleForTesting final List activeEngines = new ArrayList<>(); + /** + * Creates a {@link io.flutter.embedding.engine.FlutterEngine} in this group and run its {@link + * io.flutter.embedding.engine.dart.DartExecutor} with a default entrypoint of the "main" function + * in the "lib/main.dart" file. + * + *

If no prior {@link io.flutter.embedding.engine.FlutterEngine} were created in this group, + * the initialization cost will be slightly higher than subsequent engines. The very first {@link + * io.flutter.embedding.engine.FlutterEngine} created per program, regardless of + * FlutterEngineGroup, also incurs the Dart VM creation time. + * + *

Subsequent engine creations will share resources with existing engines. However, if all + * existing engines were {@link io.flutter.embedding.engine.FlutterEngine#destroy()}ed, the next + * engine created will recreate its dependencies. + */ public FlutterEngine createAndRunDefaultEngine(@NonNull Context context) { return createAndRunEngine(context, null); } + /** + * Creates a {@link io.flutter.embedding.engine.FlutterEngine} in this group and run its {@link + * io.flutter.embedding.engine.dart.DartExecutor} with the specified {@link DartEntrypoint}. + * + *

If no prior {@link io.flutter.embedding.engine.FlutterEngine} were created in this group, + * the initialization cost will be slightly higher than subsequent engines. The very first {@link + * io.flutter.embedding.engine.FlutterEngine} created per program, regardless of + * FlutterEngineGroup, also incurs the Dart VM creation time. + * + *

Subsequent engine creations will share resources with existing engines. However, if all + * existing engines were {@link io.flutter.embedding.engine.FlutterEngine#destroy()}ed, the next + * engine created will recreate its dependencies. + */ public FlutterEngine createAndRunEngine( @NonNull Context context, @Nullable DartEntrypoint dartEntrypoint) { FlutterEngine engine = null; From 4ec9cdd86ddd2034759235b0bf8bbfb9b4d72d5f Mon Sep 17 00:00:00 2001 From: Xiao Yu Date: Sun, 17 Jan 2021 02:32:35 -0800 Subject: [PATCH 12/19] forgot to refactor scenario test --- .../java/dev/flutter/scenarios/SpawnedEngineActivity.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SpawnedEngineActivity.java b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SpawnedEngineActivity.java index bfca91031ef2e..0d8c2fe831a75 100644 --- a/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SpawnedEngineActivity.java +++ b/testing/scenario_app/android/app/src/main/java/dev/flutter/scenarios/SpawnedEngineActivity.java @@ -14,10 +14,10 @@ public class SpawnedEngineActivity extends TestActivity { @Override public FlutterEngine provideFlutterEngine(@NonNull Context context) { - FlutterEngineGroup engineGroup = new FlutterEngineGroup(context); - engineGroup.createAndRunDefaultEngine(); + FlutterEngineGroup engineGroup = new FlutterEngineGroup(); + engineGroup.createAndRunDefaultEngine(context); - FlutterEngine secondEngine = engineGroup.createAndRunDefaultEngine(); + FlutterEngine secondEngine = engineGroup.createAndRunDefaultEngine(context); secondEngine .getDartExecutor() From 02fbbc6fe52d95c94d22bfa6346332ec8f4a0ab1 Mon Sep 17 00:00:00 2001 From: Xiao Yu Date: Sun, 17 Jan 2021 02:55:28 -0800 Subject: [PATCH 13/19] revert the nativeInit crash for now since our tests rely on it --- .../android/io/flutter/embedding/engine/FlutterJNI.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index fccd223597f8b..d37bd1fc99376 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -158,8 +158,6 @@ public void init( long initTimeMillis) { if (FlutterJNI.initCalled) { Log.w(TAG, "FlutterJNI.init called more than once"); - throw new IllegalStateException( - "FlutterJNI.init cannot be called more than once per application across all FlutterJNI instances"); } FlutterJNI.nativeInit( From 4dc1f9b52cdedf4acc77e5c7e4b80c202dafd864 Mon Sep 17 00:00:00 2001 From: Xiao Yu Date: Mon, 18 Jan 2021 23:02:08 -0800 Subject: [PATCH 14/19] having trouble getting the golden images to look right, I suspect only linux can match. Try to pull it from CI --- .../flutter/scenariosui/SpawnEngineTests.java | 2 +- ...osui.SpawnEngineTests__testSpawnedEngine.png | Bin 0 -> 35332 bytes .../scenario_app/lib/src/bogus_font_text.dart | 5 ----- 3 files changed, 1 insertion(+), 6 deletions(-) create mode 100644 testing/scenario_app/android/reports/screenshots/dev.flutter.scenariosui.SpawnEngineTests__testSpawnedEngine.png diff --git a/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/SpawnEngineTests.java b/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/SpawnEngineTests.java index 8966279b33fbc..96f91337a03fb 100644 --- a/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/SpawnEngineTests.java +++ b/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenariosui/SpawnEngineTests.java @@ -31,7 +31,7 @@ public void setUp() { @Test public void testSpawnedEngine() throws Exception { - intent.putExtra("scenario", "poppable_screen"); + intent.putExtra("scenario", "spawn_engine_works"); ScreenshotUtil.capture(activityRule.launchActivity(intent)); } } diff --git a/testing/scenario_app/android/reports/screenshots/dev.flutter.scenariosui.SpawnEngineTests__testSpawnedEngine.png b/testing/scenario_app/android/reports/screenshots/dev.flutter.scenariosui.SpawnEngineTests__testSpawnedEngine.png new file mode 100644 index 0000000000000000000000000000000000000000..1c6ed1dc1df840e68f151f220e2274e2ba24d122 GIT binary patch literal 35332 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 Date: Tue, 19 Jan 2021 00:15:46 -0800 Subject: [PATCH 15/19] fix golden size --- ...ui.SpawnEngineTests__testSpawnedEngine.png | Bin 35332 -> 22325 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/testing/scenario_app/android/reports/screenshots/dev.flutter.scenariosui.SpawnEngineTests__testSpawnedEngine.png b/testing/scenario_app/android/reports/screenshots/dev.flutter.scenariosui.SpawnEngineTests__testSpawnedEngine.png index 1c6ed1dc1df840e68f151f220e2274e2ba24d122..8771e1487403fa10afa993da5821e8c75bc726f7 100644 GIT binary patch literal 22325 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 Fe*x~3kFo#& literal 35332 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 Date: Tue, 19 Jan 2021 01:34:24 -0800 Subject: [PATCH 16/19] refactor jni --- .../flutter/embedding/engine/FlutterJNI.java | 11 +-- .../android/platform_view_android_jni_impl.cc | 80 +++++++++++++++---- 2 files changed, 66 insertions(+), 25 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index d37bd1fc99376..01797d6768fb7 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -337,12 +337,8 @@ public FlutterJNI spawn( @Nullable String entrypointFunctionName, @Nullable String pathToEntrypointFunction) { ensureRunningOnMainThread(); ensureAttachedToNative(); - FlutterJNI spawnedJNI = new FlutterJNI(); - // Call the native function with the current Shell ID. It creates a new Shell. Feed the new - // Shell ID into the new FlutterJNI instance. - spawnedJNI.nativeShellHolderId = - nativeSpawn( - spawnedJNI, nativeShellHolderId, entrypointFunctionName, pathToEntrypointFunction); + FlutterJNI spawnedJNI = + nativeSpawn(nativeShellHolderId, entrypointFunctionName, pathToEntrypointFunction); Preconditions.checkState( spawnedJNI.nativeShellHolderId != null && spawnedJNI.nativeShellHolderId > 0, "Failed to spawn new JNI connected shell from existing shell."); @@ -350,8 +346,7 @@ public FlutterJNI spawn( return spawnedJNI; } - private native long nativeSpawn( - @NonNull FlutterJNI flutterJNI, + private native FlutterJNI nativeSpawn( long nativeSpawningShellId, @Nullable String entrypointFunctionName, @Nullable String pathToEntrypointFunction); diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index 52ef4724cc982..6296144d27885 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -150,19 +150,35 @@ static void DestroyJNI(JNIEnv* env, jobject jcaller, jlong shell_holder) { // the bundle path or asset manager since we can only spawn with the same // AOT. // -// The newFlutterJNI instance must be a new FlutterJNI instance that will -// hold the reference to this new returned AndroidShellHolder (and receive -// callbacks from this new AndroidShellHolder). -// // The shell_holder instance must be a pointer address to the current // AndroidShellHolder whose Shell will be used to spawn a new Shell. -static jlong SpawnJNI(JNIEnv* env, - jobject jcaller, - jobject newFlutterJNI, - jlong shell_holder, - jstring jEntrypoint, - jstring jLibraryUrl) { - fml::jni::JavaObjectWeakGlobalRef java_jni(env, newFlutterJNI); +// +// This creates a Java Long that points to the newly created +// AndroidShellHolder's raw pointer, connects that Long to a newly created +// FlutterJNI instance, then returns the FlutterJNI instance. +static jobject SpawnJNI(JNIEnv* env, + jobject jcaller, + jlong shell_holder, + jstring jEntrypoint, + jstring jLibraryUrl) { + jclass jniClass = env->FindClass("io/flutter/embedding/engine/FlutterJNI"); + if (jniClass == nullptr) { + FML_LOG(ERROR) << "Could not locate FlutterJNI class"; + return nullptr; + } + jmethodID jniConstructor = env->GetMethodID(jniClass, "", "()V"); + if (jniConstructor == nullptr) { + FML_LOG(ERROR) << "Could not locate FlutterJNI's constructor"; + return nullptr; + } + + jobject jni = env->NewObject(jniClass, jniConstructor); + if (jni == nullptr) { + FML_LOG(ERROR) << "Could not create a FlutterJNI instance"; + return nullptr; + } + + fml::jni::JavaObjectWeakGlobalRef java_jni(env, jni); std::shared_ptr jni_facade = std::make_shared(java_jni); @@ -171,11 +187,41 @@ static jlong SpawnJNI(JNIEnv* env, auto spawned_shell_holder = ANDROID_SHELL_HOLDER->Spawn(jni_facade, entrypoint, libraryUrl); - if (spawned_shell_holder != nullptr && spawned_shell_holder->IsValid()) { - return reinterpret_cast(spawned_shell_holder.release()); - } else { - return 0; + + if (spawned_shell_holder == nullptr || !spawned_shell_holder->IsValid()) { + FML_LOG(ERROR) << "Could not spawn Shell"; + return nullptr; + } + + jfieldID shellHolderId = + env->GetFieldID(jniClass, "nativeShellHolderId", "Ljava/lang/Long;"); + if (shellHolderId == nullptr) { + FML_LOG(ERROR) << "Could not locate FlutterJNI's nativeShellHolderId field"; + return nullptr; + } + + jclass longClass = env->FindClass("java/lang/Long"); + if (longClass == nullptr) { + FML_LOG(ERROR) << "Could not locate Long class"; + return nullptr; } + jmethodID longConstructor = + env->GetStaticMethodID(longClass, "valueOf", "(J)Ljava/lang/Long;"); + if (longConstructor == nullptr) { + FML_LOG(ERROR) << "Could not locate Long's constructor"; + return nullptr; + } + jobject javaLong = env->CallStaticObjectMethod( + longClass, longConstructor, + reinterpret_cast(spawned_shell_holder.release())); + if (javaLong == nullptr) { + FML_LOG(ERROR) << "Could not create a Long instance"; + return nullptr; + } + + env->SetObjectField(jni, shellHolderId, javaLong); + + return jni; } static void SurfaceCreated(JNIEnv* env, @@ -607,8 +653,8 @@ bool RegisterApi(JNIEnv* env) { }, { .name = "nativeSpawn", - .signature = "(Lio/flutter/embedding/engine/FlutterJNI;" - "JLjava/lang/String;Ljava/lang/String;)J", + .signature = "(JLjava/lang/String;Ljava/lang/String;)Lio/flutter/" + "embedding/engine/FlutterJNI;", .fnPtr = reinterpret_cast(&SpawnJNI), }, { From 01d10579b794dd5bcc6f32d5071f745edc538039 Mon Sep 17 00:00:00 2001 From: Xiao Yu Date: Tue, 19 Jan 2021 02:51:19 -0800 Subject: [PATCH 17/19] local and ci autoformats fighting each other --- shell/platform/android/platform_view_android.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/android/platform_view_android.cc b/shell/platform/android/platform_view_android.cc index d498d4dfbd97b..3b6fbf9b5e2b4 100644 --- a/shell/platform/android/platform_view_android.cc +++ b/shell/platform/android/platform_view_android.cc @@ -68,7 +68,7 @@ PlatformViewAndroid::PlatformViewAndroid( #if SHELL_ENABLE_VULKAN android_context_ = std::make_shared(AndroidRenderingAPI::kVulkan); -#else // SHELL_ENABLE_VULKAN +#else // SHELL_ENABLE_VULKAN android_context_ = std::make_unique( AndroidRenderingAPI::kOpenGLES, fml::MakeRefCounted()); From 448bdd6267b36ef7821000aa90980d87006d3cc5 Mon Sep 17 00:00:00 2001 From: Xiao Yu Date: Tue, 19 Jan 2021 11:14:21 -0800 Subject: [PATCH 18/19] review --- .../platform/android/android_shell_holder.cc | 8 +- shell/platform/android/android_shell_holder.h | 8 +- .../platform/android/platform_view_android.cc | 34 ++++++--- .../platform/android/platform_view_android.h | 16 ++-- .../android/platform_view_android_jni_impl.cc | 73 +++++++++++-------- 5 files changed, 82 insertions(+), 57 deletions(-) diff --git a/shell/platform/android/android_shell_holder.cc b/shell/platform/android/android_shell_holder.cc index 9bba31afee782..e83980f4bf4f5 100644 --- a/shell/platform/android/android_shell_holder.cc +++ b/shell/platform/android/android_shell_holder.cc @@ -137,11 +137,11 @@ AndroidShellHolder::AndroidShellHolder( } AndroidShellHolder::AndroidShellHolder( - Settings settings, - std::shared_ptr jni_facade, - std::shared_ptr thread_host, + const Settings& settings, + const std::shared_ptr& jni_facade, + const std::shared_ptr& thread_host, std::unique_ptr shell, - fml::WeakPtr platform_view) + const fml::WeakPtr& platform_view) : settings_(std::move(settings)), jni_facade_(jni_facade), platform_view_(platform_view), diff --git a/shell/platform/android/android_shell_holder.h b/shell/platform/android/android_shell_holder.h index dbae653cf4c78..39f0274fd49b5 100644 --- a/shell/platform/android/android_shell_holder.h +++ b/shell/platform/android/android_shell_holder.h @@ -117,11 +117,11 @@ class AndroidShellHolder { /// Used when constructing the Shell from the inside out when /// spawning from an existing Shell. /// - AndroidShellHolder(flutter::Settings settings, - std::shared_ptr jni_facade, - std::shared_ptr thread_host, + AndroidShellHolder(const flutter::Settings& settings, + const std::shared_ptr& jni_facade, + const std::shared_ptr& thread_host, std::unique_ptr shell, - fml::WeakPtr platform_view); + const fml::WeakPtr& platform_view); static void ThreadDestructCallback(void* value); std::optional BuildRunConfiguration( std::shared_ptr asset_manager, diff --git a/shell/platform/android/platform_view_android.cc b/shell/platform/android/platform_view_android.cc index 3b6fbf9b5e2b4..66b7d6bf24a3e 100644 --- a/shell/platform/android/platform_view_android.cc +++ b/shell/platform/android/platform_view_android.cc @@ -68,25 +68,27 @@ PlatformViewAndroid::PlatformViewAndroid( #if SHELL_ENABLE_VULKAN android_context_ = std::make_shared(AndroidRenderingAPI::kVulkan); -#else // SHELL_ENABLE_VULKAN +#else // SHELL_ENABLE_VULKAN android_context_ = std::make_unique( AndroidRenderingAPI::kOpenGLES, fml::MakeRefCounted()); #endif // SHELL_ENABLE_VULKAN } - InitSurface(); + surface_factory_ = MakeSurfaceFactory(*android_context_, *jni_facade_); + android_surface_ = MakeSurface(surface_factory_); } PlatformViewAndroid::PlatformViewAndroid( PlatformView::Delegate& delegate, flutter::TaskRunners task_runners, - std::shared_ptr jni_facade, - std::shared_ptr android_context) + const std::shared_ptr& jni_facade, + const std::shared_ptr& android_context) : PlatformView(delegate, std::move(task_runners)), jni_facade_(jni_facade), android_context_(android_context), platform_view_android_delegate_(jni_facade) { - InitSurface(); + surface_factory_ = MakeSurfaceFactory(*android_context_, *jni_facade_); + android_surface_ = MakeSurface(surface_factory_); } PlatformViewAndroid::PlatformViewAndroid( @@ -99,17 +101,25 @@ PlatformViewAndroid::PlatformViewAndroid( PlatformViewAndroid::~PlatformViewAndroid() = default; -void PlatformViewAndroid::InitSurface() { - FML_CHECK(android_context_ && android_context_->IsValid()) - << "Could not create an Android context."; +std::shared_ptr +PlatformViewAndroid::MakeSurfaceFactory( + const AndroidContext& android_context, + const PlatformViewAndroidJNI& jni_facade) { + FML_CHECK(android_context.IsValid()) + << "Could not create surface from invalid Android context."; + + return std::make_shared(android_context, + jni_facade_); +} - surface_factory_ = std::make_shared( - *android_context_, jni_facade_); +std::unique_ptr PlatformViewAndroid::MakeSurface( + const std::shared_ptr& surface_factory) { + auto surface = surface_factory->CreateSurface(); - android_surface_ = surface_factory_->CreateSurface(); - FML_CHECK(android_surface_ && android_surface_->IsValid()) + FML_CHECK(surface && surface->IsValid()) << "Could not create an OpenGL, Vulkan or Software surface to setup " "rendering."; + return surface; } void PlatformViewAndroid::NotifyCreated( diff --git a/shell/platform/android/platform_view_android.h b/shell/platform/android/platform_view_android.h index c570f7ee973a3..2bdbb2c02a958 100644 --- a/shell/platform/android/platform_view_android.h +++ b/shell/platform/android/platform_view_android.h @@ -58,10 +58,11 @@ class PlatformViewAndroid final : public PlatformView { /// Android GPU context to create new surfaces. This maximizes /// resource sharing between 2 PlatformViewAndroids of 2 Shells. /// - PlatformViewAndroid(PlatformView::Delegate& delegate, - flutter::TaskRunners task_runners, - std::shared_ptr jni_facade, - std::shared_ptr android_context); + PlatformViewAndroid( + PlatformView::Delegate& delegate, + flutter::TaskRunners task_runners, + const std::shared_ptr& jni_facade, + const std::shared_ptr& android_context); ~PlatformViewAndroid() override; @@ -169,7 +170,12 @@ class PlatformViewAndroid final : public PlatformView { // |PlatformView| void RequestDartDeferredLibrary(intptr_t loading_unit_id) override; - void InitSurface(); + std::shared_ptr MakeSurfaceFactory( + const AndroidContext& android_context, + const PlatformViewAndroidJNI& jni_facade); + + std::unique_ptr MakeSurface( + const std::shared_ptr& surface_factory); void InstallFirstFrameCallback(); diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index 6296144d27885..54fd5e1ec21f4 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -60,6 +60,8 @@ static fml::jni::ScopedJavaGlobalRef* g_flutter_jni_class = nullptr; static fml::jni::ScopedJavaGlobalRef* g_texture_wrapper_class = nullptr; +static fml::jni::ScopedJavaGlobalRef* g_java_long_class = nullptr; + // Called By Native static jmethodID g_flutter_callback_info_constructor = nullptr; @@ -75,6 +77,12 @@ jobject CreateFlutterCallbackInformation( env->NewStringUTF(callbackLibraryPath.c_str())); } +static jfieldID g_jni_shell_holder_field = nullptr; + +static jmethodID g_jni_constructor = nullptr; + +static jmethodID g_long_constructor = nullptr; + static jmethodID g_handle_platform_message_method = nullptr; static jmethodID g_handle_platform_message_response_method = nullptr; @@ -161,18 +169,7 @@ static jobject SpawnJNI(JNIEnv* env, jlong shell_holder, jstring jEntrypoint, jstring jLibraryUrl) { - jclass jniClass = env->FindClass("io/flutter/embedding/engine/FlutterJNI"); - if (jniClass == nullptr) { - FML_LOG(ERROR) << "Could not locate FlutterJNI class"; - return nullptr; - } - jmethodID jniConstructor = env->GetMethodID(jniClass, "", "()V"); - if (jniConstructor == nullptr) { - FML_LOG(ERROR) << "Could not locate FlutterJNI's constructor"; - return nullptr; - } - - jobject jni = env->NewObject(jniClass, jniConstructor); + jobject jni = env->NewObject(g_flutter_jni_class->obj(), g_jni_constructor); if (jni == nullptr) { FML_LOG(ERROR) << "Could not create a FlutterJNI instance"; return nullptr; @@ -193,33 +190,15 @@ static jobject SpawnJNI(JNIEnv* env, return nullptr; } - jfieldID shellHolderId = - env->GetFieldID(jniClass, "nativeShellHolderId", "Ljava/lang/Long;"); - if (shellHolderId == nullptr) { - FML_LOG(ERROR) << "Could not locate FlutterJNI's nativeShellHolderId field"; - return nullptr; - } - - jclass longClass = env->FindClass("java/lang/Long"); - if (longClass == nullptr) { - FML_LOG(ERROR) << "Could not locate Long class"; - return nullptr; - } - jmethodID longConstructor = - env->GetStaticMethodID(longClass, "valueOf", "(J)Ljava/lang/Long;"); - if (longConstructor == nullptr) { - FML_LOG(ERROR) << "Could not locate Long's constructor"; - return nullptr; - } jobject javaLong = env->CallStaticObjectMethod( - longClass, longConstructor, + g_java_long_class->obj(), g_long_constructor, reinterpret_cast(spawned_shell_holder.release())); if (javaLong == nullptr) { FML_LOG(ERROR) << "Could not create a Long instance"; return nullptr; } - env->SetObjectField(jni, shellHolderId, javaLong); + env->SetObjectField(jni, g_jni_shell_holder_field, javaLong); return jni; } @@ -824,6 +803,29 @@ bool RegisterApi(JNIEnv* env) { return false; } + g_jni_shell_holder_field = env->GetFieldID( + g_flutter_jni_class->obj(), "nativeShellHolderId", "Ljava/lang/Long;"); + + if (g_jni_shell_holder_field == nullptr) { + FML_LOG(ERROR) << "Could not locate FlutterJNI's nativeShellHolderId field"; + return false; + } + + g_jni_constructor = + env->GetMethodID(g_flutter_jni_class->obj(), "", "()V"); + + if (g_jni_constructor == nullptr) { + FML_LOG(ERROR) << "Could not locate FlutterJNI's constructor"; + return false; + } + + g_long_constructor = env->GetStaticMethodID(g_java_long_class->obj(), + "valueOf", "(J)Ljava/lang/Long;"); + if (g_long_constructor == nullptr) { + FML_LOG(ERROR) << "Could not locate Long's constructor"; + return false; + } + g_handle_platform_message_method = env->GetMethodID(g_flutter_jni_class->obj(), "handlePlatformMessage", "(Ljava/lang/String;[BI)V"); @@ -1075,6 +1077,13 @@ bool PlatformViewAndroid::Register(JNIEnv* env) { return false; } + g_java_long_class = new fml::jni::ScopedJavaGlobalRef( + env, env->FindClass("java/lang/Long")); + if (g_java_long_class->is_null()) { + FML_LOG(ERROR) << "Could not locate java.lang.Long class"; + return false; + } + return RegisterApi(env); } From a591f5ff71f98209c2b9e343f906172b0728ac2d Mon Sep 17 00:00:00 2001 From: Xiao Yu Date: Tue, 19 Jan 2021 11:19:41 -0800 Subject: [PATCH 19/19] autoformat fighting each other again --- shell/platform/android/platform_view_android.cc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/android/platform_view_android.cc b/shell/platform/android/platform_view_android.cc index 66b7d6bf24a3e..b580b4049bc05 100644 --- a/shell/platform/android/platform_view_android.cc +++ b/shell/platform/android/platform_view_android.cc @@ -68,7 +68,7 @@ PlatformViewAndroid::PlatformViewAndroid( #if SHELL_ENABLE_VULKAN android_context_ = std::make_shared(AndroidRenderingAPI::kVulkan); -#else // SHELL_ENABLE_VULKAN +#else // SHELL_ENABLE_VULKAN android_context_ = std::make_unique( AndroidRenderingAPI::kOpenGLES, fml::MakeRefCounted());