Skip to content
This repository was archived by the owner on Feb 25, 2025. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions assets/asset_manager.cc
Original file line number Diff line number Diff line change
Expand Up @@ -79,4 +79,9 @@ bool AssetManager::IsValidAfterAssetManagerChange() const {
return false;
}

// |AssetResolver|
bool AssetManager::IsUpdatable() const {
return false;
}

} // namespace flutter
5 changes: 5 additions & 0 deletions assets/asset_manager.h
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,9 @@ class AssetManager final : public AssetResolver {
// |AssetResolver|
bool IsValidAfterAssetManagerChange() const override;

// |AssetResolver|
bool IsUpdatable() const override;

// |AssetResolver|
std::unique_ptr<fml::Mapping> GetAsMapping(
const std::string& asset_name) const override;
Expand All @@ -41,6 +44,8 @@ class AssetManager final : public AssetResolver {
std::vector<std::unique_ptr<fml::Mapping>> GetAsMappings(
const std::string& asset_pattern) const override;

size_t Size() { return resolvers_.size(); }

private:
std::deque<std::unique_ptr<AssetResolver>> resolvers_;

Expand Down
18 changes: 18 additions & 0 deletions assets/asset_resolver.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,24 @@ class AssetResolver {
///
virtual bool IsValidAfterAssetManagerChange() const = 0;

//----------------------------------------------------------------------------
/// @brief Some asset resolvers may be replaced by an updated version
/// during runtime. Resolvers marked `Updatable` are removed and
/// invalidated when an update is processed and is replaced by a
/// new resolver that provides the latest availablity state of
/// assets. This usually adds access to new assets or removes
/// access to old/invalid/deleted assets. For example, when
/// downloading a dynamic feature, Android provides a new java
/// asset manager that has access to the newly installed assets.
/// This new manager should replace the existing java asset
/// manager resolver. We call this replacement an update as the
/// old resolver is obsolete and the new one should assume
/// responsibility for providing access to android assets.
///
/// @return Returns whether this resolver can be updated.
///
virtual bool IsUpdatable() const = 0;

[[nodiscard]] virtual std::unique_ptr<fml::Mapping> GetAsMapping(
const std::string& asset_name) const = 0;

Expand Down
5 changes: 5 additions & 0 deletions assets/directory_asset_bundle.cc
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ bool DirectoryAssetBundle::IsValidAfterAssetManagerChange() const {
return is_valid_after_asset_manager_change_;
}

// |AssetResolver|
bool DirectoryAssetBundle::IsUpdatable() const {
return false;
}

// |AssetResolver|
std::unique_ptr<fml::Mapping> DirectoryAssetBundle::GetAsMapping(
const std::string& asset_name) const {
Expand Down
3 changes: 3 additions & 0 deletions assets/directory_asset_bundle.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@ class DirectoryAssetBundle : public AssetResolver {
// |AssetResolver|
bool IsValidAfterAssetManagerChange() const override;

// |AssetResolver|
bool IsUpdatable() const override;

// |AssetResolver|
std::unique_ptr<fml::Mapping> GetAsMapping(
const std::string& asset_name) const override;
Expand Down
6 changes: 4 additions & 2 deletions shell/common/platform_view.cc
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,9 @@ void PlatformView::LoadDartDeferredLibraryError(intptr_t loading_unit_id,
const std::string error_message,
bool transient) {}

void PlatformView::UpdateAssetManager(
std::shared_ptr<AssetManager> asset_manager) {}
void PlatformView::UpdateAssetResolvers(
std::vector<std::unique_ptr<AssetResolver>>& asset_resolvers) {
delegate_.UpdateAssetResolvers(asset_resolvers);
}

} // namespace flutter
35 changes: 26 additions & 9 deletions shell/common/platform_view.h
Original file line number Diff line number Diff line change
Expand Up @@ -280,8 +280,8 @@ class PlatformView {
///
/// @param[in] asset_manager The asset manager to use.
///
virtual void UpdateAssetManager(
std::shared_ptr<AssetManager> asset_manager) = 0;
virtual void UpdateAssetResolvers(
std::vector<std::unique_ptr<AssetResolver>>& asset_resolvers) = 0;
};

//----------------------------------------------------------------------------
Expand Down Expand Up @@ -720,14 +720,31 @@ class PlatformView {
const std::string error_message,
bool transient);

// 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);
/// @brief Replaces asset resolvers in the current engine's
/// `AssetManager` that are marked as updatable
/// (`IsUpdateable()` returns true). Updatable AssetResolvers
/// are removed and replaced with the next available resolver
/// in `asset_resolvers`.
///
/// AssetResolvers should be updated when the exisitng resolver
/// becomes obsolete and a newer one becomes available that
/// provides updated access to the same type of assets as the
/// existing one. This update process is meant to be performed at
/// runtime.
///
/// If less resolvers are provided than existing updatable
/// resolvers, the the extra existing updatable resolvers will be
/// removed without replacement. If more resolvers are provided
/// than existing updatable resolvers, then the extra provided
/// resolvers will be added to the end of the AssetManager
/// resolvers list.
///
/// @param[in] asset_resolvers The asset resolvers to replace updatable
/// existing resolvers with.
///
virtual void UpdateAssetResolvers(
std::vector<std::unique_ptr<AssetResolver>>& asset_resolvers);

protected:
PlatformView::Delegate& delegate_;
Expand Down
22 changes: 21 additions & 1 deletion shell/common/shell.cc
Original file line number Diff line number Diff line change
Expand Up @@ -1223,7 +1223,27 @@ void Shell::LoadDartDeferredLibraryError(intptr_t loading_unit_id,
transient);
}

void Shell::UpdateAssetManager(std::shared_ptr<AssetManager> asset_manager) {
void Shell::UpdateAssetResolvers(
std::vector<std::unique_ptr<AssetResolver>>& asset_resolvers) {
size_t index = 0;
auto asset_manager = std::make_shared<AssetManager>();
auto old_asset_manager = engine_->GetAssetManager();
if (old_asset_manager != nullptr) {
for (auto& old_resolver : old_asset_manager->TakeResolvers()) {
if (old_resolver->IsUpdatable()) {
if (index < asset_resolvers.size()) {
// Push the replacement updated resolver in place of the old_resolver.
asset_manager->PushBack(std::move(asset_resolvers[index++]));
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will the asset_resolvers argument always contains exactly the same number of updatable resolvers in exactly the same order as the AssetManager's current resolver list?

I'm wondering if it would be simpler to add an API to AssetManager that would replace an AssetResolver of a given type with a new instance. There would need to be some way to identify which resolver is the APKAssetProvider - perhaps each AssetResolver subclass could return an enum indicating its type.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I was hesitant to add too heavy of a solution, but I agree it would be less confusing to explicitly define which resolver type to update.

In most expected circumstances, the number of replacement resolvers would equal the number of updatable resolvers, but i'm not restricting it to be equal here since there is a reasonable course of action for extra resolvers or fewer resolvers. Restricting would necessitate API to reveal the number of updatable resolvers, which isn't necessary for what is trying to be accomplished here.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think it would be sufficient to have UpdateResolversByType accept a single AssetResolver instead of a vector. The only current use case for UpdateResolversByType is replacing the APK resolver, and the caller expects there to be exactly one of that type in the AssetManager's resolver list.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

} // Drop the resolver if no replacement available.
} else {
asset_manager->PushBack(std::move(old_resolver));
}
}
}
// Append all extra resolvers to the end.
while (index < asset_resolvers.size()) {
asset_manager->PushBack(std::move(asset_resolvers[index++]));
}
engine_->UpdateAssetManager(std::move(asset_manager));
}

Expand Down
3 changes: 2 additions & 1 deletion shell/common/shell.h
Original file line number Diff line number Diff line change
Expand Up @@ -536,7 +536,8 @@ class Shell final : public PlatformView::Delegate,
bool transient) override;

// |PlatformView::Delegate|
void UpdateAssetManager(std::shared_ptr<AssetManager> asset_manager) override;
void UpdateAssetResolvers(
std::vector<std::unique_ptr<AssetResolver>>& asset_resolvers) override;

// |Animator::Delegate|
void OnAnimatorBeginFrame(fml::TimePoint frame_target_time) override;
Expand Down
149 changes: 149 additions & 0 deletions shell/common/shell_unittests.cc
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@
#include <functional>
#include <future>
#include <memory>
#include <vector>

#include "assets/directory_asset_bundle.h"
#include "flutter/common/graphics/persistent_cache.h"
Expand Down Expand Up @@ -115,6 +116,33 @@ class MockPlatformView : public PlatformView {
};
} // namespace

class TestAssetResolver : public AssetResolver {
public:
TestAssetResolver(bool updatable, int valid)
: updatable_(updatable), valid_(valid) {}

bool IsValid() const override { return true; }

// This is used to identify if replacement was made or not.
bool IsValidAfterAssetManagerChange() const override { return valid_; }

bool IsUpdatable() const override { return updatable_; }

std::unique_ptr<fml::Mapping> GetAsMapping(
const std::string& asset_name) const override {
return nullptr;
}

std::vector<std::unique_ptr<fml::Mapping>> GetAsMappings(
const std::string& asset_pattern) const override {
return {};
};

private:
bool updatable_;
int valid_;
};

static bool ValidateShell(Shell* shell) {
if (!shell) {
return false;
Expand Down Expand Up @@ -2437,5 +2465,126 @@ TEST_F(ShellTest, Spawn) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}

TEST_F(ShellTest, UpdateAssetResolversReplaces) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
Settings settings = CreateSettingsForFixture();
ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
ThreadHost::Type::Platform);
auto task_runner = thread_host.platform_thread->GetTaskRunner();
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
task_runner);
auto shell = CreateShell(std::move(settings), task_runners);
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
ASSERT_TRUE(ValidateShell(shell.get()));

auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));

