diff --git a/shell/common/shell.cc b/shell/common/shell.cc index 71ab4a95094d2..daafbaaaac5c2 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -175,6 +175,144 @@ std::unique_ptr Shell::CreateShellOnPlatformThread( return shell; } +bool Shell::CreateShellAsyncOnPlatformThread( + ShellCreateCallback async_init_callback, + DartVMRef vm, + TaskRunners task_runners, + WindowData window_data, + Settings settings, + fml::RefPtr isolate_snapshot, + Shell::CreateCallback on_create_platform_view, + Shell::CreateCallback on_create_rasterizer) { + if (!task_runners.IsValid()) { + FML_LOG(ERROR) << "Task runners to run the shell were invalid."; + return false; + } + + auto shell = + std::unique_ptr(new Shell(std::move(vm), task_runners, settings)); + + // Create the rasterizer on the GPU thread. + auto rasterizer_promise = + std::make_shared>>(); + + fml::TaskRunner::RunNowOrPostTask( + task_runners.GetRasterTaskRunner(), + [rasterizer_promise, + on_create_rasterizer = std::move(on_create_rasterizer), // + shell = shell.get() // + ]() mutable { + TRACE_EVENT0("flutter", "ShellSetupGPUSubsystem"); + std::unique_ptr rasterizer(on_create_rasterizer(*shell)); + rasterizer_promise->set_value(std::move(rasterizer)); + }); + + // Create the platform view on the platform thread (this thread). + auto platform_view = on_create_platform_view(*shell.get()); + if (!platform_view || !platform_view->GetWeakPtr()) { + return false; + } + + // Ask the platform view for the vsync waiter. This will be used by the engine + // to create the animator. + auto vsync_waiter = platform_view->CreateVSyncWaiter(); + if (!vsync_waiter) { + return false; + } + + // Create the IO manager on the IO thread. The IO manager must be initialized + // first because it has state that the other subsystems depend on. It must + // first be booted and the necessary references obtained to initialize the + // other subsystems. + auto io_manager_promise = + std::make_shared>>(); + auto io_task_runner = shell->GetTaskRunners().GetIOTaskRunner(); + + fml::TaskRunner::RunNowOrPostTask( + io_task_runner, + [io_task_runner, + io_manager_promise, // + weak_platform_view = platform_view->GetWeakPtr(), // + is_backgrounded_sync_switch = shell->GetIsGpuDisabledSyncSwitch() // + ]() mutable { + TRACE_EVENT0("flutter", "ShellSetupIOSubsystem"); + auto io_manager = std::make_unique( + weak_platform_view.getUnsafe()->CreateResourceContext(), + is_backgrounded_sync_switch, io_task_runner); + io_manager_promise->set_value(std::move(io_manager)); + }); + + // Send dispatcher_maker to the engine constructor because shell won't have + // platform_view set until Shell::Setup is called later. + auto dispatcher_maker = platform_view->GetDispatcherMaker(); + + // Create the engine on the UI thread. + fml::TaskRunner::RunNowOrPostTask( + shell->GetTaskRunners().GetUITaskRunner(), + fml::MakeCopyable( + [shell = std::move(shell), // + platform_view = std::move(platform_view), + async_init_callback = std::move(async_init_callback), // + dispatcher_in = std::move(dispatcher_maker), // + window_data = std::move(window_data), // + isolate_snapshot = std::move(isolate_snapshot), // + vsync_waiter = std::move(vsync_waiter), // + io_manager_promise, // + // io_manager_future, // + // rasterizer_future, + rasterizer_promise // + ]() mutable { + TRACE_EVENT0("flutter", "ShellSetupUISubsystem"); + const auto& task_runners = shell->GetTaskRunners(); + + // The animator is owned by the UI thread but it gets its vsync + // pulses from the platform. + auto animator = std::make_unique(*shell, task_runners, + std::move(vsync_waiter)); + + // wait params(io、gpu task end) + auto rasterizer = rasterizer_promise->get_future().get(); + auto snapshot_delegate = rasterizer->GetSnapshotDelegate(); + auto io_manager = io_manager_promise->get_future().get(); + auto unref_queue = io_manager->GetSkiaUnrefQueue(); + + auto engine_ref = + std::make_unique(*(shell.get()), // + dispatcher_in, // + *shell->GetDartVM(), // + std::move(isolate_snapshot), // + task_runners, // + window_data, // + shell->GetSettings(), // + std::move(animator), // + io_manager->GetWeakPtr(), // + std::move(unref_queue), // + std::move(snapshot_delegate) // + ); + + fml::TaskRunner::RunNowOrPostTask( + shell->GetTaskRunners().GetPlatformTaskRunner(), + fml::MakeCopyable( + [async_init_callback = std::move(async_init_callback), + shell = std::move(shell), + platform_view = std::move(platform_view), + rasterizer = std::move(rasterizer), + io_manager = std::move(io_manager), + engine = std::move(engine_ref)]() mutable { + if (!shell->Setup(std::move(platform_view), // + std::move(engine), // + std::move(rasterizer), // + std::move(io_manager)) // + ) { + async_init_callback(false, nullptr); + } else { + async_init_callback(true, std::move(shell)); + } + })); + })); + return true; +} + static void RecordStartupTimestamp() { if (engine_main_enter_ts == 0) { engine_main_enter_ts = Dart_TimelineGetMicros(); @@ -240,6 +378,58 @@ static void PerformInitializationTasks(const Settings& settings) { }); } +void Shell::CreateAsync( + ShellCreateCallback create_callback, + TaskRunners task_runners, + WindowData window_data, + Settings settings, + Shell::CreateCallback on_create_platform_view, + Shell::CreateCallback on_create_rasterizer) { + if (!create_callback) { + FML_CHECK(create_callback) << "CreateAsync: create_callback must be vaild!"; + return; + } + PerformInitializationTasks(settings); + PersistentCache::SetCacheSkSL(settings.cache_sksl); + + TRACE_EVENT0("flutter", "Shell::CreateAsync"); + + auto vm = DartVMRef::Create(settings); + FML_CHECK(vm) << "Must be able to initialize the VM."; + + auto vm_data = vm->GetVMData(); + + if (!task_runners.IsValid() || !on_create_platform_view || + !on_create_rasterizer) { + create_callback(false, nullptr); + return; + } + auto isolate_snapshot = vm_data->GetIsolateSnapshot(); + fml::TaskRunner::RunNowOrPostTask( + task_runners.GetPlatformTaskRunner(), + fml::MakeCopyable( + [vm = std::move(vm), // + create_callback = std::move(create_callback), // + task_runners = std::move(task_runners), // + window_data = std::move(window_data), // + settings = std::move(settings), // + isolate_snapshot = std::move(isolate_snapshot), // + on_create_platform_view = std::move(on_create_platform_view), // + on_create_rasterizer = std::move(on_create_rasterizer) // + ]() mutable { + CreateShellAsyncOnPlatformThread( + std::move(create_callback), // + std::move(vm), // + std::move(task_runners), // + std::move(window_data), // + std::move(settings), // + std::move(isolate_snapshot), // + std::move(on_create_platform_view), // + std::move(on_create_rasterizer) // + ); + })); +} + std::unique_ptr Shell::Create( TaskRunners task_runners, Settings settings, diff --git a/shell/common/shell.h b/shell/common/shell.h index f8cbc85cd6c9a..f12339026aca3 100644 --- a/shell/common/shell.h +++ b/shell/common/shell.h @@ -95,6 +95,8 @@ class Shell final : public PlatformView::Delegate, public: template using CreateCallback = std::function(Shell&)>; + using ShellCreateCallback = + std::function shell)>; //---------------------------------------------------------------------------- /// @brief Creates a shell instance using the provided settings. The @@ -166,6 +168,13 @@ class Shell final : public PlatformView::Delegate, CreateCallback on_create_platform_view, CreateCallback on_create_rasterizer); + static void CreateAsync(ShellCreateCallback callBack, + TaskRunners task_runners, + WindowData window_data, + Settings settings, + CreateCallback on_create_platform_view, + CreateCallback on_create_rasterizer); + //---------------------------------------------------------------------------- /// @brief Creates a shell instance using the provided settings. The /// callbacks to create the various shell subcomponents will be @@ -431,6 +440,16 @@ class Shell final : public PlatformView::Delegate, const Shell::CreateCallback& on_create_platform_view, const Shell::CreateCallback& on_create_rasterizer); + static bool CreateShellAsyncOnPlatformThread( + ShellCreateCallback async_init_callback, + DartVMRef vm, + TaskRunners task_runners, + WindowData window_data, + Settings settings, + fml::RefPtr isolate_snapshot, + Shell::CreateCallback on_create_platform_view, + Shell::CreateCallback on_create_rasterizer); + bool Setup(std::unique_ptr platform_view, std::unique_ptr engine, std::unique_ptr rasterizer, diff --git a/shell/common/shell_benchmarks.cc b/shell/common/shell_benchmarks.cc index c5b2751204ac4..87423ac0d1c1d 100644 --- a/shell/common/shell_benchmarks.cc +++ b/shell/common/shell_benchmarks.cc @@ -81,6 +81,79 @@ static void StartupAndShutdownShell(benchmark::State& state, FML_CHECK(!shell); } +static void StartupAsync(benchmark::State& state, bool mesure_async_total) { + auto assets_dir = fml::OpenDirectory(testing::GetFixturesPath(), false, + fml::FilePermission::kRead); + std::unique_ptr shell_res; + std::unique_ptr thread_host; + testing::ELFAOTSymbols aot_symbols; + { + Settings settings = {}; + settings.task_observer_add = [](intptr_t, fml::closure) {}; + settings.task_observer_remove = [](intptr_t) {}; + + if (DartVM::IsRunningPrecompiledCode()) { + aot_symbols = testing::LoadELFSymbolFromFixturesIfNeccessary(); + FML_CHECK( + testing::PrepareSettingsForAOTWithSymbols(settings, aot_symbols)) + << "Could not setup settings with AOT symbols."; + } else { + settings.application_kernels = [&]() { + std::vector> kernel_mappings; + kernel_mappings.emplace_back( + fml::FileMapping::CreateReadOnly(assets_dir, "kernel_blob.bin")); + return kernel_mappings; + }; + } + + thread_host = std::make_unique( + "io.flutter.bench.", ThreadHost::Type::Platform | + ThreadHost::Type::GPU | ThreadHost::Type::IO | + ThreadHost::Type::UI); + + TaskRunners task_runners("test", + thread_host->platform_thread->GetTaskRunner(), + thread_host->raster_thread->GetTaskRunner(), + thread_host->ui_thread->GetTaskRunner(), + thread_host->io_thread->GetTaskRunner()); + + fml::AutoResetWaitableEvent latch; + Shell::CreateAsync( + [&latch, &shell_res](bool success, std::unique_ptr shell) { + if (success) { + shell_res = std::move(shell); + } + latch.Signal(); + }, + std::move(task_runners), WindowData{/* default window data */}, + settings, + [](Shell& shell) { + return std::make_unique(shell, shell.GetTaskRunners()); + }, + [](Shell& shell) { + return std::make_unique(shell, shell.GetTaskRunners()); + }); + benchmarking::ScopedPauseTiming pause(state, !mesure_async_total); + latch.Wait(); + FML_CHECK(shell_res); + } + { + // dont' care shutdown time here + benchmarking::ScopedPauseTiming pause(state, true); + // Shutdown must occur synchronously on the platform thread. + fml::AutoResetWaitableEvent latch; + fml::TaskRunner::RunNowOrPostTask( + thread_host->platform_thread->GetTaskRunner(), + [&shell_res, &latch]() mutable { + shell_res.reset(); + latch.Signal(); + }); + latch.Wait(); + thread_host.reset(); + } + FML_CHECK(!shell_res); +} + static void BM_ShellInitialization(benchmark::State& state) { while (state.KeepRunning()) { StartupAndShutdownShell(state, true, false); @@ -105,4 +178,18 @@ static void BM_ShellInitializationAndShutdown(benchmark::State& state) { BENCHMARK(BM_ShellInitializationAndShutdown); +static void BM_ShellInitializationAsyncLockTime(benchmark::State& state) { + while (state.KeepRunning()) { + StartupAsync(state, false); + } +} +BENCHMARK(BM_ShellInitializationAsyncLockTime); + +static void BM_ShellInitializationAsyncTotalTime(benchmark::State& state) { + while (state.KeepRunning()) { + StartupAsync(state, true); + } +} +BENCHMARK(BM_ShellInitializationAsyncTotalTime); + } // namespace flutter diff --git a/shell/common/shell_unittests.cc b/shell/common/shell_unittests.cc index c9c1345541fcc..15c01b0caba68 100644 --- a/shell/common/shell_unittests.cc +++ b/shell/common/shell_unittests.cc @@ -150,6 +150,67 @@ TEST_F(ShellTest, ASSERT_FALSE(DartVMRef::IsInstanceRunning()); } +TEST_F(ShellTest, + AsyncInitializeWithMultipleThreadButCallingThreadAsPlatformThread) { + ASSERT_FALSE(DartVMRef::IsInstanceRunning()); + Settings settings = CreateSettingsForFixture(); + // Must create new PlatformThread for this test case: + // 1、current thread is platform thread + // 2、In test case, we need call latch.wait here and latch.signal in callback + // 3、but asyncCallback will called on platform thread after next frames + // So, if use current platform thread ,will make deadlock. (callback will + // never excute cause latch.wait) But in user case, they can call CreateAsync + // directly, because then will never caled latch.wait/signal + + ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".", + ThreadHost::Type::Platform | ThreadHost::Type::GPU | + ThreadHost::Type::IO | ThreadHost::Type::UI); + fml::MessageLoop::EnsureInitializedForCurrentThread(); + TaskRunners task_runners("test", thread_host.platform_thread->GetTaskRunner(), + thread_host.raster_thread->GetTaskRunner(), + thread_host.ui_thread->GetTaskRunner(), + thread_host.io_thread->GetTaskRunner()); + fml::AutoResetWaitableEvent latch; + std::unique_ptr shell_res; + + // run on newPlatformThread + fml::TaskRunner::RunNowOrPostTask( + task_runners.GetPlatformTaskRunner(), + [&task_runners, &latch, &shell_res, &settings]() { + Shell::CreateAsync( + [&latch, &shell_res](bool success, std::unique_ptr shell) { + if (success) { + shell_res = std::move(shell); + } + // called on platformread + latch.Signal(); + }, + std::move(task_runners), WindowData{/* default window data */}, + std::move(settings), + [](Shell& shell) { + const auto vsync_clock = std::make_shared(); + return ShellTestPlatformView::Create( + shell, shell.GetTaskRunners(), vsync_clock, + [task_runners = shell.GetTaskRunners()]() { + return static_cast>( + std::make_unique(task_runners)); + }, + ShellTestPlatformView::BackendType::kDefaultBackend); + }, + [](Shell& shell) { + return std::make_unique(shell, + shell.GetTaskRunners()); + }); + }); + + // In test case, must wait excute end + latch.Wait(); + ASSERT_TRUE(ValidateShell(shell_res.get())); + ASSERT_TRUE(DartVMRef::IsInstanceRunning()); + DestroyShell(std::move(shell_res), std::move(task_runners)); + ASSERT_FALSE(DartVMRef::IsInstanceRunning()); +} + TEST_F(ShellTest, InitializeWithGPUAndPlatformThreadsTheSame) { ASSERT_FALSE(DartVMRef::IsInstanceRunning()); Settings settings = CreateSettingsForFixture(); diff --git a/shell/platform/android/android_shell_holder.cc b/shell/platform/android/android_shell_holder.cc index fe4d85ab7b259..86b96a07999d8 100644 --- a/shell/platform/android/android_shell_holder.cc +++ b/shell/platform/android/android_shell_holder.cc @@ -30,15 +30,34 @@ static WindowData GetDefaultWindowData() { AndroidShellHolder::AndroidShellHolder( flutter::Settings settings, fml::jni::JavaObjectWeakGlobalRef java_object, - bool is_background_view) - : settings_(std::move(settings)), java_object_(java_object) { + bool is_background_view) { + AndroidShellHolder(std::move(settings), java_object, is_background_view, + false); +} + +AndroidShellHolder::AndroidShellHolder( + flutter::Settings settings, + fml::jni::JavaObjectWeakGlobalRef java_object, + bool is_background_view, + bool initAsync) + : settings_(std::move(settings)), + java_object_(java_object), + is_background_view_(is_background_view) { + if (initAsync) { + return; + } + AsyncInitCallback empty; + init(empty); +} + +void AndroidShellHolder::init(AsyncInitCallback holderCallback) { static size_t shell_count = 1; auto thread_label = std::to_string(shell_count++); FML_CHECK(pthread_key_create(&thread_destruct_key_, ThreadDestructCallback) == 0); - if (is_background_view) { + if (is_background_view_) { thread_host_ = {thread_label, ThreadHost::Type::UI}; } else { thread_host_ = {thread_label, ThreadHost::Type::UI | ThreadHost::Type::GPU | @@ -50,13 +69,14 @@ AndroidShellHolder::AndroidShellHolder( FML_CHECK(pthread_setspecific(key, reinterpret_cast(1)) == 0); }); thread_host_.ui_thread->GetTaskRunner()->PostTask(jni_exit_task); - if (!is_background_view) { + if (!is_background_view_) { thread_host_.raster_thread->GetTaskRunner()->PostTask(jni_exit_task); } fml::WeakPtr weak_platform_view; Shell::CreateCallback on_create_platform_view = - [is_background_view, java_object, &weak_platform_view](Shell& shell) { + [is_background_view = is_background_view_, java_object = java_object_, + &weak_platform_view](Shell& shell) { std::unique_ptr platform_view_android; if (is_background_view) { platform_view_android = std::make_unique( @@ -90,7 +110,7 @@ AndroidShellHolder::AndroidShellHolder( fml::RefPtr io_runner; fml::RefPtr platform_runner = fml::MessageLoop::GetCurrent().GetTaskRunner(); - if (is_background_view) { + if (is_background_view_) { auto single_task_runner = thread_host_.ui_thread->GetTaskRunner(); gpu_runner = single_task_runner; ui_runner = single_task_runner; @@ -106,22 +126,38 @@ AndroidShellHolder::AndroidShellHolder( ui_runner, // ui io_runner // io ); - - shell_ = - Shell::Create(task_runners, // task runners - GetDefaultWindowData(), // window data - settings_, // settings - on_create_platform_view, // platform view create callback - on_create_rasterizer // rasterizer create callback - ); - + if (holderCallback) { + Shell::CreateAsync( + [holderCallback = std::move(holderCallback), holder_point = this]( + bool success, std::unique_ptr shell) { + if (!success) { + holderCallback(false); + return; + } + holder_point->shell_ = std::move(shell); + holder_point->setThreadPriority(); + holderCallback(true); + }, + std::move(task_runners), GetDefaultWindowData(), settings_, + std::move(on_create_platform_view), std::move(on_create_rasterizer)); + } else { + shell_ = + Shell::Create(task_runners, // task runners + GetDefaultWindowData(), // window data + settings_, // settings + on_create_platform_view, // platform view create callback + on_create_rasterizer // rasterizer create callback + ); + } platform_view_ = weak_platform_view; FML_DCHECK(platform_view_); +} +void AndroidShellHolder::setThreadPriority() { is_valid_ = shell_ != nullptr; if (is_valid_) { - task_runners.GetRasterTaskRunner()->PostTask([]() { + shell_->GetTaskRunners().GetRasterTaskRunner()->PostTask([]() { // Android describes -8 as "most important display threads, for // compositing the screen and retrieving input events". Conservatively // set the raster thread to slightly lower priority than it. @@ -133,7 +169,7 @@ AndroidShellHolder::AndroidShellHolder( } } }); - task_runners.GetUITaskRunner()->PostTask([]() { + shell_->GetTaskRunners().GetUITaskRunner()->PostTask([]() { if (::setpriority(PRIO_PROCESS, gettid(), -1) != 0) { FML_LOG(ERROR) << "Failed to set UI task runner priority"; } diff --git a/shell/platform/android/android_shell_holder.h b/shell/platform/android/android_shell_holder.h index 9be7f35df0bb8..244e56b8506fc 100644 --- a/shell/platform/android/android_shell_holder.h +++ b/shell/platform/android/android_shell_holder.h @@ -21,12 +21,21 @@ namespace flutter { class AndroidShellHolder { public: + using AsyncInitCallback = std::function; + AndroidShellHolder(flutter::Settings settings, fml::jni::JavaObjectWeakGlobalRef java_object, bool is_background_view); + AndroidShellHolder(flutter::Settings settings, + fml::jni::JavaObjectWeakGlobalRef java_object, + bool is_background_view, + bool initAsync); + ~AndroidShellHolder(); + void init(AsyncInitCallback asyncCallBack); + bool IsValid() const; void Launch(RunConfiguration configuration); @@ -47,11 +56,13 @@ class AndroidShellHolder { ThreadHost thread_host_; std::unique_ptr shell_; bool is_valid_ = false; + bool is_background_view_ = false; pthread_key_t thread_destruct_key_; uint64_t next_pointer_flow_id_ = 0; - static void ThreadDestructCallback(void* value); + void setThreadPriority(); + 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 271a1a8ec24dc..306bf317b2a0b 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -67,24 +67,27 @@ public class FlutterEngine { private static final String TAG = "FlutterEngine"; - @NonNull private final FlutterJNI flutterJNI; - @NonNull private final FlutterRenderer renderer; - @NonNull private final DartExecutor dartExecutor; - @NonNull private final FlutterEnginePluginRegistry pluginRegistry; + @NonNull private FlutterJNI flutterJNI; + @NonNull private FlutterRenderer renderer; + @NonNull private DartExecutor dartExecutor; + @NonNull private FlutterEnginePluginRegistry pluginRegistry; // System channels. - @NonNull private final AccessibilityChannel accessibilityChannel; - @NonNull private final KeyEventChannel keyEventChannel; - @NonNull private final LifecycleChannel lifecycleChannel; - @NonNull private final LocalizationChannel localizationChannel; - @NonNull private final NavigationChannel navigationChannel; - @NonNull private final PlatformChannel platformChannel; - @NonNull private final SettingsChannel settingsChannel; - @NonNull private final SystemChannel systemChannel; - @NonNull private final TextInputChannel textInputChannel; + @NonNull private AccessibilityChannel accessibilityChannel; + @NonNull private KeyEventChannel keyEventChannel; + @NonNull private LifecycleChannel lifecycleChannel; + @NonNull private LocalizationChannel localizationChannel; + @NonNull private NavigationChannel navigationChannel; + @NonNull private PlatformChannel platformChannel; + @NonNull private SettingsChannel settingsChannel; + @NonNull private SystemChannel systemChannel; + @NonNull private TextInputChannel textInputChannel; + @NonNull private Context context; + @NonNull private FlutterLoader flutterLoader; + private boolean automaticallyRegisterPlugins; // Platform Views. - @NonNull private final PlatformViewsController platformViewsController; + @NonNull private PlatformViewsController platformViewsController; // Engine Lifecycle. @NonNull private final Set engineLifecycleListeners = new HashSet<>(); @@ -101,6 +104,28 @@ public void onPreEngineRestart() { platformViewsController.onPreEngineRestart(); } + + @Override + public void onAsyncAttachEnd(boolean success) { + for (EngineLifecycleListener lifecycleListener : engineLifecycleListeners) { + lifecycleListener.onAsyncAttachEnd(success); + } + if (success) { + initAfterAttachNative(); + onAsyncCreateEngineEnd(true); + + } else { + Log.e(TAG, "asyncAttach failed"); + onAsyncCreateEngineEnd(false); + } + } + + @Override + public void onAsyncCreateEngineEnd(boolean success) { + for (EngineLifecycleListener lifecycleListener : engineLifecycleListeners) { + lifecycleListener.onAsyncCreateEngineEnd(success); + } + } }; /** @@ -200,21 +225,81 @@ public FlutterEngine( @NonNull PlatformViewsController platformViewsController, @Nullable String[] dartVmArgs, boolean automaticallyRegisterPlugins) { + doInit( + context, + flutterLoader, + flutterJNI, + platformViewsController, + dartVmArgs, + automaticallyRegisterPlugins, + null); + } + + public FlutterEngine() {} + + public void initAsync( + @NonNull Context appContext, @NonNull EngineLifecycleListener asyncInitCallback) { + if (asyncInitCallback == null) { + Log.w( + TAG, + "initAsync: callback is null, you should care lifecycle, and called other api after initCallback called"); + asyncInitCallback = + new EngineLifecycleListener() { + @Override + public void onPreEngineRestart() {} + + @Override + public void onAsyncAttachEnd(boolean success) {} + + @Override + public void onAsyncCreateEngineEnd(boolean success) {} + }; + } + doInit( + appContext, + FlutterLoader.getInstance(), + new FlutterJNI(), + new PlatformViewsController(), + null, + true, + asyncInitCallback); + } + + private void doInit( + @NonNull Context context, + @NonNull FlutterLoader flutterLoader, + @NonNull FlutterJNI flutterJNI, + @NonNull PlatformViewsController platformViewsController, + @Nullable String[] dartVmArgs, + boolean automaticallyRegisterPlugins, + EngineLifecycleListener asyncInitListener) { this.flutterJNI = flutterJNI; + this.context = context; + this.automaticallyRegisterPlugins = automaticallyRegisterPlugins; + this.flutterLoader = flutterLoader; + this.platformViewsController = platformViewsController; flutterLoader.startInitialization(context.getApplicationContext()); flutterLoader.ensureInitializationComplete(context, dartVmArgs); flutterJNI.addEngineLifecycleListener(engineLifecycleListener); - attachToJni(); + if (asyncInitListener != null) { + addEngineLifecycleListener(asyncInitListener); + attachToJni(true); + return; + } + attachToJni(false); + initAfterAttachNative(); + } - this.dartExecutor = new DartExecutor(flutterJNI, context.getAssets()); + private void initAfterAttachNative() { + this.dartExecutor = new DartExecutor(this.flutterJNI, this.context.getAssets()); this.dartExecutor.onAttachedToJNI(); // TODO(mattcarroll): FlutterRenderer is temporally coupled to attach(). Remove that coupling if // possible. - this.renderer = new FlutterRenderer(flutterJNI); + this.renderer = new FlutterRenderer(this.flutterJNI); - accessibilityChannel = new AccessibilityChannel(dartExecutor, flutterJNI); + accessibilityChannel = new AccessibilityChannel(dartExecutor, this.flutterJNI); keyEventChannel = new KeyEventChannel(dartExecutor); lifecycleChannel = new LifecycleChannel(dartExecutor); localizationChannel = new LocalizationChannel(dartExecutor); @@ -224,23 +309,22 @@ public FlutterEngine( systemChannel = new SystemChannel(dartExecutor); textInputChannel = new TextInputChannel(dartExecutor); - this.platformViewsController = platformViewsController; this.platformViewsController.onAttachedToJNI(); - this.pluginRegistry = - new FlutterEnginePluginRegistry(context.getApplicationContext(), this, flutterLoader); + new FlutterEnginePluginRegistry( + this.context.getApplicationContext(), this, this.flutterLoader); if (automaticallyRegisterPlugins) { registerPlugins(); } } - private void attachToJni() { + private void attachToJni(boolean asyncInitMode) { Log.v(TAG, "Attaching to JNI."); // TODO(mattcarroll): update native call to not take in "isBackgroundView" - flutterJNI.attachToNative(false); + flutterJNI.attachToNative(false, asyncInitMode); - if (!isAttachedToJni()) { + if (!asyncInitMode && !isAttachedToJni()) { throw new RuntimeException("FlutterEngine failed to attach to its native Object reference."); } } @@ -438,5 +522,11 @@ public ContentProviderControlSurface getContentProviderControlSurface() { public interface EngineLifecycleListener { /** Lifecycle callback invoked before a hot restart of the Flutter engine. */ void onPreEngineRestart(); + /** + * Lifecycle callback invoked after flutterJni.asyncAttachNative end (native engine env ready). + */ + void onAsyncAttachEnd(boolean success); + + void onAsyncCreateEngineEnd(boolean success); } } diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index e599281f84137..dc98b557e88c5 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -181,14 +181,31 @@ public boolean isAttached() { *

This method must not be invoked if {@code FlutterJNI} is already attached to native. */ @UiThread - public void attachToNative(boolean isBackgroundView) { + public void attachToNative(boolean isBackgroundView, boolean asyncInit) { ensureRunningOnMainThread(); ensureNotAttachedToNative(); - nativePlatformViewId = nativeAttach(this, isBackgroundView); + if (asyncInit) { + nativeAttachAsync(this, isBackgroundView); + } else { + nativePlatformViewId = nativeAttach(this, isBackgroundView); + } } private native long nativeAttach(@NonNull FlutterJNI flutterJNI, boolean isBackgroundView); + private native void nativeAttachAsync(@NonNull FlutterJNI flutterJNI, boolean isBackgroundView); + + // called by native on async init mode, means engine async init success + private void handleNativeAttachAsync(boolean success, long viewId) { + if (success) { + this.nativePlatformViewId = viewId; + } + success = success & isAttached(); + for (EngineLifecycleListener listener : engineLifecycleListeners) { + listener.onAsyncAttachEnd(success); + } + } + /** * 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/view/FlutterNativeView.java b/shell/platform/android/io/flutter/view/FlutterNativeView.java index 01e3434d89d7c..7aea2a0860eac 100644 --- a/shell/platform/android/io/flutter/view/FlutterNativeView.java +++ b/shell/platform/android/io/flutter/view/FlutterNativeView.java @@ -144,7 +144,7 @@ public void setMessageHandler(String channel, BinaryMessageHandler handler) { } private void attach(FlutterNativeView view, boolean isBackgroundView) { - mFlutterJNI.attachToNative(isBackgroundView); + mFlutterJNI.attachToNative(isBackgroundView, false); dartExecutor.onAttachedToJNI(); } @@ -160,5 +160,11 @@ public void onPreEngineRestart() { } mPluginRegistry.onPreEngineRestart(); } + + @Override + public void onAsyncAttachEnd(boolean success) {} + + @Override + public void onAsyncCreateEngineEnd(boolean success) {} } } diff --git a/shell/platform/android/platform_view_android_jni.cc b/shell/platform/android/platform_view_android_jni.cc index 8b70d145dc439..7c701724fcb5e 100644 --- a/shell/platform/android/platform_view_android_jni.cc +++ b/shell/platform/android/platform_view_android_jni.cc @@ -76,6 +76,16 @@ void FlutterViewHandlePlatformMessage(JNIEnv* env, FML_CHECK(CheckException(env)); } +static jmethodID g_handle_platform_engine_init_method = nullptr; +void FlutterViewHandleEngineInit(JNIEnv* env, + jobject obj, + jboolean success, + jlong holder_id) { + env->CallVoidMethod(obj, g_handle_platform_engine_init_method, success, + holder_id); + FML_CHECK(CheckException(env)); +} + static jmethodID g_handle_platform_message_response_method = nullptr; void FlutterViewHandlePlatformMessageResponse(JNIEnv* env, jobject obj, @@ -159,6 +169,24 @@ static jlong AttachJNI(JNIEnv* env, } } +static void AttachJNIAsync(JNIEnv* env, + jclass clazz, + jobject flutterJNI, + jboolean is_background_view) { + fml::jni::JavaObjectWeakGlobalRef java_object(env, flutterJNI); + std::shared_ptr view_id = std::make_shared(); + auto shell_holder_ref = std::make_unique( + FlutterMain::Get().GetSettings(), java_object, is_background_view, true); + + jlong shell_holder = reinterpret_cast(shell_holder_ref.release()); + ANDROID_SHELL_HOLDER->init([java_object, shell_holder](bool success) { + JNIEnv* env = fml::jni::AttachCurrentThread(); + auto scoped_jni_obj = java_object.get(env); + FlutterViewHandleEngineInit(env, scoped_jni_obj.obj(), success, + shell_holder); + }); +} + static void DestroyJNI(JNIEnv* env, jobject jcaller, jlong shell_holder) { delete ANDROID_SHELL_HOLDER; } @@ -492,6 +520,11 @@ bool RegisterApi(JNIEnv* env) { .signature = "(Lio/flutter/embedding/engine/FlutterJNI;Z)J", .fnPtr = reinterpret_cast(&AttachJNI), }, + { + .name = "nativeAttachAsync", + .signature = "(Lio/flutter/embedding/engine/FlutterJNI;Z)V", + .fnPtr = reinterpret_cast(&AttachJNIAsync), + }, { .name = "nativeDestroy", .signature = "(J)V", @@ -624,6 +657,14 @@ bool RegisterApi(JNIEnv* env) { return false; } + g_handle_platform_engine_init_method = env->GetMethodID( + g_flutter_jni_class->obj(), "handleNativeAttachAsync", "(ZJ)V"); + + if (g_handle_platform_engine_init_method == nullptr) { + FML_LOG(ERROR) << "Could not locate handleEngineCreated method"; + return false; + } + g_update_semantics_method = env->GetMethodID(g_flutter_jni_class->obj(), "updateSemantics", "(Ljava/nio/ByteBuffer;[Ljava/lang/String;)V"); diff --git a/shell/platform/android/platform_view_android_jni.h b/shell/platform/android/platform_view_android_jni.h index 07c70b2a45b50..ab55be6353da5 100644 --- a/shell/platform/android/platform_view_android_jni.h +++ b/shell/platform/android/platform_view_android_jni.h @@ -46,6 +46,11 @@ void SurfaceTextureGetTransformMatrix(JNIEnv* env, void SurfaceTextureDetachFromGLContext(JNIEnv* env, jobject obj); +void FlutterViewHandleEngineInit(JNIEnv* env, + jobject obj, + jboolean success, + jlong holder_id); + } // namespace flutter #endif // FLUTTER_SHELL_PLATFORM_ANDROID_PLATFORM_VIEW_ANDROID_JNI_H_ diff --git a/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h b/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h index 51782ceca07cb..40c01d0022be5 100644 --- a/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h +++ b/shell/platform/darwin/ios/framework/Headers/FlutterEngine.h @@ -24,6 +24,11 @@ NS_ASSUME_NONNULL_BEGIN */ extern NSString* const FlutterDefaultDartEntrypoint; +/** + * the callback of engine async init mode + */ +typedef void (^InitCallBackBlock)(bool); + /** * The FlutterEngine class coordinates a single instance of execution for a * `FlutterDartProject`. It may have zero or one `FlutterViewController` at a @@ -128,6 +133,10 @@ FLUTTER_EXPORT */ - (BOOL)run; +/** like run() method, but this method not block mainThread when init + * @param block callbackBlock, called when async init end. must not be nil! + */ +- (void)asyncRun:(InitCallBackBlock)block; /** * Runs a Dart program on an Isolate from the main Dart library (i.e. the library that * contains `main()`). diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm index bfb4a2d2ff8cd..01395a4b8bc2d 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEngine.mm @@ -409,7 +409,9 @@ - (void)launchEngine:(NSString*)entrypoint libraryURI:(NSString*)libraryOrNil { libraryOrNil:libraryOrNil]); } -- (BOOL)createShell:(NSString*)entrypoint libraryURI:(NSString*)libraryURI { +- (BOOL)createShell:(NSString*)entrypoint + libraryURI:(NSString*)libraryURI + asyncInitCallback:(InitCallBackBlock)oc_async_init_block { if (_shell != nullptr) { FML_LOG(WARNING) << "This FlutterEngine was already invoked."; return NO; @@ -485,6 +487,28 @@ - (BOOL)createShell:(NSString*)entrypoint libraryURI:(NSString*)libraryURI { _threadHost.ui_thread->GetTaskRunner(), // ui _threadHost.io_thread->GetTaskRunner() // io ); + // Create the shell with async mode . This is a not blocking operation. + if (oc_async_init_block) { + oc_async_init_block = [oc_async_init_block copy]; + FlutterEngine* selfRef = [self retain]; + flutter::Shell::CreateAsync( + [oc_async_init_block, selfRef](bool success, std::unique_ptr shell) { + if (!success) { + FML_LOG(ERROR) << "Could not start a shell FlutterEngine on asyncInitMode"; + oc_async_init_block(false); + } else { + selfRef->_shell = std::move(shell); + [selfRef setUpWhenInit]; + oc_async_init_block(true); + // release ref + [oc_async_init_block release]; + [selfRef release]; + } + }, + std::move(task_runners), std::move(windowData), std::move(settings), + on_create_platform_view, on_create_rasterizer); + return true; + } // Create the shell. This is a blocking operation. _shell = flutter::Shell::Create(std::move(task_runners), // task runners std::move(windowData), // window data @@ -498,23 +522,50 @@ - (BOOL)createShell:(NSString*)entrypoint libraryURI:(NSString*)libraryURI { FML_LOG(ERROR) << "Could not start a shell FlutterEngine with entrypoint: " << entrypoint.UTF8String; } else { - [self setupChannels]; - [self onLocaleUpdated:nil]; - if (!_platformViewsController) { - _platformViewsController.reset(new flutter::FlutterPlatformViewsController()); - } - _publisher.reset([[FlutterObservatoryPublisher alloc] init]); - [self maybeSetupPlatformViewChannels]; - _shell->GetIsGpuDisabledSyncSwitch()->SetSwitch(_isGpuDisabled ? true : false); + [self setUpWhenInit]; } - return _shell != nullptr; } +- (BOOL)createShell:(NSString*)entrypoint libraryURI:(NSString*)libraryURI { + return [self createShell:entrypoint libraryURI:libraryURI asyncInitCallback:nil]; +} + +- (void)setUpWhenInit { + [self setupChannels]; + [self onLocaleUpdated:nil]; + if (!_platformViewsController) { + _platformViewsController.reset(new flutter::FlutterPlatformViewsController()); + } + _publisher.reset([[FlutterObservatoryPublisher alloc] init]); + [self maybeSetupPlatformViewChannels]; + _shell->GetIsGpuDisabledSyncSwitch()->SetSwitch(_isGpuDisabled ? true : false); +} + - (BOOL)run { return [self runWithEntrypoint:FlutterDefaultDartEntrypoint libraryURI:nil]; } +- (void)asyncRun:(InitCallBackBlock)asyncCallback { + if (asyncCallback == nil) { + FML_LOG(WARNING) << "asyncRun: callback is nil, you should care lifecycle, and called other " + "api after initCallback called"; + asyncCallback = ^(bool success) { + }; + } + asyncCallback = [asyncCallback copy]; + [self createShell:FlutterDefaultDartEntrypoint + libraryURI:nil + asyncInitCallback:^(bool success) { + if (success) { + [self setUpWhenInit]; + [self launchEngine:FlutterDefaultDartEntrypoint libraryURI:nil]; + } + asyncCallback(success); + [asyncCallback release]; + }]; +} + - (BOOL)runWithEntrypoint:(NSString*)entrypoint libraryURI:(NSString*)libraryURI { if ([self createShell:entrypoint libraryURI:libraryURI]) { [self launchEngine:entrypoint libraryURI:libraryURI]; diff --git a/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenarios/EngineLaunchE2ETest.java b/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenarios/EngineLaunchE2ETest.java index 24d3ed5e4ae4f..4994e49a0b40c 100644 --- a/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenarios/EngineLaunchE2ETest.java +++ b/testing/scenario_app/android/app/src/androidTest/java/dev/flutter/scenarios/EngineLaunchE2ETest.java @@ -11,7 +11,9 @@ import androidx.test.internal.runner.junit4.statement.UiThreadStatement; import androidx.test.runner.AndroidJUnit4; 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 java.util.concurrent.CompletableFuture; import java.util.concurrent.ExecutionException; import java.util.concurrent.TimeUnit; @@ -65,4 +67,45 @@ public void smokeTestEngineLaunch() throws Throwable { } // If it gets to here, statusReceived is true. } + + @Test + public void smokeTestEngineAsyncLaunch() { + final CompletableFuture statusReceived = new CompletableFuture<>(); + UiThreadStatement.runOnUiThread( + () -> { + FlutterEngine engine = new FlutterEngine(); + engine.initAsync( + this, + new EngineLifecycleListener() { + @Override + public void onPreEngineRestart() {} + + @Override + public void onAsyncAttachEnd(boolean success) {} + + @Override + public void onAsyncCreateEngineEnd(boolean success) { + if (!success) { + Log.e("flutter", "========> init error ! <======================="); + + } else { + engine.getDartExecutor().executeDartEntrypoint(DartEntrypoint.createDefault()); + } + statusReceived.complete(success); + } + }); + }); + try { + Boolean result = statusReceived.get(10, TimeUnit.SECONDS); + if (!result) { + fail("flutterEngien async init failed"); + } + } catch (ExecutionException e) { + fail(e.getMessage()); + } catch (InterruptedException e) { + fail(e.getMessage()); + } catch (TimeoutException e) { + fail("timed out waiting for EngineAsyncLaunch"); + } + } } diff --git a/testing/scenario_app/ios/Scenarios/ScenariosTests/FlutterEngineTest.m b/testing/scenario_app/ios/Scenarios/ScenariosTests/FlutterEngineTest.m index f875764e99e2b..107df9092235d 100644 --- a/testing/scenario_app/ios/Scenarios/ScenariosTests/FlutterEngineTest.m +++ b/testing/scenario_app/ios/Scenarios/ScenariosTests/FlutterEngineTest.m @@ -47,4 +47,14 @@ - (void)testChannelSetup { XCTAssertNil(engine.lifecycleChannel); } +- (void)testAsyncInitEngine { + FlutterEngine* engine = [[FlutterEngine alloc] initWithName:@"my flutter engine"]; + [flutterEngine asyncRun:^(boolean success) { + // on app + if (success) { + //[GeneratedPluginRegistrant registerWithRegistry:self]; + } + }]; +} + @end