From e6783f5a4be98236fb384320787e59955fa1b016 Mon Sep 17 00:00:00 2001 From: garyqian Date: Thu, 10 Sep 2020 17:55:22 -0700 Subject: [PATCH 1/7] Initial code Begin impl android embedder work Add success and failure callbacks Success and failure calls Additional impl Begin engine impl Building No crash Additional logging Switch to int loading unit id Splits install now Begin reading libs Track loadingUnitId as well: Pass dart handler call through to embedder One direction works Move dart complete into platform_view_android Move loading into runtime Loading .so data complete Move dart set handler to dart isolate Successfully loads dart libs Filter by ABI Dynamic abi passing, cleanup Compile and cleanup Fix dependencies Separate handler into own class Move static vars to handler Clean imports, comments USe io.flutter.Log Fix host unittests Use temp void* Switch to override splitcompat Working asset manager load Merge with upstream, working again --- DEPS | 2 +- runtime/BUILD.gn | 2 + runtime/dart_deferred_load_handler.cc | 38 +++ runtime/dart_deferred_load_handler.h | 33 +++ runtime/dart_isolate.cc | 83 +++++-- runtime/dart_isolate.h | 12 +- runtime/dart_isolate_unittests.cc | 51 ++-- runtime/dart_lifecycle_unittests.cc | 20 ++ runtime/runtime_controller.cc | 115 +++++++-- runtime/runtime_controller.h | 7 + runtime/runtime_delegate.h | 2 + shell/common/engine.cc | 19 ++ shell/common/engine.h | 12 + shell/common/engine_unittests.cc | 3 + shell/common/platform_view.cc | 11 + shell/common/platform_view.h | 19 ++ shell/common/shell.cc | 22 +- shell/common/shell.h | 15 ++ shell/platform/android/BUILD.gn | 2 + .../android/embedding_bundle/build.gradle | 2 + .../io/flutter/app/FlutterApplication.java | 10 + .../embedding/engine/FlutterEngine.java | 9 + .../flutter/embedding/engine/FlutterJNI.java | 73 ++++++ .../DynamicFeatureManager.java | 23 ++ .../PlayStoreDynamicFeatureManager.java | 220 ++++++++++++++++++ .../engine/loader/ApplicationInfoLoader.java | 2 +- .../engine/loader/FlutterLoader.java | 2 +- shell/platform/android/jni/jni_mock.h | 5 + .../android/jni/platform_view_android_jni.h | 2 + .../platform/android/platform_view_android.cc | 19 ++ .../platform/android/platform_view_android.h | 11 + .../android/platform_view_android_jni_impl.cc | 89 +++++++ .../android/platform_view_android_jni_impl.h | 2 + testing/dart_isolate_runner.cc | 26 +++ 34 files changed, 895 insertions(+), 68 deletions(-) create mode 100644 runtime/dart_deferred_load_handler.cc create mode 100644 runtime/dart_deferred_load_handler.h create mode 100644 shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java create mode 100644 shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java diff --git a/DEPS b/DEPS index 4e77c00034a10..cd8856e94f7ef 100644 --- a/DEPS +++ b/DEPS @@ -469,7 +469,7 @@ deps = { 'packages': [ { 'package': 'flutter/android/embedding_bundle', - 'version': 'last_updated:2020-05-20T01:36:16-0700' + 'version': 'last_updated:2020-09-11T17:57:41-0700' } ], 'condition': 'download_android_deps', diff --git a/runtime/BUILD.gn b/runtime/BUILD.gn index 9788794f7da83..09207b330c3c4 100644 --- a/runtime/BUILD.gn +++ b/runtime/BUILD.gn @@ -37,6 +37,8 @@ group("libdart") { source_set("runtime") { sources = [ + "dart_deferred_load_handler.cc", + "dart_deferred_load_handler.h", "dart_isolate.cc", "dart_isolate.h", "dart_isolate_group_data.cc", diff --git a/runtime/dart_deferred_load_handler.cc b/runtime/dart_deferred_load_handler.cc new file mode 100644 index 0000000000000..0f203ee4937b9 --- /dev/null +++ b/runtime/dart_deferred_load_handler.cc @@ -0,0 +1,38 @@ +// Copyright 2020 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. + +#include "flutter/runtime/dart_deferred_load_handler.h" + +#include "flutter/runtime/runtime_controller.h" + +namespace flutter { + +void* DartDeferredLoadHandler::runtime_controller_ = nullptr; + +Dart_DeferredLoadHandler + DartDeferredLoadHandler::empty_dart_deferred_load_handler = + &DartDeferredLoadHandler::EmptyDartLoadLibrary; + +Dart_DeferredLoadHandler DartDeferredLoadHandler::dart_deferred_load_handler = + &DartDeferredLoadHandler::OnDartLoadLibrary; + +void DartDeferredLoadHandler::RegisterRuntimeController( + void* runtime_controller) { + runtime_controller_ = runtime_controller; +} + +Dart_Handle DartDeferredLoadHandler::OnDartLoadLibrary( + intptr_t loading_unit_id) { + if (runtime_controller_ != nullptr) + return static_cast(runtime_controller_) + ->OnDartLoadLibrary(loading_unit_id); + return Dart_Null(); +} + +Dart_Handle DartDeferredLoadHandler::EmptyDartLoadLibrary( + intptr_t loading_unit_id) { + return Dart_Null(); +} + +} // namespace flutter diff --git a/runtime/dart_deferred_load_handler.h b/runtime/dart_deferred_load_handler.h new file mode 100644 index 0000000000000..a6947570ea132 --- /dev/null +++ b/runtime/dart_deferred_load_handler.h @@ -0,0 +1,33 @@ +// Copyright 2020 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. + +#ifndef FLUTTER_RUNTIME_DART_DEFERRED_LOAD_HANDLER_H_ +#define FLUTTER_RUNTIME_DART_DEFERRED_LOAD_HANDLER_H_ + +#include +#include + +#include "third_party/dart/runtime/include/dart_api.h" + +namespace flutter { + +class DartDeferredLoadHandler { + public: + // TODO: Fix deps to not use void* + static void RegisterRuntimeController(void* runtime_controller); + + static Dart_DeferredLoadHandler empty_dart_deferred_load_handler; + static Dart_DeferredLoadHandler dart_deferred_load_handler; + + private: + static Dart_Handle OnDartLoadLibrary(intptr_t loading_unit_id); + // No-op function that returns Dart_Null() for when the isolate is not + // expected to handle deferred libraries. + static Dart_Handle EmptyDartLoadLibrary(intptr_t loading_unit_id); + static void* runtime_controller_; +}; + +} // namespace flutter + +#endif // FLUTTER_RUNTIME_DART_DEFERRED_LOAD_HANDLER_H_ diff --git a/runtime/dart_isolate.cc b/runtime/dart_isolate.cc index b834957a9281a..89c88c29012d5 100644 --- a/runtime/dart_isolate.cc +++ b/runtime/dart_isolate.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "flutter/runtime/dart_isolate.h" +#include "flutter/runtime/dart_deferred_load_handler.h" #include #include @@ -86,6 +87,7 @@ std::weak_ptr DartIsolate::CreateRunningRootIsolate( std::string advisory_script_uri, std::string advisory_script_entrypoint, Flags isolate_flags, + Dart_DeferredLoadHandler& deferred_load_handler, const fml::closure& isolate_create_callback, const fml::closure& isolate_shutdown_callback, std::optional dart_entrypoint, @@ -116,6 +118,7 @@ std::weak_ptr DartIsolate::CreateRunningRootIsolate( advisory_script_uri, // advisory_script_entrypoint, // isolate_flags, // + deferred_load_handler, // isolate_create_callback, // isolate_shutdown_callback // ) @@ -149,8 +152,8 @@ std::weak_ptr DartIsolate::CreateRunningRootIsolate( } if (settings.root_isolate_create_callback) { - // Isolate callbacks always occur in isolate scope and before user code has - // had a chance to run. + // Isolate callbacks always occur in isolate scope and before user code + // has had a chance to run. tonic::DartState::Scope scope(isolate.get()); settings.root_isolate_create_callback(*isolate.get()); } @@ -186,6 +189,7 @@ std::weak_ptr DartIsolate::CreateRootIsolate( std::string advisory_script_uri, std::string advisory_script_entrypoint, Flags flags, + Dart_DeferredLoadHandler& deferred_load_handler, const fml::closure& isolate_create_callback, const fml::closure& isolate_shutdown_callback) { TRACE_EVENT0("flutter", "DartIsolate::CreateRootIsolate"); @@ -222,7 +226,7 @@ std::weak_ptr DartIsolate::CreateRootIsolate( auto isolate_flags = flags.Get(); Dart_Isolate vm_isolate = CreateDartIsolateGroup( std::move(isolate_group_data), std::move(isolate_data), &isolate_flags, - error.error()); + deferred_load_handler, error.error()); if (error) { FML_LOG(ERROR) << "CreateDartIsolateGroup failed: " << error.str(); @@ -288,7 +292,15 @@ std::string DartIsolate::GetServiceId() { return service_id; } -bool DartIsolate::Initialize(Dart_Isolate dart_isolate) { +// Dart_Handle DartDeferredLoadHandler(intptr_t loading_unit_id) { +// FML_LOG(ERROR) << "LOAD REQUEST CALLED FOR ID: " << loading_unit_id; +// // Temporary call for prototyping. +// return +// Engine::GetEngineDelegateTemp()->OnDartLoadLibrary(loading_unit_id); +// } + +bool DartIsolate::Initialize(Dart_Isolate dart_isolate, + Dart_DeferredLoadHandler& deferred_load_handler) { TRACE_EVENT0("flutter", "DartIsolate::Initialize"); if (phase_ != Phase::Uninitialized) { return false; @@ -320,6 +332,9 @@ bool DartIsolate::Initialize(Dart_Isolate dart_isolate) { return false; } + // Dart_SetDeferredLoadHandler(&DartDeferredLoadHandler); + Dart_SetDeferredLoadHandler(deferred_load_handler); + if (!UpdateThreadPoolNames()) { return false; } @@ -332,6 +347,22 @@ fml::RefPtr DartIsolate::GetMessageHandlingTaskRunner() const { return message_handling_task_runner_; } +bool DartIsolate::LoadLoadingUnit(intptr_t loading_unit_id, + const uint8_t* snapshot_data, + const uint8_t* snapshot_instructions) { + tonic::DartState::Scope scope(this); + Dart_Handle result = Dart_DeferredLoadComplete(loading_unit_id, snapshot_data, + snapshot_instructions); + if (Dart_IsApiError(result)) { + FML_LOG(ERROR) << "LOADING FAILED " << loading_unit_id; + result = + Dart_DeferredLoadCompleteError(loading_unit_id, Dart_GetError(result), + /*transient*/ true); + return false; + } + return true; +} + void DartIsolate::SetMessageHandlingTaskRunner( fml::RefPtr runner) { if (!IsRootIsolate() || !runner) { @@ -649,8 +680,8 @@ bool DartIsolate::RunFromLibrary(std::optional library_name, bool DartIsolate::Shutdown() { TRACE_EVENT0("flutter", "DartIsolate::Shutdown"); // This call may be re-entrant since Dart_ShutdownIsolate can invoke the - // cleanup callback which deletes the embedder side object of the dart isolate - // (a.k.a. this). + // cleanup callback which deletes the embedder side object of the dart + // isolate (a.k.a. this). if (phase_ == Phase::Shutdown) { return false; } @@ -678,7 +709,8 @@ Dart_Isolate DartIsolate::DartCreateAndStartServiceIsolate( if (!vm_data) { *error = fml::strdup( - "Could not access VM data to initialize isolates. This may be because " + "Could not access VM data to initialize isolates. This may be " + "because " "the VM has initialized shutdown on another thread already."); return nullptr; } @@ -696,8 +728,8 @@ Dart_Isolate DartIsolate::DartCreateAndStartServiceIsolate( #if (FLUTTER_RUNTIME_MODE != FLUTTER_RUNTIME_MODE_DEBUG) // TODO(68663): The service isolate in debug mode is always launched without - // sound null safety. Fix after the isolate snapshot data is created with the - // right flags. + // sound null safety. Fix after the isolate snapshot data is created with + // the right flags. flags->null_safety = vm_data->GetIsolateSnapshot()->IsNullSafetyEnabled(nullptr); #endif @@ -716,8 +748,9 @@ Dart_Isolate DartIsolate::DartCreateAndStartServiceIsolate( DART_VM_SERVICE_ISOLATE_NAME, // script uri DART_VM_SERVICE_ISOLATE_NAME, // script entrypoint DartIsolate::Flags{flags}, // flags - nullptr, // isolate create callback - nullptr // isolate shutdown callback + DartDeferredLoadHandler::empty_dart_deferred_load_handler, + nullptr, // isolate create callback + nullptr // isolate shutdown callback ); std::shared_ptr service_isolate = weak_service_isolate.lock(); @@ -780,8 +813,9 @@ Dart_Isolate DartIsolate::DartIsolateGroupCreateCallback( // The VM attempts to start the VM service for us on |Dart_Initialize|. In // such a case, the callback data will be null and the script URI will be // DART_VM_SERVICE_ISOLATE_NAME. In such cases, we just create the service - // isolate like normal but dont hold a reference to it at all. We also start - // this isolate since we will never again reference it from the engine. + // isolate like normal but dont hold a reference to it at all. We also + // start this isolate since we will never again reference it from the + // engine. return DartCreateAndStartServiceIsolate(package_root, // package_config, // flags, // @@ -826,7 +860,8 @@ Dart_Isolate DartIsolate::DartIsolateGroupCreateCallback( false))); // is_root_isolate Dart_Isolate vm_isolate = CreateDartIsolateGroup( - std::move(isolate_group_data), std::move(isolate_data), flags, error); + std::move(isolate_group_data), std::move(isolate_data), flags, + DartDeferredLoadHandler::empty_dart_deferred_load_handler, error); if (*error) { FML_LOG(ERROR) << "CreateDartIsolateGroup failed: " << error; @@ -871,7 +906,9 @@ bool DartIsolate::DartIsolateInitializeCallback(void** child_callback_data, false))); // is_root_isolate // root isolate should have been created via CreateRootIsolate - if (!InitializeIsolate(*embedder_isolate, isolate, error)) { + if (!InitializeIsolate( + *embedder_isolate, isolate, + DartDeferredLoadHandler::empty_dart_deferred_load_handler, error)) { return false; } @@ -887,6 +924,7 @@ Dart_Isolate DartIsolate::CreateDartIsolateGroup( std::unique_ptr> isolate_group_data, std::unique_ptr> isolate_data, Dart_IsolateFlags* flags, + Dart_DeferredLoadHandler& deferred_load_handler, char** error) { TRACE_EVENT0("flutter", "DartIsolate::CreateDartIsolateGroup"); @@ -902,12 +940,14 @@ Dart_Isolate DartIsolate::CreateDartIsolateGroup( return nullptr; } - // Ownership of the isolate data objects has been transferred to the Dart VM. + // Ownership of the isolate data objects has been transferred to the Dart + // VM. std::shared_ptr embedder_isolate(*isolate_data); isolate_group_data.release(); isolate_data.release(); - if (!InitializeIsolate(std::move(embedder_isolate), isolate, error)) { + if (!InitializeIsolate(std::move(embedder_isolate), isolate, + deferred_load_handler, error)) { return nullptr; } @@ -917,9 +957,10 @@ Dart_Isolate DartIsolate::CreateDartIsolateGroup( bool DartIsolate::InitializeIsolate( std::shared_ptr embedder_isolate, Dart_Isolate isolate, + Dart_DeferredLoadHandler& deferred_load_handler, char** error) { TRACE_EVENT0("flutter", "DartIsolate::InitializeIsolate"); - if (!embedder_isolate->Initialize(isolate)) { + if (!embedder_isolate->Initialize(isolate, deferred_load_handler)) { *error = fml::strdup("Embedder could not initialize the Dart isolate."); FML_DLOG(ERROR) << *error; return false; @@ -932,9 +973,9 @@ bool DartIsolate::InitializeIsolate( return false; } - // Root isolates will be setup by the engine and the service isolate (which is - // also a root isolate) by the utility routines in the VM. However, secondary - // isolates will be run by the VM if they are marked as runnable. + // Root isolates will be setup by the engine and the service isolate (which + // is also a root isolate) by the utility routines in the VM. However, + // secondary isolates will be run by the VM if they are marked as runnable. if (!embedder_isolate->IsRootIsolate()) { auto child_isolate_preparer = embedder_isolate->GetIsolateGroupData().GetChildIsolatePreparer(); diff --git a/runtime/dart_isolate.h b/runtime/dart_isolate.h index 5e6913d52b185..bdbd05bc903e4 100644 --- a/runtime/dart_isolate.h +++ b/runtime/dart_isolate.h @@ -225,6 +225,7 @@ class DartIsolate : public UIDartState { std::string advisory_script_uri, std::string advisory_script_entrypoint, Flags flags, + Dart_DeferredLoadHandler& deferred_load_handler, const fml::closure& isolate_create_callback, const fml::closure& isolate_shutdown_callback, std::optional dart_entrypoint, @@ -384,6 +385,10 @@ class DartIsolate : public UIDartState { /// fml::RefPtr GetMessageHandlingTaskRunner() const; + bool LoadLoadingUnit(intptr_t loading_unit_id, + const uint8_t* snapshot_data, + const uint8_t* snapshot_instructions); + private: friend class IsolateConfiguration; class AutoFireClosure { @@ -418,6 +423,7 @@ class DartIsolate : public UIDartState { std::string advisory_script_uri, std::string advisory_script_entrypoint, Flags flags, + Dart_DeferredLoadHandler& deferred_load_handler, const fml::closure& isolate_create_callback, const fml::closure& isolate_shutdown_callback); @@ -432,7 +438,9 @@ class DartIsolate : public UIDartState { std::string advisory_script_entrypoint, bool is_root_isolate); - [[nodiscard]] bool Initialize(Dart_Isolate isolate); + [[nodiscard]] bool Initialize( + Dart_Isolate isolate, + Dart_DeferredLoadHandler& deferred_load_handler); void SetMessageHandlingTaskRunner(fml::RefPtr runner); @@ -472,10 +480,12 @@ class DartIsolate : public UIDartState { std::unique_ptr> isolate_group_data, std::unique_ptr> isolate_data, Dart_IsolateFlags* flags, + Dart_DeferredLoadHandler& deferred_load_handler, char** error); static bool InitializeIsolate(std::shared_ptr embedder_isolate, Dart_Isolate isolate, + Dart_DeferredLoadHandler& deferred_load_handler, char** error); // |Dart_IsolateShutdownCallback| diff --git a/runtime/dart_isolate_unittests.cc b/runtime/dart_isolate_unittests.cc index a90599fd8a3fb..236f3e76ce357 100644 --- a/runtime/dart_isolate_unittests.cc +++ b/runtime/dart_isolate_unittests.cc @@ -8,6 +8,7 @@ #include "flutter/fml/synchronization/count_down_latch.h" #include "flutter/fml/synchronization/waitable_event.h" #include "flutter/fml/thread.h" +#include "flutter/runtime/dart_deferred_load_handler.h" #include "flutter/runtime/dart_vm.h" #include "flutter/runtime/dart_vm_lifecycle.h" #include "flutter/runtime/isolate_configuration.h" @@ -52,18 +53,19 @@ TEST_F(DartIsolateTest, RootIsolateCreationAndShutdown) { IsolateConfiguration::InferFromSettings(settings); auto weak_isolate = DartIsolate::CreateRunningRootIsolate( - vm_data->GetSettings(), // settings - vm_data->GetIsolateSnapshot(), // isolate snapshot - std::move(task_runners), // task runners - nullptr, // window - {}, // snapshot delegate - {}, // hint freed delegate - {}, // io manager - {}, // unref queue - {}, // image decoder - "main.dart", // advisory uri - "main", // advisory entrypoint, - DartIsolate::Flags{}, // flags + vm_data->GetSettings(), // settings + vm_data->GetIsolateSnapshot(), // isolate snapshot + std::move(task_runners), // task runners + nullptr, // window + {}, // snapshot delegate + {}, // hint freed delegate + {}, // io manager + {}, // unref queue + {}, // image decoder + "main.dart", // advisory uri + "main", // advisory entrypoint, + DartIsolate::Flags{}, // flags + DartDeferredLoadHandler::empty_dart_deferred_load_handler, settings.isolate_create_callback, // isolate create callback settings.isolate_shutdown_callback, // isolate shutdown callback "main", // dart entrypoint @@ -92,18 +94,19 @@ TEST_F(DartIsolateTest, IsolateShutdownCallbackIsInIsolateScope) { auto isolate_configuration = IsolateConfiguration::InferFromSettings(settings); auto weak_isolate = DartIsolate::CreateRunningRootIsolate( - vm_data->GetSettings(), // settings - vm_data->GetIsolateSnapshot(), // isolate snapshot - std::move(task_runners), // task runners - nullptr, // window - {}, // snapshot delegate - {}, // hint freed delegate - {}, // io manager - {}, // unref queue - {}, // image decoder - "main.dart", // advisory uri - "main", // advisory entrypoint - DartIsolate::Flags{}, // flags + vm_data->GetSettings(), // settings + vm_data->GetIsolateSnapshot(), // isolate snapshot + std::move(task_runners), // task runners + nullptr, // window + {}, // snapshot delegate + {}, // hint freed delegate + {}, // io manager + {}, // unref queue + {}, // image decoder + "main.dart", // advisory uri + "main", // advisory entrypoint + DartIsolate::Flags{}, // flags + DartDeferredLoadHandler::empty_dart_deferred_load_handler, settings.isolate_create_callback, // isolate create callback settings.isolate_shutdown_callback, // isolate shutdown callback "main", // dart entrypoint diff --git a/runtime/dart_lifecycle_unittests.cc b/runtime/dart_lifecycle_unittests.cc index 8576b3fb11292..0e247cf4a5939 100644 --- a/runtime/dart_lifecycle_unittests.cc +++ b/runtime/dart_lifecycle_unittests.cc @@ -6,6 +6,7 @@ #include "flutter/fml/paths.h" #include "flutter/fml/synchronization/count_down_latch.h" #include "flutter/fml/synchronization/waitable_event.h" +#include "flutter/runtime/dart_deferred_load_handler.h" #include "flutter/runtime/dart_vm.h" #include "flutter/runtime/dart_vm_lifecycle.h" #include "flutter/runtime/isolate_configuration.h" @@ -51,6 +52,25 @@ static std::shared_ptr CreateAndRunRootIsolate( FML_CHECK(entrypoint.size() > 0); TaskRunners runners("io.flutter.test", task_runner, task_runner, task_runner, task_runner); + auto isolate_weak = DartIsolate::CreateRootIsolate( + vm.GetSettings(), // settings + vm.GetIsolateSnapshot(), // isolate_snapshot + runners, // task_runners + {}, // window + {}, // snapshot_delegate + {}, // hint_freed_delegate + {}, // io_manager + {}, // unref_queue + {}, // image_decoder + "main.dart", // advisory_script_uri + entrypoint.c_str(), // advisory_script_entrypoint + nullptr, // flags + DartDeferredLoadHandler::empty_dart_deferred_load_handler, + settings.isolate_create_callback, // isolate create callback + settings.isolate_shutdown_callback // isolate shutdown callback + ); + + auto isolate = isolate_weak.lock(); auto isolate_configuration = IsolateConfiguration::InferFromSettings(settings); diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index 3551e1967134e..0d793b05ec42d 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -4,6 +4,9 @@ #include "flutter/runtime/runtime_controller.h" +#include +#include + #include "flutter/fml/message_loop.h" #include "flutter/fml/trace_event.h" #include "flutter/lib/ui/compositing/scene.h" @@ -11,6 +14,7 @@ #include "flutter/lib/ui/window/platform_configuration.h" #include "flutter/lib/ui/window/viewport_metrics.h" #include "flutter/lib/ui/window/window.h" +#include "flutter/runtime/dart_deferred_load_handler.h" #include "flutter/runtime/isolate_configuration.h" #include "flutter/runtime/runtime_delegate.h" #include "third_party/tonic/dart_message_handler.h" @@ -53,7 +57,9 @@ RuntimeController::RuntimeController( platform_data_(std::move(p_platform_data)), isolate_create_callback_(p_isolate_create_callback), isolate_shutdown_callback_(p_isolate_shutdown_callback), - persistent_isolate_data_(std::move(p_persistent_isolate_data)) {} + persistent_isolate_data_(std::move(p_persistent_isolate_data)) { + DartDeferredLoadHandler::RegisterRuntimeController(this); +} RuntimeController::~RuntimeController() { FML_DCHECK(Dart_CurrentIsolate() == nullptr); @@ -353,23 +359,24 @@ bool RuntimeController::LaunchRootIsolate( auto strong_root_isolate = DartIsolate::CreateRunningRootIsolate( - settings, // - isolate_snapshot_, // - task_runners_, // - std::make_unique(this), // - snapshot_delegate_, // - hint_freed_delegate_, // - io_manager_, // - unref_queue_, // - image_decoder_, // - advisory_script_uri_, // - advisory_script_entrypoint_, // - DartIsolate::Flags{}, // - isolate_create_callback_, // - isolate_shutdown_callback_, // - dart_entrypoint, // - dart_entrypoint_library, // - std::move(isolate_configuration) // + settings, // + isolate_snapshot_, // + task_runners_, // + std::make_unique(this), // + snapshot_delegate_, // + hint_freed_delegate_, // + io_manager_, // + unref_queue_, // + image_decoder_, // + advisory_script_uri_, // + advisory_script_entrypoint_, // + DartIsolate::Flags{}, // + DartDeferredLoadHandler::dart_deferred_load_handler, // + isolate_create_callback_, // + isolate_shutdown_callback_, // + dart_entrypoint, // + dart_entrypoint_library, // + std::move(isolate_configuration) // ) .lock(); @@ -415,6 +422,78 @@ std::optional RuntimeController::GetRootIsolateReturnCode() { return root_isolate_return_code_; } +void RuntimeController::CompleteDartLoadLibrary( + intptr_t loading_unit_id, + std::string lib_name, + std::vector& apkPaths, + std::string abi) { + std::vector searchPaths; + for (std::string apkPath : apkPaths) { + searchPaths.push_back(apkPath + "!/lib/" + abi + "/" + lib_name); + } + + // TODO: Switch to using the NativeLibrary class, eg: + // + // fml::RefPtr native_lib = + // fml::NativeLibrary::Create(lib_name.c_str()); + void* handle = nullptr; + while (handle == nullptr && !searchPaths.empty()) { + std::string path = searchPaths.back(); + FML_LOG(ERROR) << "Attempting to open " << path; + handle = ::dlopen(path.c_str(), RTLD_NOW); + searchPaths.pop_back(); + if (handle == nullptr) { + FML_LOG(ERROR) << "Opening lib \"" << lib_name + << "\" failed: " + std::string(::dlerror()); + } + } + if (handle == nullptr) { + FML_LOG(ERROR) << "No libs Found for SearchPaths:"; + for (std::string paths : searchPaths) { + FML_LOG(ERROR) << " " << paths; + } + // FML_LOG(ERROR) << "Opening lib \"" << lib_name + // << "\" failed: " + std::string(::dlerror()); + return; + } + + uint8_t* isolate_data = + static_cast(::dlsym(handle, DartSnapshot::kIsolateDataSymbol)); + if (isolate_data == nullptr) { + // Mac sometimes requires an underscore prefix. + std::stringstream underscore_symbol_name; + underscore_symbol_name << "_" << DartSnapshot::kIsolateDataSymbol; + isolate_data = static_cast( + ::dlsym(handle, underscore_symbol_name.str().c_str())); + if (isolate_data == nullptr) { + FML_LOG(ERROR) << "Could not resolve symbol in library: " + << DartSnapshot::kIsolateDataSymbol; + return; + } + } + uint8_t* isolate_instructions = static_cast( + ::dlsym(handle, DartSnapshot::kIsolateInstructionsSymbol)); + if (isolate_instructions == nullptr) { + // Mac sometimes requires an underscore prefix. + std::stringstream underscore_symbol_name; + underscore_symbol_name << "_" << DartSnapshot::kIsolateInstructionsSymbol; + isolate_instructions = static_cast( + ::dlsym(handle, underscore_symbol_name.str().c_str())); + if (isolate_data == nullptr) { + FML_LOG(ERROR) << "Could not resolve symbol in library: " + << DartSnapshot::kIsolateInstructionsSymbol; + return; + } + } + + root_isolate_.lock()->LoadLoadingUnit(loading_unit_id, isolate_data, + isolate_instructions); +} + +Dart_Handle RuntimeController::OnDartLoadLibrary(intptr_t loading_unit_id) { + return client_.OnDartLoadLibrary(loading_unit_id); +} + RuntimeController::Locale::Locale(std::string language_code_, std::string country_code_, std::string script_code_, diff --git a/runtime/runtime_controller.h b/runtime/runtime_controller.h index 22941c7388a1b..26e41fb8875b3 100644 --- a/runtime/runtime_controller.h +++ b/runtime/runtime_controller.h @@ -473,6 +473,13 @@ class RuntimeController : public PlatformConfigurationClient { /// std::optional GetRootIsolateReturnCode(); + void CompleteDartLoadLibrary(intptr_t loading_unit_id, + std::string lib_name, + std::vector& apkPaths, + std::string abi); + + Dart_Handle OnDartLoadLibrary(intptr_t loading_unit_id); + protected: /// Constructor for Mocks. RuntimeController(RuntimeDelegate& client, TaskRunners p_task_runners); diff --git a/runtime/runtime_delegate.h b/runtime/runtime_delegate.h index 55978b4dbc39f..e5a2113c4380d 100644 --- a/runtime/runtime_delegate.h +++ b/runtime/runtime_delegate.h @@ -43,6 +43,8 @@ class RuntimeDelegate { ComputePlatformResolvedLocale( const std::vector& supported_locale_data) = 0; + virtual Dart_Handle OnDartLoadLibrary(intptr_t loading_unit_id) = 0; + protected: virtual ~RuntimeDelegate(); }; diff --git a/shell/common/engine.cc b/shell/common/engine.cc index 6c9304f1ad945..b8e0792c29e2f 100644 --- a/shell/common/engine.cc +++ b/shell/common/engine.cc @@ -507,4 +507,23 @@ const std::string& Engine::GetLastEntrypointLibrary() const { return last_entry_point_library_; } +// |RuntimeDelegate| +Dart_Handle Engine::OnDartLoadLibrary(intptr_t loading_unit_id) { + return delegate_.OnDartLoadLibrary(loading_unit_id); +} + +void Engine::CompleteDartLoadLibrary( + intptr_t loading_unit_id, + std::string lib_name, + std::vector& apkPaths, + std::string abi, + std::shared_ptr asset_manager) { + if (runtime_controller_->IsRootIsolateRunning()) { + runtime_controller_->CompleteDartLoadLibrary(loading_unit_id, lib_name, + apkPaths, abi); + } + + UpdateAssetManager(std::move(asset_manager)); +} + } // namespace flutter diff --git a/shell/common/engine.h b/shell/common/engine.h index 1eb2cf36ecc72..5f5e16793d2e0 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -30,6 +30,7 @@ #include "flutter/shell/common/rasterizer.h" #include "flutter/shell/common/run_configuration.h" #include "flutter/shell/common/shell_io_manager.h" +#include "third_party/dart/runtime/include/dart_api.h" #include "third_party/skia/include/core/SkPicture.h" namespace flutter { @@ -260,6 +261,8 @@ class Engine final : public RuntimeDelegate, virtual std::unique_ptr> ComputePlatformResolvedLocale( const std::vector& supported_locale_data) = 0; + + virtual Dart_Handle OnDartLoadLibrary(intptr_t loading_unit_id) = 0; }; //---------------------------------------------------------------------------- @@ -767,6 +770,12 @@ class Engine final : public RuntimeDelegate, /// const std::string& InitialRoute() const { return initial_route_; } + void CompleteDartLoadLibrary(intptr_t loading_unit_id, + std::string lib_name, + std::vector& apkPaths, + std::string abi, + std::shared_ptr asset_manager); + private: Engine::Delegate& delegate_; const Settings settings_; @@ -815,6 +824,9 @@ class Engine final : public RuntimeDelegate, std::unique_ptr> ComputePlatformResolvedLocale( const std::vector& supported_locale_data) override; + // |RuntimeDelegate| + Dart_Handle OnDartLoadLibrary(intptr_t loading_unit_id) override; + void SetNeedsReportTimings(bool value) override; void StopAnimator(); diff --git a/shell/common/engine_unittests.cc b/shell/common/engine_unittests.cc index 7405511513a9b..d6f3bf9fa3bcf 100644 --- a/shell/common/engine_unittests.cc +++ b/shell/common/engine_unittests.cc @@ -13,6 +13,7 @@ #include "rapidjson/document.h" #include "rapidjson/stringbuffer.h" #include "rapidjson/writer.h" +#include "third_party/dart/runtime/include/dart_api.h" ///\note Deprecated MOCK_METHOD macros used until this issue is resolved: // https://github.com/google/googletest/issues/2490 @@ -32,6 +33,7 @@ class MockDelegate : public Engine::Delegate { MOCK_METHOD1(ComputePlatformResolvedLocale, std::unique_ptr>( const std::vector&)); + MOCK_METHOD1(OnDartLoadLibrary, Dart_Handle(intptr_t)); }; class MockResponse : public PlatformMessageResponse { @@ -55,6 +57,7 @@ class MockRuntimeDelegate : public RuntimeDelegate { MOCK_METHOD1(ComputePlatformResolvedLocale, std::unique_ptr>( const std::vector&)); + MOCK_METHOD1(OnDartLoadLibrary, Dart_Handle(intptr_t)); }; class MockRuntimeController : public RuntimeController { diff --git a/shell/common/platform_view.cc b/shell/common/platform_view.cc index a933bd3b3c6bf..b68d34df84c14 100644 --- a/shell/common/platform_view.cc +++ b/shell/common/platform_view.cc @@ -152,4 +152,15 @@ PlatformView::ComputePlatformResolvedLocales( return out; } +Dart_Handle PlatformView::OnDartLoadLibrary(intptr_t loading_unit_id) { + return Dart_Null(); +} + +void PlatformView::CompleteDartLoadLibrary( + intptr_t loading_unit_id, + std::string lib_name, + std::vector& apkPaths, + std::string abi, + std::shared_ptr asset_manager) {} + } // namespace flutter diff --git a/shell/common/platform_view.h b/shell/common/platform_view.h index 5b376878ee208..8ceccba3d928f 100644 --- a/shell/common/platform_view.h +++ b/shell/common/platform_view.h @@ -228,6 +228,16 @@ class PlatformView { virtual std::unique_ptr> ComputePlatformViewResolvedLocale( const std::vector& supported_locale_data) = 0; + + virtual Dart_Handle OnPlatformViewDartLoadLibrary( + intptr_t loading_unit_id) = 0; + + virtual void CompleteDartLoadLibrary( + intptr_t loading_unit_id, + std::string lib_name, + std::vector& apkPaths, + std::string abi, + std::shared_ptr asset_manager) = 0; }; //---------------------------------------------------------------------------- @@ -581,6 +591,15 @@ class PlatformView { ComputePlatformResolvedLocales( const std::vector& supported_locale_data); + virtual Dart_Handle OnDartLoadLibrary(intptr_t loading_unit_id); + + virtual void CompleteDartLoadLibrary( + intptr_t loading_unit_id, + std::string lib_name, + std::vector& apkPaths, + std::string abi, + std::shared_ptr asset_manager); + protected: PlatformView::Delegate& delegate_; const TaskRunners task_runners_; diff --git a/shell/common/shell.cc b/shell/common/shell.cc index 0f470cff37c6f..a418383f137f6 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -1178,7 +1178,7 @@ void Shell::SetNeedsReportTimings(bool value) { // |Engine::Delegate| std::unique_ptr> Shell::ComputePlatformResolvedLocale( const std::vector& supported_locale_data) { - return ComputePlatformViewResolvedLocale(supported_locale_data); + return platform_view_->ComputePlatformResolvedLocales(supported_locale_data); } // |PlatformView::Delegate| @@ -1188,6 +1188,26 @@ Shell::ComputePlatformViewResolvedLocale( return platform_view_->ComputePlatformResolvedLocales(supported_locale_data); } +void Shell::CompleteDartLoadLibrary( + intptr_t loading_unit_id, + std::string lib_name, + std::vector& apkPaths, + std::string abi, + std::shared_ptr asset_manager) { + engine_->CompleteDartLoadLibrary(loading_unit_id, lib_name, apkPaths, abi, + std::move(asset_manager)); +} + +// |Engine::Delegate| +Dart_Handle Shell::OnDartLoadLibrary(intptr_t loading_unit_id) { + return OnPlatformViewDartLoadLibrary(loading_unit_id); +} + +// |PlatformView::Delegate| +Dart_Handle Shell::OnPlatformViewDartLoadLibrary(intptr_t loading_unit_id) { + return platform_view_->OnDartLoadLibrary(loading_unit_id); +} + void Shell::ReportTimings() { FML_DCHECK(is_setup_); FML_DCHECK(task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread()); diff --git a/shell/common/shell.h b/shell/common/shell.h index 8e8fd18b48b3b..1e799a10ef559 100644 --- a/shell/common/shell.h +++ b/shell/common/shell.h @@ -36,6 +36,7 @@ #include "flutter/shell/common/platform_view.h" #include "flutter/shell/common/rasterizer.h" #include "flutter/shell/common/shell_io_manager.h" +#include "third_party/dart/runtime/include/dart_api.h" namespace flutter { @@ -511,6 +512,17 @@ class Shell final : public PlatformView::Delegate, std::unique_ptr> ComputePlatformViewResolvedLocale( const std::vector& supported_locale_data) override; + // |PlatformView::Delegate| + Dart_Handle OnPlatformViewDartLoadLibrary(intptr_t loading_unit_id) override; + + // |PlatformView::Delegate| + void CompleteDartLoadLibrary( + intptr_t loading_unit_id, + std::string lib_name, + std::vector& apkPaths, + std::string abi, + std::shared_ptr asset_manager) override; + // |Animator::Delegate| void OnAnimatorBeginFrame(fml::TimePoint frame_target_time) override; @@ -552,6 +564,9 @@ class Shell final : public PlatformView::Delegate, std::unique_ptr> ComputePlatformResolvedLocale( const std::vector& supported_locale_data) override; + // |Engine::Delegate| + Dart_Handle OnDartLoadLibrary(intptr_t loading_unit_id) override; + // |Rasterizer::Delegate| void OnFrameRasterized(const FrameTiming&) override; diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 91d2a368b1057..1e545cc288db9 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -155,6 +155,8 @@ android_java_sources = [ "io/flutter/embedding/engine/dart/DartExecutor.java", "io/flutter/embedding/engine/dart/DartMessenger.java", "io/flutter/embedding/engine/dart/PlatformMessageHandler.java", + "io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java", + "io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java", "io/flutter/embedding/engine/loader/ApplicationInfoLoader.java", "io/flutter/embedding/engine/loader/FlutterApplicationInfo.java", "io/flutter/embedding/engine/loader/FlutterLoader.java", diff --git a/shell/platform/android/embedding_bundle/build.gradle b/shell/platform/android/embedding_bundle/build.gradle index 7a60d4c16a24c..a1e4f59a4b484 100644 --- a/shell/platform/android/embedding_bundle/build.gradle +++ b/shell/platform/android/embedding_bundle/build.gradle @@ -48,6 +48,8 @@ android { embedding "androidx.lifecycle:lifecycle-common:$lifecycle_version" embedding "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" + embedding "com.google.android.play:core:1.8.0" + // Testing // TODO(xster): remove these android-all compile time dependencies. // Use https://github.com/robolectric/robolectric/blob/master/robolectric/src/main/java/org/robolectric/plugins/LegacyDependencyResolver.java#L24 diff --git a/shell/platform/android/io/flutter/app/FlutterApplication.java b/shell/platform/android/io/flutter/app/FlutterApplication.java index a211c268548cd..1c59cad44316d 100644 --- a/shell/platform/android/io/flutter/app/FlutterApplication.java +++ b/shell/platform/android/io/flutter/app/FlutterApplication.java @@ -5,8 +5,10 @@ package io.flutter.app; import android.app.Activity; +import android.content.Context; import android.app.Application; import androidx.annotation.CallSuper; +import com.google.android.play.core.splitcompat.SplitCompat; import io.flutter.FlutterInjector; /** @@ -33,4 +35,12 @@ public Activity getCurrentActivity() { public void setCurrentActivity(Activity mCurrentActivity) { this.mCurrentActivity = mCurrentActivity; } + + // This override allows split dynamic feature modules to work. + @Override + protected void attachBaseContext(Context base) { + super.attachBaseContext(base); + // Emulates installation of future on demand modules using SplitCompat. + SplitCompat.install(this); + } } diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 2e001e7a73965..86d3f3d4102db 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -12,6 +12,8 @@ import io.flutter.FlutterInjector; import io.flutter.Log; import io.flutter.embedding.engine.dart.DartExecutor; +import io.flutter.embedding.engine.dynamicfeatures.DynamicFeatureManager; +import io.flutter.embedding.engine.dynamicfeatures.PlayStoreDynamicFeatureManager; import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.embedding.engine.plugins.PluginRegistry; import io.flutter.embedding.engine.plugins.activity.ActivityControlSurface; @@ -98,6 +100,8 @@ public class FlutterEngine { // Engine Lifecycle. @NonNull private final Set engineLifecycleListeners = new HashSet<>(); + @NonNull private DynamicFeatureManager dynamicFeatureManager; + @NonNull private final EngineLifecycleListener engineLifecycleListener = new EngineLifecycleListener() { @@ -273,6 +277,7 @@ public FlutterEngine( assetManager = context.getAssets(); } this.dartExecutor = new DartExecutor(flutterJNI, assetManager); + this.dartExecutor.onAttachedToJNI(); accessibilityChannel = new AccessibilityChannel(dartExecutor, flutterJNI); @@ -289,7 +294,9 @@ public FlutterEngine( this.localizationPlugin = new LocalizationPlugin(context, localizationChannel); + this.flutterJNI = flutterJNI; + dynamicFeatureManager = new PlayStoreDynamicFeatureManager(context, flutterJNI); if (flutterLoader == null) { flutterLoader = FlutterInjector.instance().flutterLoader(); } @@ -299,6 +306,8 @@ public FlutterEngine( flutterJNI.addEngineLifecycleListener(engineLifecycleListener); flutterJNI.setPlatformViewsController(platformViewsController); flutterJNI.setLocalizationPlugin(localizationPlugin); + flutterJNI.setDynamicFeatureManager(dynamicFeatureManager); + flutterJNI.setDynamicFeatureContext(context); attachToJni(); // TODO(mattcarroll): FlutterRenderer is temporally coupled to attach(). Remove that coupling if diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 5e9864c345bdc..f9855904dd539 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -20,6 +20,7 @@ import io.flutter.Log; import io.flutter.embedding.engine.FlutterEngine.EngineLifecycleListener; import io.flutter.embedding.engine.dart.PlatformMessageHandler; +import io.flutter.embedding.engine.dynamicfeatures.DynamicFeatureManager; import io.flutter.embedding.engine.mutatorsstack.FlutterMutatorsStack; import io.flutter.embedding.engine.renderer.FlutterUiDisplayListener; import io.flutter.embedding.engine.renderer.RenderSurface; @@ -183,6 +184,8 @@ public static native void nativeOnVsync( @Nullable private LocalizationPlugin localizationPlugin; @Nullable private PlatformViewsController platformViewsController; + @Nullable private DynamicFeatureManager dynamicFeatureManager; + @NonNull private final Set engineLifecycleListeners = new CopyOnWriteArraySet<>(); @@ -939,6 +942,76 @@ String[] computePlatformResolvedLocale(@NonNull String[] strings) { // ----- End Localization Support ---- + // ----- Start Dynamic Features Support ---- + + /** Sets the dynamic feature manager that is used to download and install split features. */ + @UiThread + public void setDynamicFeatureManager(@NonNull DynamicFeatureManager dynamicFeatureManager) { + ensureRunningOnMainThread(); + this.dynamicFeatureManager = dynamicFeatureManager; + } + + private Context dynamicFeatureContext; + @UiThread + public void setDynamicFeatureContext(@NonNull Context context) { + ensureRunningOnMainThread(); + this.dynamicFeatureContext = context; + } + + // Called by the engine upon invocation of dart loadLibrary request + @SuppressWarnings("unused") + @UiThread + public void downloadDynamicFeature(@NonNull String moduleName, int loadingUnitId) { + dynamicFeatureManager.downloadModule(moduleName, loadingUnitId); + } + + @SuppressWarnings("unused") + @UiThread + public void downloadDynamicFeature(int loadingUnitId) { + String loadingUnitIdResName = dynamicFeatureContext.getResources().getString(dynamicFeatureContext.getResources().getIdentifier("loadingUnit" + loadingUnitId, "string", dynamicFeatureContext.getPackageName())); + downloadDynamicFeature(loadingUnitIdResName, loadingUnitId); + } + + // This should be called for every loading unit to be loaded into the dart isolate. + @UiThread + public void loadDartLibrary( + int loadingUnitId, + @NonNull String abi, + @NonNull String libName, + @NonNull AssetManager assetManager, + @NonNull String[] apkPaths) { + ensureRunningOnMainThread(); + ensureAttachedToNative(); + nativeLoadDartLibrary( + nativePlatformViewId, + loadingUnitId, + abi, + libName, + assetManager, + apkPaths); + Log.e("flutter", "loadDartLibrary FlutterJNI"); + } + private native void nativeLoadDartLibrary( + long nativePlatformViewId, + int loadingUnitId, + @NonNull String abi, + @NonNull String libName, + @NonNull AssetManager assetManager, + @NonNull String[] apkPaths); + + // Called when an install encounters a failure during the Android portion of installing a module. + // When transient is false, new attempts to install will automatically result in same error in dart before + // the request is passed to Android. + @SuppressWarnings("unused") + @UiThread + public void dynamicFeatureInstallFailure(@NonNull String moduleName, int loadingUnitId, @NonNull String error, boolean trans) { + ensureRunningOnMainThread(); + nativeDynamicFeatureInstallFailure(moduleName, loadingUnitId, error, trans); + } + private native void nativeDynamicFeatureInstallFailure(@NonNull String moduleName, int loadingUnitId, @NonNull String error, boolean trans); + + // ----- End Dynamic Features Support ---- + // @SuppressWarnings("unused") @UiThread public void onDisplayPlatformView( diff --git a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java new file mode 100644 index 0000000000000..4a13fdf1d1ca7 --- /dev/null +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java @@ -0,0 +1,23 @@ +// Copyright 2020 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.dynamicfeatures; + +import androidx.annotation.NonNull; + +/** + * + */ +public interface DynamicFeatureManager { + + // Requests that the module be downloaded. + public abstract void downloadModule(@NonNull String moduleName, int loadingUnitId); + + // Extracts any assets and resources from the module for use by Flutter. + public abstract void extractModule(@NonNull String moduleName, int loadingUnitId); + + // Loads the .so file into the Dart VM. + public abstract void loadDartModules(@NonNull String moduleName, int loadingUnitId); + +} diff --git a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java new file mode 100644 index 0000000000000..47b5f97d32159 --- /dev/null +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java @@ -0,0 +1,220 @@ +// Copyright 2020 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.dynamicfeatures; + +import android.content.Context; +import android.content.pm.PackageManager.NameNotFoundException; +import android.content.res.AssetManager; +import android.os.Build; +import androidx.annotation.NonNull; +import com.google.android.play.core.splitinstall.SplitInstallException; +import com.google.android.play.core.splitinstall.SplitInstallManager; +import com.google.android.play.core.splitinstall.SplitInstallManagerFactory; +import com.google.android.play.core.splitinstall.SplitInstallRequest; +import com.google.android.play.core.splitinstall.SplitInstallSessionState; +import com.google.android.play.core.splitinstall.SplitInstallStateUpdatedListener; +import com.google.android.play.core.splitinstall.model.SplitInstallErrorCode; +import com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus; +import io.flutter.Log; +import io.flutter.embedding.engine.FlutterJNI; +import java.util.HashMap; +import java.util.Map; +import java.util.List; +import java.util.ArrayList; +import java.util.Queue; +import java.util.LinkedList; +import java.io.File; + +/** + * Flutter default implementation of DynamicFeatureManager that downloads dynamic features from the + * Google Play store. + */ +public class PlayStoreDynamicFeatureManager implements DynamicFeatureManager { + + private @NonNull SplitInstallManager splitInstallManager; + private @NonNull Map sessionIdToName; + private @NonNull Map sessionIdToLoadingUnitId; + private @NonNull FlutterJNI flutterJNI; + private @NonNull Context context; + + private FeatureInstallStateUpdatedListener listener; + + private class FeatureInstallStateUpdatedListener implements SplitInstallStateUpdatedListener { + public void onStateUpdate(SplitInstallSessionState state) { + if (sessionIdToName.containsKey(state.sessionId())) { + switch (state.status()) { + case SplitInstallSessionStatus.FAILED: { + Log.e("flutter", "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") install failed with " + state.errorCode()); + flutterJNI.dynamicFeatureInstallFailure(sessionIdToName.get(state.sessionId()), sessionIdToLoadingUnitId.get(state.sessionId()), "Module install failed with " + state.errorCode(), true); + sessionIdToName.remove(state.sessionId()); + sessionIdToLoadingUnitId.remove(state.sessionId()); + break; + } + case SplitInstallSessionStatus.INSTALLED: { + Log.e("flutter", "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") installed successfully."); + extractModule(sessionIdToName.get(state.sessionId()), sessionIdToLoadingUnitId.get(state.sessionId())); + sessionIdToName.remove(state.sessionId()); + sessionIdToLoadingUnitId.remove(state.sessionId()); + break; + } + case SplitInstallSessionStatus.CANCELED: { + Log.e("flutter", "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") cancelled"); + sessionIdToName.remove(state.sessionId()); + break; + } + case SplitInstallSessionStatus.CANCELING: { + Log.e("flutter", "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") canceling"); + sessionIdToName.remove(state.sessionId()); + break; + } + case SplitInstallSessionStatus.PENDING: { + Log.e("flutter", "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") pending."); + break; + } + case SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION: { + Log.e("flutter", "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") requires user confirmation."); + break; + } + case SplitInstallSessionStatus.DOWNLOADING: { + Log.e("flutter", "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") downloading."); + break; + } + case SplitInstallSessionStatus.DOWNLOADED: { + Log.e("flutter", "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") downloaded."); + break; + } + case SplitInstallSessionStatus.INSTALLING: { + Log.e("flutter", "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") installing."); + break; + } + default: Log.e("flutter", "Status: " + state.status()); + } + } + } + } + + public PlayStoreDynamicFeatureManager(@NonNull Context context, @NonNull FlutterJNI flutterJNI) { + this.context = context; + this.flutterJNI = flutterJNI; + splitInstallManager = SplitInstallManagerFactory.create(context); + listener = new FeatureInstallStateUpdatedListener(); + splitInstallManager.registerListener(listener); + sessionIdToName = new HashMap(); + sessionIdToLoadingUnitId = new HashMap(); + } + + public void downloadModule(@NonNull String moduleName, int loadingUnitId) { + Log.e("flutter", "INSTALLING MODULE " + moduleName + " " + flutterJNI); + + SplitInstallRequest request = + SplitInstallRequest + .newBuilder() + .addModule(moduleName) + .build(); + + splitInstallManager + // Submits the request to install the module through the + // asynchronous startInstall() task. Your app needs to be + // in the foreground to submit the request. + .startInstall(request) + // Called when the install request is sent successfully. This is different than a successful install + // which is handled in FeatureInstallStateUpdatedListener. + .addOnSuccessListener(sessionId -> { + this.sessionIdToName.put(sessionId, moduleName); + this.sessionIdToLoadingUnitId.put(sessionId, loadingUnitId); + Log.e("flutter", "Request to install module \"" + moduleName + "\" sent with session id " + sessionId + "."); + }) + .addOnFailureListener(exception -> { + switch(((SplitInstallException) exception).getErrorCode()) { + case SplitInstallErrorCode.NETWORK_ERROR: + Log.e("flutter", "Install of dynamic feature module \"" + moduleName + "\" failed with a network error"); + flutterJNI.dynamicFeatureInstallFailure(moduleName, loadingUnitId, "Install of dynamic feature module \"" + moduleName + "\" failed with a network error", true); + break; + case SplitInstallErrorCode.MODULE_UNAVAILABLE: + Log.e("flutter", "Install of dynamic feature module \"" + moduleName + "\" failed as is unavailable."); + flutterJNI.dynamicFeatureInstallFailure(moduleName, loadingUnitId, "Install of dynamic feature module \"" + moduleName + "\" failed as is unavailable.", false); + break; + default: + Log.e("flutter", "Install of dynamic feature module \"" + moduleName + "\" failed with error: \"" + ((SplitInstallException) exception).getErrorCode() + "\": " + ((SplitInstallException) exception).getMessage()); + flutterJNI.dynamicFeatureInstallFailure(moduleName, loadingUnitId, "Install of dynamic feature module \"" + moduleName + "\" failed with error: \"" + ((SplitInstallException) exception).getErrorCode() + "\": " + ((SplitInstallException) exception).getMessage(), false); + break; + } + }); + } + + public void extractModule(@NonNull String moduleName, int loadingUnitId) { + Log.e("flutter", "Extracting \"" + moduleName + "\" " + flutterJNI); + + try { + context = context.createPackageContext(context.getPackageName(), 0); + // We only load dart shared lib for the loading unit id requested. Other loading units in the + // dynamic feature module are not loaded, but can be loaded by calling again with their loading + // unit id. + loadDartModules(moduleName, loadingUnitId); + } catch (NameNotFoundException e) { + Log.e("flutter", "NameNotFoundException creating context for " + moduleName); + throw new RuntimeException(e); + } + // TODO: Handle assets here. + } + + public void loadDartModules(@NonNull String moduleName, int loadingUnitId) { + Log.e("flutter", "Loading dart modules \"" + moduleName + "\" " + flutterJNI); + // This matches/depends on dart's loading unit naming convention, which we use unchanged. + String aotSharedLibraryName = "app.so-" + loadingUnitId + ".part.so"; + + // Possible values: armeabi, armeabi-v7a, arm64-v8a, x86, x86_64, mips, mips64 + String abi; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { + abi = Build.CPU_ABI; + } else { + abi = Build.SUPPORTED_ABIS[0]; + } + String pathAbi = abi.replace("-", "_"); // abis are represented with underscores in paths. + + // Search directly in APKs first + List apkPaths = new ArrayList(); + // If not found in APKs, we check in extracted native libs for the lib directly. + String soPath = ""; + Queue searchFiles = new LinkedList(); + searchFiles.add(context.getFilesDir()); + while (!searchFiles.isEmpty()) { + File file = searchFiles.remove(); + if (file != null && file.isDirectory()) { + for (File f : file.listFiles()) { + searchFiles.add(f); + } + continue; + } + String name = file.getName(); + if (name.substring(name.length() - 4).equals(".apk") && name.substring(0, moduleName.length()).equals(moduleName) && name.contains(pathAbi)) { + apkPaths.add(file.getAbsolutePath()); + continue; + } + if (name.equals(aotSharedLibraryName)) { + soPath = file.getAbsolutePath(); + } + } + + // Usefull logging for development. Remove later. + Log.e("flutter", ""); + Log.e("flutter", "APKs " + apkPaths.size()); + for (String s : apkPaths) Log.e("flutter", s); + Log.e("flutter", ""); + Log.e("flutter", "SO: " + soPath); + Log.e("flutter", ""); + Log.e("flutter", "ARCH: " + abi); + Log.e("flutter", ""); + + AssetManager assetManager = context.getAssets(); + + flutterJNI.loadDartLibrary(loadingUnitId, abi, aotSharedLibraryName, assetManager, apkPaths.toArray(new String[apkPaths.size()])); + } + + void destroy() { + splitInstallManager.unregisterListener(listener); + } + +} diff --git a/shell/platform/android/io/flutter/embedding/engine/loader/ApplicationInfoLoader.java b/shell/platform/android/io/flutter/embedding/engine/loader/ApplicationInfoLoader.java index bc1f71af2b3fa..cccf60d7a1303 100644 --- a/shell/platform/android/io/flutter/embedding/engine/loader/ApplicationInfoLoader.java +++ b/shell/platform/android/io/flutter/embedding/engine/loader/ApplicationInfoLoader.java @@ -11,6 +11,7 @@ import android.os.Bundle; import android.security.NetworkSecurityPolicy; import androidx.annotation.NonNull; +import io.flutter.Log; import java.io.IOException; import org.json.JSONArray; import org.xmlpull.v1.XmlPullParserException; @@ -142,7 +143,6 @@ public static FlutterApplicationInfo load(@NonNull Context applicationContext) { if (android.os.Build.VERSION.SDK_INT >= 23) { clearTextPermitted = NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted(); } - return new FlutterApplicationInfo( getString(appInfo.metaData, PUBLIC_AOT_SHARED_LIBRARY_NAME), getString(appInfo.metaData, PUBLIC_VM_SNAPSHOT_DATA_KEY), diff --git a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java index e2d97c40ee174..57f5afe24d9b6 100644 --- a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java +++ b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java @@ -143,7 +143,7 @@ public void run() { if (resourceExtractor != null) { resourceExtractor.waitForCompletion(); } - + Log.e("flutter", "PATHS: " + PathUtils.getFilesDir(appContext) + "|" + PathUtils.getCacheDirectory(appContext) + "|" + PathUtils.getDataDirectory(appContext)); return new InitResult( PathUtils.getFilesDir(appContext), PathUtils.getCacheDirectory(appContext), diff --git a/shell/platform/android/jni/jni_mock.h b/shell/platform/android/jni/jni_mock.h index 65f790d972ee8..cf08766ddde13 100644 --- a/shell/platform/android/jni/jni_mock.h +++ b/shell/platform/android/jni/jni_mock.h @@ -95,6 +95,11 @@ class JNIMock final : public PlatformViewAndroidJNI { (override)); MOCK_METHOD(double, GetDisplayRefreshRate, (), (override)); + + MOCK_METHOD(bool, + FlutterViewDownloadDynamicFeature, + (int loading_unit_id), + (override)); }; } // namespace flutter diff --git a/shell/platform/android/jni/platform_view_android_jni.h b/shell/platform/android/jni/platform_view_android_jni.h index e06079c1f5c4f..1212ab82aa887 100644 --- a/shell/platform/android/jni/platform_view_android_jni.h +++ b/shell/platform/android/jni/platform_view_android_jni.h @@ -195,6 +195,8 @@ class PlatformViewAndroidJNI { std::vector supported_locales_data) = 0; virtual double GetDisplayRefreshRate() = 0; + + virtual bool FlutterViewDownloadDynamicFeature(int loading_unit_id) = 0; }; } // namespace flutter diff --git a/shell/platform/android/platform_view_android.cc b/shell/platform/android/platform_view_android.cc index 8ebac2aa929b9..e9b702d6a5130 100644 --- a/shell/platform/android/platform_view_android.cc +++ b/shell/platform/android/platform_view_android.cc @@ -342,6 +342,25 @@ PlatformViewAndroid::ComputePlatformResolvedLocales( supported_locale_data); } +// |PlatformView| +Dart_Handle PlatformViewAndroid::OnDartLoadLibrary(intptr_t loading_unit_id) { + if (jni_facade_->FlutterViewDownloadDynamicFeature(loading_unit_id)) { + return Dart_Null(); + } + return Dart_Null(); // TODO: RETURN ERROR +} + +// |PlatformView| +void PlatformViewAndroid::CompleteDartLoadLibrary( + intptr_t loading_unit_id, + std::string lib_name, + std::vector& apkPaths, + std::string abi, + std::shared_ptr asset_manager) { + delegate_.CompleteDartLoadLibrary(loading_unit_id, lib_name, apkPaths, abi, + std::move(asset_manager)); +} + void PlatformViewAndroid::InstallFirstFrameCallback() { // On Platform Task Runner. SetNextFrameCallback( diff --git a/shell/platform/android/platform_view_android.h b/shell/platform/android/platform_view_android.h index b1bf8194976f5..d015d85b1b9ca 100644 --- a/shell/platform/android/platform_view_android.h +++ b/shell/platform/android/platform_view_android.h @@ -96,6 +96,14 @@ class PlatformViewAndroid final : public PlatformView { int64_t texture_id, const fml::jni::JavaObjectWeakGlobalRef& surface_texture); + // |PlatformView| + void CompleteDartLoadLibrary( + intptr_t loading_unit_id, + std::string lib_name, + std::vector& apkPaths, + std::string abi, + std::shared_ptr asset_manager) override; + private: const std::shared_ptr jni_facade_; std::shared_ptr external_view_embedder_; @@ -137,6 +145,9 @@ class PlatformViewAndroid final : public PlatformView { std::unique_ptr> ComputePlatformResolvedLocales( const std::vector& supported_locale_data) override; + // |PlatformView| + Dart_Handle OnDartLoadLibrary(intptr_t loading_unit_id) override; + void InstallFirstFrameCallback(); void FireFirstFrameCallback(); diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index 084872a023313..c282264ecbf69 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -12,6 +12,7 @@ #include "flutter/assets/directory_asset_bundle.h" #include "flutter/common/settings.h" +#include "flutter/fml/command_line.h" #include "flutter/fml/file.h" #include "flutter/fml/platform/android/jni_util.h" #include "flutter/fml/platform/android/jni_weak_ref.h" @@ -19,7 +20,9 @@ #include "flutter/fml/size.h" #include "flutter/lib/ui/plugins/callback_cache.h" #include "flutter/runtime/dart_service_isolate.h" +#include "flutter/runtime/dart_snapshot.h" #include "flutter/shell/common/run_configuration.h" +#include "flutter/shell/common/switches.h" #include "flutter/shell/platform/android/android_external_texture_gl.h" #include "flutter/shell/platform/android/android_shell_holder.h" #include "flutter/shell/platform/android/apk_asset_provider.h" @@ -100,6 +103,8 @@ static jmethodID g_detach_from_gl_context_method = nullptr; static jmethodID g_compute_platform_resolved_locale_method = nullptr; +static jmethodID g_download_dynamic_feature_method = nullptr; + // Called By Java static jmethodID g_on_display_platform_view_method = nullptr; @@ -192,6 +197,8 @@ static void RunBundleAndSnapshotFromLibrary(JNIEnv* env, jAssetManager, // asset manager fml::jni::JavaStringToString(env, jBundlePath)) // apk asset dir ); + FML_LOG(ERROR) << "ASSETS DIR: " + << fml::jni::JavaStringToString(env, jBundlePath); std::unique_ptr isolate_configuration; if (flutter::DartVM::IsRunningPrecompiledCode()) { @@ -508,6 +515,52 @@ static jboolean FlutterTextUtilsIsRegionalIndicator(JNIEnv* env, jint codePoint) { return u_hasBinaryProperty(codePoint, UProperty::UCHAR_REGIONAL_INDICATOR); } + +static void LoadDartLibrary(JNIEnv* env, + jobject obj, + jlong shell_holder, + jint jLoadingUnitId, + jstring jAbi, + jstring jLibName, + jobject jAssetManager, + jobjectArray jApkPaths) { + // see RunBundleAndSnapshotFromLibrary above for dart loading code + auto asset_manager = std::make_shared(); + + asset_manager->PushBack(std::make_unique( + env, // jni environment + jAssetManager, // asset manager + "flutter_assets") // apk asset dir TODO: Pass in as parameter + ); + + std::string abi = fml::jni::JavaStringToString(env, jAbi); + + std::vector apkPaths = + fml::jni::StringArrayToVector(env, jApkPaths); + + ANDROID_SHELL_HOLDER->GetPlatformView()->CompleteDartLoadLibrary( + static_cast(jLoadingUnitId), + fml::jni::JavaStringToString(env, jLibName), apkPaths, abi, + std::move(asset_manager)); + + // asset_manager->PushBack(std::make_unique( + // env, // jni environment + // jAssetManager, // asset manager + // fml::jni::JavaStringToString(env, jBundlePath)) // apk asset dir + // ); + + // RunConfiguration config(std::move(isolate_configuration), + // std::move(asset_manager)); +} + +static void DynamicFeatureInstallFailure(JNIEnv* env, + jobject obj, + jobject moduleName, + jint loadigUnitId, + jobject error, + jboolean transient) { + // TODO: Implement +} bool RegisterApi(JNIEnv* env) { static const JNINativeMethod flutter_jni_methods[] = { // Start of methods from FlutterJNI @@ -664,6 +717,18 @@ bool RegisterApi(JNIEnv* env) { .fnPtr = reinterpret_cast(&FlutterTextUtilsIsRegionalIndicator), }, + { + .name = "nativeLoadDartLibrary", + .signature = + "(JILjava/lang/String;Ljava/lang/String;Landroid/content/" + "res/AssetManager;[Ljava/lang/String;)V", + .fnPtr = reinterpret_cast(&LoadDartLibrary), + }, + { + .name = "nativeDynamicFeatureInstallFailure", + .signature = "(Ljava/lang/String;ILjava/lang/String;Z)V", + .fnPtr = reinterpret_cast(&DynamicFeatureInstallFailure), + }, }; if (env->RegisterNatives(g_flutter_jni_class->obj(), flutter_jni_methods, @@ -907,6 +972,14 @@ bool PlatformViewAndroid::Register(JNIEnv* env) { return false; } + g_download_dynamic_feature_method = env->GetMethodID( + g_flutter_jni_class->obj(), "downloadDynamicFeature", "(I)V"); + + if (g_download_dynamic_feature_method == nullptr) { + FML_LOG(ERROR) << "Could not locate downloadDynamicFeature method"; + return false; + } + return RegisterApi(env); } @@ -1334,4 +1407,20 @@ double PlatformViewAndroidJNIImpl::GetDisplayRefreshRate() { return static_cast(env->GetStaticFloatField(clazz, fid)); } +bool PlatformViewAndroidJNIImpl::FlutterViewDownloadDynamicFeature( + int loading_unit_id) { + JNIEnv* env = fml::jni::AttachCurrentThread(); + + auto java_object = java_object_.get(env); + if (java_object.is_null()) { + return true; + } + + env->CallObjectMethod(java_object.obj(), g_download_dynamic_feature_method, + loading_unit_id); + + FML_CHECK(CheckException(env)); + return true; +} + } // namespace flutter diff --git a/shell/platform/android/platform_view_android_jni_impl.h b/shell/platform/android/platform_view_android_jni_impl.h index 313a9ee921e6c..3f0942d1606e7 100644 --- a/shell/platform/android/platform_view_android_jni_impl.h +++ b/shell/platform/android/platform_view_android_jni_impl.h @@ -80,6 +80,8 @@ class PlatformViewAndroidJNIImpl final : public PlatformViewAndroidJNI { double GetDisplayRefreshRate() override; + bool FlutterViewDownloadDynamicFeature(int loading_unit_id) override; + private: // Reference to FlutterJNI object. const fml::jni::JavaObjectWeakGlobalRef java_object_; diff --git a/testing/dart_isolate_runner.cc b/testing/dart_isolate_runner.cc index 43d77c0165c28..e582453036f8c 100644 --- a/testing/dart_isolate_runner.cc +++ b/testing/dart_isolate_runner.cc @@ -3,6 +3,7 @@ // found in the LICENSE file. #include "flutter/testing/dart_isolate_runner.h" +#include "flutter/runtime/dart_deferred_load_handler.h" #include "flutter/runtime/isolate_configuration.h" @@ -70,6 +71,31 @@ std::unique_ptr RunDartCodeInIsolateOnUITaskRunner( } auto settings = p_settings; + auto weak_isolate = DartIsolate::CreateRootIsolate( + vm_data->GetSettings(), // settings + vm_data->GetIsolateSnapshot(), // isolate snapshot + std::move(task_runners), // task runners + nullptr, // window + {}, // snapshot delegate + {}, // hint freed delegate + io_manager, // io manager + {}, // unref queue + {}, // image decoder + "main.dart", // advisory uri + "main", // advisory entrypoint + nullptr, // flags + DartDeferredLoadHandler::empty_dart_deferred_load_handler, + settings.isolate_create_callback, // isolate create callback + settings.isolate_shutdown_callback // isolate shutdown callback + ); + + auto root_isolate = std::make_unique( + weak_isolate.lock(), task_runners.GetUITaskRunner()); + + if (!root_isolate->IsValid()) { + FML_LOG(ERROR) << "Could not create isolate."; + return; + } settings.dart_entrypoint_args = args; From 2c77d17fdee4ad325b08438333fc34aaa0d88aa7 Mon Sep 17 00:00:00 2001 From: garyqian Date: Fri, 23 Oct 2020 15:09:10 -0700 Subject: [PATCH 2/7] Cleanup and API adjustments --- .../flutter/embedding/engine/FlutterJNI.java | 47 +++++++---- .../DynamicFeatureManager.java | 27 ++++--- .../PlayStoreDynamicFeatureManager.java | 81 ++++++++++--------- .../android/platform_view_android_jni_impl.cc | 26 +++--- 4 files changed, 101 insertions(+), 80 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index f9855904dd539..8f23caf7998cc 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -958,50 +958,65 @@ public void setDynamicFeatureContext(@NonNull Context context) { this.dynamicFeatureContext = context; } - // Called by the engine upon invocation of dart loadLibrary request @SuppressWarnings("unused") @UiThread - public void downloadDynamicFeature(@NonNull String moduleName, int loadingUnitId) { - dynamicFeatureManager.downloadModule(moduleName, loadingUnitId); + public void downloadDynamicFeature(int loadingUnitId) { + String loadingUnitIdResName = dynamicFeatureContext.getResources().getString(dynamicFeatureContext.getResources().getIdentifier("loadingUnit" + loadingUnitId, "string", dynamicFeatureContext.getPackageName())); + downloadDynamicFeature(loadingUnitIdResName, loadingUnitId); } + // Called by the engine upon invocation of dart loadLibrary() request @SuppressWarnings("unused") @UiThread - public void downloadDynamicFeature(int loadingUnitId) { - String loadingUnitIdResName = dynamicFeatureContext.getResources().getString(dynamicFeatureContext.getResources().getIdentifier("loadingUnit" + loadingUnitId, "string", dynamicFeatureContext.getPackageName())); - downloadDynamicFeature(loadingUnitIdResName, loadingUnitId); + public void downloadDynamicFeature(String moduleName, int loadingUnitId) { + dynamicFeatureManager.downloadFeature(moduleName, loadingUnitId); } - // This should be called for every loading unit to be loaded into the dart isolate. + /** + * This should be called for every loading unit to be loaded into the dart isolate. + * + * abi, libName, and apkPaths are used together to search the installed apks for the + * desired .so library. If not found, soPath may be provided as a fallback if a + * pre-extracted .so exists, especially on older devices with libs compressed in the + * apk. + * + * assetManager and asserBundlePath are used to specify a new AssetManager that has + * access to the dynamic feature's assets in addition to the base assets. + */ @UiThread public void loadDartLibrary( int loadingUnitId, - @NonNull String abi, @NonNull String libName, + @NonNull String[] apkPaths, + @NonNull String abi, + @NonNull String soPath, @NonNull AssetManager assetManager, - @NonNull String[] apkPaths) { + @NonNull String assetBundlePath) { ensureRunningOnMainThread(); ensureAttachedToNative(); nativeLoadDartLibrary( nativePlatformViewId, loadingUnitId, - abi, libName, + apkPaths, + abi, + soPath, assetManager, - apkPaths); - Log.e("flutter", "loadDartLibrary FlutterJNI"); + assetBundlePath); } private native void nativeLoadDartLibrary( long nativePlatformViewId, int loadingUnitId, - @NonNull String abi, @NonNull String libName, + @NonNull String[] apkPaths, + @NonNull String abi, + @NonNull String soPath, @NonNull AssetManager assetManager, - @NonNull String[] apkPaths); + @NonNull String assetBundlePath); // Called when an install encounters a failure during the Android portion of installing a module. - // When transient is false, new attempts to install will automatically result in same error in dart before - // the request is passed to Android. + // When transient is false, new attempts to install will automatically result in same error in + // dart before the request is passed to Android. @SuppressWarnings("unused") @UiThread public void dynamicFeatureInstallFailure(@NonNull String moduleName, int loadingUnitId, @NonNull String error, boolean trans) { diff --git a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java index 4a13fdf1d1ca7..98c4f09310e81 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java @@ -4,20 +4,27 @@ package io.flutter.embedding.engine.dynamicfeatures; -import androidx.annotation.NonNull; - /** - * + * Basic interface that may be implemented to provide custom handling of dynamic features. + * + * The Flutter default implementation is PlayStoreDynamicFeatureManager. + * + * The methods here may be called independently or in a sequence one after the other to perform + * a full install cycle of download, extract, and load dart libs. */ public interface DynamicFeatureManager { + // Request that the feature module be downloaded and installed. + public abstract void downloadFeature(String moduleName, int loadingUnitId); - // Requests that the module be downloaded. - public abstract void downloadModule(@NonNull String moduleName, int loadingUnitId); - - // Extracts any assets and resources from the module for use by Flutter. - public abstract void extractModule(@NonNull String moduleName, int loadingUnitId); + // Extract and load any assets and resources from the module for use by Flutter. + // Depending on the structure of the feature module, there may or may not be assets + // to extract. + public abstract void extractFeature(String moduleName, int loadingUnitId); - // Loads the .so file into the Dart VM. - public abstract void loadDartModules(@NonNull String moduleName, int loadingUnitId); + // Load the .so shared library file into the Dart VM. Asset only features may skip this + // step. + public abstract void loadDartLibrary(String moduleName, int loadingUnitId); + // Uninstall the specified feature module. + public abstract void uninstallFeature(String moduleName, int loadingUnitId); } diff --git a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java index 47b5f97d32159..6676d97a70b41 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java @@ -32,6 +32,7 @@ * Google Play store. */ public class PlayStoreDynamicFeatureManager implements DynamicFeatureManager { + private static final String TAG = "flutter"; private @NonNull SplitInstallManager splitInstallManager; private @NonNull Map sessionIdToName; @@ -44,52 +45,53 @@ public class PlayStoreDynamicFeatureManager implements DynamicFeatureManager { private class FeatureInstallStateUpdatedListener implements SplitInstallStateUpdatedListener { public void onStateUpdate(SplitInstallSessionState state) { if (sessionIdToName.containsKey(state.sessionId())) { + // TODO(garyq): Add capability to access the state from framework. switch (state.status()) { case SplitInstallSessionStatus.FAILED: { - Log.e("flutter", "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") install failed with " + state.errorCode()); + Log.d(TAG, "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") install failed with " + state.errorCode()); flutterJNI.dynamicFeatureInstallFailure(sessionIdToName.get(state.sessionId()), sessionIdToLoadingUnitId.get(state.sessionId()), "Module install failed with " + state.errorCode(), true); sessionIdToName.remove(state.sessionId()); sessionIdToLoadingUnitId.remove(state.sessionId()); break; } case SplitInstallSessionStatus.INSTALLED: { - Log.e("flutter", "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") installed successfully."); - extractModule(sessionIdToName.get(state.sessionId()), sessionIdToLoadingUnitId.get(state.sessionId())); + Log.d(TAG, "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") installed successfully."); + extractFeature(sessionIdToName.get(state.sessionId()), sessionIdToLoadingUnitId.get(state.sessionId())); sessionIdToName.remove(state.sessionId()); sessionIdToLoadingUnitId.remove(state.sessionId()); break; } case SplitInstallSessionStatus.CANCELED: { - Log.e("flutter", "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") cancelled"); + Log.d(TAG, "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") cancelled"); sessionIdToName.remove(state.sessionId()); break; } case SplitInstallSessionStatus.CANCELING: { - Log.e("flutter", "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") canceling"); + Log.d(TAG, "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") canceling"); sessionIdToName.remove(state.sessionId()); break; } case SplitInstallSessionStatus.PENDING: { - Log.e("flutter", "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") pending."); + Log.d(TAG, "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") pending."); break; } case SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION: { - Log.e("flutter", "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") requires user confirmation."); + Log.d(TAG, "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") requires user confirmation."); break; } case SplitInstallSessionStatus.DOWNLOADING: { - Log.e("flutter", "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") downloading."); + Log.d(TAG, "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") downloading."); break; } case SplitInstallSessionStatus.DOWNLOADED: { - Log.e("flutter", "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") downloaded."); + Log.d(TAG, "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") downloaded."); break; } case SplitInstallSessionStatus.INSTALLING: { - Log.e("flutter", "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") installing."); + Log.d(TAG, "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") installing."); break; } - default: Log.e("flutter", "Status: " + state.status()); + default: Log.d(TAG, "Status: " + state.status()); } } } @@ -105,8 +107,11 @@ public PlayStoreDynamicFeatureManager(@NonNull Context context, @NonNull Flutter sessionIdToLoadingUnitId = new HashMap(); } - public void downloadModule(@NonNull String moduleName, int loadingUnitId) { - Log.e("flutter", "INSTALLING MODULE " + moduleName + " " + flutterJNI); + public void downloadFeature(String moduleName, int loadingUnitId) { + if (moduleName == null) { + Log.e(TAG, "Dynamic feature module name was null."); + return; + } SplitInstallRequest request = SplitInstallRequest @@ -124,53 +129,50 @@ public void downloadModule(@NonNull String moduleName, int loadingUnitId) { .addOnSuccessListener(sessionId -> { this.sessionIdToName.put(sessionId, moduleName); this.sessionIdToLoadingUnitId.put(sessionId, loadingUnitId); - Log.e("flutter", "Request to install module \"" + moduleName + "\" sent with session id " + sessionId + "."); + Log.d(TAG, "Request to install module \"" + moduleName + "\" sent with session id " + sessionId + "."); }) .addOnFailureListener(exception -> { switch(((SplitInstallException) exception).getErrorCode()) { case SplitInstallErrorCode.NETWORK_ERROR: - Log.e("flutter", "Install of dynamic feature module \"" + moduleName + "\" failed with a network error"); + Log.d(TAG, "Install of dynamic feature module \"" + moduleName + "\" failed with a network error"); flutterJNI.dynamicFeatureInstallFailure(moduleName, loadingUnitId, "Install of dynamic feature module \"" + moduleName + "\" failed with a network error", true); break; case SplitInstallErrorCode.MODULE_UNAVAILABLE: - Log.e("flutter", "Install of dynamic feature module \"" + moduleName + "\" failed as is unavailable."); + Log.d(TAG, "Install of dynamic feature module \"" + moduleName + "\" failed as is unavailable."); flutterJNI.dynamicFeatureInstallFailure(moduleName, loadingUnitId, "Install of dynamic feature module \"" + moduleName + "\" failed as is unavailable.", false); break; default: - Log.e("flutter", "Install of dynamic feature module \"" + moduleName + "\" failed with error: \"" + ((SplitInstallException) exception).getErrorCode() + "\": " + ((SplitInstallException) exception).getMessage()); + Log.d(TAG, "Install of dynamic feature module \"" + moduleName + "\" failed with error: \"" + ((SplitInstallException) exception).getErrorCode() + "\": " + ((SplitInstallException) exception).getMessage()); flutterJNI.dynamicFeatureInstallFailure(moduleName, loadingUnitId, "Install of dynamic feature module \"" + moduleName + "\" failed with error: \"" + ((SplitInstallException) exception).getErrorCode() + "\": " + ((SplitInstallException) exception).getMessage(), false); break; } }); } - public void extractModule(@NonNull String moduleName, int loadingUnitId) { - Log.e("flutter", "Extracting \"" + moduleName + "\" " + flutterJNI); - + public void extractFeature(@NonNull String moduleName, int loadingUnitId) { try { context = context.createPackageContext(context.getPackageName(), 0); // We only load dart shared lib for the loading unit id requested. Other loading units in the // dynamic feature module are not loaded, but can be loaded by calling again with their loading // unit id. - loadDartModules(moduleName, loadingUnitId); + loadDartLibrary(moduleName, loadingUnitId); } catch (NameNotFoundException e) { - Log.e("flutter", "NameNotFoundException creating context for " + moduleName); + Log.d(TAG, "NameNotFoundException creating context for " + moduleName); throw new RuntimeException(e); } // TODO: Handle assets here. } - public void loadDartModules(@NonNull String moduleName, int loadingUnitId) { - Log.e("flutter", "Loading dart modules \"" + moduleName + "\" " + flutterJNI); + public void loadDartLibrary(String moduleName, int loadingUnitId) { // This matches/depends on dart's loading unit naming convention, which we use unchanged. String aotSharedLibraryName = "app.so-" + loadingUnitId + ".part.so"; // Possible values: armeabi, armeabi-v7a, arm64-v8a, x86, x86_64, mips, mips64 String abi; - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) { - abi = Build.CPU_ABI; - } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { abi = Build.SUPPORTED_ABIS[0]; + } else { + abi = Build.CPU_ABI; } String pathAbi = abi.replace("-", "_"); // abis are represented with underscores in paths. @@ -198,19 +200,22 @@ public void loadDartModules(@NonNull String moduleName, int loadingUnitId) { } } - // Usefull logging for development. Remove later. - Log.e("flutter", ""); - Log.e("flutter", "APKs " + apkPaths.size()); - for (String s : apkPaths) Log.e("flutter", s); - Log.e("flutter", ""); - Log.e("flutter", "SO: " + soPath); - Log.e("flutter", ""); - Log.e("flutter", "ARCH: " + abi); - Log.e("flutter", ""); - + // TODO(garyq): Handle assets in extractModule() above; AssetManager assetManager = context.getAssets(); - flutterJNI.loadDartLibrary(loadingUnitId, abi, aotSharedLibraryName, assetManager, apkPaths.toArray(new String[apkPaths.size()])); + flutterJNI.loadDartLibrary( + loadingUnitId, + aotSharedLibraryName, + apkPaths.toArray(new String[apkPaths.size()]), + abi, + soPath, + assetManager, + // TODO(garyq): Made the "flutter_assets" directory dynamic based off of DartEntryPoint. + "flutter_assets"); + } + + public void uninstallFeature(String moduleName, int loadingUnitId) { + // TODO(garyq): support uninstalling. } void destroy() { diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index c282264ecbf69..1c0b094f50edd 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -520,17 +520,17 @@ static void LoadDartLibrary(JNIEnv* env, jobject obj, jlong shell_holder, jint jLoadingUnitId, - jstring jAbi, jstring jLibName, + jobjectArray jApkPaths, + jstring jAbi, + jstring jSoPath, jobject jAssetManager, - jobjectArray jApkPaths) { - // see RunBundleAndSnapshotFromLibrary above for dart loading code + jstring jAssetBundlePath) { auto asset_manager = std::make_shared(); - asset_manager->PushBack(std::make_unique( - env, // jni environment - jAssetManager, // asset manager - "flutter_assets") // apk asset dir TODO: Pass in as parameter + env, // jni environment + jAssetManager, // asset manager + fml::jni::JavaStringToString(env, jAssetBundlePath)) // apk asset dir ); std::string abi = fml::jni::JavaStringToString(env, jAbi); @@ -543,14 +543,7 @@ static void LoadDartLibrary(JNIEnv* env, fml::jni::JavaStringToString(env, jLibName), apkPaths, abi, std::move(asset_manager)); - // asset_manager->PushBack(std::make_unique( - // env, // jni environment - // jAssetManager, // asset manager - // fml::jni::JavaStringToString(env, jBundlePath)) // apk asset dir - // ); - - // RunConfiguration config(std::move(isolate_configuration), - // std::move(asset_manager)); + // TODO(garyq): fallback on soPath. } static void DynamicFeatureInstallFailure(JNIEnv* env, @@ -559,8 +552,9 @@ static void DynamicFeatureInstallFailure(JNIEnv* env, jint loadigUnitId, jobject error, jboolean transient) { - // TODO: Implement + // TODO(garyq): Implement } + bool RegisterApi(JNIEnv* env) { static const JNINativeMethod flutter_jni_methods[] = { // Start of methods from FlutterJNI From 591cebdc3aaa5a0fcfee93ec19fea38868691c50 Mon Sep 17 00:00:00 2001 From: garyqian Date: Sat, 24 Oct 2020 00:01:44 -0700 Subject: [PATCH 3/7] Additional cleanup and docs Build on host --- runtime/dart_isolate.cc | 14 ++---- runtime/dart_isolate_unittests.cc | 25 +++++----- runtime/dart_lifecycle_unittests.cc | 44 +++++----------- runtime/runtime_controller.cc | 2 - .../embedding/engine/FlutterEngine.java | 2 - .../engine/loader/ApplicationInfoLoader.java | 2 +- .../engine/loader/FlutterLoader.java | 2 +- testing/dart_isolate_runner.cc | 50 +++++-------------- 8 files changed, 44 insertions(+), 97 deletions(-) diff --git a/runtime/dart_isolate.cc b/runtime/dart_isolate.cc index 89c88c29012d5..b415707eb0e4d 100644 --- a/runtime/dart_isolate.cc +++ b/runtime/dart_isolate.cc @@ -292,13 +292,6 @@ std::string DartIsolate::GetServiceId() { return service_id; } -// Dart_Handle DartDeferredLoadHandler(intptr_t loading_unit_id) { -// FML_LOG(ERROR) << "LOAD REQUEST CALLED FOR ID: " << loading_unit_id; -// // Temporary call for prototyping. -// return -// Engine::GetEngineDelegateTemp()->OnDartLoadLibrary(loading_unit_id); -// } - bool DartIsolate::Initialize(Dart_Isolate dart_isolate, Dart_DeferredLoadHandler& deferred_load_handler) { TRACE_EVENT0("flutter", "DartIsolate::Initialize"); @@ -709,8 +702,7 @@ Dart_Isolate DartIsolate::DartCreateAndStartServiceIsolate( if (!vm_data) { *error = fml::strdup( - "Could not access VM data to initialize isolates. This may be " - "because " + "Could not access VM data to initialize isolates. This may be because " "the VM has initialized shutdown on another thread already."); return nullptr; } @@ -728,8 +720,8 @@ Dart_Isolate DartIsolate::DartCreateAndStartServiceIsolate( #if (FLUTTER_RUNTIME_MODE != FLUTTER_RUNTIME_MODE_DEBUG) // TODO(68663): The service isolate in debug mode is always launched without - // sound null safety. Fix after the isolate snapshot data is created with - // the right flags. + // sound null safety. Fix after the isolate snapshot data is created with the + // right flags. flags->null_safety = vm_data->GetIsolateSnapshot()->IsNullSafetyEnabled(nullptr); #endif diff --git a/runtime/dart_isolate_unittests.cc b/runtime/dart_isolate_unittests.cc index 236f3e76ce357..be62acbabd811 100644 --- a/runtime/dart_isolate_unittests.cc +++ b/runtime/dart_isolate_unittests.cc @@ -353,18 +353,19 @@ TEST_F(DartIsolateTest, CanCreateServiceIsolate) { auto isolate_configuration = IsolateConfiguration::InferFromSettings(settings); auto weak_isolate = DartIsolate::CreateRunningRootIsolate( - vm_data->GetSettings(), // settings - vm_data->GetIsolateSnapshot(), // isolate snapshot - std::move(task_runners), // task runners - nullptr, // window - {}, // snapshot delegate - {}, // hint freed delegate - {}, // io manager - {}, // unref queue - {}, // image decoder - "main.dart", // advisory uri - "main", // advisory entrypoint, - DartIsolate::Flags{}, // flags + vm_data->GetSettings(), // settings + vm_data->GetIsolateSnapshot(), // isolate snapshot + std::move(task_runners), // task runners + nullptr, // window + {}, // snapshot delegate + {}, // hint freed delegate + {}, // io manager + {}, // unref queue + {}, // image decoder + "main.dart", // advisory uri + "main", // advisory entrypoint, + DartIsolate::Flags{}, // flags + DartDeferredLoadHandler::empty_dart_deferred_load_handler, settings.isolate_create_callback, // isolate create callback settings.isolate_shutdown_callback, // isolate shutdown callback "main", // dart entrypoint diff --git a/runtime/dart_lifecycle_unittests.cc b/runtime/dart_lifecycle_unittests.cc index 0e247cf4a5939..793247654a47d 100644 --- a/runtime/dart_lifecycle_unittests.cc +++ b/runtime/dart_lifecycle_unittests.cc @@ -52,43 +52,25 @@ static std::shared_ptr CreateAndRunRootIsolate( FML_CHECK(entrypoint.size() > 0); TaskRunners runners("io.flutter.test", task_runner, task_runner, task_runner, task_runner); - auto isolate_weak = DartIsolate::CreateRootIsolate( - vm.GetSettings(), // settings - vm.GetIsolateSnapshot(), // isolate_snapshot - runners, // task_runners - {}, // window - {}, // snapshot_delegate - {}, // hint_freed_delegate - {}, // io_manager - {}, // unref_queue - {}, // image_decoder - "main.dart", // advisory_script_uri - entrypoint.c_str(), // advisory_script_entrypoint - nullptr, // flags - DartDeferredLoadHandler::empty_dart_deferred_load_handler, - settings.isolate_create_callback, // isolate create callback - settings.isolate_shutdown_callback // isolate shutdown callback - ); - - auto isolate = isolate_weak.lock(); auto isolate_configuration = IsolateConfiguration::InferFromSettings(settings); auto isolate = DartIsolate::CreateRunningRootIsolate( - vm.GetSettings(), // settings - vm.GetIsolateSnapshot(), // isolate_snapshot - runners, // task_runners - {}, // window - {}, // snapshot_delegate - {}, // hint_freed_delegate - {}, // io_manager - {}, // unref_queue - {}, // image_decoder - "main.dart", // advisory_script_uri - entrypoint.c_str(), // advisory_script_entrypoint - DartIsolate::Flags{}, // flags + vm.GetSettings(), // settings + vm.GetIsolateSnapshot(), // isolate_snapshot + runners, // task_runners + {}, // window + {}, // snapshot_delegate + {}, // hint_freed_delegate + {}, // io_manager + {}, // unref_queue + {}, // image_decoder + "main.dart", // advisory_script_uri + entrypoint.c_str(), // advisory_script_entrypoint + DartIsolate::Flags{}, // flags + DartDeferredLoadHandler::empty_dart_deferred_load_handler, settings.isolate_create_callback, // isolate create callback settings.isolate_shutdown_callback, // isolate shutdown callback, entrypoint, // dart entrypoint diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index 0d793b05ec42d..4a22e1d67d7bb 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -452,8 +452,6 @@ void RuntimeController::CompleteDartLoadLibrary( for (std::string paths : searchPaths) { FML_LOG(ERROR) << " " << paths; } - // FML_LOG(ERROR) << "Opening lib \"" << lib_name - // << "\" failed: " + std::string(::dlerror()); return; } diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 86d3f3d4102db..ac36e56c87f5c 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -277,7 +277,6 @@ public FlutterEngine( assetManager = context.getAssets(); } this.dartExecutor = new DartExecutor(flutterJNI, assetManager); - this.dartExecutor.onAttachedToJNI(); accessibilityChannel = new AccessibilityChannel(dartExecutor, flutterJNI); @@ -294,7 +293,6 @@ public FlutterEngine( this.localizationPlugin = new LocalizationPlugin(context, localizationChannel); - this.flutterJNI = flutterJNI; dynamicFeatureManager = new PlayStoreDynamicFeatureManager(context, flutterJNI); if (flutterLoader == null) { diff --git a/shell/platform/android/io/flutter/embedding/engine/loader/ApplicationInfoLoader.java b/shell/platform/android/io/flutter/embedding/engine/loader/ApplicationInfoLoader.java index cccf60d7a1303..bc1f71af2b3fa 100644 --- a/shell/platform/android/io/flutter/embedding/engine/loader/ApplicationInfoLoader.java +++ b/shell/platform/android/io/flutter/embedding/engine/loader/ApplicationInfoLoader.java @@ -11,7 +11,6 @@ import android.os.Bundle; import android.security.NetworkSecurityPolicy; import androidx.annotation.NonNull; -import io.flutter.Log; import java.io.IOException; import org.json.JSONArray; import org.xmlpull.v1.XmlPullParserException; @@ -143,6 +142,7 @@ public static FlutterApplicationInfo load(@NonNull Context applicationContext) { if (android.os.Build.VERSION.SDK_INT >= 23) { clearTextPermitted = NetworkSecurityPolicy.getInstance().isCleartextTrafficPermitted(); } + return new FlutterApplicationInfo( getString(appInfo.metaData, PUBLIC_AOT_SHARED_LIBRARY_NAME), getString(appInfo.metaData, PUBLIC_VM_SNAPSHOT_DATA_KEY), diff --git a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java index 57f5afe24d9b6..e2d97c40ee174 100644 --- a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java +++ b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java @@ -143,7 +143,7 @@ public void run() { if (resourceExtractor != null) { resourceExtractor.waitForCompletion(); } - Log.e("flutter", "PATHS: " + PathUtils.getFilesDir(appContext) + "|" + PathUtils.getCacheDirectory(appContext) + "|" + PathUtils.getDataDirectory(appContext)); + return new InitResult( PathUtils.getFilesDir(appContext), PathUtils.getCacheDirectory(appContext), diff --git a/testing/dart_isolate_runner.cc b/testing/dart_isolate_runner.cc index e582453036f8c..95c6a1e047d60 100644 --- a/testing/dart_isolate_runner.cc +++ b/testing/dart_isolate_runner.cc @@ -71,31 +71,6 @@ std::unique_ptr RunDartCodeInIsolateOnUITaskRunner( } auto settings = p_settings; - auto weak_isolate = DartIsolate::CreateRootIsolate( - vm_data->GetSettings(), // settings - vm_data->GetIsolateSnapshot(), // isolate snapshot - std::move(task_runners), // task runners - nullptr, // window - {}, // snapshot delegate - {}, // hint freed delegate - io_manager, // io manager - {}, // unref queue - {}, // image decoder - "main.dart", // advisory uri - "main", // advisory entrypoint - nullptr, // flags - DartDeferredLoadHandler::empty_dart_deferred_load_handler, - settings.isolate_create_callback, // isolate create callback - settings.isolate_shutdown_callback // isolate shutdown callback - ); - - auto root_isolate = std::make_unique( - weak_isolate.lock(), task_runners.GetUITaskRunner()); - - if (!root_isolate->IsValid()) { - FML_LOG(ERROR) << "Could not create isolate."; - return; - } settings.dart_entrypoint_args = args; @@ -136,18 +111,19 @@ std::unique_ptr RunDartCodeInIsolateOnUITaskRunner( auto isolate = DartIsolate::CreateRunningRootIsolate( - settings, // settings - vm_data->GetIsolateSnapshot(), // isolate snapshot - std::move(task_runners), // task runners - nullptr, // window - {}, // snapshot delegate - {}, // hint freed delegate - io_manager, // io manager - {}, // unref queue - {}, // image decoder - "main.dart", // advisory uri - entrypoint.c_str(), // advisory entrypoint - DartIsolate::Flags{}, // flags + settings, // settings + vm_data->GetIsolateSnapshot(), // isolate snapshot + std::move(task_runners), // task runners + nullptr, // window + {}, // snapshot delegate + {}, // hint freed delegate + io_manager, // io manager + {}, // unref queue + {}, // image decoder + "main.dart", // advisory uri + entrypoint.c_str(), // advisory entrypoint + DartIsolate::Flags{}, // flags + DartDeferredLoadHandler::empty_dart_deferred_load_handler, settings.isolate_create_callback, // isolate create callback settings.isolate_shutdown_callback, // isolate shutdown callback entrypoint, // entrypoint From 5c635163d04a50dafb0267b30ba665993d5b14b8 Mon Sep 17 00:00:00 2001 From: garyqian Date: Tue, 27 Oct 2020 15:08:13 -0700 Subject: [PATCH 4/7] Separate UpdateAssetManager from CompleteDartLoadLibrary --- shell/common/engine.cc | 12 ++---- shell/common/engine.h | 3 +- shell/common/platform_view.cc | 11 ++--- shell/common/platform_view.h | 23 ++++++----- shell/common/shell.cc | 17 ++++---- shell/common/shell.h | 13 +++--- .../flutter/embedding/engine/FlutterJNI.java | 36 ++++++++++++----- .../PlayStoreDynamicFeatureManager.java | 30 +++++++------- .../platform/android/platform_view_android.cc | 10 +++-- .../platform/android/platform_view_android.h | 13 +++--- .../android/platform_view_android_jni_impl.cc | 40 ++++++++++++------- 11 files changed, 118 insertions(+), 90 deletions(-) diff --git a/shell/common/engine.cc b/shell/common/engine.cc index b8e0792c29e2f..6ac9c2bbfeb5d 100644 --- a/shell/common/engine.cc +++ b/shell/common/engine.cc @@ -512,18 +512,14 @@ Dart_Handle Engine::OnDartLoadLibrary(intptr_t loading_unit_id) { return delegate_.OnDartLoadLibrary(loading_unit_id); } -void Engine::CompleteDartLoadLibrary( - intptr_t loading_unit_id, - std::string lib_name, - std::vector& apkPaths, - std::string abi, - std::shared_ptr asset_manager) { +void Engine::CompleteDartLoadLibrary(intptr_t loading_unit_id, + std::string lib_name, + std::vector& apkPaths, + std::string abi) { if (runtime_controller_->IsRootIsolateRunning()) { runtime_controller_->CompleteDartLoadLibrary(loading_unit_id, lib_name, apkPaths, abi); } - - UpdateAssetManager(std::move(asset_manager)); } } // namespace flutter diff --git a/shell/common/engine.h b/shell/common/engine.h index 5f5e16793d2e0..60dd5e9aeef49 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -773,8 +773,7 @@ class Engine final : public RuntimeDelegate, void CompleteDartLoadLibrary(intptr_t loading_unit_id, std::string lib_name, std::vector& apkPaths, - std::string abi, - std::shared_ptr asset_manager); + std::string abi); private: Engine::Delegate& delegate_; diff --git a/shell/common/platform_view.cc b/shell/common/platform_view.cc index b68d34df84c14..ce00da2eb2718 100644 --- a/shell/common/platform_view.cc +++ b/shell/common/platform_view.cc @@ -156,11 +156,12 @@ Dart_Handle PlatformView::OnDartLoadLibrary(intptr_t loading_unit_id) { return Dart_Null(); } -void PlatformView::CompleteDartLoadLibrary( - intptr_t loading_unit_id, - std::string lib_name, - std::vector& apkPaths, - std::string abi, +void PlatformView::CompleteDartLoadLibrary(intptr_t loading_unit_id, + std::string lib_name, + std::vector& apkPaths, + std::string abi) {} + +void PlatformView::UpdateAssetManager( std::shared_ptr asset_manager) {} } // namespace flutter diff --git a/shell/common/platform_view.h b/shell/common/platform_view.h index 8ceccba3d928f..147fa17e751f0 100644 --- a/shell/common/platform_view.h +++ b/shell/common/platform_view.h @@ -232,11 +232,12 @@ class PlatformView { virtual Dart_Handle OnPlatformViewDartLoadLibrary( intptr_t loading_unit_id) = 0; - virtual void CompleteDartLoadLibrary( - intptr_t loading_unit_id, - std::string lib_name, - std::vector& apkPaths, - std::string abi, + virtual void CompleteDartLoadLibrary(intptr_t loading_unit_id, + std::string lib_name, + std::vector& apkPaths, + std::string abi) = 0; + + virtual void UpdateAssetManager( std::shared_ptr asset_manager) = 0; }; @@ -593,12 +594,12 @@ class PlatformView { virtual Dart_Handle OnDartLoadLibrary(intptr_t loading_unit_id); - virtual void CompleteDartLoadLibrary( - intptr_t loading_unit_id, - std::string lib_name, - std::vector& apkPaths, - std::string abi, - std::shared_ptr asset_manager); + virtual void CompleteDartLoadLibrary(intptr_t loading_unit_id, + std::string lib_name, + std::vector& apkPaths, + std::string abi); + + virtual void UpdateAssetManager(std::shared_ptr asset_manager); protected: PlatformView::Delegate& delegate_; diff --git a/shell/common/shell.cc b/shell/common/shell.cc index a418383f137f6..1a2c6a02719e3 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -1188,14 +1188,15 @@ Shell::ComputePlatformViewResolvedLocale( return platform_view_->ComputePlatformResolvedLocales(supported_locale_data); } -void Shell::CompleteDartLoadLibrary( - intptr_t loading_unit_id, - std::string lib_name, - std::vector& apkPaths, - std::string abi, - std::shared_ptr asset_manager) { - engine_->CompleteDartLoadLibrary(loading_unit_id, lib_name, apkPaths, abi, - std::move(asset_manager)); +void Shell::CompleteDartLoadLibrary(intptr_t loading_unit_id, + std::string lib_name, + std::vector& apkPaths, + std::string abi) { + engine_->CompleteDartLoadLibrary(loading_unit_id, lib_name, apkPaths, abi); +} + +void Shell::UpdateAssetManager(std::shared_ptr asset_manager) { + engine_->UpdateAssetManager(std::move(asset_manager)); } // |Engine::Delegate| diff --git a/shell/common/shell.h b/shell/common/shell.h index 1e799a10ef559..ecdebe2e1d80d 100644 --- a/shell/common/shell.h +++ b/shell/common/shell.h @@ -516,12 +516,13 @@ class Shell final : public PlatformView::Delegate, Dart_Handle OnPlatformViewDartLoadLibrary(intptr_t loading_unit_id) override; // |PlatformView::Delegate| - void CompleteDartLoadLibrary( - intptr_t loading_unit_id, - std::string lib_name, - std::vector& apkPaths, - std::string abi, - std::shared_ptr asset_manager) override; + void CompleteDartLoadLibrary(intptr_t loading_unit_id, + std::string lib_name, + std::vector& apkPaths, + std::string abi) override; + + // |PlatformView::Delegate| + void UpdateAssetManager(std::shared_ptr asset_manager) override; // |Animator::Delegate| void OnAnimatorBeginFrame(fml::TimePoint frame_target_time) override; diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 8f23caf7998cc..57fc2e45600aa 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -980,8 +980,8 @@ public void downloadDynamicFeature(String moduleName, int loadingUnitId) { * pre-extracted .so exists, especially on older devices with libs compressed in the * apk. * - * assetManager and asserBundlePath are used to specify a new AssetManager that has - * access to the dynamic feature's assets in addition to the base assets. + * Successful loading of the dart library also completes the loadLibrary() future + * that triggered the install/load process. */ @UiThread public void loadDartLibrary( @@ -989,9 +989,7 @@ public void loadDartLibrary( @NonNull String libName, @NonNull String[] apkPaths, @NonNull String abi, - @NonNull String soPath, - @NonNull AssetManager assetManager, - @NonNull String assetBundlePath) { + @NonNull String soPath) { ensureRunningOnMainThread(); ensureAttachedToNative(); nativeLoadDartLibrary( @@ -1000,9 +998,7 @@ public void loadDartLibrary( libName, apkPaths, abi, - soPath, - assetManager, - assetBundlePath); + soPath); } private native void nativeLoadDartLibrary( long nativePlatformViewId, @@ -1010,9 +1006,29 @@ private native void nativeLoadDartLibrary( @NonNull String libName, @NonNull String[] apkPaths, @NonNull String abi, - @NonNull String soPath, + @NonNull String soPath); + + + /** + * Specifies a new AssetManager that has access to the dynamic feature's assets in addition + * to the base module's assets. + * + * assetBundlePath is the subdirectory that the flutter assets are stored in. The typical + * value is `flutter_assets`. + */ + @UiThread + public void updateAssetManager( + @NonNull AssetManager assetManager, + @NonNull String assetBundlePath + ) { + ensureRunningOnMainThread(); + ensureAttachedToNative(); + nativeUpdateAssetManager(assetManager, assetBundlePath); + } + private native void nativeUpdateAssetManager( @NonNull AssetManager assetManager, - @NonNull String assetBundlePath); + @NonNull String assetBundlePath + ); // Called when an install encounters a failure during the Android portion of installing a module. // When transient is false, new attempts to install will automatically result in same error in diff --git a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java index 6676d97a70b41..97f82a8ae3ed0 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java @@ -152,9 +152,17 @@ public void downloadFeature(String moduleName, int loadingUnitId) { public void extractFeature(@NonNull String moduleName, int loadingUnitId) { try { context = context.createPackageContext(context.getPackageName(), 0); - // We only load dart shared lib for the loading unit id requested. Other loading units in the - // dynamic feature module are not loaded, but can be loaded by calling again with their loading - // unit id. + + AssetManager assetManager = context.getAssets(); + + flutterJNI.updateAssetManager( + assetManager, + // TODO(garyq): Made the "flutter_assets" directory dynamic based off of DartEntryPoint. + "flutter_assets"); + + // We only load dart shared lib for the loading unit id requested. Other loading units (if present) + // in the dynamic feature module are not loaded, but can be loaded by calling again with their + // loading unit id. loadDartLibrary(moduleName, loadingUnitId); } catch (NameNotFoundException e) { Log.d(TAG, "NameNotFoundException creating context for " + moduleName); @@ -176,6 +184,9 @@ public void loadDartLibrary(String moduleName, int loadingUnitId) { } String pathAbi = abi.replace("-", "_"); // abis are represented with underscores in paths. + // TODO(garyq): Optimize this apk/file discovery process to use less i/o and be more + // performant. + // Search directly in APKs first List apkPaths = new ArrayList(); // If not found in APKs, we check in extracted native libs for the lib directly. @@ -199,19 +210,6 @@ public void loadDartLibrary(String moduleName, int loadingUnitId) { soPath = file.getAbsolutePath(); } } - - // TODO(garyq): Handle assets in extractModule() above; - AssetManager assetManager = context.getAssets(); - - flutterJNI.loadDartLibrary( - loadingUnitId, - aotSharedLibraryName, - apkPaths.toArray(new String[apkPaths.size()]), - abi, - soPath, - assetManager, - // TODO(garyq): Made the "flutter_assets" directory dynamic based off of DartEntryPoint. - "flutter_assets"); } public void uninstallFeature(String moduleName, int loadingUnitId) { diff --git a/shell/platform/android/platform_view_android.cc b/shell/platform/android/platform_view_android.cc index e9b702d6a5130..dc6534061b7fb 100644 --- a/shell/platform/android/platform_view_android.cc +++ b/shell/platform/android/platform_view_android.cc @@ -355,10 +355,14 @@ void PlatformViewAndroid::CompleteDartLoadLibrary( intptr_t loading_unit_id, std::string lib_name, std::vector& apkPaths, - std::string abi, + std::string abi) { + delegate_.CompleteDartLoadLibrary(loading_unit_id, lib_name, apkPaths, abi); +} + +// |PlatformView| +void PlatformViewAndroid::UpdateAssetManager( std::shared_ptr asset_manager) { - delegate_.CompleteDartLoadLibrary(loading_unit_id, lib_name, apkPaths, abi, - std::move(asset_manager)); + delegate_.UpdateAssetManager(std::move(asset_manager)); } void PlatformViewAndroid::InstallFirstFrameCallback() { diff --git a/shell/platform/android/platform_view_android.h b/shell/platform/android/platform_view_android.h index d015d85b1b9ca..430b947b992e6 100644 --- a/shell/platform/android/platform_view_android.h +++ b/shell/platform/android/platform_view_android.h @@ -97,12 +97,13 @@ class PlatformViewAndroid final : public PlatformView { const fml::jni::JavaObjectWeakGlobalRef& surface_texture); // |PlatformView| - void CompleteDartLoadLibrary( - intptr_t loading_unit_id, - std::string lib_name, - std::vector& apkPaths, - std::string abi, - std::shared_ptr asset_manager) override; + void CompleteDartLoadLibrary(intptr_t loading_unit_id, + std::string lib_name, + std::vector& apkPaths, + std::string abi) override; + + // |PlatformView| + void UpdateAssetManager(std::shared_ptr asset_manager) override; 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 1c0b094f50edd..daec5824ed01b 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -523,16 +523,7 @@ static void LoadDartLibrary(JNIEnv* env, jstring jLibName, jobjectArray jApkPaths, jstring jAbi, - jstring jSoPath, - jobject jAssetManager, - jstring jAssetBundlePath) { - auto asset_manager = std::make_shared(); - asset_manager->PushBack(std::make_unique( - env, // jni environment - jAssetManager, // asset manager - fml::jni::JavaStringToString(env, jAssetBundlePath)) // apk asset dir - ); - + jstring jSoPath) { std::string abi = fml::jni::JavaStringToString(env, jAbi); std::vector apkPaths = @@ -540,12 +531,27 @@ static void LoadDartLibrary(JNIEnv* env, ANDROID_SHELL_HOLDER->GetPlatformView()->CompleteDartLoadLibrary( static_cast(jLoadingUnitId), - fml::jni::JavaStringToString(env, jLibName), apkPaths, abi, - std::move(asset_manager)); + fml::jni::JavaStringToString(env, jLibName), apkPaths, abi); // TODO(garyq): fallback on soPath. } +static void UpdateAssetManager(JNIEnv* env, + jobject obj, + jlong shell_holder, + jobject jAssetManager, + jstring jAssetBundlePath) { + auto asset_manager = std::make_shared(); + asset_manager->PushBack(std::make_unique( + env, // jni environment + jAssetManager, // asset manager + fml::jni::JavaStringToString(env, jAssetBundlePath)) // apk asset dir + ); + + ANDROID_SHELL_HOLDER->GetPlatformView()->UpdateAssetManager( + std::move(asset_manager)); +} + static void DynamicFeatureInstallFailure(JNIEnv* env, jobject obj, jobject moduleName, @@ -713,11 +719,15 @@ bool RegisterApi(JNIEnv* env) { }, { .name = "nativeLoadDartLibrary", - .signature = - "(JILjava/lang/String;Ljava/lang/String;Landroid/content/" - "res/AssetManager;[Ljava/lang/String;)V", + .signature = "(JILjava/lang/String;[Ljava/lang/String;)V", .fnPtr = reinterpret_cast(&LoadDartLibrary), }, + { + .name = "nativeUpdateAssetManager", + .signature = + "(Landroid/content/res/AssetManager;Ljava/lang/String;)V", + .fnPtr = reinterpret_cast(&UpdateAssetManager), + }, { .name = "nativeDynamicFeatureInstallFailure", .signature = "(Ljava/lang/String;ILjava/lang/String;Z)V", From c75072dd0500c32bc33fcc8b3667e2e98fef4be6 Mon Sep 17 00:00:00 2001 From: garyqian Date: Tue, 27 Oct 2020 17:34:13 -0700 Subject: [PATCH 5/7] Revised the Java API --- runtime/runtime_controller.cc | 1 + .../flutter/embedding/engine/FlutterJNI.java | 3 +- .../DynamicFeatureManager.java | 42 +++++++++++++++---- .../PlayStoreDynamicFeatureManager.java | 20 ++++----- .../android/platform_view_android_jni_impl.cc | 8 ++-- 5 files changed, 50 insertions(+), 24 deletions(-) diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index 4a22e1d67d7bb..27ed33c895d2f 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -489,6 +489,7 @@ void RuntimeController::CompleteDartLoadLibrary( } Dart_Handle RuntimeController::OnDartLoadLibrary(intptr_t loading_unit_id) { + FML_LOG(ERROR) << "RUNTIME CONTroLLER OnDartLoadLibrary"; return client_.OnDartLoadLibrary(loading_unit_id); } diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 57fc2e45600aa..02f9bf78cbd89 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -1023,9 +1023,10 @@ public void updateAssetManager( ) { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeUpdateAssetManager(assetManager, assetBundlePath); + nativeUpdateAssetManager(nativePlatformViewId, assetManager, assetBundlePath); } private native void nativeUpdateAssetManager( + long nativePlatformViewId, @NonNull AssetManager assetManager, @NonNull String assetBundlePath ); diff --git a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java index 98c4f09310e81..6d52064b16f79 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java @@ -4,6 +4,9 @@ package io.flutter.embedding.engine.dynamicfeatures; +// Flutter dynamic feature support is still in early developer preview, and should +// not be used in production apps yet. + /** * Basic interface that may be implemented to provide custom handling of dynamic features. * @@ -11,20 +14,43 @@ * * The methods here may be called independently or in a sequence one after the other to perform * a full install cycle of download, extract, and load dart libs. + * + * A dynamic feature module is uniquely identified by a module name. Each feature module may + * also contain one or more loading units, uniquely identified by the loading unit ID. */ public interface DynamicFeatureManager { - // Request that the feature module be downloaded and installed. + /** + * Request that the feature module be downloaded and installed. + * + * This method is called when loadLibrary() is called on a dart library. + * Upon completion of download, loadAssets and loadDartLibrary should + * be called to complete the dynamic feature load process. + */ public abstract void downloadFeature(String moduleName, int loadingUnitId); - // Extract and load any assets and resources from the module for use by Flutter. - // Depending on the structure of the feature module, there may or may not be assets - // to extract. - public abstract void extractFeature(String moduleName, int loadingUnitId); + /** + * Extract and load any assets and resources from the module for use by Flutter. + * + * Assets shoud be loaded before the dart library is loaded, as successful loading + * of the dart loading unit indicates the dynamic feature is fully loaded. + * + * Depending on the structure of the feature module, there may or may not be assets + * to extract. + */ + public abstract void loadAssets(String moduleName, int loadingUnitId); - // Load the .so shared library file into the Dart VM. Asset only features may skip this - // step. + /** + * Load the .so shared library file into the Dart VM. + * + * Upon successful load of the dart library, the feature corresponding to the + * loadingUnitId is considered finished loading, and the dart future completes. + * Developers are expected to begin using symbols and assets from the feature + * module after completion. + */ public abstract void loadDartLibrary(String moduleName, int loadingUnitId); - // Uninstall the specified feature module. + /** + * Uninstall the specified feature module. + */ public abstract void uninstallFeature(String moduleName, int loadingUnitId); } diff --git a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java index 97f82a8ae3ed0..4213c19cf6905 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java @@ -28,8 +28,8 @@ import java.io.File; /** - * Flutter default implementation of DynamicFeatureManager that downloads dynamic features from the - * Google Play store. + * Flutter default implementation of DynamicFeatureManager that downloads dynamic feature modules + * from the Google Play store. */ public class PlayStoreDynamicFeatureManager implements DynamicFeatureManager { private static final String TAG = "flutter"; @@ -56,7 +56,11 @@ public void onStateUpdate(SplitInstallSessionState state) { } case SplitInstallSessionStatus.INSTALLED: { Log.d(TAG, "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") installed successfully."); - extractFeature(sessionIdToName.get(state.sessionId()), sessionIdToLoadingUnitId.get(state.sessionId())); + loadAssets(sessionIdToName.get(state.sessionId()), sessionIdToLoadingUnitId.get(state.sessionId())); + // We only load dart shared lib for the loading unit id requested. Other loading units (if present) + // in the dynamic feature module are not loaded, but can be loaded by calling again with their + // loading unit id. + loadDartLibrary(sessionIdToName.get(state.sessionId()), sessionIdToLoadingUnitId.get(state.sessionId())); sessionIdToName.remove(state.sessionId()); sessionIdToLoadingUnitId.remove(state.sessionId()); break; @@ -108,6 +112,7 @@ public PlayStoreDynamicFeatureManager(@NonNull Context context, @NonNull Flutter } public void downloadFeature(String moduleName, int loadingUnitId) { + Log.e(TAG, "DOWNLOADING " + moduleName); if (moduleName == null) { Log.e(TAG, "Dynamic feature module name was null."); return; @@ -149,26 +154,19 @@ public void downloadFeature(String moduleName, int loadingUnitId) { }); } - public void extractFeature(@NonNull String moduleName, int loadingUnitId) { + public void loadAssets(@NonNull String moduleName, int loadingUnitId) { try { context = context.createPackageContext(context.getPackageName(), 0); AssetManager assetManager = context.getAssets(); - flutterJNI.updateAssetManager( assetManager, // TODO(garyq): Made the "flutter_assets" directory dynamic based off of DartEntryPoint. "flutter_assets"); - - // We only load dart shared lib for the loading unit id requested. Other loading units (if present) - // in the dynamic feature module are not loaded, but can be loaded by calling again with their - // loading unit id. - loadDartLibrary(moduleName, loadingUnitId); } catch (NameNotFoundException e) { Log.d(TAG, "NameNotFoundException creating context for " + moduleName); throw new RuntimeException(e); } - // TODO: Handle assets here. } public void loadDartLibrary(String moduleName, int loadingUnitId) { diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index daec5824ed01b..a026a1b9d8781 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -197,8 +197,6 @@ static void RunBundleAndSnapshotFromLibrary(JNIEnv* env, jAssetManager, // asset manager fml::jni::JavaStringToString(env, jBundlePath)) // apk asset dir ); - FML_LOG(ERROR) << "ASSETS DIR: " - << fml::jni::JavaStringToString(env, jBundlePath); std::unique_ptr isolate_configuration; if (flutter::DartVM::IsRunningPrecompiledCode()) { @@ -719,13 +717,14 @@ bool RegisterApi(JNIEnv* env) { }, { .name = "nativeLoadDartLibrary", - .signature = "(JILjava/lang/String;[Ljava/lang/String;)V", + .signature = "(JILjava/lang/String;[Ljava/lang/String;Ljava/lang/" + "String;Ljava/lang/String;)V", .fnPtr = reinterpret_cast(&LoadDartLibrary), }, { .name = "nativeUpdateAssetManager", .signature = - "(Landroid/content/res/AssetManager;Ljava/lang/String;)V", + "(JLandroid/content/res/AssetManager;Ljava/lang/String;)V", .fnPtr = reinterpret_cast(&UpdateAssetManager), }, { @@ -1413,6 +1412,7 @@ double PlatformViewAndroidJNIImpl::GetDisplayRefreshRate() { bool PlatformViewAndroidJNIImpl::FlutterViewDownloadDynamicFeature( int loading_unit_id) { + FML_LOG(ERROR) << "PLFVJNIIMPL cALLING JNI"; JNIEnv* env = fml::jni::AttachCurrentThread(); auto java_object = java_object_.get(env); From 5ad8d1b8267c2b5d33f37860928bd10a213fbc6c Mon Sep 17 00:00:00 2001 From: garyqian Date: Wed, 28 Oct 2020 11:44:33 -0700 Subject: [PATCH 6/7] Pass on failures to Dart --- runtime/dart_isolate.cc | 11 +++++++++ runtime/dart_isolate.h | 4 ++++ runtime/runtime_controller.cc | 24 ++++++++++++------- .../PlayStoreDynamicFeatureManager.java | 8 ++++++- .../android/platform_view_android_jni_impl.cc | 1 - 5 files changed, 37 insertions(+), 11 deletions(-) diff --git a/runtime/dart_isolate.cc b/runtime/dart_isolate.cc index b415707eb0e4d..cf5f9cd8df584 100644 --- a/runtime/dart_isolate.cc +++ b/runtime/dart_isolate.cc @@ -356,6 +356,17 @@ bool DartIsolate::LoadLoadingUnit(intptr_t loading_unit_id, return true; } +void DartIsolate::LoadLoadingUnitFailure(intptr_t loading_unit_id, + const std::string error_message, + bool transient) { + tonic::DartState::Scope scope(this); + Dart_Handle result = Dart_DeferredLoadCompleteError( + loading_unit_id, error_message.c_str(), transient); + if (Dart_IsApiError(result)) { + FML_LOG(ERROR) << "Dart error: " << Dart_GetError(result); + } +} + void DartIsolate::SetMessageHandlingTaskRunner( fml::RefPtr runner) { if (!IsRootIsolate() || !runner) { diff --git a/runtime/dart_isolate.h b/runtime/dart_isolate.h index bdbd05bc903e4..ca9ea2b80ad52 100644 --- a/runtime/dart_isolate.h +++ b/runtime/dart_isolate.h @@ -389,6 +389,10 @@ class DartIsolate : public UIDartState { const uint8_t* snapshot_data, const uint8_t* snapshot_instructions); + void LoadLoadingUnitFailure(intptr_t loading_unit_id, + const std::string error_message, + bool transient); + private: friend class IsolateConfiguration; class AutoFireClosure { diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index 27ed33c895d2f..4a1f1e4e4a67b 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -448,10 +448,12 @@ void RuntimeController::CompleteDartLoadLibrary( } } if (handle == nullptr) { - FML_LOG(ERROR) << "No libs Found for SearchPaths:"; - for (std::string paths : searchPaths) { - FML_LOG(ERROR) << " " << paths; - } + // FML_LOG(ERROR) << "No libs Found for SearchPaths:"; + // for (std::string paths : searchPaths) { + // FML_LOG(ERROR) << " " << paths; + // } + root_isolate_.lock()->LoadLoadingUnitFailure( + loading_unit_id, "No lib .so found for provided search paths.", true); return; } @@ -464,8 +466,10 @@ void RuntimeController::CompleteDartLoadLibrary( isolate_data = static_cast( ::dlsym(handle, underscore_symbol_name.str().c_str())); if (isolate_data == nullptr) { - FML_LOG(ERROR) << "Could not resolve symbol in library: " - << DartSnapshot::kIsolateDataSymbol; + // FML_LOG(ERROR) << "Could not resolve symbol in library: " + // << DartSnapshot::kIsolateDataSymbol; + root_isolate_.lock()->LoadLoadingUnitFailure( + loading_unit_id, "Could not resolve data symbol in library", true); return; } } @@ -478,8 +482,11 @@ void RuntimeController::CompleteDartLoadLibrary( isolate_instructions = static_cast( ::dlsym(handle, underscore_symbol_name.str().c_str())); if (isolate_data == nullptr) { - FML_LOG(ERROR) << "Could not resolve symbol in library: " - << DartSnapshot::kIsolateInstructionsSymbol; + // FML_LOG(ERROR) << "Could not resolve symbol in library: " + // << DartSnapshot::kIsolateInstructionsSymbol; + root_isolate_.lock()->LoadLoadingUnitFailure( + loading_unit_id, "Could not resolve instructions symbol in library", + true); return; } } @@ -489,7 +496,6 @@ void RuntimeController::CompleteDartLoadLibrary( } Dart_Handle RuntimeController::OnDartLoadLibrary(intptr_t loading_unit_id) { - FML_LOG(ERROR) << "RUNTIME CONTroLLER OnDartLoadLibrary"; return client_.OnDartLoadLibrary(loading_unit_id); } diff --git a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java index 4213c19cf6905..67c649f4d3261 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java @@ -112,7 +112,6 @@ public PlayStoreDynamicFeatureManager(@NonNull Context context, @NonNull Flutter } public void downloadFeature(String moduleName, int loadingUnitId) { - Log.e(TAG, "DOWNLOADING " + moduleName); if (moduleName == null) { Log.e(TAG, "Dynamic feature module name was null."); return; @@ -208,6 +207,13 @@ public void loadDartLibrary(String moduleName, int loadingUnitId) { soPath = file.getAbsolutePath(); } } + + flutterJNI.loadDartLibrary( + loadingUnitId, + aotSharedLibraryName, + apkPaths.toArray(new String[apkPaths.size()]), + abi, + soPath); } public void uninstallFeature(String moduleName, int loadingUnitId) { diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index a026a1b9d8781..df506c9dd7d89 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -1412,7 +1412,6 @@ double PlatformViewAndroidJNIImpl::GetDisplayRefreshRate() { bool PlatformViewAndroidJNIImpl::FlutterViewDownloadDynamicFeature( int loading_unit_id) { - FML_LOG(ERROR) << "PLFVJNIIMPL cALLING JNI"; JNIEnv* env = fml::jni::AttachCurrentThread(); auto java_object = java_object_.get(env); From a30d1976c274f7e369dc696d9310825638edfce3 Mon Sep 17 00:00:00 2001 From: garyqian Date: Wed, 28 Oct 2020 13:43:00 -0700 Subject: [PATCH 7/7] Documentation --- runtime/runtime_controller.cc | 5 -- runtime/runtime_controller.h | 28 +++++++++ shell/common/engine.h | 28 +++++++++ shell/common/platform_view.h | 61 +++++++++++++++++++ .../embedding/engine/FlutterEngine.java | 4 +- .../DynamicFeatureManager.java | 8 +-- .../platform/android/platform_view_android.cc | 2 +- 7 files changed, 125 insertions(+), 11 deletions(-) diff --git a/runtime/runtime_controller.cc b/runtime/runtime_controller.cc index 4a1f1e4e4a67b..185f451189c39 100644 --- a/runtime/runtime_controller.cc +++ b/runtime/runtime_controller.cc @@ -439,7 +439,6 @@ void RuntimeController::CompleteDartLoadLibrary( void* handle = nullptr; while (handle == nullptr && !searchPaths.empty()) { std::string path = searchPaths.back(); - FML_LOG(ERROR) << "Attempting to open " << path; handle = ::dlopen(path.c_str(), RTLD_NOW); searchPaths.pop_back(); if (handle == nullptr) { @@ -448,10 +447,6 @@ void RuntimeController::CompleteDartLoadLibrary( } } if (handle == nullptr) { - // FML_LOG(ERROR) << "No libs Found for SearchPaths:"; - // for (std::string paths : searchPaths) { - // FML_LOG(ERROR) << " " << paths; - // } root_isolate_.lock()->LoadLoadingUnitFailure( loading_unit_id, "No lib .so found for provided search paths.", true); return; diff --git a/runtime/runtime_controller.h b/runtime/runtime_controller.h index 26e41fb8875b3..02fc925403ccc 100644 --- a/runtime/runtime_controller.h +++ b/runtime/runtime_controller.h @@ -473,11 +473,39 @@ class RuntimeController : public PlatformConfigurationClient { /// std::optional GetRootIsolateReturnCode(); + //-------------------------------------------------------------------------- + /// @brief Loads the dart shared library from disk and into the dart VM + /// based off of the search parameters. When the dart library is + /// loaded successfully, the dart future returned by the + /// originating loadLibrary() call completes. + /// + /// @param[in] loading_unit_id The unique id of the deferred library's + /// loading unit. + /// + /// @param[in] lib_name The file name of the .so shared library + /// file. + /// + /// @param[in] apkPaths The paths of the APKs that may or may not + /// contain the lib_name file. + /// + /// @param[in] abi The abi of the library, eg, arm64-v8a + /// void CompleteDartLoadLibrary(intptr_t loading_unit_id, std::string lib_name, std::vector& apkPaths, std::string abi); + //-------------------------------------------------------------------------- + /// @brief Invoked when the dart VM requests that a deferred library + /// be loaded. Notifies the engine that the requested loading + /// unit should be downloaded and loaded. + /// + /// @param[in] loading_unit_id The unique id of the deferred library's + /// loading unit. + /// + /// @return A Dart_Handle that is Dart_Null on success, and a dart error + /// on failure. + /// Dart_Handle OnDartLoadLibrary(intptr_t loading_unit_id); protected: diff --git a/shell/common/engine.h b/shell/common/engine.h index 60dd5e9aeef49..c5c623508af7e 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -262,6 +262,17 @@ class Engine final : public RuntimeDelegate, ComputePlatformResolvedLocale( const std::vector& supported_locale_data) = 0; + //-------------------------------------------------------------------------- + /// @brief Invoked when the dart VM requests that a deferred library + /// be loaded. Notifies the engine that the requested loading + /// unit should be downloaded and loaded. + /// + /// @param[in] loading_unit_id The unique id of the deferred library's + /// loading unit. + /// + /// @return A Dart_Handle that is Dart_Null on success, and a dart error + /// on failure. + /// virtual Dart_Handle OnDartLoadLibrary(intptr_t loading_unit_id) = 0; }; @@ -770,6 +781,23 @@ class Engine final : public RuntimeDelegate, /// const std::string& InitialRoute() const { return initial_route_; } + //-------------------------------------------------------------------------- + /// @brief Loads the dart shared library from disk and into the dart VM + /// based off of the search parameters. When the dart library is + /// loaded successfully, the dart future returned by the + /// originating loadLibrary() call completes. + /// + /// @param[in] loading_unit_id The unique id of the deferred library's + /// loading unit. + /// + /// @param[in] lib_name The file name of the .so shared library + /// file. + /// + /// @param[in] apkPaths The paths of the APKs that may or may not + /// contain the lib_name file. + /// + /// @param[in] abi The abi of the library, eg, arm64-v8a + /// void CompleteDartLoadLibrary(intptr_t loading_unit_id, std::string lib_name, std::vector& apkPaths, diff --git a/shell/common/platform_view.h b/shell/common/platform_view.h index 147fa17e751f0..b0e7cf8a294dd 100644 --- a/shell/common/platform_view.h +++ b/shell/common/platform_view.h @@ -229,14 +229,47 @@ class PlatformView { ComputePlatformViewResolvedLocale( const std::vector& supported_locale_data) = 0; + //-------------------------------------------------------------------------- + /// @brief Invoked when the dart VM requests that a deferred library + /// be loaded. Notifies the engine that the requested loading + /// unit should be downloaded and loaded. + /// + /// @param[in] loading_unit_id The unique id of the deferred library's + /// loading unit. + /// + /// @return A Dart_Handle that is Dart_Null on success, and a dart error + /// on failure. + /// virtual Dart_Handle OnPlatformViewDartLoadLibrary( intptr_t loading_unit_id) = 0; + //-------------------------------------------------------------------------- + /// @brief Loads the dart shared library from disk and into the dart VM + /// based off of the search parameters. When the dart library is + /// loaded successfully, the dart future returned by the + /// originating loadLibrary() call completes. + /// + /// @param[in] loading_unit_id The unique id of the deferred library's + /// loading unit. + /// + /// @param[in] lib_name The file name of the .so shared library + /// file. + /// + /// @param[in] apkPaths The paths of the APKs that may or may not + /// contain the lib_name file. + /// + /// @param[in] abi The abi of the library, eg, arm64-v8a + /// virtual void CompleteDartLoadLibrary(intptr_t loading_unit_id, std::string lib_name, std::vector& apkPaths, std::string abi) = 0; + //-------------------------------------------------------------------------- + /// @brief Sets the asset manager of the engine to asset_manager + /// + /// @param[in] asset_manager The asset manager to use. + /// virtual void UpdateAssetManager( std::shared_ptr asset_manager) = 0; }; @@ -592,8 +625,36 @@ class PlatformView { ComputePlatformResolvedLocales( const std::vector& supported_locale_data); + //-------------------------------------------------------------------------- + /// @brief Invoked when the dart VM requests that a deferred library + /// be loaded. Notifies the engine that the requested loading + /// unit should be downloaded and loaded. + /// + /// @param[in] loading_unit_id The unique id of the deferred library's + /// loading unit. + /// + /// @return A Dart_Handle that is Dart_Null on success, and a dart error + /// on failure. + /// virtual Dart_Handle OnDartLoadLibrary(intptr_t loading_unit_id); + //-------------------------------------------------------------------------- + /// @brief Loads the dart shared library from disk and into the dart VM + /// based off of the search parameters. When the dart library is + /// loaded successfully, the dart future returned by the + /// originating loadLibrary() call completes. + /// + /// @param[in] loading_unit_id The unique id of the deferred library's + /// loading unit. + /// + /// @param[in] lib_name The file name of the .so shared library + /// file. + /// + /// @param[in] apkPaths The paths of the APKs that may or may not + /// contain the lib_name file. + /// + /// @param[in] abi The abi of the library, eg, arm64-v8a + /// virtual void CompleteDartLoadLibrary(intptr_t loading_unit_id, std::string lib_name, std::vector& apkPaths, diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index ac36e56c87f5c..a4e845aad7317 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -294,7 +294,6 @@ public FlutterEngine( this.localizationPlugin = new LocalizationPlugin(context, localizationChannel); this.flutterJNI = flutterJNI; - dynamicFeatureManager = new PlayStoreDynamicFeatureManager(context, flutterJNI); if (flutterLoader == null) { flutterLoader = FlutterInjector.instance().flutterLoader(); } @@ -304,8 +303,11 @@ public FlutterEngine( flutterJNI.addEngineLifecycleListener(engineLifecycleListener); flutterJNI.setPlatformViewsController(platformViewsController); flutterJNI.setLocalizationPlugin(localizationPlugin); + + dynamicFeatureManager = new PlayStoreDynamicFeatureManager(context, flutterJNI); flutterJNI.setDynamicFeatureManager(dynamicFeatureManager); flutterJNI.setDynamicFeatureContext(context); + attachToJni(); // TODO(mattcarroll): FlutterRenderer is temporally coupled to attach(). Remove that coupling if diff --git a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java index 6d52064b16f79..0d162b292ea53 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java @@ -4,19 +4,19 @@ package io.flutter.embedding.engine.dynamicfeatures; -// Flutter dynamic feature support is still in early developer preview, and should +// Flutter dynamic feature support is still in early developer preview and should // not be used in production apps yet. /** - * Basic interface that may be implemented to provide custom handling of dynamic features. + * Basic interface that handles downloading and loading of dynamic features. * * The Flutter default implementation is PlayStoreDynamicFeatureManager. * * The methods here may be called independently or in a sequence one after the other to perform - * a full install cycle of download, extract, and load dart libs. + * a full install cycle of download, load assets, and load dart libs. * * A dynamic feature module is uniquely identified by a module name. Each feature module may - * also contain one or more loading units, uniquely identified by the loading unit ID. + * contain one or more loading units, uniquely identified by the loading unit ID. */ public interface DynamicFeatureManager { /** diff --git a/shell/platform/android/platform_view_android.cc b/shell/platform/android/platform_view_android.cc index dc6534061b7fb..7103444ac8efb 100644 --- a/shell/platform/android/platform_view_android.cc +++ b/shell/platform/android/platform_view_android.cc @@ -347,7 +347,7 @@ Dart_Handle PlatformViewAndroid::OnDartLoadLibrary(intptr_t loading_unit_id) { if (jni_facade_->FlutterViewDownloadDynamicFeature(loading_unit_id)) { return Dart_Null(); } - return Dart_Null(); // TODO: RETURN ERROR + return Dart_Null(); // TODO(garyq): RETURN ERROR } // |PlatformView|