auto platform_view =
std::make_unique<PlatformView>(*shell.get(), std::move(task_runners));

auto asset_manager = shell->GetEngine()->GetAssetManager();

auto old_resolver = std::make_unique<TestAssetResolver>(true, true);
ASSERT_TRUE(old_resolver->IsUpdatable());
ASSERT_TRUE(old_resolver->IsValid());
asset_manager->PushBack(std::move(old_resolver));

std::vector<std::unique_ptr<AssetResolver>> resolver_vector;
auto updated_resolver = std::make_unique<TestAssetResolver>(true, false);
ASSERT_TRUE(updated_resolver->IsUpdatable());
ASSERT_FALSE(updated_resolver->IsValidAfterAssetManagerChange());
resolver_vector.push_back(std::move(updated_resolver));
platform_view->UpdateAssetResolvers(resolver_vector);

auto resolvers = shell->GetEngine()->GetAssetManager()->TakeResolvers();
ASSERT_EQ(resolvers.size(), 2ull);
ASSERT_FALSE(resolvers[0]->IsUpdatable());
ASSERT_TRUE(resolvers[0]->IsValidAfterAssetManagerChange());

ASSERT_TRUE(resolvers[1]->IsUpdatable());
ASSERT_FALSE(resolvers[1]->IsValidAfterAssetManagerChange());

