未验证 提交 53fc019a 编写于 作者: G Gary Qian 提交者: GitHub

Split AOT Android Embedder and shell (#22179)

上级 23a8e027
......@@ -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',
......
......@@ -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
......
......@@ -507,4 +507,21 @@ 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|
// void Engine::RequestDartDeferredLibrary(intptr_t loading_unit_id) {
// return delegate_.RequestDartDeferredLibrary(loading_unit_id);
// }
void Engine::LoadDartDeferredLibrary(intptr_t loading_unit_id,
const uint8_t* snapshot_data,
const uint8_t* snapshot_instructions) {
if (runtime_controller_->IsRootIsolateRunning()) {
// runtime_controller_->LoadDartDeferredLibrary(loading_unit_id,
// snapshot_data, snapshot_instructions);
}
}
} // namespace flutter
......@@ -260,6 +260,21 @@ class Engine final : public RuntimeDelegate,
virtual std::unique_ptr<std::vector<std::string>>
ComputePlatformResolvedLocale(
const std::vector<std::string>& supported_locale_data) = 0;
//--------------------------------------------------------------------------
/// @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
/// `LoadDartDeferredLibrary`
///
/// @param[in] loading_unit_id The unique id of the deferred library's
/// 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;
};
//----------------------------------------------------------------------------
......@@ -767,6 +782,38 @@ 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
/// returned by the originating loadLibrary() call completes.
///
/// 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. 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
/// of the deferred library to load.
///
///
/// @param[in] loading_unit_id The unique id of the deferred library's
/// loading unit, as passed in by
/// RequestDartDeferredLibrary.
///
/// @param[in] snapshot_data Dart snapshot data of the loading unit's
/// shared library.
///
/// @param[in] snapshot_data Dart snapshot instructions of the loading
/// unit's shared library.
///
void LoadDartDeferredLibrary(intptr_t loading_unit_id,
const uint8_t* snapshot_data,
const uint8_t* snapshot_instructions);
private:
Engine::Delegate& delegate_;
const Settings settings_;
......@@ -815,6 +862,12 @@ class Engine final : public RuntimeDelegate,
std::unique_ptr<std::vector<std::string>> ComputePlatformResolvedLocale(
const std::vector<std::string>& 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|
// void RequestDartDeferredLibrary(intptr_t loading_unit_id) override;
void SetNeedsReportTimings(bool value) override;
void StopAnimator();
......
......@@ -32,6 +32,7 @@ class MockDelegate : public Engine::Delegate {
MOCK_METHOD1(ComputePlatformResolvedLocale,
std::unique_ptr<std::vector<std::string>>(
const std::vector<std::string>&));
MOCK_METHOD1(RequestDartDeferredLibrary, void(intptr_t));
};
class MockResponse : public PlatformMessageResponse {
......@@ -55,6 +56,7 @@ class MockRuntimeDelegate : public RuntimeDelegate {
MOCK_METHOD1(ComputePlatformResolvedLocale,
std::unique_ptr<std::vector<std::string>>(
const std::vector<std::string>&));
MOCK_METHOD1(RequestDartDeferredLibrary, void(intptr_t));
};
class MockRuntimeController : public RuntimeController {
......
......@@ -159,4 +159,14 @@ PlatformView::ComputePlatformResolvedLocales(
return out;
}
void PlatformView::RequestDartDeferredLibrary(intptr_t loading_unit_id) {}
void PlatformView::LoadDartDeferredLibrary(
intptr_t loading_unit_id,
const uint8_t* snapshot_data,
const uint8_t* snapshot_instructions) {}
void PlatformView::UpdateAssetManager(
std::shared_ptr<AssetManager> asset_manager) {}
} // namespace flutter
......@@ -210,6 +210,43 @@ class PlatformView {
///
virtual void OnPlatformViewMarkTextureFrameAvailable(
int64_t texture_id) = 0;
//--------------------------------------------------------------------------
/// @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
/// 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.
///
/// @param[in] snapshot_data Dart snapshot data of the loading unit's
/// shared library.
///
/// @param[in] snapshot_data Dart snapshot instructions of the loading
/// unit's shared library.
///
virtual void LoadDartDeferredLibrary(
intptr_t loading_unit_id,
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
///
/// @param[in] asset_manager The asset manager to use.
///
virtual void UpdateAssetManager(
std::shared_ptr<AssetManager> asset_manager) = 0;
};
//----------------------------------------------------------------------------
......@@ -565,6 +602,62 @@ class PlatformView {
virtual std::shared_ptr<ExternalViewEmbedder> CreateExternalViewEmbedder();
//--------------------------------------------------------------------------
/// @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
/// `LoadDartDeferredLibrary`
///
/// @param[in] loading_unit_id The unique id of the deferred library's
/// 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);
//--------------------------------------------------------------------------
/// @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 libraries
/// files called 'loading units' when libraries are imported
/// as deferred. Each of these shared libraries are identified
/// 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
/// of the deferred library to load.
///
///
/// @param[in] loading_unit_id The unique id of the deferred library's
/// loading unit, as passed in by
/// RequestDartDeferredLibrary.
///
/// @param[in] snapshot_data Dart snapshot data of the loading unit's
/// shared library.
///
/// @param[in] snapshot_data Dart snapshot instructions of the loading
/// unit's shared library.
///
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<AssetManager> asset_manager);
protected:
PlatformView::Delegate& delegate_;
const TaskRunners task_runners_;
......
......@@ -1185,6 +1185,22 @@ std::unique_ptr<std::vector<std::string>> Shell::ComputePlatformResolvedLocale(
return platform_view_->ComputePlatformResolvedLocales(supported_locale_data);
}
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<AssetManager> asset_manager) {
engine_->UpdateAssetManager(std::move(asset_manager));
}
// |Engine::Delegate|
void Shell::RequestDartDeferredLibrary(intptr_t loading_unit_id) {
platform_view_->RequestDartDeferredLibrary(loading_unit_id);
}
void Shell::ReportTimings() {
FML_DCHECK(is_setup_);
FML_DCHECK(task_runners_.GetRasterTaskRunner()->RunsTasksOnCurrentThread());
......
......@@ -507,6 +507,14 @@ class Shell final : public PlatformView::Delegate,
// |PlatformView::Delegate|
void OnPlatformViewSetNextFrameCallback(const fml::closure& closure) override;
// |PlatformView::Delegate|
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<AssetManager> asset_manager) override;
// |Animator::Delegate|
void OnAnimatorBeginFrame(fml::TimePoint frame_target_time) override;
......@@ -548,6 +556,9 @@ class Shell final : public PlatformView::Delegate,
std::unique_ptr<std::vector<std::string>> ComputePlatformResolvedLocale(
const std::vector<std::string>& supported_locale_data) override;
// |Engine::Delegate|
void RequestDartDeferredLibrary(intptr_t loading_unit_id) override;
// |Rasterizer::Delegate|
void OnFrameRasterized(const FrameTiming&) override;
......
......@@ -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",
......@@ -462,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",
......
......@@ -48,6 +48,12 @@ 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
// 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
......
......@@ -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,14 @@ public final class FlutterInjector {
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 +79,15 @@ public final class FlutterInjector {
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 +96,7 @@ public final class FlutterInjector {
*/
public static final class Builder {
private FlutterLoader flutterLoader;
private DynamicFeatureManager dynamicFeatureManager;
/**
* Sets a {@link FlutterLoader} override.
*
......@@ -92,10 +107,16 @@ public final class FlutterInjector {
return this;
}
public Builder setDynamicFeatureManager(@Nullable DynamicFeatureManager dynamicFeatureManager) {
this.dynamicFeatureManager = dynamicFeatureManager;
return this;
}
private void fillDefaults() {
if (flutterLoader == null) {
flutterLoader = new FlutterLoader();
}
// DynamicFeatureManager's intended default is null.
}
/**
......@@ -105,7 +126,7 @@ public final class FlutterInjector {
public FlutterInjector build() {
fillDefaults();
return new FlutterInjector(flutterLoader);
return new FlutterInjector(flutterLoader, dynamicFeatureManager);
}
}
}
......@@ -299,6 +299,8 @@ public class FlutterEngine {
flutterJNI.addEngineLifecycleListener(engineLifecycleListener);
flutterJNI.setPlatformViewsController(platformViewsController);
flutterJNI.setLocalizationPlugin(localizationPlugin);
flutterJNI.setDynamicFeatureManager(FlutterInjector.instance().dynamicFeatureManager());
attachToJni();
// TODO(mattcarroll): FlutterRenderer is temporally coupled to attach(). Remove that coupling if
......@@ -374,7 +376,11 @@ public class FlutterEngine {
platformViewsController.onDetachedFromJNI();
dartExecutor.onDetachedFromJNI();
flutterJNI.removeEngineLifecycleListener(engineLifecycleListener);
flutterJNI.setDynamicFeatureManager(null);
flutterJNI.detachFromNativeAndReleaseResources();
if (FlutterInjector.instance().dynamicFeatureManager() != null) {
FlutterInjector.instance().dynamicFeatureManager().destroy();
}
}
/**
......
......@@ -20,6 +20,7 @@ import androidx.annotation.VisibleForTesting;
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 class FlutterJNI {
@Nullable private LocalizationPlugin localizationPlugin;
@Nullable private PlatformViewsController platformViewsController;
@Nullable private DynamicFeatureManager dynamicFeatureManager;
@NonNull
private final Set<EngineLifecycleListener> engineLifecycleListeners = new CopyOnWriteArraySet<>();
......@@ -981,6 +984,117 @@ public class FlutterJNI {
// ----- 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(@Nullable DynamicFeatureManager dynamicFeatureManager) {
ensureRunningOnMainThread();
this.dynamicFeatureManager = dynamicFeatureManager;
if (dynamicFeatureManager != null) {
dynamicFeatureManager.setJNI(this);
}
}
/**
* Called by dart to request that a Dart deferred library corresponding to loadingUnitId be
* downloaded (if necessary) and loaded into the dart vm.
*
* <p>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 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.");
}
}
/**
* Searches each of the provided paths for a valid Dart shared library .so file and resolves
* symbols to load into the dart VM.
*
* <p>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 loadDartDeferredLibrary(int loadingUnitId, @NonNull String[] searchPaths) {
ensureRunningOnMainThread();
ensureAttachedToNative();
nativeLoadDartDeferredLibrary(nativePlatformViewId, loadingUnitId, searchPaths);
}
private native void nativeLoadDartDeferredLibrary(
long nativePlatformViewId, int loadingUnitId, @NonNull String[] searchPaths);
/**
* Adds the specified AssetManager as an APKAssetResolver in the Flutter Engine's AssetManager.
*
* <p>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
* 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);
/**
* 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.
*
* <p>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(
int loadingUnitId, @NonNull String error, boolean isTransient) {
ensureRunningOnMainThread();
nativeDynamicFeatureInstallFailure(loadingUnitId, error, isTransient);
}
private native void nativeDynamicFeatureInstallFailure(
int loadingUnitId, @NonNull String error, boolean isTransient);
// ----- End Dynamic Features Support ----
// @SuppressWarnings("unused")
@UiThread
public void onDisplayPlatformView(
......
// 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 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.
*
* <p>Flutter dynamic feature support is still in early developer preview and should not be used in
* production apps yet.
*
* <p>The Flutter default implementation is PlayStoreDynamicFeatureManager.
*
* <p>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 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.
*
* <p>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.
*
* <p>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 {
/**
* Sets the FlutterJNI to be used to communication with the Flutter native engine.
*
* <p>A FlutterJNI is required in order to properly execute loadAssets and loadDartLibrary.
*
* <p>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);
/**
* Request that the feature module be downloaded and installed.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*
* <p>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 downloadDynamicFeature(int loadingUnitId, String moduleName);
/**
* Extract and load any assets and resources from the module for use by Flutter.
*
* <p>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.
*
* <p>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.
*
* <p>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.
*/
public abstract void loadAssets(int loadingUnitId, String moduleName);
/**
* Load the .so shared library file into the Dart VM.
*
* <p>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.
*
* <p>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.
*
* <p>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(int loadingUnitId, String moduleName);
/**
* Uninstall the specified feature module.
*
* <p>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);
/**
* Cleans up and releases resources. This object is no longer usable after calling this method.
*/
public abstract void destroy();
}
// 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 android.content.Context;
import android.content.pm.PackageManager.NameNotFoundException;
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;
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.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
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 = "PlayStoreDynamicFeatureManager";
private @NonNull SplitInstallManager splitInstallManager;
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.
private @NonNull Map<Integer, String> sessionIdToName;
private @NonNull Map<Integer, Integer> sessionIdToLoadingUnitId;
private FeatureInstallStateUpdatedListener listener;
private class FeatureInstallStateUpdatedListener implements SplitInstallStateUpdatedListener {
public void onStateUpdate(SplitInstallSessionState state) {
if (sessionIdToName.containsKey(state.sessionId())) {
// TODO(garyq): Add system channel for split aot messages.
switch (state.status()) {
case SplitInstallSessionStatus.FAILED:
{
Log.e(
TAG,
String.format(
"Module \"%s\" (sessionId %d) install failed with: %s",
sessionIdToName.get(state.sessionId()),
state.sessionId(),
state.errorCode()));
flutterJNI.dynamicFeatureInstallFailure(
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,
String.format(
"Module \"%s\" (sessionId %d) install successfully.",
sessionIdToName.get(state.sessionId()), state.sessionId()));
loadAssets(
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(
sessionIdToLoadingUnitId.get(state.sessionId()),
sessionIdToName.get(state.sessionId()));
sessionIdToName.remove(state.sessionId());
sessionIdToLoadingUnitId.remove(state.sessionId());
break;
}
case SplitInstallSessionStatus.CANCELED:
{
Log.d(
TAG,
String.format(
"Module \"%s\" (sessionId %d) install canceled.",
sessionIdToName.get(state.sessionId()), state.sessionId()));
sessionIdToName.remove(state.sessionId());
break;
}
case SplitInstallSessionStatus.CANCELING:
{
Log.d(
TAG,
String.format(
"Module \"%s\" (sessionId %d) install canceling.",
sessionIdToName.get(state.sessionId()), state.sessionId()));
break;
}
case SplitInstallSessionStatus.PENDING:
{
Log.d(
TAG,
String.format(
"Module \"%s\" (sessionId %d) install pending.",
sessionIdToName.get(state.sessionId()), state.sessionId()));
break;
}
case SplitInstallSessionStatus.REQUIRES_USER_CONFIRMATION:
{
Log.d(
TAG,
String.format(
"Module \"%s\" (sessionId %d) install requires user confirmation.",
sessionIdToName.get(state.sessionId()), state.sessionId()));
break;
}
case SplitInstallSessionStatus.DOWNLOADING:
{
Log.d(
TAG,
String.format(
"Module \"%s\" (sessionId %d) downloading.",
sessionIdToName.get(state.sessionId()), state.sessionId()));
break;
}
case SplitInstallSessionStatus.DOWNLOADED:
{
Log.d(
TAG,
String.format(
"Module \"%s\" (sessionId %d) downloaded.",
sessionIdToName.get(state.sessionId()), state.sessionId()));
break;
}
case SplitInstallSessionStatus.INSTALLING:
{
Log.d(
TAG,
String.format(
"Module \"%s\" (sessionId %d) installing.",
sessionIdToName.get(state.sessionId()), state.sessionId()));
break;
}
default:
Log.d(TAG, "Status: " + state.status());
}
}
}
}
public PlayStoreDynamicFeatureManager(@NonNull Context context, @Nullable 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 setJNI(@NonNull FlutterJNI flutterJNI) {
this.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.");
return false;
}
return true;
}
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;
}
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);
})
.addOnFailureListener(
exception -> {
switch (((SplitInstallException) exception).getErrorCode()) {
case SplitInstallErrorCode.NETWORK_ERROR:
flutterJNI.dynamicFeatureInstallFailure(
loadingUnitId,
String.format(
"Install of dynamic feature module \"%s\" failed with a network error",
moduleName),
true);
break;
case SplitInstallErrorCode.MODULE_UNAVAILABLE:
flutterJNI.dynamicFeatureInstallFailure(
loadingUnitId,
String.format(
"Install of dynamic feature module \"%s\" failed as it is unavailable",
moduleName),
false);
break;
default:
flutterJNI.dynamicFeatureInstallFailure(
loadingUnitId,
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(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
// Android's AssetManager directly.
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) {
throw new RuntimeException(e);
}
}
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;
}
// 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 and robust.
// Search directly in APKs first
List<String> apkPaths = new ArrayList();
// If not found in APKs, we check in extracted native libs for the lib directly.
List<String> soPaths = new ArrayList();
Queue<File> 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.endsWith(".apk") && name.startsWith(moduleName) && name.contains(pathAbi)) {
apkPaths.add(file.getAbsolutePath());
continue;
}
if (name.equals(aotSharedLibraryName)) {
soPaths.add(file.getAbsolutePath());
}
}
List<String> searchPaths = new ArrayList();
for (String path : apkPaths) {
searchPaths.add(path + "!lib/" + abi + "/" + aotSharedLibraryName);
}
for (String path : soPaths) {
searchPaths.add(path);
}
flutterJNI.loadDartDeferredLibrary(
loadingUnitId, searchPaths.toArray(new String[apkPaths.size()]));
}
public void uninstallFeature(int loadingUnitId, String moduleName) {
// TODO(garyq): support uninstalling.
}
public void destroy() {
splitInstallManager.unregisterListener(listener);
flutterJNI = null;
}
}
......@@ -95,6 +95,11 @@ class JNIMock final : public PlatformViewAndroidJNI {
(override));
MOCK_METHOD(double, GetDisplayRefreshRate, (), (override));
MOCK_METHOD(bool,
RequestDartDeferredLibrary,
(int loading_unit_id),
(override));
};
} // namespace flutter
......
......@@ -195,6 +195,8 @@ class PlatformViewAndroidJNI {
std::vector<std::string> supported_locales_data) = 0;
virtual double GetDisplayRefreshRate() = 0;
virtual bool RequestDartDeferredLibrary(int loading_unit_id) = 0;
};
} // namespace flutter
......
......@@ -336,6 +336,29 @@ PlatformViewAndroid::ComputePlatformResolvedLocales(
supported_locale_data);
}
// |PlatformView|
void PlatformViewAndroid::RequestDartDeferredLibrary(intptr_t loading_unit_id) {
if (jni_facade_->RequestDartDeferredLibrary(loading_unit_id)) {
return;
}
return; // TODO(garyq): Call LoadDartDeferredLibraryFailure()
}
// |PlatformView|
void PlatformViewAndroid::LoadDartDeferredLibrary(
intptr_t loading_unit_id,
const uint8_t* snapshot_data,
const uint8_t* snapshot_instructions) {
delegate_.LoadDartDeferredLibrary(loading_unit_id, snapshot_data,
snapshot_instructions);
}
// |PlatformView|
void PlatformViewAndroid::UpdateAssetManager(
std::shared_ptr<AssetManager> asset_manager) {
delegate_.UpdateAssetManager(std::move(asset_manager));
}
void PlatformViewAndroid::InstallFirstFrameCallback() {
// On Platform Task Runner.
SetNextFrameCallback(
......
......@@ -93,6 +93,14 @@ class PlatformViewAndroid final : public PlatformView {
int64_t texture_id,
const fml::jni::JavaObjectWeakGlobalRef& surface_texture);
// |PlatformView|
void LoadDartDeferredLibrary(intptr_t loading_unit_id,
const uint8_t* snapshot_data,
const uint8_t* snapshot_instructions) override;
// |PlatformView|
void UpdateAssetManager(std::shared_ptr<AssetManager> asset_manager) override;
private:
const std::shared_ptr<PlatformViewAndroidJNI> jni_facade_;
std::unique_ptr<AndroidContext> android_context_;
......@@ -137,6 +145,9 @@ class PlatformViewAndroid final : public PlatformView {
std::unique_ptr<std::vector<std::string>> ComputePlatformResolvedLocales(
const std::vector<std::string>& supported_locale_data) override;
// |PlatformView|
void RequestDartDeferredLibrary(intptr_t loading_unit_id) override;
void InstallFirstFrameCallback();
void FireFirstFrameCallback();
......
......@@ -5,7 +5,9 @@
#include "flutter/shell/platform/android/platform_view_android_jni_impl.h"
#include <android/native_window_jni.h>
#include <dlfcn.h>
#include <jni.h>
#include <sstream>
#include <utility>
#include "unicode/uchar.h"
......@@ -100,6 +102,8 @@ static jmethodID g_detach_from_gl_context_method = nullptr;
static jmethodID g_compute_platform_resolved_locale_method = nullptr;
static jmethodID g_request_dart_deferred_library_method = nullptr;
// Called By Java
static jmethodID g_on_display_platform_view_method = nullptr;
......@@ -508,6 +512,108 @@ static jboolean FlutterTextUtilsIsRegionalIndicator(JNIEnv* env,
jint codePoint) {
return u_hasBinaryProperty(codePoint, UProperty::UCHAR_REGIONAL_INDICATOR);
}
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<intptr_t>(jLoadingUnitId),
fml::jni::JavaStringToString(env, jError),
static_cast<bool>(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<intptr_t>(jLoadingUnitId);
std::vector<std::string> search_paths =
fml::jni::StringArrayToVector(env, jSearchPaths);
// TODO: Switch to using the NativeLibrary class, eg:
//
// fml::RefPtr<fml::NativeLibrary> 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;
}
// Resolve symbols.
uint8_t* isolate_data =
static_cast<uint8_t*>(::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<uint8_t*>(
::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<uint8_t*>(
::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<uint8_t*>(
::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()->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,
jobject jAssetManager,
jstring jAssetBundlePath) {
auto asset_manager = std::make_shared<flutter::AssetManager>();
asset_manager->PushBack(std::make_unique<flutter::APKAssetProvider>(
env, // jni environment
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(
config.GetAssetManager());
}
bool RegisterApi(JNIEnv* env) {
static const JNINativeMethod flutter_jni_methods[] = {
// Start of methods from FlutterJNI
......@@ -664,6 +770,22 @@ bool RegisterApi(JNIEnv* env) {
.fnPtr =
reinterpret_cast<void*>(&FlutterTextUtilsIsRegionalIndicator),
},
{
.name = "nativeLoadDartDeferredLibrary",
.signature = "(JI[Ljava/lang/String;)V",
.fnPtr = reinterpret_cast<void*>(&LoadDartDeferredLibrary),
},
{
.name = "nativeUpdateAssetManager",
.signature =
"(JLandroid/content/res/AssetManager;Ljava/lang/String;)V",
.fnPtr = reinterpret_cast<void*>(&UpdateAssetManager),
},
{
.name = "nativeDynamicFeatureInstallFailure",
.signature = "(ILjava/lang/String;Z)V",
.fnPtr = reinterpret_cast<void*>(&DynamicFeatureInstallFailure),
},
};
if (env->RegisterNatives(g_flutter_jni_class->obj(), flutter_jni_methods,
......@@ -907,6 +1029,14 @@ bool PlatformViewAndroid::Register(JNIEnv* env) {
return false;
}
g_request_dart_deferred_library_method = env->GetMethodID(
g_flutter_jni_class->obj(), "requestDartDeferredLibrary", "(I)V");
if (g_request_dart_deferred_library_method == nullptr) {
FML_LOG(ERROR) << "Could not locate requestDartDeferredLibrary method";
return false;
}
return RegisterApi(env);
}
......@@ -1334,4 +1464,21 @@ double PlatformViewAndroidJNIImpl::GetDisplayRefreshRate() {
return static_cast<double>(env->GetStaticFloatField(clazz, fid));
}
bool PlatformViewAndroidJNIImpl::RequestDartDeferredLibrary(
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_request_dart_deferred_library_method,
loading_unit_id);
FML_CHECK(CheckException(env));
return true;
}
} // namespace flutter
......@@ -80,6 +80,8 @@ class PlatformViewAndroidJNIImpl final : public PlatformViewAndroidJNI {
double GetDisplayRefreshRate() override;
bool RequestDartDeferredLibrary(int loading_unit_id) override;
private:
// Reference to FlutterJNI object.
const fml::jni::JavaObjectWeakGlobalRef java_object_;
......
......@@ -6,8 +6,10 @@ package io.flutter;
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.dynamicfeatures.PlayStoreDynamicFeatureManager;
import io.flutter.embedding.engine.loader.FlutterLoader;
import org.junit.Before;
import org.junit.Test;
......@@ -21,6 +23,7 @@ import org.robolectric.annotation.Config;
@RunWith(RobolectricTestRunner.class)
public class FlutterInjectorTest {
@Mock FlutterLoader mockFlutterLoader;
@Mock PlayStoreDynamicFeatureManager mockDynamicFeatureManager;
@Before
public void setUp() {
......@@ -34,6 +37,7 @@ public class FlutterInjectorTest {
// Implicitly builds when first accessed.
FlutterInjector injector = FlutterInjector.instance();
assertNotNull(injector.flutterLoader());
assertNull(injector.dynamicFeatureManager());
}
@Test
......@@ -44,6 +48,14 @@ public class FlutterInjectorTest {
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();
......
......@@ -18,6 +18,7 @@ import io.flutter.embedding.engine.LocalizationPluginTest;
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 @@ import test.io.flutter.embedding.engine.PluginComponentTest;
PlatformChannelTest.class,
PlatformPluginTest.class,
PlatformViewsControllerTest.class,
PlayStoreDynamicFeatureManagerTest.class,
PluginComponentTest.class,
PreconditionsTest.class,
RenderingComponentTest.class,
......
// 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.assertEquals;
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.spy;
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;
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 int loadDartDeferredLibraryCalled = 0;
public int updateAssetManagerCalled = 0;
public int dynamicFeatureInstallFailureCalled = 0;
public String[] searchPaths;
public int loadingUnitId;
public AssetManager assetManager;
public TestFlutterJNI() {}
@Override
public void loadDartDeferredLibrary(int loadingUnitId, @NonNull String[] searchPaths) {
loadDartDeferredLibraryCalled++;
this.searchPaths = searchPaths;
this.loadingUnitId = loadingUnitId;
}
@Override
public void updateAssetManager(
@NonNull AssetManager assetManager, @NonNull String assetBundlePath) {
updateAssetManagerCalled++;
this.loadingUnitId = loadingUnitId;
this.assetManager = assetManager;
}
@Override
public void dynamicFeatureInstallFailure(
int loadingUnitId, @NonNull String error, boolean isTransient) {
dynamicFeatureInstallFailureCalled++;
}
}
// 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) {
// Override this to skip the online SplitInstallManager portion.
loadAssets(loadingUnitId, moduleName);
loadDartLibrary(loadingUnitId, moduleName);
}
}
@Test
public void downloadCallsJNIFunctions() throws NameNotFoundException {
TestFlutterJNI jni = new TestFlutterJNI();
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";
doReturn(new File(soTestPath)).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);
assertTrue(jni.searchPaths[0].endsWith(soTestPath));
assertEquals(jni.searchPaths.length, 1);
assertEquals(jni.loadingUnitId, 123);
}
@Test
public void searchPathsAddsApks() throws NameNotFoundException {
TestFlutterJNI jni = new TestFlutterJNI();
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";
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);
assertTrue(jni.searchPaths[0].endsWith(apkTestPath + "!lib/armeabi-v7a/app.so-123.part.so"));
assertEquals(jni.searchPaths.length, 1);
assertEquals(jni.loadingUnitId, 123);
}
@Test
public void invalidSearchPathsAreIgnored() throws NameNotFoundException {
TestFlutterJNI jni = new TestFlutterJNI();
Context spyContext = spy(RuntimeEnvironment.systemContext);
doReturn(spyContext).when(spyContext).createPackageContext(any(), anyInt());
doReturn(null).when(spyContext).getAssets();
String apkTestPath = "test/path/invalidpath.apk";
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.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);
}
}
......@@ -33,6 +33,11 @@ class MockDelegate : public PlatformView::Delegate {
void OnPlatformViewRegisterTexture(std::shared_ptr<Texture> texture) override {}
void OnPlatformViewUnregisterTexture(int64_t texture_id) override {}
void OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) override {}
void LoadDartDeferredLibrary(intptr_t loading_unit_id,
const uint8_t* snapshot_data,
const uint8_t* snapshot_instructions) override {}
void UpdateAssetManager(std::shared_ptr<AssetManager> asset_manager) override {}
};
} // namespace
......
......@@ -103,6 +103,11 @@ class FlutterPlatformViewsTestMockPlatformViewDelegate : public PlatformView::De
void OnPlatformViewRegisterTexture(std::shared_ptr<Texture> texture) override {}
void OnPlatformViewUnregisterTexture(int64_t texture_id) override {}
void OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) override {}
void LoadDartDeferredLibrary(intptr_t loading_unit_id,
const uint8_t* snapshot_data,
const uint8_t* snapshot_instructions) override {}
void UpdateAssetManager(std::shared_ptr<AssetManager> asset_manager) override {}
};
} // namespace
......
......@@ -86,6 +86,11 @@ class MockDelegate : public PlatformView::Delegate {
void OnPlatformViewRegisterTexture(std::shared_ptr<Texture> texture) override {}
void OnPlatformViewUnregisterTexture(int64_t texture_id) override {}
void OnPlatformViewMarkTextureFrameAvailable(int64_t texture_id) override {}
void LoadDartDeferredLibrary(intptr_t loading_unit_id,
const uint8_t* snapshot_data,
const uint8_t* snapshot_instructions) override {}
void UpdateAssetManager(std::shared_ptr<AssetManager> asset_manager) override {}
};
class MockIosDelegate : public AccessibilityBridge::IosDelegate {
......
......@@ -104,6 +104,13 @@ class MockPlatformViewDelegate : public flutter::PlatformView::Delegate {
const std::vector<std::string>& supported_locale_data) {
return nullptr;
}
// |flutter::PlatformView::Delegate|
void LoadDartDeferredLibrary(intptr_t loading_unit_id,
const uint8_t* snapshot_data,
const uint8_t* snapshot_instructions) {}
// |flutter::PlatformView::Delegate|
void UpdateAssetManager(
std::shared_ptr<flutter::AssetManager> asset_manager) {}
flutter::Surface* surface() const { return surface_.get(); }
flutter::PlatformMessage* message() const { return message_.get(); }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册