From a9776d662d9077b7ed8a138069b31e4499b944cd Mon Sep 17 00:00:00 2001 From: garyqian Date: Thu, 10 Sep 2020 17:55:22 -0700 Subject: [PATCH 01/29] 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 Cleanup and API adjustments Additional cleanup and docs Build on host Separate UpdateAssetManager from CompleteDartLoadLibrary Revised the Java API Pass on failures to Dart Documentation Revert runtime changes: Compiling --- DEPS | 2 +- shell/common/engine.cc | 18 ++ shell/common/engine.h | 42 ++++ shell/common/engine_unittests.cc | 3 + shell/common/platform_view.cc | 12 + shell/common/platform_view.h | 81 +++++++ shell/common/shell.cc | 21 ++ shell/common/shell.h | 16 ++ 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 | 105 ++++++++ .../DynamicFeatureManager.java | 56 +++++ .../PlayStoreDynamicFeatureManager.java | 227 ++++++++++++++++++ shell/platform/android/jni/jni_mock.h | 5 + .../android/jni/platform_view_android_jni.h | 2 + .../platform/android/platform_view_android.cc | 23 ++ .../platform/android/platform_view_android.h | 12 + .../android/platform_view_android_jni_impl.cc | 89 +++++++ .../android/platform_view_android_jni_impl.h | 2 + 21 files changed, 738 insertions(+), 1 deletion(-) 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 679383dd9f702..96a25ad91cc4c 100644 --- a/DEPS +++ b/DEPS @@ -482,7 +482,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/shell/common/engine.cc b/shell/common/engine.cc index 6c9304f1ad945..91b925f665c51 100644 --- a/shell/common/engine.cc +++ b/shell/common/engine.cc @@ -507,4 +507,22 @@ const std::string& Engine::GetLastEntrypointLibrary() const { return last_entry_point_library_; } +// The Following commented out code connects into part 2 of the split AOT +// feature. Left commented out until it lands: + +// // |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) { + if (runtime_controller_->IsRootIsolateRunning()) { + // runtime_controller_->CompleteDartLoadLibrary(loading_unit_id, lib_name, + // apkPaths, abi); + } +} + } // namespace flutter diff --git a/shell/common/engine.h b/shell/common/engine.h index 1eb2cf36ecc72..8d33814e44c9a 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,19 @@ class Engine final : public RuntimeDelegate, virtual std::unique_ptr> 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; }; //---------------------------------------------------------------------------- @@ -767,6 +781,28 @@ 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, + std::string abi); + private: Engine::Delegate& delegate_; const Settings settings_; @@ -815,6 +851,12 @@ class Engine final : public RuntimeDelegate, std::unique_ptr> ComputePlatformResolvedLocale( const std::vector& supported_locale_data) override; + // The Following commented out code connects into part 2 of the split AOT + // feature. Left commented out until it lands: + + // // |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 3e2b1daa476a6..389d29f4970d0 100644 --- a/shell/common/platform_view.cc +++ b/shell/common/platform_view.cc @@ -159,4 +159,16 @@ 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) {} + +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 07ba3a1bb6d93..96b739672cee8 100644 --- a/shell/common/platform_view.h +++ b/shell/common/platform_view.h @@ -210,6 +210,50 @@ class PlatformView { /// virtual void OnPlatformViewMarkTextureFrameAvailable( int64_t texture_id) = 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; }; //---------------------------------------------------------------------------- @@ -565,6 +609,43 @@ class PlatformView { virtual std::shared_ptr CreateExternalViewEmbedder(); + //-------------------------------------------------------------------------- + /// @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, + std::string abi); + + virtual void UpdateAssetManager(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 a8a8b884e4c0f..ae0a8ea719942 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -1185,6 +1185,27 @@ std::unique_ptr> Shell::ComputePlatformResolvedLocale( 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) { + 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| +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 ffb07b877d493..cc548f1d27d1d 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 { @@ -507,6 +508,18 @@ class Shell final : public PlatformView::Delegate, // |PlatformView::Delegate| void OnPlatformViewSetNextFrameCallback(const fml::closure& closure) 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) override; + + // |PlatformView::Delegate| + void UpdateAssetManager(std::shared_ptr asset_manager) override; + // |Animator::Delegate| void OnAnimatorBeginFrame(fml::TimePoint frame_target_time) override; @@ -548,6 +561,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 a7ebd146db06e..1cbbc51cf7ff4 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -156,6 +156,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..a4e845aad7317 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() { @@ -299,6 +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/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 604bb0ebc4e2d..856203dd7e6c3 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; @@ -225,6 +226,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<>(); @@ -981,6 +984,108 @@ 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; + } + + @SuppressWarnings("unused") + @UiThread + 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(String moduleName, int loadingUnitId) { + dynamicFeatureManager.downloadFeature(moduleName, loadingUnitId); + } + + /** + * 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. + * + * Successful loading of the dart library also completes the loadLibrary() future + * that triggered the install/load process. + */ + @UiThread + public void loadDartLibrary( + int loadingUnitId, + @NonNull String libName, + @NonNull String[] apkPaths, + @NonNull String abi, + @NonNull String soPath) { + ensureRunningOnMainThread(); + ensureAttachedToNative(); + nativeLoadDartLibrary( + nativePlatformViewId, + loadingUnitId, + libName, + apkPaths, + abi, + soPath); + } + private native void nativeLoadDartLibrary( + long nativePlatformViewId, + int loadingUnitId, + @NonNull String libName, + @NonNull String[] apkPaths, + @NonNull String abi, + @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(nativePlatformViewId, assetManager, assetBundlePath); + } + private native void nativeUpdateAssetManager( + long nativePlatformViewId, + @NonNull AssetManager assetManager, + @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. + @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..0d162b292ea53 --- /dev/null +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java @@ -0,0 +1,56 @@ +// 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; + +// Flutter dynamic feature support is still in early developer preview and should +// not be used in production apps yet. + +/** + * 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, load assets, and load dart libs. + * + * A dynamic feature module is uniquely identified by a module name. Each feature module may + * 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. + * + * 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. + * + * 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. + * + * 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. + */ + 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 new file mode 100644 index 0000000000000..67c649f4d3261 --- /dev/null +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java @@ -0,0 +1,227 @@ +// 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 feature modules + * from the Google Play store. + */ +public class PlayStoreDynamicFeatureManager implements DynamicFeatureManager { + private static final String TAG = "flutter"; + + 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())) { + // TODO(garyq): Add capability to access the state from framework. + switch (state.status()) { + case SplitInstallSessionStatus.FAILED: { + 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.d(TAG, "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") installed successfully."); + 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; + } + case SplitInstallSessionStatus.CANCELED: { + Log.d(TAG, "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") cancelled"); + sessionIdToName.remove(state.sessionId()); + break; + } + case SplitInstallSessionStatus.CANCELING: { + Log.d(TAG, "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") canceling"); + sessionIdToName.remove(state.sessionId()); + break; + } + case SplitInstallSessionStatus.PENDING: { + Log.d(TAG, "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") pending."); + break; + } + case SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION: { + Log.d(TAG, "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") requires user confirmation."); + break; + } + case SplitInstallSessionStatus.DOWNLOADING: { + Log.d(TAG, "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") downloading."); + break; + } + case SplitInstallSessionStatus.DOWNLOADED: { + Log.d(TAG, "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") downloaded."); + break; + } + case SplitInstallSessionStatus.INSTALLING: { + Log.d(TAG, "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") installing."); + break; + } + default: Log.d(TAG, "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 downloadFeature(String moduleName, int loadingUnitId) { + if (moduleName == null) { + Log.e(TAG, "Dynamic feature module name was null."); + return; + } + + 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.d(TAG, "Request to install module \"" + moduleName + "\" sent with session id " + sessionId + "."); + }) + .addOnFailureListener(exception -> { + switch(((SplitInstallException) exception).getErrorCode()) { + case SplitInstallErrorCode.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.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.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 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"); + } catch (NameNotFoundException e) { + Log.d(TAG, "NameNotFoundException creating context for " + moduleName); + throw new RuntimeException(e); + } + } + + 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.SUPPORTED_ABIS[0]; + } else { + abi = Build.CPU_ABI; + } + 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. + 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(); + } + } + + flutterJNI.loadDartLibrary( + loadingUnitId, + aotSharedLibraryName, + apkPaths.toArray(new String[apkPaths.size()]), + abi, + soPath); + } + + public void uninstallFeature(String moduleName, int loadingUnitId) { + // TODO(garyq): support uninstalling. + } + + void destroy() { + splitInstallManager.unregisterListener(listener); + } + +} 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 8b6b27658b621..426ae8adfd6a4 100644 --- a/shell/platform/android/platform_view_android.cc +++ b/shell/platform/android/platform_view_android.cc @@ -336,6 +336,29 @@ 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(garyq): RETURN ERROR +} + +// |PlatformView| +void PlatformViewAndroid::CompleteDartLoadLibrary( + intptr_t loading_unit_id, + std::string lib_name, + std::vector& apkPaths, + std::string abi) { + delegate_.CompleteDartLoadLibrary(loading_unit_id, lib_name, apkPaths, abi); +} + +// |PlatformView| +void PlatformViewAndroid::UpdateAssetManager( + std::shared_ptr asset_manager) { + delegate_.UpdateAssetManager(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 3b424f2920a6f..7a624f748047c 100644 --- a/shell/platform/android/platform_view_android.h +++ b/shell/platform/android/platform_view_android.h @@ -93,6 +93,15 @@ 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) override; + + // |PlatformView| + void UpdateAssetManager(std::shared_ptr asset_manager) override; + private: const std::shared_ptr jni_facade_; std::unique_ptr android_context_; @@ -137,6 +146,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..234807cee8fee 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -100,6 +100,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; @@ -508,6 +510,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 jLibName, + jobjectArray jApkPaths, + jstring jAbi, + jstring jSoPath) { + 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); + + // 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, + jint loadigUnitId, + jobject error, + jboolean transient) { + // TODO(garyq): Implement +} + bool RegisterApi(JNIEnv* env) { static const JNINativeMethod flutter_jni_methods[] = { // Start of methods from FlutterJNI @@ -664,6 +712,23 @@ bool RegisterApi(JNIEnv* env) { .fnPtr = reinterpret_cast(&FlutterTextUtilsIsRegionalIndicator), }, + { + .name = "nativeLoadDartLibrary", + .signature = "(JILjava/lang/String;[Ljava/lang/String;Ljava/lang/" + "String;Ljava/lang/String;)V", + .fnPtr = reinterpret_cast(&LoadDartLibrary), + }, + { + .name = "nativeUpdateAssetManager", + .signature = + "(JLandroid/content/res/AssetManager;Ljava/lang/String;)V", + .fnPtr = reinterpret_cast(&UpdateAssetManager), + }, + { + .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_; From ea56d3f402e921fa29c6eb21bdf999a16715d5e1 Mon Sep 17 00:00:00 2001 From: garyqian Date: Wed, 28 Oct 2020 18:02:58 -0700 Subject: [PATCH 02/29] Java formatting formatting format --- .../io/flutter/app/FlutterApplication.java | 2 +- .../flutter/embedding/engine/FlutterJNI.java | 58 ++-- .../DynamicFeatureManager.java | 36 ++- .../PlayStoreDynamicFeatureManager.java | 259 ++++++++++++------ 4 files changed, 222 insertions(+), 133 deletions(-) diff --git a/shell/platform/android/io/flutter/app/FlutterApplication.java b/shell/platform/android/io/flutter/app/FlutterApplication.java index 1c59cad44316d..0031837467b67 100644 --- a/shell/platform/android/io/flutter/app/FlutterApplication.java +++ b/shell/platform/android/io/flutter/app/FlutterApplication.java @@ -5,8 +5,8 @@ package io.flutter.app; import android.app.Activity; -import android.content.Context; import android.app.Application; +import android.content.Context; import androidx.annotation.CallSuper; import com.google.android.play.core.splitcompat.SplitCompat; import io.flutter.FlutterInjector; diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 856203dd7e6c3..23a62c8f092da 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -994,6 +994,7 @@ public void setDynamicFeatureManager(@NonNull DynamicFeatureManager dynamicFeatu } private Context dynamicFeatureContext; + @UiThread public void setDynamicFeatureContext(@NonNull Context context) { ensureRunningOnMainThread(); @@ -1003,7 +1004,16 @@ public void setDynamicFeatureContext(@NonNull Context context) { @SuppressWarnings("unused") @UiThread public void downloadDynamicFeature(int loadingUnitId) { - String loadingUnitIdResName = dynamicFeatureContext.getResources().getString(dynamicFeatureContext.getResources().getIdentifier("loadingUnit" + loadingUnitId, "string", dynamicFeatureContext.getPackageName())); + String loadingUnitIdResName = + dynamicFeatureContext + .getResources() + .getString( + dynamicFeatureContext + .getResources() + .getIdentifier( + "loadingUnit" + loadingUnitId, + "string", + dynamicFeatureContext.getPackageName())); downloadDynamicFeature(loadingUnitIdResName, loadingUnitId); } @@ -1016,14 +1026,13 @@ public void downloadDynamicFeature(String moduleName, int loadingUnitId) { /** * 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. * - * Successful loading of the dart library also completes the loadLibrary() future - * that triggered the install/load process. + *

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. + * + *