DestroyShell(std::move(shell), std::move(task_runners));
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}

TEST_F(ShellTest, UpdateAssetResolversAppends) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
Settings settings = CreateSettingsForFixture();
ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
ThreadHost::Type::Platform);
auto task_runner = thread_host.platform_thread->GetTaskRunner();
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
task_runner);
auto shell = CreateShell(std::move(settings), task_runners);
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
ASSERT_TRUE(ValidateShell(shell.get()));

auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));

auto platform_view =
std::make_unique<PlatformView>(*shell.get(), std::move(task_runners));

std::vector<std::unique_ptr<AssetResolver>> resolver_vector;
auto updated_resolver = std::make_unique<TestAssetResolver>(true, false);
ASSERT_TRUE(updated_resolver->IsUpdatable());
ASSERT_FALSE(updated_resolver->IsValidAfterAssetManagerChange());
resolver_vector.push_back(std::move(updated_resolver));
platform_view->UpdateAssetResolvers(resolver_vector);

auto resolvers = shell->GetEngine()->GetAssetManager()->TakeResolvers();
ASSERT_EQ(resolvers.size(), 2ull);
ASSERT_FALSE(resolvers[0]->IsUpdatable());
ASSERT_TRUE(resolvers[0]->IsValidAfterAssetManagerChange());

ASSERT_TRUE(resolvers[1]->IsUpdatable());
ASSERT_FALSE(resolvers[1]->IsValidAfterAssetManagerChange());

DestroyShell(std::move(shell), std::move(task_runners));
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}

TEST_F(ShellTest, UpdateAssetResolversRemoves) {
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
Settings settings = CreateSettingsForFixture();
ThreadHost thread_host("io.flutter.test." + GetCurrentTestName() + ".",
ThreadHost::Type::Platform);
auto task_runner = thread_host.platform_thread->GetTaskRunner();
TaskRunners task_runners("test", task_runner, task_runner, task_runner,
task_runner);
auto shell = CreateShell(std::move(settings), task_runners);
ASSERT_TRUE(DartVMRef::IsInstanceRunning());
ASSERT_TRUE(ValidateShell(shell.get()));

auto configuration = RunConfiguration::InferFromSettings(settings);
configuration.SetEntrypoint("emptyMain");
RunEngine(shell.get(), std::move(configuration));

auto platform_view =
std::make_unique<PlatformView>(*shell.get(), std::move(task_runners));

auto asset_manager = shell->GetEngine()->GetAssetManager();

auto old_resolver = std::make_unique<TestAssetResolver>(true, true);
ASSERT_TRUE(old_resolver->IsUpdatable());
ASSERT_TRUE(old_resolver->IsValid());
asset_manager->PushBack(std::move(old_resolver));

std::vector<std::unique_ptr<AssetResolver>> resolver_vector;
platform_view->UpdateAssetResolvers(resolver_vector);

auto resolvers = shell->GetEngine()->GetAssetManager()->TakeResolvers();
ASSERT_EQ(resolvers.size(), 1ull);
ASSERT_FALSE(resolvers[0]->IsUpdatable());
ASSERT_TRUE(resolvers[0]->IsValidAfterAssetManagerChange());

DestroyShell(std::move(shell), std::move(task_runners));
ASSERT_FALSE(DartVMRef::IsInstanceRunning());
}

} // namespace testing
} // namespace flutter
7 changes: 7 additions & 0 deletions shell/platform/android/apk_asset_provider.cc
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,13 @@ bool APKAssetProvider::IsValidAfterAssetManagerChange() const {
return true;
}

bool APKAssetProvider::IsUpdatable() const {
// APKAssetProvider is always updatable to allow dynamic features to
// runtime-update the Java AssetManager instance being used when a new dynamic
// feature is installed.
return true;
}

class APKAssetMapping : public fml::Mapping {
public:
APKAssetMapping(AAsset* asset) : asset_(asset) {}
Expand Down
3 changes: 3 additions & 0 deletions shell/platform/android/apk_asset_provider.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ class APKAssetProvider final : public AssetResolver {
// |flutter::AssetResolver|
bool IsValidAfterAssetManagerChange() const override;

// |flutter::AssetResolver|
bool IsUpdatable() const override;

// |flutter::AssetResolver|
std::unique_ptr<fml::Mapping> GetAsMapping(
const std::string& asset_name) const override;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1056,14 +1056,14 @@ private native void nativeLoadDartDeferredLibrary(
* value is `flutter_assets`.
*/
@UiThread
public void updateAssetManager(
public void updateJavaAssetManager(
@NonNull AssetManager assetManager, @NonNull String assetBundlePath) {
ensureRunningOnMainThread();
ensureAttachedToNative();
nativeUpdateAssetManager(nativePlatformViewId, assetManager, assetBundlePath);
nativeUpdateJavaAssetManager(nativePlatformViewId, assetManager, assetBundlePath);
}

private native void nativeUpdateAssetManager(
private native void nativeUpdateJavaAssetManager(
long nativePlatformViewId,
@NonNull AssetManager assetManager,
@NonNull String assetBundlePath);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -317,7 +317,7 @@ public void loadAssets(int loadingUnitId, String moduleName) {
context = context.createPackageContext(context.getPackageName(), 0);

AssetManager assetManager = context.getAssets();
flutterJNI.updateAssetManager(
flutterJNI.updateJavaAssetManager(
assetManager,
// TODO(garyq): Made the "flutter_assets" directory dynamic based off of DartEntryPoint.
"flutter_assets");
Expand Down
Loading