Successful loading of the dart library also completes the loadLibrary() future that + * triggered the install/load process. */ @UiThread public void loadDartLibrary( @@ -1034,14 +1043,9 @@ public void loadDartLibrary( @NonNull String soPath) { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeLoadDartLibrary( - nativePlatformViewId, - loadingUnitId, - libName, - apkPaths, - abi, - soPath); + nativeLoadDartLibrary(nativePlatformViewId, loadingUnitId, libName, apkPaths, abi, soPath); } + private native void nativeLoadDartLibrary( long nativePlatformViewId, int loadingUnitId, @@ -1050,39 +1054,39 @@ private native void nativeLoadDartLibrary( @NonNull String abi, @NonNull String soPath); - /** - * Specifies a new AssetManager that has access to the dynamic feature's assets in addition - * to the base module's assets. + * 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`. + *

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 - ) { + @NonNull AssetManager assetManager, @NonNull String assetBundlePath) { ensureRunningOnMainThread(); ensureAttachedToNative(); nativeUpdateAssetManager(nativePlatformViewId, assetManager, assetBundlePath); } + private native void nativeUpdateAssetManager( long nativePlatformViewId, @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 // dart before the request is passed to Android. @SuppressWarnings("unused") @UiThread - public void dynamicFeatureInstallFailure(@NonNull String moduleName, int loadingUnitId, @NonNull String error, boolean trans) { + 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); + + private native void nativeDynamicFeatureInstallFailure( + @NonNull String moduleName, int loadingUnitId, @NonNull String error, boolean trans); // ----- End Dynamic Features Support ---- 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 0d162b292ea53..459b7404b502a 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java @@ -10,47 +10,43 @@ /** * Basic interface that handles downloading and loading of dynamic features. * - * The Flutter default implementation is PlayStoreDynamicFeatureManager. + *

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, load assets, and load dart libs. + *

The methods here may be called independently or in a sequence one after the other to perform 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 + *

A dynamic feature module is uniquely identified by a module name. Each feature module may * 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. - * - * 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. + * + *

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. * - * 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. + *

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. + *

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. - * - * 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. + * + *

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 67c649f4d3261..f6afd695066f0 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java @@ -19,13 +19,13 @@ import com.google.android.play.core.splitinstall.model.SplitInstallSessionStatus; import io.flutter.Log; import io.flutter.embedding.engine.FlutterJNI; +import java.io.File; +import java.util.ArrayList; import java.util.HashMap; -import java.util.Map; +import java.util.LinkedList; import java.util.List; -import java.util.ArrayList; +import java.util.Map; import java.util.Queue; -import java.util.LinkedList; -import java.io.File; /** * Flutter default implementation of DynamicFeatureManager that downloads dynamic feature modules @@ -47,55 +47,128 @@ 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.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.d(TAG, "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") installed successfully."); - 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; - } - case SplitInstallSessionStatus.CANCELED: { - Log.d(TAG, "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") cancelled"); - sessionIdToName.remove(state.sessionId()); - break; - } - case SplitInstallSessionStatus.CANCELING: { - Log.d(TAG, "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") canceling"); - sessionIdToName.remove(state.sessionId()); - break; - } - case SplitInstallSessionStatus.PENDING: { - Log.d(TAG, "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") pending."); - break; - } - case SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION: { - Log.d(TAG, "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") requires user confirmation."); - break; - } - case SplitInstallSessionStatus.DOWNLOADING: { - Log.d(TAG, "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") downloading."); - break; - } - case SplitInstallSessionStatus.DOWNLOADED: { - Log.d(TAG, "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") downloaded."); - break; - } - case SplitInstallSessionStatus.INSTALLING: { - Log.d(TAG, "Module \"" + sessionIdToName.get(state.sessionId()) + "\" (sessionId " + state.sessionId() + ") installing."); - break; - } - default: Log.d(TAG, "Status: " + state.status()); + case SplitInstallSessionStatus.FAILED: + { + Log.e( + 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.d( + TAG, + "Module \"" + + sessionIdToName.get(state.sessionId()) + + "\" (sessionId " + + state.sessionId() + + ") installed successfully."); + 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; + } + case SplitInstallSessionStatus.CANCELED: + { + Log.d( + TAG, + "Module \"" + + sessionIdToName.get(state.sessionId()) + + "\" (sessionId " + + state.sessionId() + + ") cancelled"); + sessionIdToName.remove(state.sessionId()); + break; + } + case SplitInstallSessionStatus.CANCELING: + { + Log.d( + TAG, + "Module \"" + + sessionIdToName.get(state.sessionId()) + + "\" (sessionId " + + state.sessionId() + + ") canceling"); + sessionIdToName.remove(state.sessionId()); + break; + } + case SplitInstallSessionStatus.PENDING: + { + Log.d( + TAG, + "Module \"" + + sessionIdToName.get(state.sessionId()) + + "\" (sessionId " + + state.sessionId() + + ") pending."); + break; + } + case SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION: + { + Log.d( + TAG, + "Module \"" + + sessionIdToName.get(state.sessionId()) + + "\" (sessionId " + + state.sessionId() + + ") requires user confirmation."); + break; + } + case SplitInstallSessionStatus.DOWNLOADING: + { + Log.d( + TAG, + "Module \"" + + sessionIdToName.get(state.sessionId()) + + "\" (sessionId " + + state.sessionId() + + ") downloading."); + break; + } + case SplitInstallSessionStatus.DOWNLOADED: + { + Log.d( + TAG, + "Module \"" + + sessionIdToName.get(state.sessionId()) + + "\" (sessionId " + + state.sessionId() + + ") downloaded."); + break; + } + case SplitInstallSessionStatus.INSTALLING: + { + Log.d( + TAG, + "Module \"" + + sessionIdToName.get(state.sessionId()) + + "\" (sessionId " + + state.sessionId() + + ") installing."); + break; + } + default: + Log.d(TAG, "Status: " + state.status()); } } } @@ -117,40 +190,55 @@ public void downloadFeature(String moduleName, int loadingUnitId) { return; } - SplitInstallRequest request = - SplitInstallRequest - .newBuilder() - .addModule(moduleName) - .build(); + 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.d(TAG, "Request to install module \"" + moduleName + "\" sent with session id " + sessionId + "."); - }) - .addOnFailureListener(exception -> { - switch(((SplitInstallException) exception).getErrorCode()) { - case SplitInstallErrorCode.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.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.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; - } - }); + // 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); + }) + .addOnFailureListener( + exception -> { + switch (((SplitInstallException) exception).getErrorCode()) { + case SplitInstallErrorCode.NETWORK_ERROR: + flutterJNI.dynamicFeatureInstallFailure( + moduleName, + loadingUnitId, + "Install of dynamic feature module \"" + + moduleName + + "\" failed with a network error", + true); + break; + case SplitInstallErrorCode.MODULE_UNAVAILABLE: + flutterJNI.dynamicFeatureInstallFailure( + moduleName, + loadingUnitId, + "Install of dynamic feature module \"" + + moduleName + + "\" failed as is unavailable.", + false); + break; + default: + flutterJNI.dynamicFeatureInstallFailure( + moduleName, + loadingUnitId, + "Install of dynamic feature module \"" + + moduleName + + "\" failed with error: \"" + + ((SplitInstallException) exception).getErrorCode() + + "\": " + + ((SplitInstallException) exception).getMessage(), + false); + break; + } + }); } public void loadAssets(@NonNull String moduleName, int loadingUnitId) { @@ -175,9 +263,9 @@ public void loadDartLibrary(String moduleName, int loadingUnitId) { // 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.SUPPORTED_ABIS[0]; + abi = Build.SUPPORTED_ABIS[0]; } else { - abi = Build.CPU_ABI; + abi = Build.CPU_ABI; } String pathAbi = abi.replace("-", "_"); // abis are represented with underscores in paths. @@ -199,7 +287,9 @@ public void loadDartLibrary(String moduleName, int loadingUnitId) { continue; } String name = file.getName(); - if (name.substring(name.length() - 4).equals(".apk") && name.substring(0, moduleName.length()).equals(moduleName) && name.contains(pathAbi)) { + if (name.substring(name.length() - 4).equals(".apk") + && name.substring(0, moduleName.length()).equals(moduleName) + && name.contains(pathAbi)) { apkPaths.add(file.getAbsolutePath()); continue; } @@ -223,5 +313,4 @@ public void uninstallFeature(String moduleName, int loadingUnitId) { void destroy() { splitInstallManager.unregisterListener(listener); } - } From 5b2cc97341d209c06cfd22f52e663ea984625159 Mon Sep 17 00:00:00 2001 From: garyqian Date: Thu, 29 Oct 2020 12:59:23 -0700 Subject: [PATCH 03/29] Build on linux and fuchsia Build Build Build Use string.format Const-ness, nits, codestyle Typo Formatting: Handle apk and so loading in android jni native Update docs Remove dart_handle return Refactor and rename methods Rename to LoadDartDeferredLibrary Additional renaming Add back moduleName to params Documentation Pass asset manager to persistent cache Fix license, cleanup, fix docs, rename methods Javadocs, API adjustments More docs, rename log tag remove extra import FlutterEngine constructor Improve docs, nits, renaming Documentation --- shell/common/engine.cc | 15 +- shell/common/engine.h | 35 ++-- shell/common/engine_unittests.cc | 5 +- shell/common/platform_view.cc | 12 +- shell/common/platform_view.h | 89 ++++----- shell/common/shell.cc | 19 +- shell/common/shell.h | 13 +- .../embedding/engine/FlutterEngine.java | 32 +++- .../flutter/embedding/engine/FlutterJNI.java | 107 ++++++----- .../DynamicFeatureManager.java | 134 +++++++++++--- .../PlayStoreDynamicFeatureManager.java | 175 +++++++++--------- .../android/jni/platform_view_android_jni.h | 2 +- .../platform/android/platform_view_android.cc | 18 +- .../platform/android/platform_view_android.h | 9 +- .../android/platform_view_android_jni_impl.cc | 127 +++++++++---- .../android/platform_view_android_jni_impl.h | 2 +- .../Source/FlutterEnginePlatformViewTest.mm | 6 + .../Source/FlutterPlatformViewsTest.mm | 6 + .../Source/accessibility_bridge_test.mm | 6 + .../fuchsia/flutter/platform_view_unittest.cc | 8 + 20 files changed, 502 insertions(+), 318 deletions(-) diff --git a/shell/common/engine.cc b/shell/common/engine.cc index 91b925f665c51..1b9c02413138b 100644 --- a/shell/common/engine.cc +++ b/shell/common/engine.cc @@ -511,17 +511,16 @@ const std::string& Engine::GetLastEntrypointLibrary() const { // feature. Left commented out until it lands: // // |RuntimeDelegate| -// Dart_Handle Engine::OnDartLoadLibrary(intptr_t loading_unit_id) { -// return delegate_.OnDartLoadLibrary(loading_unit_id); +// void Engine::RequestDartDeferredLibrary(intptr_t loading_unit_id) { +// return delegate_.RequestDartDeferredLibrary(loading_unit_id); // } -void Engine::CompleteDartLoadLibrary(intptr_t loading_unit_id, - std::string lib_name, - std::vector& apkPaths, - std::string abi) { +void Engine::LoadDartDeferredLibrary(intptr_t loading_unit_id, + const uint8_t* snapshot_data, + const uint8_t* snapshot_instructions) { if (runtime_controller_->IsRootIsolateRunning()) { - // runtime_controller_->CompleteDartLoadLibrary(loading_unit_id, lib_name, - // apkPaths, abi); + // runtime_controller_->LoadDartDeferredLibrary(loading_unit_id, + // snapshot_data, snapshot_instructions); } } diff --git a/shell/common/engine.h b/shell/common/engine.h index 8d33814e44c9a..6e0e392a5f367 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -30,7 +30,6 @@ #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 { @@ -270,10 +269,7 @@ class Engine final : public RuntimeDelegate, /// @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; + virtual void RequestDartDeferredLibrary(intptr_t loading_unit_id) = 0; }; //---------------------------------------------------------------------------- @@ -782,26 +778,25 @@ 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. + /// @brief Loads the dart shared library into the dart VM. When the + /// dart library is loaded successfully, the dart future + /// returned by the originating loadLibrary() call completes. + /// Each shared library is a loading unit, which consists of + /// deferred libraries that can be compiled split from the + /// base dart library by gen_snapshot. /// /// @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] snapshot_data Dart snapshot data of the loading unit's + /// shared library. /// - /// @param[in] abi The abi of the library, eg, arm64-v8a + /// @param[in] snapshot_data Dart snapshot instructions of the loading + /// unit's shared library. /// - void CompleteDartLoadLibrary(intptr_t loading_unit_id, - std::string lib_name, - std::vector& apkPaths, - std::string abi); + void LoadDartDeferredLibrary(intptr_t loading_unit_id, + const uint8_t* snapshot_data, + const uint8_t* snapshot_instructions); private: Engine::Delegate& delegate_; @@ -855,7 +850,7 @@ class Engine final : public RuntimeDelegate, // feature. Left commented out until it lands: // // |RuntimeDelegate| - // Dart_Handle OnDartLoadLibrary(intptr_t loading_unit_id) override; + // void RequestDartDeferredLibrary(intptr_t loading_unit_id) override; void SetNeedsReportTimings(bool value) override; diff --git a/shell/common/engine_unittests.cc b/shell/common/engine_unittests.cc index d6f3bf9fa3bcf..e563a92a454a4 100644 --- a/shell/common/engine_unittests.cc +++ b/shell/common/engine_unittests.cc @@ -13,7 +13,6 @@ #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 @@ -33,7 +32,7 @@ class MockDelegate : public Engine::Delegate { MOCK_METHOD1(ComputePlatformResolvedLocale, std::unique_ptr>( const std::vector&)); - MOCK_METHOD1(OnDartLoadLibrary, Dart_Handle(intptr_t)); + MOCK_METHOD1(RequestDartDeferredLibrary, void(intptr_t)); }; class MockResponse : public PlatformMessageResponse { @@ -57,7 +56,7 @@ class MockRuntimeDelegate : public RuntimeDelegate { MOCK_METHOD1(ComputePlatformResolvedLocale, std::unique_ptr>( const std::vector&)); - MOCK_METHOD1(OnDartLoadLibrary, Dart_Handle(intptr_t)); + MOCK_METHOD1(RequestDartDeferredLibrary, void(intptr_t)); }; class MockRuntimeController : public RuntimeController { diff --git a/shell/common/platform_view.cc b/shell/common/platform_view.cc index 389d29f4970d0..55d3ebbcc7147 100644 --- a/shell/common/platform_view.cc +++ b/shell/common/platform_view.cc @@ -159,14 +159,12 @@ PlatformView::ComputePlatformResolvedLocales( return out; } -Dart_Handle PlatformView::OnDartLoadLibrary(intptr_t loading_unit_id) { - return Dart_Null(); -} +void PlatformView::RequestDartDeferredLibrary(intptr_t loading_unit_id) {} -void PlatformView::CompleteDartLoadLibrary(intptr_t loading_unit_id, - std::string lib_name, - std::vector& apkPaths, - std::string abi) {} +void PlatformView::LoadDartDeferredLibrary( + intptr_t loading_unit_id, + const uint8_t* snapshot_data, + const uint8_t* snapshot_instructions) {} void PlatformView::UpdateAssetManager( std::shared_ptr asset_manager) {} diff --git a/shell/common/platform_view.h b/shell/common/platform_view.h index 96b739672cee8..5bb5b2fb16dfd 100644 --- a/shell/common/platform_view.h +++ b/shell/common/platform_view.h @@ -212,40 +212,28 @@ class PlatformView { int64_t texture_id) = 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. + /// @brief Loads the dart shared library into the dart VM. When the + /// dart library is loaded successfully, the dart future + /// returned by the originating loadLibrary() call completes. + /// Each shared library is a loading unit, which consists of + /// deferred libraries that can be compiled split from the + /// base dart library by gen_snapshot. /// /// @param[in] loading_unit_id The unique id of the deferred library's - /// loading unit. + /// loading unit. This is the same id as the + /// one passed in by the corresponding + /// RequestDartDeferredLibrary. /// - /// @return A Dart_Handle that is Dart_Null on success, and a dart error - /// on failure. + /// @param[in] snapshot_data Dart snapshot data of the loading unit's + /// shared library. /// - 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 + /// @param[in] snapshot_data Dart snapshot instructions of the loading + /// unit's shared library. /// - virtual void CompleteDartLoadLibrary(intptr_t loading_unit_id, - std::string lib_name, - std::vector& apkPaths, - std::string abi) = 0; + virtual void LoadDartDeferredLibrary( + intptr_t loading_unit_id, + const uint8_t* snapshot_data, + const uint8_t* snapshot_instructions) = 0; //-------------------------------------------------------------------------- /// @brief Sets the asset manager of the engine to asset_manager @@ -617,33 +605,38 @@ class PlatformView { /// @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); + virtual void RequestDartDeferredLibrary(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. + /// @brief Loads the dart shared library into the dart VM. When the + /// dart library is loaded successfully, the dart future + /// returned by the originating loadLibrary() call completes. + /// Each shared library is a loading unit, which consists of + /// deferred libraries that can be compiled split from the + /// base dart library by gen_snapshot. /// /// @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. + /// loading unit. This is the same id as the + /// one passed in by the corresponding + /// RequestDartDeferredLibrary. /// - /// @param[in] apkPaths The paths of the APKs that may or may not - /// contain the lib_name file. + /// @param[in] snapshot_data Dart snapshot data of the loading unit's + /// shared library. /// - /// @param[in] abi The abi of the library, eg, arm64-v8a + /// @param[in] snapshot_data Dart snapshot instructions of the loading + /// unit's shared library. /// - virtual void CompleteDartLoadLibrary(intptr_t loading_unit_id, - std::string lib_name, - std::vector& apkPaths, - std::string abi); + virtual void LoadDartDeferredLibrary(intptr_t loading_unit_id, + const uint8_t* snapshot_data, + const uint8_t* snapshot_instructions); + // TODO(garyq): Implement a proper asset_resolver replacement instead of + // overwriting the entire asset manager. + //-------------------------------------------------------------------------- + /// @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); protected: diff --git a/shell/common/shell.cc b/shell/common/shell.cc index ae0a8ea719942..aaf3c3c2242ae 100644 --- a/shell/common/shell.cc +++ b/shell/common/shell.cc @@ -1185,11 +1185,11 @@ std::unique_ptr> Shell::ComputePlatformResolvedLocale( 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) { - engine_->CompleteDartLoadLibrary(loading_unit_id, lib_name, apkPaths, abi); +void Shell::LoadDartDeferredLibrary(intptr_t loading_unit_id, + const uint8_t* snapshot_data, + const uint8_t* snapshot_instructions) { + engine_->LoadDartDeferredLibrary(loading_unit_id, snapshot_data, + snapshot_instructions); } void Shell::UpdateAssetManager(std::shared_ptr asset_manager) { @@ -1197,13 +1197,8 @@ void Shell::UpdateAssetManager(std::shared_ptr 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::RequestDartDeferredLibrary(intptr_t loading_unit_id) { + platform_view_->RequestDartDeferredLibrary(loading_unit_id); } void Shell::ReportTimings() { diff --git a/shell/common/shell.h b/shell/common/shell.h index cc548f1d27d1d..7ac297e5d07c6 100644 --- a/shell/common/shell.h +++ b/shell/common/shell.h @@ -36,7 +36,6 @@ #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 { @@ -509,13 +508,9 @@ class Shell final : public PlatformView::Delegate, void OnPlatformViewSetNextFrameCallback(const fml::closure& closure) 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) override; + void LoadDartDeferredLibrary(intptr_t loading_unit_id, + const uint8_t* snapshot_data, + const uint8_t* snapshot_instructions) override; // |PlatformView::Delegate| void UpdateAssetManager(std::shared_ptr asset_manager) override; @@ -562,7 +557,7 @@ class Shell final : public PlatformView::Delegate, const std::vector& supported_locale_data) override; // |Engine::Delegate| - Dart_Handle OnDartLoadLibrary(intptr_t loading_unit_id) override; + void RequestDartDeferredLibrary(intptr_t loading_unit_id) override; // |Rasterizer::Delegate| void OnFrameRasterized(const FrameTiming&) override; diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index a4e845aad7317..484f777841090 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -261,7 +261,10 @@ public FlutterEngine( false); } - /** Fully configurable {@code FlutterEngine} constructor. */ + /** + * Same as {@link #FlutterEngine(Context, FlutterLoader, FlutterJNI, String[], boolean, boolean)}, plus the + * ability to control {@code waitForRestorationData}. + */ public FlutterEngine( @NonNull Context context, @Nullable FlutterLoader flutterLoader, @@ -270,6 +273,28 @@ public FlutterEngine( @Nullable String[] dartVmArgs, boolean automaticallyRegisterPlugins, boolean waitForRestorationData) { + this( + context, + flutterLoader, + flutterJNI, + platformViewsController, + dartVmArgs, + automaticallyRegisterPlugins, + waitForRestorationData, + null); + } + + // TODO(garyq): Move this to a better injection system. + /** Fully configurable {@code FlutterEngine} constructor. */ + public FlutterEngine( + @NonNull Context context, + @Nullable FlutterLoader flutterLoader, + @NonNull FlutterJNI flutterJNI, + @NonNull PlatformViewsController platformViewsController, + @Nullable String[] dartVmArgs, + boolean automaticallyRegisterPlugins, + boolean waitForRestorationData, + @Nullable DynamicFeatureManager dynamicFeatureManager) { AssetManager assetManager; try { assetManager = context.createPackageContext(context.getPackageName(), 0).getAssets(); @@ -304,9 +329,9 @@ public FlutterEngine( flutterJNI.setPlatformViewsController(platformViewsController); flutterJNI.setLocalizationPlugin(localizationPlugin); - dynamicFeatureManager = new PlayStoreDynamicFeatureManager(context, flutterJNI); + this.dynamicFeatureManager = dynamicFeatureManager != null ? + dynamicFeatureManager : new PlayStoreDynamicFeatureManager(context, flutterJNI); flutterJNI.setDynamicFeatureManager(dynamicFeatureManager); - flutterJNI.setDynamicFeatureContext(context); attachToJni(); @@ -383,6 +408,7 @@ public void destroy() { platformViewsController.onDetachedFromJNI(); dartExecutor.onDetachedFromJNI(); flutterJNI.removeEngineLifecycleListener(engineLifecycleListener); + flutterJNI.removeDynamicFeatureManager(); flutterJNI.detachFromNativeAndReleaseResources(); } diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 23a62c8f092da..3c25915433a10 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -993,73 +993,72 @@ public void setDynamicFeatureManager(@NonNull DynamicFeatureManager dynamicFeatu this.dynamicFeatureManager = dynamicFeatureManager; } - private Context dynamicFeatureContext; - + /** Sets the dynamic feature manager that is used to download and install split features. */ @UiThread - public void setDynamicFeatureContext(@NonNull Context context) { + public void removeDynamicFeatureManager() { ensureRunningOnMainThread(); - this.dynamicFeatureContext = context; + this.dynamicFeatureManager = null; } + /** + * Called by dart to request that a dart deferred library corresponding to loadingUnitId be + * downloaded (if necessary) and loaded into the dart vm. + * + *

This method delegates the task to DynamicFeatureManager, which handles the download and + * loading of the dart library and any assets. + * + * @param loadingUnitId The loadingUnitId is assigned during compile time by gen_snapshot and is + * automatically retrieved when loadLibrary() is called on a dart deferred + * library. + */ @SuppressWarnings("unused") @UiThread - 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(String moduleName, int loadingUnitId) { - dynamicFeatureManager.downloadFeature(moduleName, loadingUnitId); + public void RequestDartDeferredLibrary(int loadingUnitId) { + if (dynamicFeatureManager != null) { + dynamicFeatureManager.downloadDynamicFeature(loadingUnitId, null); + } } /** - * 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. + * Searches each of the provided paths for a valid dart shared library .so file and resolves + * symbols to load into the dart VM. * - *

Successful loading of the dart library also completes the loadLibrary() future that + *

Successful loading of the dart library completes the future returned by loadLibrary() that * triggered the install/load process. + * + * @param loadingUnitId The loadingUnitId is assigned during compile time by gen_snapshot and is + * automatically retrieved when loadLibrary() is called on a dart deferred + * library. This is used to identify which dart deferred library the resolved + * correspond to. + * + * @param searchPaths An array of paths in which to look for valid dart shared libraries. This + * supports paths within zipped apks as long as the apks are not compressed + * using the `path/to/apk.apk!path/inside/apk/lib.so` format. Paths will be + * tried first to last and ends when a library is sucessfully found. When the + * found library is invalid, no additional paths will be attempted. */ @UiThread - public void loadDartLibrary( + public void loadDartDeferredLibrary( int loadingUnitId, - @NonNull String libName, - @NonNull String[] apkPaths, - @NonNull String abi, - @NonNull String soPath) { + @NonNull String[] searchPaths) { ensureRunningOnMainThread(); ensureAttachedToNative(); - nativeLoadDartLibrary(nativePlatformViewId, loadingUnitId, libName, apkPaths, abi, soPath); + nativeLoadDartDeferredLibrary(nativePlatformViewId, loadingUnitId, searchPaths); } - private native void nativeLoadDartLibrary( + private native void nativeLoadDartDeferredLibrary( long nativePlatformViewId, int loadingUnitId, - @NonNull String libName, - @NonNull String[] apkPaths, - @NonNull String abi, - @NonNull String soPath); + @NonNull String[] searchPaths); /** * 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`. + * @param assetManager An android AssetManager that is able to access the newly downloaded assets. + * + * @param assetBundlePath The subdirectory that the flutter assets are stored in. The typical value + * is `flutter_assets`. */ @UiThread public void updateAssetManager( @@ -1074,19 +1073,31 @@ private native void nativeUpdateAssetManager( @NonNull AssetManager assetManager, @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. + /** + * Indicates that a failure was encountered during the Android portion of downloading a dynamic + * feature module and loading a dart deferred library, which is typically done by + * DynamicFeatureManager. + * + *

This will inform dart that the future returned by loadLibrary() should complete with an error. + * + * @param loadingUnitId The loadingUnitId that corresponds to the dart deferred library that + * failed to install. + * + * @param error The error message to display. + * + * @param isTransient When isTransient 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) { + int loadingUnitId, @NonNull String error, boolean isTransient) { ensureRunningOnMainThread(); - nativeDynamicFeatureInstallFailure(moduleName, loadingUnitId, error, trans); + nativeDynamicFeatureInstallFailure(loadingUnitId, error, isTransient); } private native void nativeDynamicFeatureInstallFailure( - @NonNull String moduleName, int loadingUnitId, @NonNull String error, boolean trans); + int loadingUnitId, @NonNull String error, boolean isTransient); // ----- End Dynamic Features Support ---- 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 459b7404b502a..3f3f1b7936b8f 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java @@ -1,52 +1,144 @@ -// Copyright 2020 The Flutter Authors. All rights reserved. +// Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package io.flutter.embedding.engine.dynamicfeatures; -// Flutter dynamic feature support is still in early developer preview and should -// not be used in production apps yet. - +// TODO: add links to external documentation on how to use split aot features. /** * Basic interface that handles downloading and loading of dynamic features. * + *

Flutter dynamic feature support is still in early developer preview and should + * not be used in production apps yet. + * *

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, load assets, and load dart libs. + *

DynamicFeatureManager handles the embedder/android level tasks of downloading, installing, and + * loading dart deferred libraries. A typical code-flow begins with a dart call to loadLibrary() on + * deferred imported library. See https://dart.dev/guides/language/language-tour#deferred-loading + * This call retrieves a unique identifier called the loading unit id, which is assigned by + * gen_snapshot during compilation. The loading unit id is passed down through the engine and invokes + * downloadDynamicFeature. Once the feature module is downloaded, loadAssets and loadDartLibrary should + * be invoked. loadDartLibrary should find shared library .so files for the engine to open. loadAssets + * should typically ensure the new assets are available to the engine's asset manager by passing an + * updated android AssetManager to the engine. + * + *

The loadAssets and loadDartLibrary methods are separated out because they may also be called + * manually via platform channel messages. A full downloadDynamicFeature implementation should call + * these two methods as needed. * - *

A dynamic feature module is uniquely identified by a module name. Each feature module may - * contain one or more loading units, uniquely identified by the loading unit ID. + *

A dynamic feature module is uniquely identified by a module name as defined in bundle_config.yaml. + * Each feature module may contain one or more loading units, uniquely identified by the loading unit ID + * and assets. */ public interface DynamicFeatureManager { /** * 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. + *

This method is called by Flutter when loadLibrary() is called on a dart deferred library + * which relies on this method to download the requested module under the hood. This method may + * also be manually invoked via a system channel message. + * + *

This method begins the download and installation of the specified feature module. For example, + * the Play Store dynamic delivery implementation uses SplitInstallManager to request the download of + * the module. Download is not complete when this method returns. The download process should be + * listened for and upon completion of download, listeners should invoke loadAssets and loadDartLibrary + * to complete the dynamic feature load process. + * + *

Both parameters are not always necessary to identify which module to install. Asset-only modules + * do not have an associated loadingUnitId. Instead, an invalid ID like -1 may be passed to download + * only with moduleName. On the other hand, it can be possible to resolve the moduleName based + * on the loadingUnitId. This resolution is done if moduleName is null. At least one of + * loadingUnitId or moduleName must be valid or non-null. + * + *

Flutter will typically call this method in two ways. When invoked as part of a dart loadLibrary() + * call, a valid loadingUnitId is passed in while the moduleName is null. In this case, this method is + * responsible for figuring out what module the loadingUnitId corresponds to. + * + *

When invoked manually as part of loading an assets-only module, loadingUnitId is -1 (invalid) + * and moduleName is supplied. Without a loadingUnitId, this method just downloads the module by name + * and attempts to load assets via loadAssets. + * + * @param loadingUnitId The unique identifier associated with a dart deferred library. This id is + * assigned by the compiler and can be seen for reference in bundle_config.yaml. + * This ID is primarily used in loadDartLibrary to indicate to dart which + * dart library is being loaded. Loading unit ids range from 0 to the number + * existing loading units. Passing a negative loading unit id indicates + * that no dart deferred library should be loaded after download completes. + * This is the case when the dynamic feature module is an assets-only module. + * If a negative loadingUnitId is passed, then moduleName must not be null. + * Passing a loadingUnitId larger than the highest valid loading unit's id will + * cause the dart loadLibrary() to complete with a failure. + * + * @param moduleName The dynamic feature module name as defined in bundle_config.yaml. This may + * be null if the dynamic feature to be loaded is associated with a + * loading unit/deferred dart library. In this case, it is this method's + * responsibility to map the loadingUnitId to its corresponding moduleName. + * When loading asset-only or other dynamic features without an associated + * dart deferred library, loading unit id should a negative value and moduleName + * must be non-null. */ - public abstract void downloadFeature(String moduleName, int loadingUnitId); + public abstract void downloadDynamicFeature(int loadingUnitId, String moduleName); /** * 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. + *

Assets shoud be loaded before the dart derferred 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. + * + *

If using the Play Store dynamic feature delivery, refresh the context via: + * {@code context.createPackageContext(context.getPackageName(), 0);} + * This returns a new context, from which an updated asset manager may be obtained and passed to + * updateAssetManager in FlutterJNI. This process does not require loadingUnitId or moduleName, + * however, the two parameters are still present for custom implementations that store assets + * outside of Android's native system. + * + * @param loadingUnitId The unique identifier associated with a dart deferred library. + * + * @param moduleName The dynamic feature module name as defined in bundle_config.yaml. */ - public abstract void loadAssets(String moduleName, int loadingUnitId); + public abstract void loadAssets(int loadingUnitId, String moduleName); /** * 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. + *

When a feature module download is triggered via a dart loadLibrary() call on a deferred library + * and the download completes, this method should be called to find the path .so library file and + * passed to FlutterJNI.loadDartDeferredLibrary to be dlopen-ed. + * + *

Specifically, APKs distributed by android's app bundle format may vary by device and API number, + * so FlutterJNI's loadDartDeferredLibrary accepts a list of search paths with can include paths + * within APKs that have not been unpacked using the `path/to/apk.apk!path/inside/apk/lib.so` format. + * Each search path will be attempted in order until a shared library is found. This allows for + * the developer to avoid unpacking the apk zip. + * + *

Upon successful load of the dart library, the dart future from the originating loadLibary() + * call completes and developers are able to use symbols and assets from the feature module. + * + * @param loadingUnitId The unique identifier associated with a dart deferred library. This id is + * assigned by the compiler and can be seen for reference in bundle_config.yaml. + * This ID is primarily used in loadDartLibrary to indicate to dart which + * dart library is being loaded. Loading unit ids range from 0 to the number + * existing loading units. Negative loading unit ids are considered + * invalid and this method will result in a no-op. + * + * @param moduleName The dynamic feature module name as defined in bundle_config.yaml. If using + * Play Store dynamic feature delivery, this name corresponds to the root name + * on the installed APKs in which to search for the desired shared library .so + * file. */ - public abstract void loadDartLibrary(String moduleName, int loadingUnitId); + public abstract void loadDartLibrary(int loadingUnitId, String moduleName); - /** Uninstall the specified feature module. */ - public abstract void uninstallFeature(String moduleName, int loadingUnitId); + /** + * Uninstall the specified feature module. + * + *

Both parameters are not always necessary to identify which module to uninstall. Asset-only + * modules do not have an associated loadingUnitId. Instead, an invalid ID like -1 may be passed to + * download only with moduleName. On the other hand, it can be possible to resolve the moduleName based + * on the loadingUnitId. This resolution is done if moduleName is null. At least one of + * loadingUnitId or moduleName must be valid or non-null. + */ + public abstract void uninstallFeature(int loadingUnitId, String moduleName); } 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 f6afd695066f0..3ef5b9bc97d48 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java @@ -1,4 +1,4 @@ -// Copyright 2020 The Flutter Authors. All rights reserved. +// Copyright 2013 The Flutter Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. @@ -27,38 +27,39 @@ import java.util.Map; import java.util.Queue; + /** * 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"; + private static final String TAG = "PlayStoreDynamicFeatureManager"; private @NonNull SplitInstallManager splitInstallManager; - private @NonNull Map sessionIdToName; - private @NonNull Map sessionIdToLoadingUnitId; private @NonNull FlutterJNI flutterJNI; private @NonNull Context context; + // Each request to install a feature module gets a session ID. These maps associate + // the session ID with the loading unit and module name that was requested. + private @NonNull Map sessionIdToName; + private @NonNull Map sessionIdToLoadingUnitId; private FeatureInstallStateUpdatedListener listener; 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. + // TODO(garyq): Add system channel for split aot messages. switch (state.status()) { case SplitInstallSessionStatus.FAILED: { Log.e( TAG, - "Module \"" - + sessionIdToName.get(state.sessionId()) - + "\" (sessionId " - + state.sessionId() - + ") install failed with " - + state.errorCode()); + String.format( + "Module \"%s\" (sessionId %d) install failed with: %s", + sessionIdToName.get(state.sessionId()), + state.sessionId(), + state.errorCode())); flutterJNI.dynamicFeatureInstallFailure( - sessionIdToName.get(state.sessionId()), sessionIdToLoadingUnitId.get(state.sessionId()), "Module install failed with " + state.errorCode(), true); @@ -70,20 +71,18 @@ public void onStateUpdate(SplitInstallSessionState state) { { Log.d( TAG, - "Module \"" - + sessionIdToName.get(state.sessionId()) - + "\" (sessionId " - + state.sessionId() - + ") installed successfully."); + String.format( + "Module \"%s\" (sessionId %d) install successfully.", + sessionIdToName.get(state.sessionId()), state.sessionId())); loadAssets( - sessionIdToName.get(state.sessionId()), - sessionIdToLoadingUnitId.get(state.sessionId())); + sessionIdToLoadingUnitId.get(state.sessionId()), + sessionIdToName.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())); + sessionIdToLoadingUnitId.get(state.sessionId()), + sessionIdToName.get(state.sessionId())); sessionIdToName.remove(state.sessionId()); sessionIdToLoadingUnitId.remove(state.sessionId()); break; @@ -92,11 +91,9 @@ public void onStateUpdate(SplitInstallSessionState state) { { Log.d( TAG, - "Module \"" - + sessionIdToName.get(state.sessionId()) - + "\" (sessionId " - + state.sessionId() - + ") cancelled"); + String.format( + "Module \"%s\" (sessionId %d) install canceled.", + sessionIdToName.get(state.sessionId()), state.sessionId())); sessionIdToName.remove(state.sessionId()); break; } @@ -104,67 +101,54 @@ public void onStateUpdate(SplitInstallSessionState state) { { Log.d( TAG, - "Module \"" - + sessionIdToName.get(state.sessionId()) - + "\" (sessionId " - + state.sessionId() - + ") canceling"); - sessionIdToName.remove(state.sessionId()); + String.format( + "Module \"%s\" (sessionId %d) install canceling.", + sessionIdToName.get(state.sessionId()), state.sessionId())); break; } case SplitInstallSessionStatus.PENDING: { Log.d( TAG, - "Module \"" - + sessionIdToName.get(state.sessionId()) - + "\" (sessionId " - + state.sessionId() - + ") pending."); + String.format( + "Module \"%s\" (sessionId %d) install pending.", + sessionIdToName.get(state.sessionId()), state.sessionId())); break; } case SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION: { Log.d( TAG, - "Module \"" - + sessionIdToName.get(state.sessionId()) - + "\" (sessionId " - + state.sessionId() - + ") requires user confirmation."); + String.format( + "Module \"%s\" (sessionId %d) install requires user confirmation.", + sessionIdToName.get(state.sessionId()), state.sessionId())); break; } case SplitInstallSessionStatus.DOWNLOADING: { Log.d( TAG, - "Module \"" - + sessionIdToName.get(state.sessionId()) - + "\" (sessionId " - + state.sessionId() - + ") downloading."); + String.format( + "Module \"%s\" (sessionId %d) downloading.", + sessionIdToName.get(state.sessionId()), state.sessionId())); break; } case SplitInstallSessionStatus.DOWNLOADED: { Log.d( TAG, - "Module \"" - + sessionIdToName.get(state.sessionId()) - + "\" (sessionId " - + state.sessionId() - + ") downloaded."); + String.format( + "Module \"%s\" (sessionId %d) downloaded.", + sessionIdToName.get(state.sessionId()), state.sessionId())); break; } case SplitInstallSessionStatus.INSTALLING: { Log.d( TAG, - "Module \"" - + sessionIdToName.get(state.sessionId()) - + "\" (sessionId " - + state.sessionId() - + ") installing."); + String.format( + "Module \"%s\" (sessionId %d) installing.", + sessionIdToName.get(state.sessionId()), state.sessionId())); break; } default: @@ -184,9 +168,21 @@ public PlayStoreDynamicFeatureManager(@NonNull Context context, @NonNull Flutter sessionIdToLoadingUnitId = new HashMap(); } - public void downloadFeature(String moduleName, int loadingUnitId) { - if (moduleName == null) { - Log.e(TAG, "Dynamic feature module name was null."); + private String loadingUnitIdToModuleName(int loadingUnitId) { + // Loading unit id to module name mapping stored in android Strings + // resources. + int moduleNameIdentifier = + context + .getResources() + .getIdentifier( + "loadingUnit" + loadingUnitId, "string", context.getPackageName()); + return context.getResources().getString(moduleNameIdentifier); + } + + public void downloadDynamicFeature(int loadingUnitId, String moduleName) { + String resolvedModuleName = moduleName == null ? moduleName : loadingUnitIdToModuleName(loadingUnitId); + if (resolvedModuleName == null) { + Log.d(TAG, "Dynamic feature module name was null."); return; } @@ -209,39 +205,37 @@ public void downloadFeature(String moduleName, int loadingUnitId) { switch (((SplitInstallException) exception).getErrorCode()) { case SplitInstallErrorCode.NETWORK_ERROR: flutterJNI.dynamicFeatureInstallFailure( - moduleName, loadingUnitId, - "Install of dynamic feature module \"" - + moduleName - + "\" failed with a network error", + String.format( + "Install of dynamic feature module \"%s\" failed with a network error", + moduleName), true); break; case SplitInstallErrorCode.MODULE_UNAVAILABLE: flutterJNI.dynamicFeatureInstallFailure( - moduleName, loadingUnitId, - "Install of dynamic feature module \"" - + moduleName - + "\" failed as is unavailable.", + String.format( + "Install of dynamic feature module \"%s\" failed as it is unavailable", + moduleName), false); break; default: flutterJNI.dynamicFeatureInstallFailure( - moduleName, loadingUnitId, - "Install of dynamic feature module \"" - + moduleName - + "\" failed with error: \"" - + ((SplitInstallException) exception).getErrorCode() - + "\": " - + ((SplitInstallException) exception).getMessage(), + String.format( + "Install of dynamic feature module \"%s\" failed with error %d: %s", + moduleName, + ((SplitInstallException) exception).getErrorCode(), + ((SplitInstallException) exception).getMessage()), false); break; } }); } - public void loadAssets(@NonNull String moduleName, int loadingUnitId) { + public void loadAssets(int loadingUnitId, String moduleName) { + // Since android dynamic feature asset manager is handled through + // context, neither parameter is used here. try { context = context.createPackageContext(context.getPackageName(), 0); @@ -251,12 +245,16 @@ public void loadAssets(@NonNull String moduleName, int loadingUnitId) { // TODO(garyq): Made the "flutter_assets" directory dynamic based off of DartEntryPoint. "flutter_assets"); } catch (NameNotFoundException e) { - Log.d(TAG, "NameNotFoundException creating context for " + moduleName); throw new RuntimeException(e); } } - public void loadDartLibrary(String moduleName, int loadingUnitId) { + public void loadDartLibrary(int loadingUnitId, String moduleName) { + // Loading unit must be specified and valid to load a dart library. + if (loadingUnitId < 0) { + return; + } + // This matches/depends on dart's loading unit naming convention, which we use unchanged. String aotSharedLibraryName = "app.so-" + loadingUnitId + ".part.so"; @@ -270,7 +268,7 @@ 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. + // performant and robust. // Search directly in APKs first List apkPaths = new ArrayList(); @@ -287,9 +285,7 @@ public void loadDartLibrary(String moduleName, int loadingUnitId) { continue; } String name = file.getName(); - if (name.substring(name.length() - 4).equals(".apk") - && name.substring(0, moduleName.length()).equals(moduleName) - && name.contains(pathAbi)) { + if (name.endsWith(".apk") && name.startsWith(moduleName) && name.contains(pathAbi)) { apkPaths.add(file.getAbsolutePath()); continue; } @@ -298,15 +294,18 @@ public void loadDartLibrary(String moduleName, int loadingUnitId) { } } - flutterJNI.loadDartLibrary( + List searchPaths = new ArrayList(); + for (String path : apkPaths) { + searchPaths.add(path + "!lib/" + abi + "/" + aotSharedLibraryName); + } + searchPaths.add(soPath); + + flutterJNI.loadDartDeferredLibrary( loadingUnitId, - aotSharedLibraryName, - apkPaths.toArray(new String[apkPaths.size()]), - abi, - soPath); + searchPaths.toArray(new String[apkPaths.size()])); } - public void uninstallFeature(String moduleName, int loadingUnitId) { + public void uninstallFeature(int loadingUnitId, String moduleName) { // TODO(garyq): support uninstalling. } diff --git a/shell/platform/android/jni/platform_view_android_jni.h b/shell/platform/android/jni/platform_view_android_jni.h index 1212ab82aa887..81323605de68c 100644 --- a/shell/platform/android/jni/platform_view_android_jni.h +++ b/shell/platform/android/jni/platform_view_android_jni.h @@ -196,7 +196,7 @@ class PlatformViewAndroidJNI { virtual double GetDisplayRefreshRate() = 0; - virtual bool FlutterViewDownloadDynamicFeature(int loading_unit_id) = 0; + virtual bool RequestDartDeferredLibrary(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 426ae8adfd6a4..f5049b23223d6 100644 --- a/shell/platform/android/platform_view_android.cc +++ b/shell/platform/android/platform_view_android.cc @@ -337,20 +337,20 @@ PlatformViewAndroid::ComputePlatformResolvedLocales( } // |PlatformView| -Dart_Handle PlatformViewAndroid::OnDartLoadLibrary(intptr_t loading_unit_id) { - if (jni_facade_->FlutterViewDownloadDynamicFeature(loading_unit_id)) { - return Dart_Null(); +void PlatformViewAndroid::RequestDartDeferredLibrary(intptr_t loading_unit_id) { + if (jni_facade_->RequestDartDeferredLibrary(loading_unit_id)) { + return; } - return Dart_Null(); // TODO(garyq): RETURN ERROR + return; // TODO(garyq): Call LoadDartDeferredLibraryFailure() } // |PlatformView| -void PlatformViewAndroid::CompleteDartLoadLibrary( +void PlatformViewAndroid::LoadDartDeferredLibrary( intptr_t loading_unit_id, - std::string lib_name, - std::vector& apkPaths, - std::string abi) { - delegate_.CompleteDartLoadLibrary(loading_unit_id, lib_name, apkPaths, abi); + const uint8_t* snapshot_data, + const uint8_t* snapshot_instructions) { + delegate_.LoadDartDeferredLibrary(loading_unit_id, snapshot_data, + snapshot_instructions); } // |PlatformView| diff --git a/shell/platform/android/platform_view_android.h b/shell/platform/android/platform_view_android.h index 7a624f748047c..ff2ac1353f2b3 100644 --- a/shell/platform/android/platform_view_android.h +++ b/shell/platform/android/platform_view_android.h @@ -94,10 +94,9 @@ 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) override; + void LoadDartDeferredLibrary(intptr_t loading_unit_id, + const uint8_t* snapshot_data, + const uint8_t* snapshot_instructions) override; // |PlatformView| void UpdateAssetManager(std::shared_ptr asset_manager) override; @@ -147,7 +146,7 @@ class PlatformViewAndroid final : public PlatformView { const std::vector& supported_locale_data) override; // |PlatformView| - Dart_Handle OnDartLoadLibrary(intptr_t loading_unit_id) override; + void RequestDartDeferredLibrary(intptr_t loading_unit_id) override; void InstallFirstFrameCallback(); diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index 234807cee8fee..aa1ffedf58dee 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -5,7 +5,9 @@ #include "flutter/shell/platform/android/platform_view_android_jni_impl.h" #include +#include #include +#include #include #include "unicode/uchar.h" @@ -100,7 +102,7 @@ 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; +static jmethodID g_install_dynamic_library_method = nullptr; // Called By Java static jmethodID g_on_display_platform_view_method = nullptr; @@ -511,26 +513,89 @@ static jboolean FlutterTextUtilsIsRegionalIndicator(JNIEnv* env, return u_hasBinaryProperty(codePoint, UProperty::UCHAR_REGIONAL_INDICATOR); } -static void LoadDartLibrary(JNIEnv* env, - jobject obj, - jlong shell_holder, - jint jLoadingUnitId, - jstring jLibName, - jobjectArray jApkPaths, - jstring jAbi, - jstring jSoPath) { - std::string abi = fml::jni::JavaStringToString(env, jAbi); +static void LoadLoadingUnitFailure(intptr_t loading_unit_id, + std::string message, + bool transient) { + // TODO(garyq): Implement +} + +static void DynamicFeatureInstallFailure(JNIEnv* env, + jobject obj, + jint jLoadingUnitId, + jstring jError, + jboolean jTransient) { + LoadLoadingUnitFailure(static_cast(jLoadingUnitId), + fml::jni::JavaStringToString(env, jError), + static_cast(jTransient)); +} + +static void LoadDartDeferredLibrary(JNIEnv* env, + jobject obj, + jlong shell_holder, + jint jLoadingUnitId, + jobjectArray jSearchPaths) { + // Convert java->c++ + intptr_t loading_unit_id = static_cast(jLoadingUnitId); + std::vector search_paths = + fml::jni::StringArrayToVector(env, jSearchPaths); + + // TODO: Switch to using the NativeLibrary class, eg: + // + // fml::RefPtr native_lib = + // fml::NativeLibrary::Create(lib_name.c_str()); + // + // Find and open the shared library. + void* handle = nullptr; + while (handle == nullptr && !search_paths.empty()) { + std::string path = search_paths.back(); + handle = ::dlopen(path.c_str(), RTLD_NOW); + search_paths.pop_back(); + } + if (handle == nullptr) { + LoadLoadingUnitFailure(loading_unit_id, + "No lib .so found for provided search paths.", true); + return; + } - std::vector apkPaths = - fml::jni::StringArrayToVector(env, jApkPaths); + // Resolve symbols. + 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) { + LoadLoadingUnitFailure(loading_unit_id, + "Could not resolve data symbol in library", true); + 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) { + LoadLoadingUnitFailure(loading_unit_id, + "Could not resolve instructions symbol in library", + true); + return; + } + } - ANDROID_SHELL_HOLDER->GetPlatformView()->CompleteDartLoadLibrary( - static_cast(jLoadingUnitId), - fml::jni::JavaStringToString(env, jLibName), apkPaths, abi); + ANDROID_SHELL_HOLDER->GetPlatformView()->LoadDartDeferredLibrary( + loading_unit_id, isolate_data, isolate_instructions); // TODO(garyq): fallback on soPath. } +// TODO(garyq): persist additional asset resolvers by updating instead of +// replacing with newly created asset_manager static void UpdateAssetManager(JNIEnv* env, jobject obj, jlong shell_holder, @@ -542,18 +607,11 @@ static void UpdateAssetManager(JNIEnv* env, jAssetManager, // asset manager fml::jni::JavaStringToString(env, jAssetBundlePath)) // apk asset dir ); + // Create config to set persistent cache asset manager + RunConfiguration config(nullptr, std::move(asset_manager)); ANDROID_SHELL_HOLDER->GetPlatformView()->UpdateAssetManager( - std::move(asset_manager)); -} - -static void DynamicFeatureInstallFailure(JNIEnv* env, - jobject obj, - jobject moduleName, - jint loadigUnitId, - jobject error, - jboolean transient) { - // TODO(garyq): Implement + config.GetAssetManager()); } bool RegisterApi(JNIEnv* env) { @@ -713,10 +771,9 @@ bool RegisterApi(JNIEnv* env) { reinterpret_cast(&FlutterTextUtilsIsRegionalIndicator), }, { - .name = "nativeLoadDartLibrary", - .signature = "(JILjava/lang/String;[Ljava/lang/String;Ljava/lang/" - "String;Ljava/lang/String;)V", - .fnPtr = reinterpret_cast(&LoadDartLibrary), + .name = "nativeLoadDartDeferredLibrary", + .signature = "(JI[Ljava/lang/String;)V", + .fnPtr = reinterpret_cast(&LoadDartDeferredLibrary), }, { .name = "nativeUpdateAssetManager", @@ -726,7 +783,7 @@ bool RegisterApi(JNIEnv* env) { }, { .name = "nativeDynamicFeatureInstallFailure", - .signature = "(Ljava/lang/String;ILjava/lang/String;Z)V", + .signature = "(ILjava/lang/String;Z)V", .fnPtr = reinterpret_cast(&DynamicFeatureInstallFailure), }, }; @@ -972,10 +1029,10 @@ bool PlatformViewAndroid::Register(JNIEnv* env) { return false; } - g_download_dynamic_feature_method = env->GetMethodID( - g_flutter_jni_class->obj(), "downloadDynamicFeature", "(I)V"); + g_install_dynamic_library_method = env->GetMethodID( + g_flutter_jni_class->obj(), "RequestDartDeferredLibrary", "(I)V"); - if (g_download_dynamic_feature_method == nullptr) { + if (g_install_dynamic_library_method == nullptr) { FML_LOG(ERROR) << "Could not locate downloadDynamicFeature method"; return false; } @@ -1407,7 +1464,7 @@ double PlatformViewAndroidJNIImpl::GetDisplayRefreshRate() { return static_cast(env->GetStaticFloatField(clazz, fid)); } -bool PlatformViewAndroidJNIImpl::FlutterViewDownloadDynamicFeature( +bool PlatformViewAndroidJNIImpl::RequestDartDeferredLibrary( int loading_unit_id) { JNIEnv* env = fml::jni::AttachCurrentThread(); @@ -1416,7 +1473,7 @@ bool PlatformViewAndroidJNIImpl::FlutterViewDownloadDynamicFeature( return true; } - env->CallObjectMethod(java_object.obj(), g_download_dynamic_feature_method, + env->CallObjectMethod(java_object.obj(), g_install_dynamic_library_method, loading_unit_id); FML_CHECK(CheckException(env)); diff --git a/shell/platform/android/platform_view_android_jni_impl.h b/shell/platform/android/platform_view_android_jni_impl.h index 3f0942d1606e7..8fe0618929e95 100644 --- a/shell/platform/android/platform_view_android_jni_impl.h +++ b/shell/platform/android/platform_view_android_jni_impl.h @@ -80,7 +80,7 @@ class PlatformViewAndroidJNIImpl final : public PlatformViewAndroidJNI { double GetDisplayRefreshRate() override; - bool FlutterViewDownloadDynamicFeature(int loading_unit_id) override; + bool RequestDartDeferredLibrary(int loading_unit_id) override; private: // Reference to FlutterJNI object. diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm index 125551a841c42..a33acbe4c6321 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm @@ -33,6 +33,12 @@ void OnPlatformViewSetAccessibilityFeatures(int32_t flags) override {} void OnPlatformViewRegisterTexture(std::shared_ptr texture) override {} void OnPlatformViewUnregisterTexture(int64_t texture_id) override {} void OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) override {} + + void LoadDartDeferredLibrary(intptr_t loading_unit_id, + std::string lib_name, + const std::vector& apkPaths, + std::string abi) override {} + void UpdateAssetManager(std::shared_ptr asset_manager) override {} }; } // namespace diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm index 0b0aa2b80da11..93964c591aab8 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm @@ -103,6 +103,12 @@ void OnPlatformViewSetAccessibilityFeatures(int32_t flags) override {} void OnPlatformViewRegisterTexture(std::shared_ptr texture) override {} void OnPlatformViewUnregisterTexture(int64_t texture_id) override {} void OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) override {} + + void LoadDartDeferredLibrary(intptr_t loading_unit_id, + std::string lib_name, + const std::vector& apkPaths, + std::string abi) override {} + void UpdateAssetManager(std::shared_ptr asset_manager) override {} }; } // namespace diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm index fbb2404b7a3e2..8447bf57db9f0 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm @@ -86,6 +86,12 @@ void OnPlatformViewSetAccessibilityFeatures(int32_t flags) override {} void OnPlatformViewRegisterTexture(std::shared_ptr texture) override {} void OnPlatformViewUnregisterTexture(int64_t texture_id) override {} void OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) override {} + + void LoadDartDeferredLibrary(intptr_t loading_unit_id, + std::string lib_name, + const std::vector& apkPaths, + std::string abi) override {} + void UpdateAssetManager(std::shared_ptr asset_manager) override {} }; class MockIosDelegate : public AccessibilityBridge::IosDelegate { diff --git a/shell/platform/fuchsia/flutter/platform_view_unittest.cc b/shell/platform/fuchsia/flutter/platform_view_unittest.cc index 559d2b8f7cbfb..2f676dd4a1f1d 100644 --- a/shell/platform/fuchsia/flutter/platform_view_unittest.cc +++ b/shell/platform/fuchsia/flutter/platform_view_unittest.cc @@ -104,6 +104,14 @@ class MockPlatformViewDelegate : public flutter::PlatformView::Delegate { const std::vector& supported_locale_data) { return nullptr; } + // |flutter::PlatformView::Delegate| + void LoadDartDeferredLibrary(intptr_t loading_unit_id, + std::string lib_name, + const std::vector& apkPaths, + std::string abi) {} + // |flutter::PlatformView::Delegate| + void UpdateAssetManager( + std::shared_ptr asset_manager) {} flutter::Surface* surface() const { return surface_.get(); } flutter::PlatformMessage* message() const { return message_.get(); } From 2190ed4047be35e2a4b5200f9b1c6299a428f01a Mon Sep 17 00:00:00 2001 From: garyqian Date: Thu, 12 Nov 2020 00:58:08 -0800 Subject: [PATCH 04/29] Initial tests --- shell/platform/android/BUILD.gn | 1 + shell/platform/android/jni/jni_mock.h | 2 +- .../test/io/flutter/FlutterTestSuite.java | 2 + .../PlayStoreDynamicFeatureManagerTest.java | 80 +++++++++++++++++++ 4 files changed, 84 insertions(+), 1 deletion(-) create mode 100644 shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java diff --git a/shell/platform/android/BUILD.gn b/shell/platform/android/BUILD.gn index 1cbbc51cf7ff4..29f204fd82dcc 100644 --- a/shell/platform/android/BUILD.gn +++ b/shell/platform/android/BUILD.gn @@ -464,6 +464,7 @@ action("robolectric_tests") { "test/io/flutter/embedding/engine/RenderingComponentTest.java", "test/io/flutter/embedding/engine/dart/DartExecutorTest.java", "test/io/flutter/embedding/engine/dart/DartMessengerTest.java", + "test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java", "test/io/flutter/embedding/engine/loader/ApplicationInfoLoaderTest.java", "test/io/flutter/embedding/engine/loader/FlutterLoaderTest.java", "test/io/flutter/embedding/engine/mutatorsstack/FlutterMutatorViewTest.java", diff --git a/shell/platform/android/jni/jni_mock.h b/shell/platform/android/jni/jni_mock.h index cf08766ddde13..52b38e1e84308 100644 --- a/shell/platform/android/jni/jni_mock.h +++ b/shell/platform/android/jni/jni_mock.h @@ -97,7 +97,7 @@ class JNIMock final : public PlatformViewAndroidJNI { MOCK_METHOD(double, GetDisplayRefreshRate, (), (override)); MOCK_METHOD(bool, - FlutterViewDownloadDynamicFeature, + RequestDartDeferredLibrary, (int loading_unit_id), (override)); }; diff --git a/shell/platform/android/test/io/flutter/FlutterTestSuite.java b/shell/platform/android/test/io/flutter/FlutterTestSuite.java index 635b6680475d0..8a7332d9a0ad0 100644 --- a/shell/platform/android/test/io/flutter/FlutterTestSuite.java +++ b/shell/platform/android/test/io/flutter/FlutterTestSuite.java @@ -18,6 +18,7 @@ import io.flutter.embedding.engine.RenderingComponentTest; import io.flutter.embedding.engine.dart.DartExecutorTest; import io.flutter.embedding.engine.dart.DartMessengerTest; +import io.flutter.embedding.engine.dynamicfeatures.PlayStoreDynamicFeatureManagerTest; import io.flutter.embedding.engine.loader.ApplicationInfoLoaderTest; import io.flutter.embedding.engine.loader.FlutterLoaderTest; import io.flutter.embedding.engine.mutatorsstack.FlutterMutatorViewTest; @@ -77,6 +78,7 @@ PlatformChannelTest.class, PlatformPluginTest.class, PlatformViewsControllerTest.class, + PlayStoreDynamicFeatureManagerTest.class, PluginComponentTest.class, PreconditionsTest.class, RenderingComponentTest.class, diff --git a/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java b/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java new file mode 100644 index 0000000000000..a32d8496a8f18 --- /dev/null +++ b/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java @@ -0,0 +1,80 @@ +// Copyright 2013 The Flutter Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +package io.flutter.embedding.engine.dynamicfeatures; + +import static junit.framework.TestCase.assertFalse; +import static junit.framework.TestCase.assertTrue; +import static org.mockito.Mockito.mock; + +import android.content.Context; +import androidx.annotation.NonNull; +import android.content.res.AssetManager; +import io.flutter.Log; +import io.flutter.embedding.engine.FlutterJNI; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; +import org.robolectric.annotation.Config; + +@Config(manifest = Config.NONE) +@RunWith(RobolectricTestRunner.class) +public class PlayStoreDynamicFeatureManagerTest { + private class TestFlutterJNI extends FlutterJNI { + public boolean loadDartDeferredLibraryCalled = false; + public boolean updateAssetManagerCalled = false; + public boolean dynamicFeatureInstallFailureCalled = false; + + public TestFlutterJNI() {} + + @Override + public void loadDartDeferredLibrary( + int loadingUnitId, + @NonNull String[] searchPaths) { + loadDartDeferredLibraryCalled = true; + } + + @Override + public void updateAssetManager( + @NonNull AssetManager assetManager, @NonNull String assetBundlePath) { + updateAssetManagerCalled = true; + } + + @Override + public void dynamicFeatureInstallFailure( + int loadingUnitId, @NonNull String error, boolean isTransient) { + dynamicFeatureInstallFailureCalled = true; + } + } + + // Skips the download process to directly call the loadAssets and loadDartLibrary methods. + private class TestPlayStoreDynamicFeatureManager extends PlayStoreDynamicFeatureManager { + public TestPlayStoreDynamicFeatureManager(Context context, FlutterJNI jni) { + super(context, jni); + } + + @Override + public void downloadDynamicFeature(int loadingUnitId, String moduleName) { + loadAssets(loadingUnitId, moduleName); + loadDartLibrary(loadingUnitId, moduleName); + } + } + + TestFlutterJNI jni; + PlayStoreDynamicFeatureManager playStoreManager; + + @Test + public void downloadCallsJNIFunctions() { + jni = new TestFlutterJNI(); + Context context = mock(Context.class); + playStoreManager = new PlayStoreDynamicFeatureManager(context, jni); + jni.setDynamicFeatureManager(playStoreManager); + + playStoreManager.downloadDynamicFeature(0, "test"); + assertTrue(jni.loadDartDeferredLibraryCalled); + assertTrue(jni.updateAssetManagerCalled); + assertFalse(jni.dynamicFeatureInstallFailureCalled); + } +} From 5d647338bcb47c90f1a5f9e7281f49d0f67d7e38 Mon Sep 17 00:00:00 2001 From: garyqian Date: Thu, 12 Nov 2020 01:28:14 -0800 Subject: [PATCH 05/29] Formatting Formatting --- .../embedding/engine/FlutterEngine.java | 10 +- .../flutter/embedding/engine/FlutterJNI.java | 47 +++--- .../DynamicFeatureManager.java | 142 ++++++++---------- .../PlayStoreDynamicFeatureManager.java | 10 +- .../PlayStoreDynamicFeatureManagerTest.java | 8 +- 5 files changed, 96 insertions(+), 121 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 484f777841090..a9f652768769a 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -262,8 +262,8 @@ public FlutterEngine( } /** - * Same as {@link #FlutterEngine(Context, FlutterLoader, FlutterJNI, String[], boolean, boolean)}, plus the - * ability to control {@code waitForRestorationData}. + * Same as {@link #FlutterEngine(Context, FlutterLoader, FlutterJNI, String[], boolean, boolean)}, + * plus the ability to control {@code waitForRestorationData}. */ public FlutterEngine( @NonNull Context context, @@ -329,8 +329,10 @@ public FlutterEngine( flutterJNI.setPlatformViewsController(platformViewsController); flutterJNI.setLocalizationPlugin(localizationPlugin); - this.dynamicFeatureManager = dynamicFeatureManager != null ? - dynamicFeatureManager : new PlayStoreDynamicFeatureManager(context, flutterJNI); + this.dynamicFeatureManager = + dynamicFeatureManager != null + ? dynamicFeatureManager + : new PlayStoreDynamicFeatureManager(context, flutterJNI); flutterJNI.setDynamicFeatureManager(dynamicFeatureManager); attachToJni(); diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 3c25915433a10..1f3edb1a965dc 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -1008,8 +1008,7 @@ public void removeDynamicFeatureManager() { * loading of the dart library and any assets. * * @param loadingUnitId The loadingUnitId is assigned during compile time by gen_snapshot and is - * automatically retrieved when loadLibrary() is called on a dart deferred - * library. + * automatically retrieved when loadLibrary() is called on a dart deferred library. */ @SuppressWarnings("unused") @UiThread @@ -1027,38 +1026,31 @@ public void RequestDartDeferredLibrary(int loadingUnitId) { * triggered the install/load process. * * @param loadingUnitId The loadingUnitId is assigned during compile time by gen_snapshot and is - * automatically retrieved when loadLibrary() is called on a dart deferred - * library. This is used to identify which dart deferred library the resolved - * correspond to. - * - * @param searchPaths An array of paths in which to look for valid dart shared libraries. This - * supports paths within zipped apks as long as the apks are not compressed - * using the `path/to/apk.apk!path/inside/apk/lib.so` format. Paths will be - * tried first to last and ends when a library is sucessfully found. When the - * found library is invalid, no additional paths will be attempted. + * automatically retrieved when loadLibrary() is called on a dart deferred library. This is + * used to identify which dart deferred library the resolved correspond to. + * @param searchPaths An array of paths in which to look for valid dart shared libraries. This + * supports paths within zipped apks as long as the apks are not compressed using the + * `path/to/apk.apk!path/inside/apk/lib.so` format. Paths will be tried first to last and ends + * when a library is sucessfully found. When the found library is invalid, no additional paths + * will be attempted. */ @UiThread - public void loadDartDeferredLibrary( - int loadingUnitId, - @NonNull String[] searchPaths) { + public void loadDartDeferredLibrary(int loadingUnitId, @NonNull String[] searchPaths) { ensureRunningOnMainThread(); ensureAttachedToNative(); nativeLoadDartDeferredLibrary(nativePlatformViewId, loadingUnitId, searchPaths); } private native void nativeLoadDartDeferredLibrary( - long nativePlatformViewId, - int loadingUnitId, - @NonNull String[] searchPaths); + long nativePlatformViewId, int loadingUnitId, @NonNull String[] searchPaths); /** * Specifies a new AssetManager that has access to the dynamic feature's assets in addition to the * base module's assets. * - * @param assetManager An android AssetManager that is able to access the newly downloaded assets. - * - * @param assetBundlePath The subdirectory that the flutter assets are stored in. The typical value - * is `flutter_assets`. + * @param assetManager An android AssetManager that is able to access the newly downloaded assets. + * @param assetBundlePath The subdirectory that the flutter assets are stored in. The typical + * value is `flutter_assets`. */ @UiThread public void updateAssetManager( @@ -1078,15 +1070,14 @@ private native void nativeUpdateAssetManager( * feature module and loading a dart deferred library, which is typically done by * DynamicFeatureManager. * - *

This will inform dart that the future returned by loadLibrary() should complete with an error. + *

This will inform dart that the future returned by loadLibrary() should complete with an + * error. * * @param loadingUnitId The loadingUnitId that corresponds to the dart deferred library that - * failed to install. - * - * @param error The error message to display. - * - * @param isTransient When isTransient is false, new attempts to install will automatically result in - * same error in dart before the request is passed to Android. + * failed to install. + * @param error The error message to display. + * @param isTransient When isTransient is false, new attempts to install will automatically result + * in same error in dart before the request is passed to Android. */ @SuppressWarnings("unused") @UiThread 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 3f3f1b7936b8f..37bb35362eaf0 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java @@ -8,8 +8,8 @@ /** * Basic interface that handles downloading and loading of dynamic features. * - *

Flutter dynamic feature support is still in early developer preview and should - * not be used in production apps yet. + *

Flutter dynamic feature support is still in early developer preview and should not be used in + * production apps yet. * *

The Flutter default implementation is PlayStoreDynamicFeatureManager. * @@ -17,66 +17,59 @@ * loading dart deferred libraries. A typical code-flow begins with a dart call to loadLibrary() on * deferred imported library. See https://dart.dev/guides/language/language-tour#deferred-loading * This call retrieves a unique identifier called the loading unit id, which is assigned by - * gen_snapshot during compilation. The loading unit id is passed down through the engine and invokes - * downloadDynamicFeature. Once the feature module is downloaded, loadAssets and loadDartLibrary should - * be invoked. loadDartLibrary should find shared library .so files for the engine to open. loadAssets - * should typically ensure the new assets are available to the engine's asset manager by passing an - * updated android AssetManager to the engine. + * gen_snapshot during compilation. The loading unit id is passed down through the engine and + * invokes downloadDynamicFeature. Once the feature module is downloaded, loadAssets and + * loadDartLibrary should be invoked. loadDartLibrary should find shared library .so files for the + * engine to open. loadAssets should typically ensure the new assets are available to the engine's + * asset manager by passing an updated android AssetManager to the engine. * *

The loadAssets and loadDartLibrary methods are separated out because they may also be called * manually via platform channel messages. A full downloadDynamicFeature implementation should call * these two methods as needed. * - *

A dynamic feature module is uniquely identified by a module name as defined in bundle_config.yaml. - * Each feature module may contain one or more loading units, uniquely identified by the loading unit ID - * and assets. + *

A dynamic feature module is uniquely identified by a module name as defined in + * bundle_config.yaml. Each feature module may contain one or more loading units, uniquely + * identified by the loading unit ID and assets. */ public interface DynamicFeatureManager { /** * Request that the feature module be downloaded and installed. * - *

This method is called by Flutter when loadLibrary() is called on a dart deferred library - * which relies on this method to download the requested module under the hood. This method may - * also be manually invoked via a system channel message. + *

This method begins the download and installation of the specified feature module. For + * example, the Play Store dynamic delivery implementation uses SplitInstallManager to request the + * download of the module. Download is not complete when this method returns. The download process + * should be listened for and upon completion of download, listeners should invoke loadAssets and + * loadDartLibrary to complete the dynamic feature load process. * - *

This method begins the download and installation of the specified feature module. For example, - * the Play Store dynamic delivery implementation uses SplitInstallManager to request the download of - * the module. Download is not complete when this method returns. The download process should be - * listened for and upon completion of download, listeners should invoke loadAssets and loadDartLibrary - * to complete the dynamic feature load process. + *

Both parameters are not always necessary to identify which module to install. Asset-only + * modules do not have an associated loadingUnitId. Instead, an invalid ID like -1 may be passed + * to download only with moduleName. On the other hand, it can be possible to resolve the + * moduleName based on the loadingUnitId. This resolution is done if moduleName is null. At least + * one of loadingUnitId or moduleName must be valid or non-null. * - *

Both parameters are not always necessary to identify which module to install. Asset-only modules - * do not have an associated loadingUnitId. Instead, an invalid ID like -1 may be passed to download - * only with moduleName. On the other hand, it can be possible to resolve the moduleName based - * on the loadingUnitId. This resolution is done if moduleName is null. At least one of - * loadingUnitId or moduleName must be valid or non-null. + *

Flutter will typically call this method in two ways. When invoked as part of a dart + * loadLibrary() call, a valid loadingUnitId is passed in while the moduleName is null. In this + * case, this method is responsible for figuring out what module the loadingUnitId corresponds to. * - *

Flutter will typically call this method in two ways. When invoked as part of a dart loadLibrary() - * call, a valid loadingUnitId is passed in while the moduleName is null. In this case, this method is - * responsible for figuring out what module the loadingUnitId corresponds to. - * - *

When invoked manually as part of loading an assets-only module, loadingUnitId is -1 (invalid) - * and moduleName is supplied. Without a loadingUnitId, this method just downloads the module by name - * and attempts to load assets via loadAssets. + *

When invoked manually as part of loading an assets-only module, loadingUnitId is -1 + * (invalid) and moduleName is supplied. Without a loadingUnitId, this method just downloads the + * module by name and attempts to load assets via loadAssets. * * @param loadingUnitId The unique identifier associated with a dart deferred library. This id is - * assigned by the compiler and can be seen for reference in bundle_config.yaml. - * This ID is primarily used in loadDartLibrary to indicate to dart which - * dart library is being loaded. Loading unit ids range from 0 to the number - * existing loading units. Passing a negative loading unit id indicates - * that no dart deferred library should be loaded after download completes. - * This is the case when the dynamic feature module is an assets-only module. - * If a negative loadingUnitId is passed, then moduleName must not be null. - * Passing a loadingUnitId larger than the highest valid loading unit's id will - * cause the dart loadLibrary() to complete with a failure. - * - * @param moduleName The dynamic feature module name as defined in bundle_config.yaml. This may - * be null if the dynamic feature to be loaded is associated with a - * loading unit/deferred dart library. In this case, it is this method's - * responsibility to map the loadingUnitId to its corresponding moduleName. - * When loading asset-only or other dynamic features without an associated - * dart deferred library, loading unit id should a negative value and moduleName - * must be non-null. + * assigned by the compiler and can be seen for reference in bundle_config.yaml. This ID is + * primarily used in loadDartLibrary to indicate to dart which dart library is being loaded. + * Loading unit ids range from 0 to the number existing loading units. Passing a negative + * loading unit id indicates that no dart deferred library should be loaded after download + * completes. This is the case when the dynamic feature module is an assets-only module. If a + * negative loadingUnitId is passed, then moduleName must not be null. Passing a loadingUnitId + * larger than the highest valid loading unit's id will cause the dart loadLibrary() to + * complete with a failure. + * @param moduleName The dynamic feature module name as defined in bundle_config.yaml. This may be + * null if the dynamic feature to be loaded is associated with a loading unit/deferred dart + * library. In this case, it is this method's responsibility to map the loadingUnitId to its + * corresponding moduleName. When loading asset-only or other dynamic features without an + * associated dart deferred library, loading unit id should a negative value and moduleName + * must be non-null. */ public abstract void downloadDynamicFeature(int loadingUnitId, String moduleName); @@ -88,46 +81,41 @@ public interface DynamicFeatureManager { * *

Depending on the structure of the feature module, there may or may not be assets to extract. * - *

If using the Play Store dynamic feature delivery, refresh the context via: - * {@code context.createPackageContext(context.getPackageName(), 0);} - * This returns a new context, from which an updated asset manager may be obtained and passed to - * updateAssetManager in FlutterJNI. This process does not require loadingUnitId or moduleName, - * however, the two parameters are still present for custom implementations that store assets - * outside of Android's native system. + *

If using the Play Store dynamic feature delivery, refresh the context via: {@code + * context.createPackageContext(context.getPackageName(), 0);} This returns a new context, from + * which an updated asset manager may be obtained and passed to updateAssetManager in FlutterJNI. + * This process does not require loadingUnitId or moduleName, however, the two parameters are + * still present for custom implementations that store assets outside of Android's native system. * * @param loadingUnitId The unique identifier associated with a dart deferred library. - * - * @param moduleName The dynamic feature module name as defined in bundle_config.yaml. + * @param moduleName The dynamic feature module name as defined in bundle_config.yaml. */ public abstract void loadAssets(int loadingUnitId, String moduleName); /** * Load the .so shared library file into the Dart VM. * - *

When a feature module download is triggered via a dart loadLibrary() call on a deferred library - * and the download completes, this method should be called to find the path .so library file and - * passed to FlutterJNI.loadDartDeferredLibrary to be dlopen-ed. + *

When a feature module download is triggered via a dart loadLibrary() call on a deferred + * library and the download completes, this method should be called to find the path .so library + * file and passed to FlutterJNI.loadDartDeferredLibrary to be dlopen-ed. * - *

Specifically, APKs distributed by android's app bundle format may vary by device and API number, - * so FlutterJNI's loadDartDeferredLibrary accepts a list of search paths with can include paths - * within APKs that have not been unpacked using the `path/to/apk.apk!path/inside/apk/lib.so` format. - * Each search path will be attempted in order until a shared library is found. This allows for - * the developer to avoid unpacking the apk zip. + *

Specifically, APKs distributed by android's app bundle format may vary by device and API + * number, so FlutterJNI's loadDartDeferredLibrary accepts a list of search paths with can include + * paths within APKs that have not been unpacked using the + * `path/to/apk.apk!path/inside/apk/lib.so` format. Each search path will be attempted in order + * until a shared library is found. This allows for the developer to avoid unpacking the apk zip. * *

Upon successful load of the dart library, the dart future from the originating loadLibary() * call completes and developers are able to use symbols and assets from the feature module. * * @param loadingUnitId The unique identifier associated with a dart deferred library. This id is - * assigned by the compiler and can be seen for reference in bundle_config.yaml. - * This ID is primarily used in loadDartLibrary to indicate to dart which - * dart library is being loaded. Loading unit ids range from 0 to the number - * existing loading units. Negative loading unit ids are considered - * invalid and this method will result in a no-op. - * - * @param moduleName The dynamic feature module name as defined in bundle_config.yaml. If using - * Play Store dynamic feature delivery, this name corresponds to the root name - * on the installed APKs in which to search for the desired shared library .so - * file. + * assigned by the compiler and can be seen for reference in bundle_config.yaml. This ID is + * primarily used in loadDartLibrary to indicate to dart which dart library is being loaded. + * Loading unit ids range from 0 to the number existing loading units. Negative loading unit + * ids are considered invalid and this method will result in a no-op. + * @param moduleName The dynamic feature module name as defined in bundle_config.yaml. If using + * Play Store dynamic feature delivery, this name corresponds to the root name on the + * installed APKs in which to search for the desired shared library .so file. */ public abstract void loadDartLibrary(int loadingUnitId, String moduleName); @@ -135,10 +123,10 @@ public interface DynamicFeatureManager { * Uninstall the specified feature module. * *

Both parameters are not always necessary to identify which module to uninstall. Asset-only - * modules do not have an associated loadingUnitId. Instead, an invalid ID like -1 may be passed to - * download only with moduleName. On the other hand, it can be possible to resolve the moduleName based - * on the loadingUnitId. This resolution is done if moduleName is null. At least one of - * loadingUnitId or moduleName must be valid or non-null. + * modules do not have an associated loadingUnitId. Instead, an invalid ID like -1 may be passed + * to download only with moduleName. On the other hand, it can be possible to resolve the + * moduleName based on the loadingUnitId. This resolution is done if moduleName is null. At least + * one of loadingUnitId or moduleName must be valid or non-null. */ public abstract void uninstallFeature(int loadingUnitId, String moduleName); } 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 3ef5b9bc97d48..6c49637cf1c59 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java @@ -27,7 +27,6 @@ import java.util.Map; import java.util.Queue; - /** * Flutter default implementation of DynamicFeatureManager that downloads dynamic feature modules * from the Google Play store. @@ -174,13 +173,13 @@ private String loadingUnitIdToModuleName(int loadingUnitId) { int moduleNameIdentifier = context .getResources() - .getIdentifier( - "loadingUnit" + loadingUnitId, "string", context.getPackageName()); + .getIdentifier("loadingUnit" + loadingUnitId, "string", context.getPackageName()); return context.getResources().getString(moduleNameIdentifier); } public void downloadDynamicFeature(int loadingUnitId, String moduleName) { - String resolvedModuleName = moduleName == null ? moduleName : loadingUnitIdToModuleName(loadingUnitId); + String resolvedModuleName = + moduleName == null ? moduleName : loadingUnitIdToModuleName(loadingUnitId); if (resolvedModuleName == null) { Log.d(TAG, "Dynamic feature module name was null."); return; @@ -301,8 +300,7 @@ public void loadDartLibrary(int loadingUnitId, String moduleName) { searchPaths.add(soPath); flutterJNI.loadDartDeferredLibrary( - loadingUnitId, - searchPaths.toArray(new String[apkPaths.size()])); + loadingUnitId, searchPaths.toArray(new String[apkPaths.size()])); } public void uninstallFeature(int loadingUnitId, String moduleName) { diff --git a/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java b/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java index a32d8496a8f18..a1e69ed88cd49 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java @@ -9,14 +9,12 @@ import static org.mockito.Mockito.mock; import android.content.Context; -import androidx.annotation.NonNull; import android.content.res.AssetManager; -import io.flutter.Log; +import androidx.annotation.NonNull; import io.flutter.embedding.engine.FlutterJNI; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; -import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; @Config(manifest = Config.NONE) @@ -30,9 +28,7 @@ private class TestFlutterJNI extends FlutterJNI { public TestFlutterJNI() {} @Override - public void loadDartDeferredLibrary( - int loadingUnitId, - @NonNull String[] searchPaths) { + public void loadDartDeferredLibrary(int loadingUnitId, @NonNull String[] searchPaths) { loadDartDeferredLibraryCalled = true; } From 76071fb5e448b70c0cd5a5fe526a20d489e714b0 Mon Sep 17 00:00:00 2001 From: garyqian Date: Thu, 12 Nov 2020 02:43:00 -0800 Subject: [PATCH 06/29] Fix test --- .../dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java b/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java index a1e69ed88cd49..9b14e61022059 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java @@ -65,10 +65,10 @@ public void downloadDynamicFeature(int loadingUnitId, String moduleName) { public void downloadCallsJNIFunctions() { jni = new TestFlutterJNI(); Context context = mock(Context.class); - playStoreManager = new PlayStoreDynamicFeatureManager(context, jni); + playStoreManager = new TestPlayStoreDynamicFeatureManager(context, jni); jni.setDynamicFeatureManager(playStoreManager); - playStoreManager.downloadDynamicFeature(0, "test"); + playStoreManager.downloadDynamicFeature(0, "TestModuleName"); assertTrue(jni.loadDartDeferredLibraryCalled); assertTrue(jni.updateAssetManagerCalled); assertFalse(jni.dynamicFeatureInstallFailureCalled); From 396e40b08a4a10ab7c30c8f77bc160afcd392a49 Mon Sep 17 00:00:00 2001 From: garyqian Date: Thu, 12 Nov 2020 10:27:18 -0800 Subject: [PATCH 07/29] Fix tests --- .../dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java b/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java index 9b14e61022059..b2d6be89e3a30 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java @@ -59,7 +59,7 @@ public void downloadDynamicFeature(int loadingUnitId, String moduleName) { } TestFlutterJNI jni; - PlayStoreDynamicFeatureManager playStoreManager; + TestPlayStoreDynamicFeatureManager playStoreManager; @Test public void downloadCallsJNIFunctions() { From 44e9750c2c8ad280ec979e0f8ff99a5a7347f161 Mon Sep 17 00:00:00 2001 From: garyqian Date: Thu, 12 Nov 2020 16:24:03 -0800 Subject: [PATCH 08/29] Working test, more tests --- .../PlayStoreDynamicFeatureManagerTest.java | 41 +++++++++++++------ 1 file changed, 29 insertions(+), 12 deletions(-) diff --git a/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java b/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java index b2d6be89e3a30..64eacac0351f9 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java @@ -4,14 +4,18 @@ package io.flutter.embedding.engine.dynamicfeatures; -import static junit.framework.TestCase.assertFalse; import static junit.framework.TestCase.assertTrue; +import static org.mockito.Mockito.any; +import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import android.content.Context; +import android.content.pm.PackageManager.NameNotFoundException; import android.content.res.AssetManager; import androidx.annotation.NonNull; import io.flutter.embedding.engine.FlutterJNI; +import java.io.File; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; @@ -21,27 +25,32 @@ @RunWith(RobolectricTestRunner.class) public class PlayStoreDynamicFeatureManagerTest { private class TestFlutterJNI extends FlutterJNI { - public boolean loadDartDeferredLibraryCalled = false; - public boolean updateAssetManagerCalled = false; - public boolean dynamicFeatureInstallFailureCalled = false; + public int loadDartDeferredLibraryCalled = 0; + public int updateAssetManagerCalled = 0; + public int dynamicFeatureInstallFailureCalled = 0; + public String[] searchPaths; + public int loadingUnitId; public TestFlutterJNI() {} @Override public void loadDartDeferredLibrary(int loadingUnitId, @NonNull String[] searchPaths) { - loadDartDeferredLibraryCalled = true; + loadDartDeferredLibraryCalled++; + this.searchPaths = searchPaths; + this.loadingUnitId = loadingUnitId; } @Override public void updateAssetManager( @NonNull AssetManager assetManager, @NonNull String assetBundlePath) { - updateAssetManagerCalled = true; + updateAssetManagerCalled++; + this.loadingUnitId = loadingUnitId; } @Override public void dynamicFeatureInstallFailure( int loadingUnitId, @NonNull String error, boolean isTransient) { - dynamicFeatureInstallFailureCalled = true; + dynamicFeatureInstallFailureCalled++; } } @@ -62,15 +71,23 @@ public void downloadDynamicFeature(int loadingUnitId, String moduleName) { TestPlayStoreDynamicFeatureManager playStoreManager; @Test - public void downloadCallsJNIFunctions() { + public void downloadCallsJNIFunctions() throws NameNotFoundException { jni = new TestFlutterJNI(); Context context = mock(Context.class); + when(context.createPackageContext(any(), anyInt())).thenReturn(context); + when(context.getAssets()).thenReturn(null); + String soTestPath = "test/path/app.so-123.part.so"; + when(context.getFilesDir()).thenReturn(new File(soTestPath)); playStoreManager = new TestPlayStoreDynamicFeatureManager(context, jni); jni.setDynamicFeatureManager(playStoreManager); - playStoreManager.downloadDynamicFeature(0, "TestModuleName"); - assertTrue(jni.loadDartDeferredLibraryCalled); - assertTrue(jni.updateAssetManagerCalled); - assertFalse(jni.dynamicFeatureInstallFailureCalled); + playStoreManager.downloadDynamicFeature(123, "TestModuleName"); + assertTrue(jni.loadDartDeferredLibraryCalled == 1); + assertTrue(jni.updateAssetManagerCalled == 1); + assertTrue(jni.dynamicFeatureInstallFailureCalled == 0); + + assertTrue(jni.searchPaths[0] == soTestPath); + assertTrue(jni.searchPaths.length == 1); + assertTrue(jni.loadingUnitId == 123); } } From 32982a79ef4731b6dcd13f614d8945ac4738013a Mon Sep 17 00:00:00 2001 From: garyqian Date: Thu, 12 Nov 2020 18:55:15 -0800 Subject: [PATCH 09/29] Add platformview test, adjust java tests Rtemove PlayStoreDynamicFeatureManagerInstantiation, another test Format --- .../embedding/engine/FlutterEngine.java | 6 +-- .../PlayStoreDynamicFeatureManager.java | 8 ++-- ...latform_view_android_delegate_unittests.cc | 9 +++++ .../PlayStoreDynamicFeatureManagerTest.java | 38 ++++++++++++++++--- 4 files changed, 48 insertions(+), 13 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index a9f652768769a..52e89873f4134 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -329,10 +329,8 @@ public FlutterEngine( flutterJNI.setPlatformViewsController(platformViewsController); flutterJNI.setLocalizationPlugin(localizationPlugin); - this.dynamicFeatureManager = - dynamicFeatureManager != null - ? dynamicFeatureManager - : new PlayStoreDynamicFeatureManager(context, flutterJNI); + this.dynamicFeatureManager = dynamicFeatureManager; + // TODO(garyq): Inject PlayStoreDynamicFeatureManager(context, flutterJNI); flutterJNI.setDynamicFeatureManager(dynamicFeatureManager); attachToJni(); 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 6c49637cf1c59..27ace8d67bcf6 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java @@ -272,7 +272,7 @@ public void loadDartLibrary(int loadingUnitId, String moduleName) { // 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 = ""; + List soPaths = new ArrayList(); Queue searchFiles = new LinkedList(); searchFiles.add(context.getFilesDir()); while (!searchFiles.isEmpty()) { @@ -289,7 +289,7 @@ public void loadDartLibrary(int loadingUnitId, String moduleName) { continue; } if (name.equals(aotSharedLibraryName)) { - soPath = file.getAbsolutePath(); + soPaths.add(file.getAbsolutePath()); } } @@ -297,7 +297,9 @@ public void loadDartLibrary(int loadingUnitId, String moduleName) { for (String path : apkPaths) { searchPaths.add(path + "!lib/" + abi + "/" + aotSharedLibraryName); } - searchPaths.add(soPath); + for (String path : soPaths) { + searchPaths.add(path); + } flutterJNI.loadDartDeferredLibrary( loadingUnitId, searchPaths.toArray(new String[apkPaths.size()])); diff --git a/shell/platform/android/platform_view_android_delegate/platform_view_android_delegate_unittests.cc b/shell/platform/android/platform_view_android_delegate/platform_view_android_delegate_unittests.cc index 9e1095ea6dea1..006d9430cce7d 100644 --- a/shell/platform/android/platform_view_android_delegate/platform_view_android_delegate_unittests.cc +++ b/shell/platform/android/platform_view_android_delegate/platform_view_android_delegate_unittests.cc @@ -93,5 +93,14 @@ TEST(PlatformViewShell, delegate->UpdateSemantics(update, actions); } +TEST(PlatformViewShell, RequestDartDeferredLibraryCallsJNIDownload) { + auto jni_mock = std::make_shared(); + auto delegate = std::make_unique(jni_mock); + + int loadingUnitId = 123; + EXPECT_CALL(*jni_mock, RequestDartDeferredLibrary(loadingUnitId)); + delegate->RequestDartDeferredLibrary(loadingUnitId); +} + } // namespace testing } // namespace flutter diff --git a/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java b/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java index 64eacac0351f9..a6827eed28469 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java @@ -4,6 +4,7 @@ package io.flutter.embedding.engine.dynamicfeatures; +import static junit.framework.TestCase.assertEquals; import static junit.framework.TestCase.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; @@ -81,13 +82,38 @@ public void downloadCallsJNIFunctions() throws NameNotFoundException { playStoreManager = new TestPlayStoreDynamicFeatureManager(context, jni); jni.setDynamicFeatureManager(playStoreManager); + assertEquals(jni.loadingUnitId, 0); + + playStoreManager.downloadDynamicFeature(123, "TestModuleName"); + assertEquals(jni.loadDartDeferredLibraryCalled, 1); + assertEquals(jni.updateAssetManagerCalled, 1); + assertEquals(jni.dynamicFeatureInstallFailureCalled, 0); + + assertTrue(jni.searchPaths[0].endsWith(soTestPath)); + assertEquals(jni.searchPaths.length, 1); + assertEquals(jni.loadingUnitId, 123); + } + + @Test + public void searchPathsAddsApks() throws NameNotFoundException { + jni = new TestFlutterJNI(); + Context context = mock(Context.class); + when(context.createPackageContext(any(), anyInt())).thenReturn(context); + when(context.getAssets()).thenReturn(null); + String apkTestPath = "test/path/TestModuleName_armeabi_v7a.apk"; + when(context.getFilesDir()).thenReturn(new File(apkTestPath)); + playStoreManager = new TestPlayStoreDynamicFeatureManager(context, jni); + jni.setDynamicFeatureManager(playStoreManager); + + assertEquals(jni.loadingUnitId, 0); + playStoreManager.downloadDynamicFeature(123, "TestModuleName"); - assertTrue(jni.loadDartDeferredLibraryCalled == 1); - assertTrue(jni.updateAssetManagerCalled == 1); - assertTrue(jni.dynamicFeatureInstallFailureCalled == 0); + assertEquals(jni.loadDartDeferredLibraryCalled, 1); + assertEquals(jni.updateAssetManagerCalled, 1); + assertEquals(jni.dynamicFeatureInstallFailureCalled, 0); - assertTrue(jni.searchPaths[0] == soTestPath); - assertTrue(jni.searchPaths.length == 1); - assertTrue(jni.loadingUnitId == 123); + assertTrue(jni.searchPaths[0].endsWith(apkTestPath + "!lib/armeabi-v7a/app.so-123.part.so")); + assertEquals(jni.searchPaths.length, 1); + assertEquals(jni.loadingUnitId, 123); } } From 4df3e33ce49a920faad3622f0ddf2b922c2432c8 Mon Sep 17 00:00:00 2001 From: garyqian Date: Thu, 12 Nov 2020 23:23:51 -0800 Subject: [PATCH 10/29] Add another test --- .../embedding/engine/FlutterEngine.java | 5 +++- .../PlayStoreDynamicFeatureManagerTest.java | 30 ++++++++++++++++--- 2 files changed, 30 insertions(+), 5 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 52e89873f4134..d25f2d0b58aa5 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -329,7 +329,10 @@ public FlutterEngine( flutterJNI.setPlatformViewsController(platformViewsController); flutterJNI.setLocalizationPlugin(localizationPlugin); - this.dynamicFeatureManager = dynamicFeatureManager; + this.dynamicFeatureManager = + dynamicFeatureManager != null + ? dynamicFeatureManager + : new PlayStoreDynamicFeatureManager(context, flutterJNI); // TODO(garyq): Inject PlayStoreDynamicFeatureManager(context, flutterJNI); flutterJNI.setDynamicFeatureManager(dynamicFeatureManager); diff --git a/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java b/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java index a6827eed28469..9c335904951a6 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java @@ -73,13 +73,13 @@ public void downloadDynamicFeature(int loadingUnitId, String moduleName) { @Test public void downloadCallsJNIFunctions() throws NameNotFoundException { - jni = new TestFlutterJNI(); + TestFlutterJNI jni = new TestFlutterJNI(); Context context = mock(Context.class); when(context.createPackageContext(any(), anyInt())).thenReturn(context); when(context.getAssets()).thenReturn(null); String soTestPath = "test/path/app.so-123.part.so"; when(context.getFilesDir()).thenReturn(new File(soTestPath)); - playStoreManager = new TestPlayStoreDynamicFeatureManager(context, jni); + TestPlayStoreDynamicFeatureManager playStoreManager = new TestPlayStoreDynamicFeatureManager(context, jni); jni.setDynamicFeatureManager(playStoreManager); assertEquals(jni.loadingUnitId, 0); @@ -96,13 +96,13 @@ public void downloadCallsJNIFunctions() throws NameNotFoundException { @Test public void searchPathsAddsApks() throws NameNotFoundException { - jni = new TestFlutterJNI(); + TestFlutterJNI jni = new TestFlutterJNI(); Context context = mock(Context.class); when(context.createPackageContext(any(), anyInt())).thenReturn(context); when(context.getAssets()).thenReturn(null); String apkTestPath = "test/path/TestModuleName_armeabi_v7a.apk"; when(context.getFilesDir()).thenReturn(new File(apkTestPath)); - playStoreManager = new TestPlayStoreDynamicFeatureManager(context, jni); + TestPlayStoreDynamicFeatureManager playStoreManager = new TestPlayStoreDynamicFeatureManager(context, jni); jni.setDynamicFeatureManager(playStoreManager); assertEquals(jni.loadingUnitId, 0); @@ -116,4 +116,26 @@ public void searchPathsAddsApks() throws NameNotFoundException { assertEquals(jni.searchPaths.length, 1); assertEquals(jni.loadingUnitId, 123); } + + @Test + public void invalidSearchPathsAreIgnored() throws NameNotFoundException { + TestFlutterJNI jni = new TestFlutterJNI(); + Context context = mock(Context.class); + when(context.createPackageContext(any(), anyInt())).thenReturn(context); + when(context.getAssets()).thenReturn(null); + String apkTestPath = "test/path/invalidpath.apk"; + when(context.getFilesDir()).thenReturn(new File(apkTestPath)); + TestPlayStoreDynamicFeatureManager playStoreManager = new TestPlayStoreDynamicFeatureManager(context, jni); + jni.setDynamicFeatureManager(playStoreManager); + + assertEquals(jni.loadingUnitId, 0); + + playStoreManager.downloadDynamicFeature(123, "TestModuleName"); + assertEquals(jni.loadDartDeferredLibraryCalled, 1); + assertEquals(jni.updateAssetManagerCalled, 1); + assertEquals(jni.dynamicFeatureInstallFailureCalled, 0); + + assertEquals(jni.searchPaths.length, 0); + assertEquals(jni.loadingUnitId, 123); + } } From de074f8d4d0b7a53873c2380d3d38d772384375e Mon Sep 17 00:00:00 2001 From: garyqian Date: Fri, 13 Nov 2020 12:22:27 -0800 Subject: [PATCH 11/29] CLeanup --- .../PlayStoreDynamicFeatureManagerTest.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java b/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java index 9c335904951a6..a892ebd2d35da 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java @@ -68,9 +68,6 @@ public void downloadDynamicFeature(int loadingUnitId, String moduleName) { } } - TestFlutterJNI jni; - TestPlayStoreDynamicFeatureManager playStoreManager; - @Test public void downloadCallsJNIFunctions() throws NameNotFoundException { TestFlutterJNI jni = new TestFlutterJNI(); @@ -79,7 +76,8 @@ public void downloadCallsJNIFunctions() throws NameNotFoundException { when(context.getAssets()).thenReturn(null); String soTestPath = "test/path/app.so-123.part.so"; when(context.getFilesDir()).thenReturn(new File(soTestPath)); - TestPlayStoreDynamicFeatureManager playStoreManager = new TestPlayStoreDynamicFeatureManager(context, jni); + TestPlayStoreDynamicFeatureManager playStoreManager = + new TestPlayStoreDynamicFeatureManager(context, jni); jni.setDynamicFeatureManager(playStoreManager); assertEquals(jni.loadingUnitId, 0); @@ -102,7 +100,8 @@ public void searchPathsAddsApks() throws NameNotFoundException { when(context.getAssets()).thenReturn(null); String apkTestPath = "test/path/TestModuleName_armeabi_v7a.apk"; when(context.getFilesDir()).thenReturn(new File(apkTestPath)); - TestPlayStoreDynamicFeatureManager playStoreManager = new TestPlayStoreDynamicFeatureManager(context, jni); + TestPlayStoreDynamicFeatureManager playStoreManager = + new TestPlayStoreDynamicFeatureManager(context, jni); jni.setDynamicFeatureManager(playStoreManager); assertEquals(jni.loadingUnitId, 0); @@ -125,7 +124,8 @@ public void invalidSearchPathsAreIgnored() throws NameNotFoundException { when(context.getAssets()).thenReturn(null); String apkTestPath = "test/path/invalidpath.apk"; when(context.getFilesDir()).thenReturn(new File(apkTestPath)); - TestPlayStoreDynamicFeatureManager playStoreManager = new TestPlayStoreDynamicFeatureManager(context, jni); + TestPlayStoreDynamicFeatureManager playStoreManager = + new TestPlayStoreDynamicFeatureManager(context, jni); jni.setDynamicFeatureManager(playStoreManager); assertEquals(jni.loadingUnitId, 0); From feb4c99c3f3dc388b515d6889c684af2cf2e7044 Mon Sep 17 00:00:00 2001 From: garyqian Date: Mon, 16 Nov 2020 15:28:12 -0800 Subject: [PATCH 12/29] Tests avoid splitManager global state bug --- .../embedding/engine/FlutterEngine.java | 6 +--- ...latform_view_android_delegate_unittests.cc | 9 ----- .../PlayStoreDynamicFeatureManagerTest.java | 36 ++++++++++--------- 3 files changed, 21 insertions(+), 30 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index d25f2d0b58aa5..e8b0b57ed3127 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -13,7 +13,6 @@ 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; @@ -329,10 +328,7 @@ public FlutterEngine( flutterJNI.setPlatformViewsController(platformViewsController); flutterJNI.setLocalizationPlugin(localizationPlugin); - this.dynamicFeatureManager = - dynamicFeatureManager != null - ? dynamicFeatureManager - : new PlayStoreDynamicFeatureManager(context, flutterJNI); + this.dynamicFeatureManager = dynamicFeatureManager; // TODO(garyq): Inject PlayStoreDynamicFeatureManager(context, flutterJNI); flutterJNI.setDynamicFeatureManager(dynamicFeatureManager); diff --git a/shell/platform/android/platform_view_android_delegate/platform_view_android_delegate_unittests.cc b/shell/platform/android/platform_view_android_delegate/platform_view_android_delegate_unittests.cc index 006d9430cce7d..9e1095ea6dea1 100644 --- a/shell/platform/android/platform_view_android_delegate/platform_view_android_delegate_unittests.cc +++ b/shell/platform/android/platform_view_android_delegate/platform_view_android_delegate_unittests.cc @@ -93,14 +93,5 @@ TEST(PlatformViewShell, delegate->UpdateSemantics(update, actions); } -TEST(PlatformViewShell, RequestDartDeferredLibraryCallsJNIDownload) { - auto jni_mock = std::make_shared(); - auto delegate = std::make_unique(jni_mock); - - int loadingUnitId = 123; - EXPECT_CALL(*jni_mock, RequestDartDeferredLibrary(loadingUnitId)); - delegate->RequestDartDeferredLibrary(loadingUnitId); -} - } // namespace testing } // namespace flutter diff --git a/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java b/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java index a892ebd2d35da..87eca314e3a14 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java @@ -8,7 +8,9 @@ import static junit.framework.TestCase.assertTrue; import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; +import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.spy; import static org.mockito.Mockito.when; import android.content.Context; @@ -20,6 +22,7 @@ import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.RobolectricTestRunner; +import org.robolectric.RuntimeEnvironment; import org.robolectric.annotation.Config; @Config(manifest = Config.NONE) @@ -59,10 +62,12 @@ public void dynamicFeatureInstallFailure( private class TestPlayStoreDynamicFeatureManager extends PlayStoreDynamicFeatureManager { public TestPlayStoreDynamicFeatureManager(Context context, FlutterJNI jni) { super(context, jni); + jni.setDynamicFeatureManager(this); } @Override public void downloadDynamicFeature(int loadingUnitId, String moduleName) { + // Override this to skip the online SplitInstallManager portion. loadAssets(loadingUnitId, moduleName); loadDartLibrary(loadingUnitId, moduleName); } @@ -71,15 +76,14 @@ public void downloadDynamicFeature(int loadingUnitId, String moduleName) { @Test public void downloadCallsJNIFunctions() throws NameNotFoundException { TestFlutterJNI jni = new TestFlutterJNI(); - Context context = mock(Context.class); - when(context.createPackageContext(any(), anyInt())).thenReturn(context); - when(context.getAssets()).thenReturn(null); + Context spyContext = spy(RuntimeEnvironment.systemContext); + doReturn(spyContext).when(spyContext).createPackageContext(any(), anyInt()); + doReturn(null).when(spyContext).getAssets(); String soTestPath = "test/path/app.so-123.part.so"; - when(context.getFilesDir()).thenReturn(new File(soTestPath)); + doReturn(new File(soTestPath)).when(spyContext).getFilesDir(); TestPlayStoreDynamicFeatureManager playStoreManager = - new TestPlayStoreDynamicFeatureManager(context, jni); + new TestPlayStoreDynamicFeatureManager(spyContext, jni); jni.setDynamicFeatureManager(playStoreManager); - assertEquals(jni.loadingUnitId, 0); playStoreManager.downloadDynamicFeature(123, "TestModuleName"); @@ -95,13 +99,13 @@ public void downloadCallsJNIFunctions() throws NameNotFoundException { @Test public void searchPathsAddsApks() throws NameNotFoundException { TestFlutterJNI jni = new TestFlutterJNI(); - Context context = mock(Context.class); - when(context.createPackageContext(any(), anyInt())).thenReturn(context); - when(context.getAssets()).thenReturn(null); + Context spyContext = spy(RuntimeEnvironment.systemContext); + doReturn(spyContext).when(spyContext).createPackageContext(any(), anyInt()); + doReturn(null).when(spyContext).getAssets(); String apkTestPath = "test/path/TestModuleName_armeabi_v7a.apk"; - when(context.getFilesDir()).thenReturn(new File(apkTestPath)); + doReturn(new File(apkTestPath)).when(spyContext).getFilesDir(); TestPlayStoreDynamicFeatureManager playStoreManager = - new TestPlayStoreDynamicFeatureManager(context, jni); + new TestPlayStoreDynamicFeatureManager(spyContext, jni); jni.setDynamicFeatureManager(playStoreManager); assertEquals(jni.loadingUnitId, 0); @@ -119,13 +123,13 @@ public void searchPathsAddsApks() throws NameNotFoundException { @Test public void invalidSearchPathsAreIgnored() throws NameNotFoundException { TestFlutterJNI jni = new TestFlutterJNI(); - Context context = mock(Context.class); - when(context.createPackageContext(any(), anyInt())).thenReturn(context); - when(context.getAssets()).thenReturn(null); + Context spyContext = spy(RuntimeEnvironment.systemContext); + doReturn(spyContext).when(spyContext).createPackageContext(any(), anyInt()); + doReturn(null).when(spyContext).getAssets(); String apkTestPath = "test/path/invalidpath.apk"; - when(context.getFilesDir()).thenReturn(new File(apkTestPath)); + doReturn(new File(apkTestPath)).when(spyContext).getFilesDir(); TestPlayStoreDynamicFeatureManager playStoreManager = - new TestPlayStoreDynamicFeatureManager(context, jni); + new TestPlayStoreDynamicFeatureManager(spyContext, jni); jni.setDynamicFeatureManager(playStoreManager); assertEquals(jni.loadingUnitId, 0); From 49a05328562726534d82c5ee92573577dd4d5b60 Mon Sep 17 00:00:00 2001 From: garyqian Date: Mon, 16 Nov 2020 16:04:41 -0800 Subject: [PATCH 13/29] Docs --- shell/common/engine.h | 17 ++++++++++----- shell/common/platform_view.h | 21 ++++++++++++------- .../DynamicFeatureManager.java | 11 ++++++---- .../PlayStoreDynamicFeatureManager.java | 8 +++---- 4 files changed, 35 insertions(+), 22 deletions(-) diff --git a/shell/common/engine.h b/shell/common/engine.h index 6e0e392a5f367..13b4cb56c1491 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -263,8 +263,10 @@ class Engine final : public RuntimeDelegate, //-------------------------------------------------------------------------- /// @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. + /// be loaded. Notifies the engine that the deferred library + /// identified by the specified loading unit id should be + /// downloaded and loaded into the Dart VM via + /// `LoadDartDeferredLibrary` /// /// @param[in] loading_unit_id The unique id of the deferred library's /// loading unit. @@ -781,9 +783,14 @@ class Engine final : public RuntimeDelegate, /// @brief Loads the dart shared library into the dart VM. When the /// dart library is loaded successfully, the dart future /// returned by the originating loadLibrary() call completes. - /// Each shared library is a loading unit, which consists of - /// deferred libraries that can be compiled split from the - /// base dart library by gen_snapshot. + /// + /// The Dart compiler may generate separate shared library .so + /// files called 'loading units' when libraries are imported + /// as deferred. Each of these shared libraries are identified + /// by a unique loading unit id and can be dynamically loaded + /// into the VM by dlopen-ing and resolving the data and + /// instructions symbols. + /// /// /// @param[in] loading_unit_id The unique id of the deferred library's /// loading unit. diff --git a/shell/common/platform_view.h b/shell/common/platform_view.h index 5bb5b2fb16dfd..6050e177ca0fe 100644 --- a/shell/common/platform_view.h +++ b/shell/common/platform_view.h @@ -215,14 +215,17 @@ class PlatformView { /// @brief Loads the dart shared library into the dart VM. When the /// dart library is loaded successfully, the dart future /// returned by the originating loadLibrary() call completes. - /// Each shared library is a loading unit, which consists of - /// deferred libraries that can be compiled split from the - /// base dart library by gen_snapshot. + /// + /// The Dart compiler may generate separate shared library .so + /// files called 'loading units' when libraries are imported + /// as deferred. Each of these shared libraries are identified + /// by a unique loading unit id and can be dynamically loaded + /// into the VM by dlopen-ing and resolving the data and + /// instructions symbols. + /// /// /// @param[in] loading_unit_id The unique id of the deferred library's - /// loading unit. This is the same id as the - /// one passed in by the corresponding - /// RequestDartDeferredLibrary. + /// loading unit. /// /// @param[in] snapshot_data Dart snapshot data of the loading unit's /// shared library. @@ -599,8 +602,10 @@ class PlatformView { //-------------------------------------------------------------------------- /// @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. + /// be loaded. Notifies the engine that the deferred library + /// identified by the specified loading unit id should be + /// downloaded and loaded into the Dart VM via + /// `LoadDartDeferredLibrary` /// /// @param[in] loading_unit_id The unique id of the deferred library's /// loading unit. 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 37bb35362eaf0..b97fecb01cb71 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java @@ -76,10 +76,9 @@ public interface DynamicFeatureManager { /** * Extract and load any assets and resources from the module for use by Flutter. * - *

Assets shoud be loaded before the dart derferred 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. + *

This method should provide a refreshed AssetManager to FlutterJNI.updateAssetManager that + * can access the new assets. If no assets are included as part of the dynamic feature, then + * nothing needs to be done. * *

If using the Play Store dynamic feature delivery, refresh the context via: {@code * context.createPackageContext(context.getPackageName(), 0);} This returns a new context, from @@ -87,6 +86,10 @@ public interface DynamicFeatureManager { * This process does not require loadingUnitId or moduleName, however, the two parameters are * still present for custom implementations that store assets outside of Android's native system. * + *

Assets shoud be loaded before the dart deferred library is loaded, as successful loading of + * the dart loading unit indicates the dynamic feature is fully loaded. Implementations of + * downloadDynamicFeature should invoke this after successful download. + * * @param loadingUnitId The unique identifier associated with a dart deferred library. * @param moduleName The dynamic feature module name as defined in bundle_config.yaml. */ 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 27ace8d67bcf6..0f9b7f44fdf80 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java @@ -234,7 +234,9 @@ public void downloadDynamicFeature(int loadingUnitId, String moduleName) { public void loadAssets(int loadingUnitId, String moduleName) { // Since android dynamic feature asset manager is handled through - // context, neither parameter is used here. + // context, neither parameter is used here. Assets are stored in + // the apk's `assets` directory allowing them to be accessed by + // Android's AssetManager directly. try { context = context.createPackageContext(context.getPackageName(), 0); @@ -308,8 +310,4 @@ public void loadDartLibrary(int loadingUnitId, String moduleName) { public void uninstallFeature(int loadingUnitId, String moduleName) { // TODO(garyq): support uninstalling. } - - void destroy() { - splitInstallManager.unregisterListener(listener); - } } From 20d4e3efccdedbc6b302be56ec6a55551fa7d08a Mon Sep 17 00:00:00 2001 From: garyqian Date: Mon, 16 Nov 2020 16:12:25 -0800 Subject: [PATCH 14/29] Remove extra imports --- .../dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java b/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java index 87eca314e3a14..18dbd81155ad0 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java @@ -9,9 +9,7 @@ import static org.mockito.Mockito.any; import static org.mockito.Mockito.anyInt; import static org.mockito.Mockito.doReturn; -import static org.mockito.Mockito.mock; import static org.mockito.Mockito.spy; -import static org.mockito.Mockito.when; import android.content.Context; import android.content.pm.PackageManager.NameNotFoundException; From b0002e41721134c07ce1488f48ba38698671f350 Mon Sep 17 00:00:00 2001 From: garyqian Date: Mon, 16 Nov 2020 21:12:35 -0800 Subject: [PATCH 15/29] Fix ios and fuchsia tests Fit x tests --- .../ios/framework/Source/FlutterEnginePlatformViewTest.mm | 5 ++--- .../darwin/ios/framework/Source/FlutterPlatformViewsTest.mm | 5 ++--- .../darwin/ios/framework/Source/accessibility_bridge_test.mm | 5 ++--- 3 files changed, 6 insertions(+), 9 deletions(-) diff --git a/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm index a33acbe4c6321..fbccf6677b02f 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterEnginePlatformViewTest.mm @@ -35,9 +35,8 @@ void OnPlatformViewUnregisterTexture(int64_t texture_id) override {} void OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) override {} void LoadDartDeferredLibrary(intptr_t loading_unit_id, - std::string lib_name, - const std::vector& apkPaths, - std::string abi) override {} + const uint8_t* snapshot_data, + const uint8_t* snapshot_instructions) override {} void UpdateAssetManager(std::shared_ptr asset_manager) override {} }; diff --git a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm index 93964c591aab8..d9abef5a093ef 100644 --- a/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm +++ b/shell/platform/darwin/ios/framework/Source/FlutterPlatformViewsTest.mm @@ -105,9 +105,8 @@ void OnPlatformViewUnregisterTexture(int64_t texture_id) override {} void OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) override {} void LoadDartDeferredLibrary(intptr_t loading_unit_id, - std::string lib_name, - const std::vector& apkPaths, - std::string abi) override {} + const uint8_t* snapshot_data, + const uint8_t* snapshot_instructions) override {} void UpdateAssetManager(std::shared_ptr asset_manager) override {} }; diff --git a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm index 8447bf57db9f0..b027b20e915b4 100644 --- a/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm +++ b/shell/platform/darwin/ios/framework/Source/accessibility_bridge_test.mm @@ -88,9 +88,8 @@ void OnPlatformViewUnregisterTexture(int64_t texture_id) override {} void OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) override {} void LoadDartDeferredLibrary(intptr_t loading_unit_id, - std::string lib_name, - const std::vector& apkPaths, - std::string abi) override {} + const uint8_t* snapshot_data, + const uint8_t* snapshot_instructions) override {} void UpdateAssetManager(std::shared_ptr asset_manager) override {} }; From 340eb5e7aeb1ea3cd93ca1700c6d9ca3ea05b1fa Mon Sep 17 00:00:00 2001 From: garyqian Date: Tue, 17 Nov 2020 01:26:48 -0800 Subject: [PATCH 16/29] Remove splitcompat --- .../android/io/flutter/app/FlutterApplication.java | 9 --------- 1 file changed, 9 deletions(-) diff --git a/shell/platform/android/io/flutter/app/FlutterApplication.java b/shell/platform/android/io/flutter/app/FlutterApplication.java index 0031837467b67..6da36e6b2f089 100644 --- a/shell/platform/android/io/flutter/app/FlutterApplication.java +++ b/shell/platform/android/io/flutter/app/FlutterApplication.java @@ -8,7 +8,6 @@ import android.app.Application; import android.content.Context; import androidx.annotation.CallSuper; -import com.google.android.play.core.splitcompat.SplitCompat; import io.flutter.FlutterInjector; /** @@ -35,12 +34,4 @@ 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); - } } From cfeb10936c9ad83e308b6e994276007d9cdf5a7d Mon Sep 17 00:00:00 2001 From: garyqian Date: Tue, 17 Nov 2020 02:21:24 -0800 Subject: [PATCH 17/29] Licenses fix --- ci/licenses_golden/licenses_flutter | 2 ++ shell/platform/android/io/flutter/app/FlutterApplication.java | 1 - 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/ci/licenses_golden/licenses_flutter b/ci/licenses_golden/licenses_flutter index b2a91424b1af5..d7eb1dac4dc2a 100755 --- a/ci/licenses_golden/licenses_flutter +++ b/ci/licenses_golden/licenses_flutter @@ -747,6 +747,8 @@ FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/Flutte FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartExecutor.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/dart/DartMessenger.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/dart/PlatformMessageHandler.java +FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java +FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/loader/ApplicationInfoLoader.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterApplicationInfo.java FILE: ../../../flutter/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java diff --git a/shell/platform/android/io/flutter/app/FlutterApplication.java b/shell/platform/android/io/flutter/app/FlutterApplication.java index 6da36e6b2f089..a211c268548cd 100644 --- a/shell/platform/android/io/flutter/app/FlutterApplication.java +++ b/shell/platform/android/io/flutter/app/FlutterApplication.java @@ -6,7 +6,6 @@ import android.app.Activity; import android.app.Application; -import android.content.Context; import androidx.annotation.CallSuper; import io.flutter.FlutterInjector; From ae7b771988231d3761fe809247884e9694c2a95c Mon Sep 17 00:00:00 2001 From: garyqian Date: Tue, 17 Nov 2020 02:37:56 -0800 Subject: [PATCH 18/29] Fix fuchsia mock --- shell/platform/fuchsia/flutter/platform_view_unittest.cc | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/shell/platform/fuchsia/flutter/platform_view_unittest.cc b/shell/platform/fuchsia/flutter/platform_view_unittest.cc index 2f676dd4a1f1d..a54504a92ea74 100644 --- a/shell/platform/fuchsia/flutter/platform_view_unittest.cc +++ b/shell/platform/fuchsia/flutter/platform_view_unittest.cc @@ -106,9 +106,8 @@ class MockPlatformViewDelegate : public flutter::PlatformView::Delegate { } // |flutter::PlatformView::Delegate| void LoadDartDeferredLibrary(intptr_t loading_unit_id, - std::string lib_name, - const std::vector& apkPaths, - std::string abi) {} + const uint8_t* snapshot_data, + const uint8_t* snapshot_instructions) {} // |flutter::PlatformView::Delegate| void UpdateAssetManager( std::shared_ptr asset_manager) {} From 9ed709484c44aec887136dcdea01a4cd983e9dd4 Mon Sep 17 00:00:00 2001 From: garyqian Date: Tue, 17 Nov 2020 17:26:34 -0800 Subject: [PATCH 19/29] Address docs comments, add destructor --- .../embedding/engine/FlutterEngine.java | 3 ++ .../flutter/embedding/engine/FlutterJNI.java | 16 ++++--- .../DynamicFeatureManager.java | 48 +++++++++++-------- .../PlayStoreDynamicFeatureManager.java | 6 ++- .../android/platform_view_android_jni_impl.cc | 13 ++--- .../PlayStoreDynamicFeatureManagerTest.java | 25 +++++++++- 6 files changed, 75 insertions(+), 36 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index e8b0b57ed3127..a3657944052cf 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -404,6 +404,9 @@ public void destroy() { Log.v(TAG, "Destroying."); // The order that these things are destroyed is important. pluginRegistry.destroy(); + if (dynamicFeatureManager != null) { + dynamicFeatureManager.destroy(); + } platformViewsController.onDetachedFromJNI(); dartExecutor.onDetachedFromJNI(); flutterJNI.removeEngineLifecycleListener(engineLifecycleListener); diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 1f3edb1a965dc..f4170c54bbea3 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -1001,7 +1001,7 @@ public void removeDynamicFeatureManager() { } /** - * Called by dart to request that a dart deferred library corresponding to loadingUnitId be + * Called by dart to request that a Dart deferred library corresponding to loadingUnitId be * downloaded (if necessary) and loaded into the dart vm. * *

This method delegates the task to DynamicFeatureManager, which handles the download and @@ -1012,14 +1012,14 @@ public void removeDynamicFeatureManager() { */ @SuppressWarnings("unused") @UiThread - public void RequestDartDeferredLibrary(int loadingUnitId) { + public void requestDartDeferredLibrary(int loadingUnitId) { if (dynamicFeatureManager != null) { dynamicFeatureManager.downloadDynamicFeature(loadingUnitId, null); } } /** - * Searches each of the provided paths for a valid dart shared library .so file and resolves + * Searches each of the provided paths for a valid Dart shared library .so file and resolves * symbols to load into the dart VM. * *

Successful loading of the dart library completes the future returned by loadLibrary() that @@ -1027,7 +1027,7 @@ public void RequestDartDeferredLibrary(int loadingUnitId) { * * @param loadingUnitId The loadingUnitId is assigned during compile time by gen_snapshot and is * automatically retrieved when loadLibrary() is called on a dart deferred library. This is - * used to identify which dart deferred library the resolved correspond to. + * used to identify which Dart deferred library the resolved correspond to. * @param searchPaths An array of paths in which to look for valid dart shared libraries. This * supports paths within zipped apks as long as the apks are not compressed using the * `path/to/apk.apk!path/inside/apk/lib.so` format. Paths will be tried first to last and ends @@ -1045,8 +1045,10 @@ private native void nativeLoadDartDeferredLibrary( long nativePlatformViewId, int loadingUnitId, @NonNull String[] searchPaths); /** - * Specifies a new AssetManager that has access to the dynamic feature's assets in addition to the - * base module's assets. + * Adds the specified AssetManager as an APKAssetResolver in the Flutter Engine's AssetManager. + * + * This may be used to update the engine AssetManager when a new dynamic feature is installed and a + * new Android AssetManager is created with access to new assets. * * @param assetManager An android AssetManager that is able to access the newly downloaded assets. * @param assetBundlePath The subdirectory that the flutter assets are stored in. The typical @@ -1077,7 +1079,7 @@ private native void nativeUpdateAssetManager( * failed to install. * @param error The error message to display. * @param isTransient When isTransient is false, new attempts to install will automatically result - * in same error in dart before the request is passed to Android. + * in same error in Dart before the request is passed to Android. */ @SuppressWarnings("unused") @UiThread 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 b97fecb01cb71..668d5a9014dbc 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java @@ -13,15 +13,16 @@ * *

The Flutter default implementation is PlayStoreDynamicFeatureManager. * - *

DynamicFeatureManager handles the embedder/android level tasks of downloading, installing, and - * loading dart deferred libraries. A typical code-flow begins with a dart call to loadLibrary() on + *

DynamicFeatureManager handles the embedder/Android level tasks of downloading, installing, and + * loading Dart deferred libraries. A typical code-flow begins with a Dart call to loadLibrary() on * deferred imported library. See https://dart.dev/guides/language/language-tour#deferred-loading * This call retrieves a unique identifier called the loading unit id, which is assigned by * gen_snapshot during compilation. The loading unit id is passed down through the engine and * invokes downloadDynamicFeature. Once the feature module is downloaded, loadAssets and * loadDartLibrary should be invoked. loadDartLibrary should find shared library .so files for the - * engine to open. loadAssets should typically ensure the new assets are available to the engine's - * asset manager by passing an updated android AssetManager to the engine. + * engine to open and pass the .so path to FlutterJNI.loadDartDeferredLibrary. loadAssets should + * typically ensure the new assets are available to the engine's asset manager by passing an + * updated Android AssetManager to the engine via FlutterJNI.updateAssetManager. * *

The loadAssets and loadDartLibrary methods are separated out because they may also be called * manually via platform channel messages. A full downloadDynamicFeature implementation should call @@ -38,8 +39,8 @@ public interface DynamicFeatureManager { *

This method begins the download and installation of the specified feature module. For * example, the Play Store dynamic delivery implementation uses SplitInstallManager to request the * download of the module. Download is not complete when this method returns. The download process - * should be listened for and upon completion of download, listeners should invoke loadAssets and - * loadDartLibrary to complete the dynamic feature load process. + * should be listened for and upon completion of download, listeners should invoke loadAssets first + * and then loadDartLibrary to complete the dynamic feature load process. * *

Both parameters are not always necessary to identify which module to install. Asset-only * modules do not have an associated loadingUnitId. Instead, an invalid ID like -1 may be passed @@ -55,20 +56,20 @@ public interface DynamicFeatureManager { * (invalid) and moduleName is supplied. Without a loadingUnitId, this method just downloads the * module by name and attempts to load assets via loadAssets. * - * @param loadingUnitId The unique identifier associated with a dart deferred library. This id is + * @param loadingUnitId The unique identifier associated with a Dart deferred library. This id is * assigned by the compiler and can be seen for reference in bundle_config.yaml. This ID is - * primarily used in loadDartLibrary to indicate to dart which dart library is being loaded. + * primarily used in loadDartLibrary to indicate to Dart which Dart library is being loaded. * Loading unit ids range from 0 to the number existing loading units. Passing a negative - * loading unit id indicates that no dart deferred library should be loaded after download + * loading unit id indicates that no Dart deferred library should be loaded after download * completes. This is the case when the dynamic feature module is an assets-only module. If a * negative loadingUnitId is passed, then moduleName must not be null. Passing a loadingUnitId - * larger than the highest valid loading unit's id will cause the dart loadLibrary() to + * larger than the highest valid loading unit's id will cause the Dart loadLibrary() to * complete with a failure. * @param moduleName The dynamic feature module name as defined in bundle_config.yaml. This may be * null if the dynamic feature to be loaded is associated with a loading unit/deferred dart * library. In this case, it is this method's responsibility to map the loadingUnitId to its * corresponding moduleName. When loading asset-only or other dynamic features without an - * associated dart deferred library, loading unit id should a negative value and moduleName + * associated Dart deferred library, loading unit id should a negative value and moduleName * must be non-null. */ public abstract void downloadDynamicFeature(int loadingUnitId, String moduleName); @@ -86,11 +87,11 @@ public interface DynamicFeatureManager { * This process does not require loadingUnitId or moduleName, however, the two parameters are * still present for custom implementations that store assets outside of Android's native system. * - *

Assets shoud be loaded before the dart deferred library is loaded, as successful loading of - * the dart loading unit indicates the dynamic feature is fully loaded. Implementations of + *

Assets shoud be loaded before the Dart deferred library is loaded, as successful loading of + * the Dart loading unit indicates the dynamic feature is fully loaded. Implementations of * downloadDynamicFeature should invoke this after successful download. * - * @param loadingUnitId The unique identifier associated with a dart deferred library. + * @param loadingUnitId The unique identifier associated with a Dart deferred library. * @param moduleName The dynamic feature module name as defined in bundle_config.yaml. */ public abstract void loadAssets(int loadingUnitId, String moduleName); @@ -98,22 +99,22 @@ public interface DynamicFeatureManager { /** * Load the .so shared library file into the Dart VM. * - *

When a feature module download is triggered via a dart loadLibrary() call on a deferred - * library and the download completes, this method should be called to find the path .so library - * file and passed to FlutterJNI.loadDartDeferredLibrary to be dlopen-ed. + *

When the download of a dynamic feature module completes, this method should be called to + * find the path .so library file. The path(s) should then be passed to + * FlutterJNI.loadDartDeferredLibrary to be dlopen-ed and loaded into the Dart VM. * - *

Specifically, APKs distributed by android's app bundle format may vary by device and API + *

Specifically, APKs distributed by Android's app bundle format may vary by device and API * number, so FlutterJNI's loadDartDeferredLibrary accepts a list of search paths with can include * paths within APKs that have not been unpacked using the * `path/to/apk.apk!path/inside/apk/lib.so` format. Each search path will be attempted in order * until a shared library is found. This allows for the developer to avoid unpacking the apk zip. * - *

Upon successful load of the dart library, the dart future from the originating loadLibary() + *

Upon successful load of the Dart library, the Dart future from the originating loadLibary() * call completes and developers are able to use symbols and assets from the feature module. * - * @param loadingUnitId The unique identifier associated with a dart deferred library. This id is + * @param loadingUnitId The unique identifier associated with a Dart deferred library. This id is * assigned by the compiler and can be seen for reference in bundle_config.yaml. This ID is - * primarily used in loadDartLibrary to indicate to dart which dart library is being loaded. + * primarily used in loadDartLibrary to indicate to Dart which Dart library is being loaded. * Loading unit ids range from 0 to the number existing loading units. Negative loading unit * ids are considered invalid and this method will result in a no-op. * @param moduleName The dynamic feature module name as defined in bundle_config.yaml. If using @@ -132,4 +133,9 @@ public interface DynamicFeatureManager { * one of loadingUnitId or moduleName must be valid or non-null. */ public abstract void uninstallFeature(int loadingUnitId, String moduleName); + + /** + * Destructor that cleans up any leftover objects that are no longer needed. + */ + public abstract void destroy(); } 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 0f9b7f44fdf80..c7ff3ba23b34e 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java @@ -76,7 +76,7 @@ public void onStateUpdate(SplitInstallSessionState state) { loadAssets( sessionIdToLoadingUnitId.get(state.sessionId()), sessionIdToName.get(state.sessionId())); - // We only load dart shared lib for the loading unit id requested. Other loading units + // 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( @@ -310,4 +310,8 @@ public void loadDartLibrary(int loadingUnitId, String moduleName) { public void uninstallFeature(int loadingUnitId, String moduleName) { // TODO(garyq): support uninstalling. } + + public void destroy() { + splitInstallManager.unregisterListener(listener); + } } diff --git a/shell/platform/android/platform_view_android_jni_impl.cc b/shell/platform/android/platform_view_android_jni_impl.cc index aa1ffedf58dee..a252d167b07c3 100644 --- a/shell/platform/android/platform_view_android_jni_impl.cc +++ b/shell/platform/android/platform_view_android_jni_impl.cc @@ -102,7 +102,7 @@ static jmethodID g_detach_from_gl_context_method = nullptr; static jmethodID g_compute_platform_resolved_locale_method = nullptr; -static jmethodID g_install_dynamic_library_method = nullptr; +static jmethodID g_request_dart_deferred_library_method = nullptr; // Called By Java static jmethodID g_on_display_platform_view_method = nullptr; @@ -1029,11 +1029,11 @@ bool PlatformViewAndroid::Register(JNIEnv* env) { return false; } - g_install_dynamic_library_method = env->GetMethodID( - g_flutter_jni_class->obj(), "RequestDartDeferredLibrary", "(I)V"); + g_request_dart_deferred_library_method = env->GetMethodID( + g_flutter_jni_class->obj(), "requestDartDeferredLibrary", "(I)V"); - if (g_install_dynamic_library_method == nullptr) { - FML_LOG(ERROR) << "Could not locate downloadDynamicFeature method"; + if (g_request_dart_deferred_library_method == nullptr) { + FML_LOG(ERROR) << "Could not locate requestDartDeferredLibrary method"; return false; } @@ -1473,7 +1473,8 @@ bool PlatformViewAndroidJNIImpl::RequestDartDeferredLibrary( return true; } - env->CallObjectMethod(java_object.obj(), g_install_dynamic_library_method, + env->CallObjectMethod(java_object.obj(), + g_request_dart_deferred_library_method, loading_unit_id); FML_CHECK(CheckException(env)); diff --git a/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java b/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java index 18dbd81155ad0..1fafbb3437890 100644 --- a/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java +++ b/shell/platform/android/test/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManagerTest.java @@ -32,6 +32,7 @@ private class TestFlutterJNI extends FlutterJNI { public int dynamicFeatureInstallFailureCalled = 0; public String[] searchPaths; public int loadingUnitId; + public AssetManager assetManager; public TestFlutterJNI() {} @@ -47,6 +48,7 @@ public void updateAssetManager( @NonNull AssetManager assetManager, @NonNull String assetBundlePath) { updateAssetManagerCalled++; this.loadingUnitId = loadingUnitId; + this.assetManager = assetManager; } @Override @@ -60,7 +62,6 @@ public void dynamicFeatureInstallFailure( private class TestPlayStoreDynamicFeatureManager extends PlayStoreDynamicFeatureManager { public TestPlayStoreDynamicFeatureManager(Context context, FlutterJNI jni) { super(context, jni); - jni.setDynamicFeatureManager(this); } @Override @@ -140,4 +141,26 @@ public void invalidSearchPathsAreIgnored() throws NameNotFoundException { assertEquals(jni.searchPaths.length, 0); assertEquals(jni.loadingUnitId, 123); } + + @Test + public void assetManagerUpdateInvoked() throws NameNotFoundException { + TestFlutterJNI jni = new TestFlutterJNI(); + Context spyContext = spy(RuntimeEnvironment.systemContext); + doReturn(spyContext).when(spyContext).createPackageContext(any(), anyInt()); + AssetManager assetManager = spyContext.getAssets(); + String apkTestPath = "blah doesn't matter here"; + doReturn(new File(apkTestPath)).when(spyContext).getFilesDir(); + TestPlayStoreDynamicFeatureManager playStoreManager = + new TestPlayStoreDynamicFeatureManager(spyContext, jni); + jni.setDynamicFeatureManager(playStoreManager); + + assertEquals(jni.loadingUnitId, 0); + + playStoreManager.downloadDynamicFeature(123, "TestModuleName"); + assertEquals(jni.loadDartDeferredLibraryCalled, 1); + assertEquals(jni.updateAssetManagerCalled, 1); + assertEquals(jni.dynamicFeatureInstallFailureCalled, 0); + + assertEquals(jni.assetManager, assetManager); + } } From eb3cceffe89a04059f5919b989f016cf10138b69 Mon Sep 17 00:00:00 2001 From: garyqian Date: Tue, 17 Nov 2020 18:30:15 -0800 Subject: [PATCH 20/29] Formatting --- .../io/flutter/embedding/engine/FlutterJNI.java | 4 ++-- .../dynamicfeatures/DynamicFeatureManager.java | 12 +++++------- 2 files changed, 7 insertions(+), 9 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index f4170c54bbea3..a03d0debce61d 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -1047,8 +1047,8 @@ private native void nativeLoadDartDeferredLibrary( /** * Adds the specified AssetManager as an APKAssetResolver in the Flutter Engine's AssetManager. * - * This may be used to update the engine AssetManager when a new dynamic feature is installed and a - * new Android AssetManager is created with access to new assets. + *

This may be used to update the engine AssetManager when a new dynamic feature is installed + * and a new Android AssetManager is created with access to new assets. * * @param assetManager An android AssetManager that is able to access the newly downloaded assets. * @param assetBundlePath The subdirectory that the flutter assets are stored in. The typical 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 668d5a9014dbc..95b952e8a2c4c 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java @@ -21,8 +21,8 @@ * invokes downloadDynamicFeature. Once the feature module is downloaded, loadAssets and * loadDartLibrary should be invoked. loadDartLibrary should find shared library .so files for the * engine to open and pass the .so path to FlutterJNI.loadDartDeferredLibrary. loadAssets should - * typically ensure the new assets are available to the engine's asset manager by passing an - * updated Android AssetManager to the engine via FlutterJNI.updateAssetManager. + * typically ensure the new assets are available to the engine's asset manager by passing an updated + * Android AssetManager to the engine via FlutterJNI.updateAssetManager. * *

The loadAssets and loadDartLibrary methods are separated out because they may also be called * manually via platform channel messages. A full downloadDynamicFeature implementation should call @@ -39,8 +39,8 @@ public interface DynamicFeatureManager { *

This method begins the download and installation of the specified feature module. For * example, the Play Store dynamic delivery implementation uses SplitInstallManager to request the * download of the module. Download is not complete when this method returns. The download process - * should be listened for and upon completion of download, listeners should invoke loadAssets first - * and then loadDartLibrary to complete the dynamic feature load process. + * should be listened for and upon completion of download, listeners should invoke loadAssets + * first and then loadDartLibrary to complete the dynamic feature load process. * *

Both parameters are not always necessary to identify which module to install. Asset-only * modules do not have an associated loadingUnitId. Instead, an invalid ID like -1 may be passed @@ -134,8 +134,6 @@ public interface DynamicFeatureManager { */ public abstract void uninstallFeature(int loadingUnitId, String moduleName); - /** - * Destructor that cleans up any leftover objects that are no longer needed. - */ + /** Destructor that cleans up any leftover objects that are no longer needed. */ public abstract void destroy(); } From a1c603cc2e5d0e399e045b8afc5fee3b5a4dfa68 Mon Sep 17 00:00:00 2001 From: garyqian Date: Wed, 18 Nov 2020 01:10:07 -0800 Subject: [PATCH 21/29] provide DynamicFeatureManager in FlutterLoader Implement injector --- shell/common/engine.h | 12 ++++--- shell/common/platform_view.h | 23 ++++++++----- .../android/io/flutter/FlutterInjector.java | 21 ++++++++++-- .../embedding/engine/FlutterEngine.java | 34 +------------------ .../DynamicFeatureManager.java | 11 ++++++ .../PlayStoreDynamicFeatureManager.java | 23 +++++++++++-- .../engine/loader/FlutterLoader.java | 8 ++++- 7 files changed, 82 insertions(+), 50 deletions(-) diff --git a/shell/common/engine.h b/shell/common/engine.h index 13b4cb56c1491..2cfe693265b0d 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -262,7 +262,7 @@ class Engine final : public RuntimeDelegate, const std::vector& supported_locale_data) = 0; //-------------------------------------------------------------------------- - /// @brief Invoked when the dart VM requests that a deferred library + /// @brief Invoked when the Dart VM requests that a deferred library /// be loaded. Notifies the engine that the deferred library /// identified by the specified loading unit id should be /// downloaded and loaded into the Dart VM via @@ -780,17 +780,21 @@ class Engine final : public RuntimeDelegate, const std::string& InitialRoute() const { return initial_route_; } //-------------------------------------------------------------------------- - /// @brief Loads the dart shared library into the dart VM. When the - /// dart library is loaded successfully, the dart future + /// @brief Loads the Dart shared library into the Dart VM. When the + /// Dart library is loaded successfully, the Dart future /// returned by the originating loadLibrary() call completes. /// - /// The Dart compiler may generate separate shared library .so + /// The Dart compiler may generate separate shared libraries /// files called 'loading units' when libraries are imported /// as deferred. Each of these shared libraries are identified /// by a unique loading unit id and can be dynamically loaded /// into the VM by dlopen-ing and resolving the data and /// instructions symbols. /// + /// This method is paired with a RequestDartDeferredLibrary + /// invocation that provides the embedder with the loading unit id + /// of the deferred library to load. + /// /// /// @param[in] loading_unit_id The unique id of the deferred library's /// loading unit. diff --git a/shell/common/platform_view.h b/shell/common/platform_view.h index 6050e177ca0fe..9f85b675760b5 100644 --- a/shell/common/platform_view.h +++ b/shell/common/platform_view.h @@ -613,17 +613,24 @@ class PlatformView { virtual void RequestDartDeferredLibrary(intptr_t loading_unit_id); //-------------------------------------------------------------------------- - /// @brief Loads the dart shared library into the dart VM. When the - /// dart library is loaded successfully, the dart future + /// @brief Loads the Dart shared library into the Dart VM. When the + /// Dart library is loaded successfully, the Dart future /// returned by the originating loadLibrary() call completes. - /// Each shared library is a loading unit, which consists of - /// deferred libraries that can be compiled split from the - /// base dart library by gen_snapshot. + /// + /// The Dart compiler may generate separate shared libraries + /// files called 'loading units' when libraries are imported + /// as deferred. Each of these shared libraries are identified + /// by a unique loading unit id and can be dynamically loaded + /// into the VM by dlopen-ing and resolving the data and + /// instructions symbols. + /// + /// This method is paired with a RequestDartDeferredLibrary + /// invocation that provides the embedder with the loading unit id + /// of the deferred library to load. + /// /// /// @param[in] loading_unit_id The unique id of the deferred library's - /// loading unit. This is the same id as the - /// one passed in by the corresponding - /// RequestDartDeferredLibrary. + /// loading unit. /// /// @param[in] snapshot_data Dart snapshot data of the loading unit's /// shared library. diff --git a/shell/platform/android/io/flutter/FlutterInjector.java b/shell/platform/android/io/flutter/FlutterInjector.java index 23324c7b7fe42..cb3e1c8983e0d 100644 --- a/shell/platform/android/io/flutter/FlutterInjector.java +++ b/shell/platform/android/io/flutter/FlutterInjector.java @@ -5,7 +5,9 @@ package io.flutter; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; +import io.flutter.embedding.engine.dynamicfeatures.DynamicFeatureManager; import io.flutter.embedding.engine.loader.FlutterLoader; /** @@ -62,11 +64,13 @@ public static void reset() { instance = null; } - private FlutterInjector(@NonNull FlutterLoader flutterLoader) { + private FlutterInjector(@NonNull FlutterLoader flutterLoader, DynamicFeatureManager dynamicFeatureManager) { this.flutterLoader = flutterLoader; + this.dynamicFeatureManager = dynamicFeatureManager; } private FlutterLoader flutterLoader; + private DynamicFeatureManager dynamicFeatureManager; /** Returns the {@link FlutterLoader} instance to use for the Flutter Android engine embedding. */ @NonNull @@ -74,6 +78,12 @@ public FlutterLoader flutterLoader() { return flutterLoader; } + /** Returns the {@link DynamicFeatureManager} instance to use for the Flutter Android engine embedding. */ + @Nullable + public DynamicFeatureManager dynamicFeatureManager() { + return dynamicFeatureManager; + } + /** * Builder used to supply a custom FlutterInjector instance to {@link * FlutterInjector#setInstance(FlutterInjector)}. @@ -82,6 +92,7 @@ public FlutterLoader flutterLoader() { */ public static final class Builder { private FlutterLoader flutterLoader; + private DynamicFeatureManager dynamicFeatureManager; /** * Sets a {@link FlutterLoader} override. * @@ -92,10 +103,16 @@ public Builder setFlutterLoader(@NonNull FlutterLoader flutterLoader) { return this; } + public Builder setDynamicFeatureManager(@Nullable DynamicFeatureManager dynamicFeatureManager) { + this.dynamicFeatureManager = dynamicFeatureManager; + return this; + } + private void fillDefaults() { if (flutterLoader == null) { flutterLoader = new FlutterLoader(); } + // dynamicFeatureManager can be null, so no default is necessary. } /** @@ -105,7 +122,7 @@ private void fillDefaults() { public FlutterInjector build() { fillDefaults(); - return new FlutterInjector(flutterLoader); + return new FlutterInjector(flutterLoader, dynamicFeatureManager); } } } diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index a3657944052cf..fb19f4fb44467 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -260,30 +260,6 @@ public FlutterEngine( false); } - /** - * Same as {@link #FlutterEngine(Context, FlutterLoader, FlutterJNI, String[], boolean, boolean)}, - * plus the ability to control {@code waitForRestorationData}. - */ - public FlutterEngine( - @NonNull Context context, - @Nullable FlutterLoader flutterLoader, - @NonNull FlutterJNI flutterJNI, - @NonNull PlatformViewsController platformViewsController, - @Nullable String[] dartVmArgs, - boolean automaticallyRegisterPlugins, - boolean waitForRestorationData) { - this( - context, - flutterLoader, - flutterJNI, - platformViewsController, - dartVmArgs, - automaticallyRegisterPlugins, - waitForRestorationData, - null); - } - - // TODO(garyq): Move this to a better injection system. /** Fully configurable {@code FlutterEngine} constructor. */ public FlutterEngine( @NonNull Context context, @@ -292,8 +268,7 @@ public FlutterEngine( @NonNull PlatformViewsController platformViewsController, @Nullable String[] dartVmArgs, boolean automaticallyRegisterPlugins, - boolean waitForRestorationData, - @Nullable DynamicFeatureManager dynamicFeatureManager) { + boolean waitForRestorationData) { AssetManager assetManager; try { assetManager = context.createPackageContext(context.getPackageName(), 0).getAssets(); @@ -328,10 +303,6 @@ public FlutterEngine( flutterJNI.setPlatformViewsController(platformViewsController); flutterJNI.setLocalizationPlugin(localizationPlugin); - this.dynamicFeatureManager = dynamicFeatureManager; - // TODO(garyq): Inject PlayStoreDynamicFeatureManager(context, flutterJNI); - flutterJNI.setDynamicFeatureManager(dynamicFeatureManager); - attachToJni(); // TODO(mattcarroll): FlutterRenderer is temporally coupled to attach(). Remove that coupling if @@ -404,9 +375,6 @@ public void destroy() { Log.v(TAG, "Destroying."); // The order that these things are destroyed is important. pluginRegistry.destroy(); - if (dynamicFeatureManager != null) { - dynamicFeatureManager.destroy(); - } platformViewsController.onDetachedFromJNI(); dartExecutor.onDetachedFromJNI(); flutterJNI.removeEngineLifecycleListener(engineLifecycleListener); 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 95b952e8a2c4c..e809b8a2c1f66 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,8 @@ package io.flutter.embedding.engine.dynamicfeatures; +import io.flutter.embedding.engine.FlutterJNI; + // TODO: add links to external documentation on how to use split aot features. /** * Basic interface that handles downloading and loading of dynamic features. @@ -33,6 +35,15 @@ * identified by the loading unit ID and assets. */ public interface DynamicFeatureManager { + /** + * Sets the FlutterJNI to use to communicate with the Flutter native engine. + * + *

Since this class may be instantiated before the FlutterEngine and FlutterJNI is fully + * initialized, this method should be called to provide the FlutterJNI instance to use for use in + * loadDartLibrary and loadAssets. + */ + public abstract void setJNI(FlutterJNI flutterJNI); + /** * Request that the feature module be downloaded and installed. * 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 c7ff3ba23b34e..88c35b72fbc72 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java @@ -9,6 +9,7 @@ import android.content.res.AssetManager; import android.os.Build; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import com.google.android.play.core.splitinstall.SplitInstallException; import com.google.android.play.core.splitinstall.SplitInstallManager; import com.google.android.play.core.splitinstall.SplitInstallManagerFactory; @@ -35,7 +36,7 @@ public class PlayStoreDynamicFeatureManager implements DynamicFeatureManager { private static final String TAG = "PlayStoreDynamicFeatureManager"; private @NonNull SplitInstallManager splitInstallManager; - private @NonNull FlutterJNI flutterJNI; + private @Nullable FlutterJNI flutterJNI; private @NonNull Context context; // Each request to install a feature module gets a session ID. These maps associate // the session ID with the loading unit and module name that was requested. @@ -157,7 +158,7 @@ public void onStateUpdate(SplitInstallSessionState state) { } } - public PlayStoreDynamicFeatureManager(@NonNull Context context, @NonNull FlutterJNI flutterJNI) { + public PlayStoreDynamicFeatureManager(@NonNull Context context, @Nullable FlutterJNI flutterJNI) { this.context = context; this.flutterJNI = flutterJNI; splitInstallManager = SplitInstallManagerFactory.create(context); @@ -167,6 +168,18 @@ public PlayStoreDynamicFeatureManager(@NonNull Context context, @NonNull Flutter sessionIdToLoadingUnitId = new HashMap(); } + public void setJNI(@NonNull FlutterJNI flutterJNI) { + this.flutterJNI = flutterJNI; + } + + private boolean verifyJNI() { + if (flutterJNI == null) { + Log.e(TAG, "No FlutterJNI provided."); + return false; + } + return true; + } + private String loadingUnitIdToModuleName(int loadingUnitId) { // Loading unit id to module name mapping stored in android Strings // resources. @@ -233,6 +246,9 @@ public void downloadDynamicFeature(int loadingUnitId, String moduleName) { } public void loadAssets(int loadingUnitId, String moduleName) { + if (!verifyJNI()) { + return; + } // Since android dynamic feature asset manager is handled through // context, neither parameter is used here. Assets are stored in // the apk's `assets` directory allowing them to be accessed by @@ -251,6 +267,9 @@ public void loadAssets(int loadingUnitId, String moduleName) { } public void loadDartLibrary(int loadingUnitId, String moduleName) { + if (!verifyJNI()) { + return; + } // Loading unit must be specified and valid to load a dart library. if (loadingUnitId < 0) { return; 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 95e96d3473e05..4c27fdb0b46dd 100644 --- a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java +++ b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java @@ -17,6 +17,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import io.flutter.BuildConfig; +import io.flutter.FlutterInjector; import io.flutter.Log; import io.flutter.embedding.engine.FlutterJNI; import io.flutter.util.PathUtils; @@ -78,6 +79,9 @@ public FlutterLoader() { */ public FlutterLoader(@NonNull FlutterJNI flutterJNI) { this.flutterJNI = flutterJNI; + if (FlutterInjector.instance().dynamicFeatureManager() != null) { + FlutterInjector.instance().dynamicFeatureManager().setJNI(this.flutterJNI); + } } private boolean initialized = false; @@ -120,7 +124,9 @@ public void startInitialization(@NonNull Context applicationContext) { * @param applicationContext The Android application context. * @param settings Configuration settings. */ - public void startInitialization(@NonNull Context applicationContext, @NonNull Settings settings) { + public void startInitialization( + @NonNull Context applicationContext, + @NonNull Settings settings) { // Do not run startInitialization more than once. if (this.settings != null) { return; From 372adf9c3e864384b20ba624b606034f3b62211c Mon Sep 17 00:00:00 2001 From: garyqian Date: Wed, 18 Nov 2020 02:28:34 -0800 Subject: [PATCH 22/29] Set JNI in the JNI itself --- .../io/flutter/embedding/engine/FlutterEngine.java | 5 ++--- .../io/flutter/embedding/engine/FlutterJNI.java | 12 ++++-------- .../dynamicfeatures/DynamicFeatureManager.java | 10 ++++++---- .../embedding/engine/loader/FlutterLoader.java | 4 ---- 4 files changed, 12 insertions(+), 19 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index fb19f4fb44467..c19500d27f372 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -99,8 +99,6 @@ public class FlutterEngine { // Engine Lifecycle. @NonNull private final Set engineLifecycleListeners = new HashSet<>(); - @NonNull private DynamicFeatureManager dynamicFeatureManager; - @NonNull private final EngineLifecycleListener engineLifecycleListener = new EngineLifecycleListener() { @@ -302,6 +300,7 @@ public FlutterEngine( flutterJNI.addEngineLifecycleListener(engineLifecycleListener); flutterJNI.setPlatformViewsController(platformViewsController); flutterJNI.setLocalizationPlugin(localizationPlugin); + flutterJNI.setDynamicFeatureManager(FlutterInjector.instance().dynamicFeatureManager()); attachToJni(); @@ -378,7 +377,7 @@ public void destroy() { platformViewsController.onDetachedFromJNI(); dartExecutor.onDetachedFromJNI(); flutterJNI.removeEngineLifecycleListener(engineLifecycleListener); - flutterJNI.removeDynamicFeatureManager(); + flutterJNI.setDynamicFeatureManager(null); flutterJNI.detachFromNativeAndReleaseResources(); } diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index a03d0debce61d..320e4c2458f79 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -988,16 +988,12 @@ String[] computePlatformResolvedLocale(@NonNull String[] strings) { /** Sets the dynamic feature manager that is used to download and install split features. */ @UiThread - public void setDynamicFeatureManager(@NonNull DynamicFeatureManager dynamicFeatureManager) { + public void setDynamicFeatureManager(@Nullable DynamicFeatureManager dynamicFeatureManager) { ensureRunningOnMainThread(); this.dynamicFeatureManager = dynamicFeatureManager; - } - - /** Sets the dynamic feature manager that is used to download and install split features. */ - @UiThread - public void removeDynamicFeatureManager() { - ensureRunningOnMainThread(); - this.dynamicFeatureManager = null; + if (dynamicFeatureManager != null) { + dynamicFeatureManager.setJNI(this); + } } /** 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 e809b8a2c1f66..2f6ec7ecb65cb 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java @@ -36,11 +36,13 @@ */ public interface DynamicFeatureManager { /** - * Sets the FlutterJNI to use to communicate with the Flutter native engine. + * Sets the FlutterJNI to be used to communication with the Flutter native engine. * - *

Since this class may be instantiated before the FlutterEngine and FlutterJNI is fully - * initialized, this method should be called to provide the FlutterJNI instance to use for use in - * loadDartLibrary and loadAssets. + *

A FlutterJNI is required in order to properly execute loadAssets and loadDartLibrary. + * + *

Since this class may be instantiated for injection before the FlutterEngine and FlutterJNI + * is fully initialized, this method should be called to provide the FlutterJNI instance to use + * for use in loadDartLibrary and loadAssets. */ public abstract void setJNI(FlutterJNI flutterJNI); 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 4c27fdb0b46dd..a1e04849844a0 100644 --- a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java +++ b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java @@ -17,7 +17,6 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import io.flutter.BuildConfig; -import io.flutter.FlutterInjector; import io.flutter.Log; import io.flutter.embedding.engine.FlutterJNI; import io.flutter.util.PathUtils; @@ -79,9 +78,6 @@ public FlutterLoader() { */ public FlutterLoader(@NonNull FlutterJNI flutterJNI) { this.flutterJNI = flutterJNI; - if (FlutterInjector.instance().dynamicFeatureManager() != null) { - FlutterInjector.instance().dynamicFeatureManager().setJNI(this.flutterJNI); - } } private boolean initialized = false; From e8861b5fe702c5f74d677084e101fa01ef335ad2 Mon Sep 17 00:00:00 2001 From: garyqian Date: Wed, 18 Nov 2020 11:11:15 -0800 Subject: [PATCH 23/29] Formatting --- shell/platform/android/io/flutter/FlutterInjector.java | 8 ++++++-- .../io/flutter/embedding/engine/FlutterEngine.java | 1 - .../io/flutter/embedding/engine/loader/FlutterLoader.java | 4 +--- 3 files changed, 7 insertions(+), 6 deletions(-) diff --git a/shell/platform/android/io/flutter/FlutterInjector.java b/shell/platform/android/io/flutter/FlutterInjector.java index cb3e1c8983e0d..b207f403fc0b2 100644 --- a/shell/platform/android/io/flutter/FlutterInjector.java +++ b/shell/platform/android/io/flutter/FlutterInjector.java @@ -64,7 +64,8 @@ public static void reset() { instance = null; } - private FlutterInjector(@NonNull FlutterLoader flutterLoader, DynamicFeatureManager dynamicFeatureManager) { + private FlutterInjector( + @NonNull FlutterLoader flutterLoader, DynamicFeatureManager dynamicFeatureManager) { this.flutterLoader = flutterLoader; this.dynamicFeatureManager = dynamicFeatureManager; } @@ -78,7 +79,10 @@ public FlutterLoader flutterLoader() { return flutterLoader; } - /** Returns the {@link DynamicFeatureManager} instance to use for the Flutter Android engine embedding. */ + /** + * Returns the {@link DynamicFeatureManager} instance to use for the Flutter Android engine + * embedding. + */ @Nullable public DynamicFeatureManager dynamicFeatureManager() { return dynamicFeatureManager; diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index c19500d27f372..7371cce3fe369 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -12,7 +12,6 @@ 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.loader.FlutterLoader; import io.flutter.embedding.engine.plugins.PluginRegistry; import io.flutter.embedding.engine.plugins.activity.ActivityControlSurface; 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 a1e04849844a0..95e96d3473e05 100644 --- a/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java +++ b/shell/platform/android/io/flutter/embedding/engine/loader/FlutterLoader.java @@ -120,9 +120,7 @@ public void startInitialization(@NonNull Context applicationContext) { * @param applicationContext The Android application context. * @param settings Configuration settings. */ - public void startInitialization( - @NonNull Context applicationContext, - @NonNull Settings settings) { + public void startInitialization(@NonNull Context applicationContext, @NonNull Settings settings) { // Do not run startInitialization more than once. if (this.settings != null) { return; From 4366c79bbe493edff3876a541a84a3a20c67a2a1 Mon Sep 17 00:00:00 2001 From: garyqian Date: Wed, 18 Nov 2020 11:15:59 -0800 Subject: [PATCH 24/29] Add flutter injector tests --- .../android/test/io/flutter/FlutterInjectorTest.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/shell/platform/android/test/io/flutter/FlutterInjectorTest.java b/shell/platform/android/test/io/flutter/FlutterInjectorTest.java index 9809095a0746d..d970c7015468d 100644 --- a/shell/platform/android/test/io/flutter/FlutterInjectorTest.java +++ b/shell/platform/android/test/io/flutter/FlutterInjectorTest.java @@ -6,9 +6,11 @@ import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; +import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; import io.flutter.embedding.engine.loader.FlutterLoader; +import io.flutter.embedding.engine.dynamicfeatures.PlayStoreDynamicFeatureManager; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -21,6 +23,7 @@ @RunWith(RobolectricTestRunner.class) public class FlutterInjectorTest { @Mock FlutterLoader mockFlutterLoader; + @Mock PlayStoreDynamicFeatureManager mockDynamicFeatureManager; @Before public void setUp() { @@ -34,6 +37,7 @@ public void itHasSomeReasonableDefaults() { // Implicitly builds when first accessed. FlutterInjector injector = FlutterInjector.instance(); assertNotNull(injector.flutterLoader()); + assertNull(injector.dynamicFeatureManager()); } @Test @@ -44,6 +48,14 @@ public void canPartiallyOverride() { assertEquals(injector.flutterLoader(), mockFlutterLoader); } + @Test + public void canInjectDynamicFeatureManager() { + FlutterInjector.setInstance( + new FlutterInjector.Builder().setDynamicFeatureManager(mockDynamicFeatureManager).build()); + FlutterInjector injector = FlutterInjector.instance(); + assertEquals(injector.dynamicFeatureManager(), mockDynamicFeatureManager); + } + @Test() public void cannotBeChangedOnceRead() { FlutterInjector.instance(); From 2130d59e9bfb6e01c74cb9e7daeb273c6eab53e1 Mon Sep 17 00:00:00 2001 From: garyqian Date: Wed, 18 Nov 2020 12:15:59 -0800 Subject: [PATCH 25/29] Formatting and comment nit --- shell/platform/android/io/flutter/FlutterInjector.java | 2 +- shell/platform/android/test/io/flutter/FlutterInjectorTest.java | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/shell/platform/android/io/flutter/FlutterInjector.java b/shell/platform/android/io/flutter/FlutterInjector.java index b207f403fc0b2..d703007ff968e 100644 --- a/shell/platform/android/io/flutter/FlutterInjector.java +++ b/shell/platform/android/io/flutter/FlutterInjector.java @@ -116,7 +116,7 @@ private void fillDefaults() { if (flutterLoader == null) { flutterLoader = new FlutterLoader(); } - // dynamicFeatureManager can be null, so no default is necessary. + // DynamicFeatureManager's intended default is null. } /** diff --git a/shell/platform/android/test/io/flutter/FlutterInjectorTest.java b/shell/platform/android/test/io/flutter/FlutterInjectorTest.java index d970c7015468d..66384fcb7c27c 100644 --- a/shell/platform/android/test/io/flutter/FlutterInjectorTest.java +++ b/shell/platform/android/test/io/flutter/FlutterInjectorTest.java @@ -9,8 +9,8 @@ import static org.junit.Assert.assertNull; import static org.junit.Assert.assertThrows; -import io.flutter.embedding.engine.loader.FlutterLoader; import io.flutter.embedding.engine.dynamicfeatures.PlayStoreDynamicFeatureManager; +import io.flutter.embedding.engine.loader.FlutterLoader; import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; From b8cfc01a1454f9c094262a6a0d1bc9a7eadcd88c Mon Sep 17 00:00:00 2001 From: garyqian Date: Wed, 18 Nov 2020 15:03:37 -0800 Subject: [PATCH 26/29] More documentation changes --- shell/common/engine.h | 15 ++++++++++----- shell/common/platform_view.h | 17 ++++++++++++----- .../android/embedding_bundle/build.gradle | 4 ++++ 3 files changed, 26 insertions(+), 10 deletions(-) diff --git a/shell/common/engine.h b/shell/common/engine.h index 2cfe693265b0d..93a5f2367dbc3 100644 --- a/shell/common/engine.h +++ b/shell/common/engine.h @@ -269,7 +269,10 @@ class Engine final : public RuntimeDelegate, /// `LoadDartDeferredLibrary` /// /// @param[in] loading_unit_id The unique id of the deferred library's - /// loading unit. + /// loading unit. This id is to be passed + /// back into LoadDartDeferredLibrary + /// in order to identify which deferred + /// library to load. /// virtual void RequestDartDeferredLibrary(intptr_t loading_unit_id) = 0; }; @@ -787,9 +790,10 @@ class Engine final : public RuntimeDelegate, /// The Dart compiler may generate separate shared libraries /// files called 'loading units' when libraries are imported /// as deferred. Each of these shared libraries are identified - /// by a unique loading unit id and can be dynamically loaded - /// into the VM by dlopen-ing and resolving the data and - /// instructions symbols. + /// by a unique loading unit id. Callers should dlopen the + /// shared library file and use dlsym to resolve the dart + /// symbols. These symbols can then be passed to this method to + /// be dynamically loaded into the VM. /// /// This method is paired with a RequestDartDeferredLibrary /// invocation that provides the embedder with the loading unit id @@ -797,7 +801,8 @@ class Engine final : public RuntimeDelegate, /// /// /// @param[in] loading_unit_id The unique id of the deferred library's - /// loading unit. + /// loading unit, as passed in by + /// RequestDartDeferredLibrary. /// /// @param[in] snapshot_data Dart snapshot data of the loading unit's /// shared library. diff --git a/shell/common/platform_view.h b/shell/common/platform_view.h index 9f85b675760b5..dd200b5948fdb 100644 --- a/shell/common/platform_view.h +++ b/shell/common/platform_view.h @@ -238,6 +238,8 @@ class PlatformView { const uint8_t* snapshot_data, const uint8_t* snapshot_instructions) = 0; + // TODO(garyq): Implement a proper asset_resolver replacement instead of + // overwriting the entire asset manager. //-------------------------------------------------------------------------- /// @brief Sets the asset manager of the engine to asset_manager /// @@ -608,7 +610,10 @@ class PlatformView { /// `LoadDartDeferredLibrary` /// /// @param[in] loading_unit_id The unique id of the deferred library's - /// loading unit. + /// loading unit. This id is to be passed + /// back into LoadDartDeferredLibrary + /// in order to identify which deferred + /// library to load. /// virtual void RequestDartDeferredLibrary(intptr_t loading_unit_id); @@ -620,9 +625,10 @@ class PlatformView { /// The Dart compiler may generate separate shared libraries /// files called 'loading units' when libraries are imported /// as deferred. Each of these shared libraries are identified - /// by a unique loading unit id and can be dynamically loaded - /// into the VM by dlopen-ing and resolving the data and - /// instructions symbols. + /// by a unique loading unit id. Callers should dlopen the + /// shared library file and use dlsym to resolve the dart + /// symbols. These symbols can then be passed to this method to + /// be dynamically loaded into the VM. /// /// This method is paired with a RequestDartDeferredLibrary /// invocation that provides the embedder with the loading unit id @@ -630,7 +636,8 @@ class PlatformView { /// /// /// @param[in] loading_unit_id The unique id of the deferred library's - /// loading unit. + /// loading unit, as passed in by + /// RequestDartDeferredLibrary. /// /// @param[in] snapshot_data Dart snapshot data of the loading unit's /// shared library. diff --git a/shell/platform/android/embedding_bundle/build.gradle b/shell/platform/android/embedding_bundle/build.gradle index a1e4f59a4b484..c3eea2b22fbf4 100644 --- a/shell/platform/android/embedding_bundle/build.gradle +++ b/shell/platform/android/embedding_bundle/build.gradle @@ -48,6 +48,10 @@ android { embedding "androidx.lifecycle:lifecycle-common:$lifecycle_version" embedding "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version" + // This dependency is here to allow linking to Play core in tests, but + // is not used in a default Flutter app. This dependency should be manually + // added to the user's app gradle in order to opt into using split AOT + // dynamic features. embedding "com.google.android.play:core:1.8.0" // Testing From c99567317d5a553292773284e7e36ab5ee597b6f Mon Sep 17 00:00:00 2001 From: garyqian Date: Thu, 19 Nov 2020 15:20:13 -0800 Subject: [PATCH 27/29] Address comments --- .../android/io/flutter/embedding/engine/FlutterEngine.java | 3 +++ .../android/io/flutter/embedding/engine/FlutterJNI.java | 5 +++++ .../engine/dynamicfeatures/DynamicFeatureManager.java | 2 +- .../dynamicfeatures/PlayStoreDynamicFeatureManager.java | 3 ++- 4 files changed, 11 insertions(+), 2 deletions(-) diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java index 7371cce3fe369..556b4cd3af021 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterEngine.java @@ -378,6 +378,9 @@ public void destroy() { flutterJNI.removeEngineLifecycleListener(engineLifecycleListener); flutterJNI.setDynamicFeatureManager(null); flutterJNI.detachFromNativeAndReleaseResources(); + if (FlutterInjector.instance().dynamicFeatureManager() != null) { + FlutterInjector.instance().dynamicFeatureManager().destroy(); + } } /** diff --git a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java index 320e4c2458f79..94576fb302f10 100644 --- a/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java +++ b/shell/platform/android/io/flutter/embedding/engine/FlutterJNI.java @@ -1011,6 +1011,11 @@ public void setDynamicFeatureManager(@Nullable DynamicFeatureManager dynamicFeat public void requestDartDeferredLibrary(int loadingUnitId) { if (dynamicFeatureManager != null) { dynamicFeatureManager.downloadDynamicFeature(loadingUnitId, null); + } else { + // TODO(garyq): Add link to setup/instructions guide wiki. + Log.e( + TAG, + "No DynamicFeatureManager found. Android setup must be completed before using split AOT dynamic features."); } } 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 2f6ec7ecb65cb..73a41e80f2a4a 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java @@ -147,6 +147,6 @@ public interface DynamicFeatureManager { */ public abstract void uninstallFeature(int loadingUnitId, String moduleName); - /** Destructor that cleans up any leftover objects that are no longer needed. */ + /** Cleans up and releases resources. This object is no longer usable after calling this method. */ public abstract void destroy(); } 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 88c35b72fbc72..3132a415d7471 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java @@ -174,7 +174,7 @@ public void setJNI(@NonNull FlutterJNI flutterJNI) { private boolean verifyJNI() { if (flutterJNI == null) { - Log.e(TAG, "No FlutterJNI provided."); + Log.e(TAG, "No FlutterJNI provided. `setJNI` must be called on the DynamicFeatureManager before attempting to load dart libraries or invoking with platform channels."); return false; } return true; @@ -332,5 +332,6 @@ public void uninstallFeature(int loadingUnitId, String moduleName) { public void destroy() { splitInstallManager.unregisterListener(listener); + flutterJNI = null; } } From e4e9a1ed47a36b4def0ea4f7a67f73f8d646efb7 Mon Sep 17 00:00:00 2001 From: garyqian Date: Thu, 19 Nov 2020 15:32:22 -0800 Subject: [PATCH 28/29] Formatting --- .../engine/dynamicfeatures/DynamicFeatureManager.java | 4 +++- .../dynamicfeatures/PlayStoreDynamicFeatureManager.java | 5 ++++- 2 files changed, 7 insertions(+), 2 deletions(-) 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 73a41e80f2a4a..747f6df01d56f 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/DynamicFeatureManager.java @@ -147,6 +147,8 @@ public interface DynamicFeatureManager { */ public abstract void uninstallFeature(int loadingUnitId, String moduleName); - /** Cleans up and releases resources. This object is no longer usable after calling this method. */ + /** + * Cleans up and releases resources. This object is no longer usable after calling this method. + */ public abstract void destroy(); } 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 3132a415d7471..cc0cbe1e25487 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java @@ -174,7 +174,10 @@ public void setJNI(@NonNull FlutterJNI flutterJNI) { private boolean verifyJNI() { if (flutterJNI == null) { - Log.e(TAG, "No FlutterJNI provided. `setJNI` must be called on the DynamicFeatureManager before attempting to load dart libraries or invoking with platform channels."); + Log.e( + TAG, + "No FlutterJNI provided. `setJNI` must be called on the DynamicFeatureManager before attempting to load dart libraries or invoking with platform channels."); + return false; return false; } return true; From f006e9b144fc801ae6bda8ab3016b404db9ce296 Mon Sep 17 00:00:00 2001 From: garyqian Date: Thu, 19 Nov 2020 15:41:46 -0800 Subject: [PATCH 29/29] FOrmatting --- .../engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java | 1 - 1 file changed, 1 deletion(-) 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 cc0cbe1e25487..de613cacbcf2f 100644 --- a/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java +++ b/shell/platform/android/io/flutter/embedding/engine/dynamicfeatures/PlayStoreDynamicFeatureManager.java @@ -177,7 +177,6 @@ private boolean verifyJNI() { Log.e( TAG, "No FlutterJNI provided. `setJNI` must be called on the DynamicFeatureManager before attempting to load dart libraries or invoking with platform channels."); - return false; return false; } return true;