Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

AssetPack Support #8631

Merged
merged 16 commits into from
Mar 15, 2024
Merged

AssetPack Support #8631

merged 16 commits into from
Mar 15, 2024

Conversation

dellis1972
Copy link
Contributor

@dellis1972 dellis1972 commented Jan 12, 2024

Context #4810

Google Android began supporting splitting up the app package into multiple
packs with the introduction of the aab package format. This format allows
the developer to split the app up into multiple packs. Each pack can be
downloaded to the device either at install time or on demand. This allows
application developers to save space and install time by only installing
the required parts of the app initially. Then installing other packs
as required.

There are two types of pack. The first is a Feature pack, this type
of pack contains code and other resources. Code in these types of pack
can be launched via the StartActivity API call. At this time due to
various constraints .NET Android cannot support Feature packs.

The second type of pack is the Asset pack. This type of pack ONLY
contains AndroidAsset items. It CANNOT contain any code or other
resources. This type of feature pack can be installed at install-time,
fast-follow or ondemand. It is most useful for apps which contain allot
of Assets, such as Games or Multi Media applications.
See the documentation for details on how this all works.
.NET Android does not have any official support for this type of pack.
However a hack is available via the excellent @infinitespace-studios on
github.
This hack allows developers to place additional assets in a special
NoTargets project. This project is built just after the final aab is
produced. It builds a zip file which is then added to the @(AndroidAppBundleModules)
ItemGroup in the main application. This zip is then included into the
final app as an additional feature.

Asset Pack Specification

We want to provide our users the ability to use Asset packs without
having to implement the hack provided by @infinitespace-studios. Using
a separate project like in the hack is one way to go. It does have some
issues though.

  1. It is a special type of project. It requires a global.json which imports the
    NoTargets sdk.
  2. There is no IDE support for building this type of project.

Having the user go through a number of hoops to implement this for
.NET Android or .net Maui is not ideal. We need a simpler method.

The new idea is to make use of additional metadata on AndroidAsset
Items to allow the build system to split up the assets into packs
automatically. So it is proposed that we implement support for something
like this

<ItemGroup>
   <AndroidAsset Include="Asset/data.xml" />
   <AndroidAsset Include="Asset/movie.mp4" AssetPack="assets1" />
   <AndroidAsset Include="Asset/movie2.mp4" AssetPack="assets1" />
</ItemGroup>

In this case the additional AssetPack attribute is used to tell the
build system which pack to place this asset in. Since auto import of items
is common now we need a way for a user to add this additional attribute
to auto included items. Fortunately we are able to use the following.

<ItemGroup>
   <AndroidAsset Update="Asset/movie.mp4" AssetPack="assets1" />
   <AndroidAsset Update="Asset/movie2.mp4" AssetPack="assets1" />
   <AndroidAsset Update="Asset/movie3.mp4" AssetPack="assets2" />
</ItemGroup>

This code uses the Update attribute to tell MSBuild that we are going
to update a specific item. Note in the sample we do NOT need to include
an Update for the data.xml, since this is auto imported it will still
end up in the main feature in the aab.

Additional attributes can be used to control what type of asset pack is
produced. The only extra one supported at this time is DeliveryType,
this can have a value of InstallTime, FastFollow or OnDemand.
Additional attributes do not need to be included on ALL items. Any one
will do, only the AssetPack attribute will be needed.
See Google's documentation for details on what each item does.

<ItemGroup>
   <AndroidAsset Update="Asset/movie.mp4" AssetPack="assets1" DeliveryType="InstallTime" />
   <AndroidAsset Update="Asset/movie2.mp4" AssetPack="assets1" />
   <AndroidAsset Update="Asset/movie3.mp4" AssetPack="assets2" />
</ItemGroup>

If the AssetPack attribute is not present, the default behavior will
be to include the asset in the main application package.

If however you have a large number of assets it might be more efficient to make use of the base asset pack setting. In this scenario you update ALL assets to be in a single asset pack then use the AssetPack="base" metadata to declare which specific assets end up in the base aab file. With this you can use wildcards to move most assets into the asset pack.

<ItemGroup>
   <AndroidAsset Update="Assets/*" AssetPack="assets1" />
   <AndroidAsset Update="Assets/movie.mp4" AssetPack="base" />
   <AndroidAsset Update="Assets/some.png" AssetPack="base" />
</ItemGroup>

In this example, movie.mp4 and some.png will end up in the base aab file, but ALL the other assets
will end up in the assets1 asset pack.

Implementation Details

There are a few changes we need to make in order to support this feature.
One of the issues we will hit is the build times when dealing with large assets.
Current the assets which are to be included in the aab are COPIED
into the $(IntermediateOutputPath)assets directory. This folder is
then passed to aapt2 for the build process.

The new system adds a new directory $(IntermediateOutputPath)assetpacks.
This directory would contain a subdirectory for each pack that the
user wants to include.

assetpacks/
    assets1/
        assets/
            movie1.mp4
    feature2/
        assets/
             movie2.mp4

All the building of the pack zip file would take place in these subfolders.
The name of the pack will be based on the main "packagename" with the asset pack
name appended to the end. e.g com.microsoft.assetpacksample.assets1.

During the build process we identify ALL the AndroidAsset items which
define an AssetPack attribute. These files are then copied to the
new $(IntermediateOutputPath)assetpacks directory rather than the
existing $(IntermediateOutputPath)assets directory. This allows us to
continue to support the normal AndroidAsset behavior while adding the
new system.

Once we have collected and copied all the assets we then use the new
GetAssetPacks Task to figure out which asset packs we need to create.
We then call the CreateDynamicFeatureManifest to create a required
AndroidManifest.xml file for the asset pack. This file will end
up in the same $(IntermediateOutputPath)assetpacks directory.
We call this Task CreateDynamicFeatureManifest because it can be used
to create any feature pack if and when we get to implement full feature
packs.

assetpacks/
    assets1/
        AndroidManifest.xml
        assets/
            movie1.mp4
    feature2/
        AndroidManifest.xml
        assets/
             movie2.mp4

We can then call aapt2 to build these packs into .zip files. A new
task Aapt2LinkAssetPack takes care of this. This is a special version
of Aapt2Link which implements linking for asset packs only.
It also takes care of a few problems which aapt2 introduces. For some
reason the zip file that is created has the AndroidManifest.xml file
in the wrong place. It creates it in the root of the zip file, but the
bundletool expects it to be in a manifest directory.
bundletool will error out if its not in the right place.
So Aapt2LinkAssetPack takes care of this for us. It also removes a
resources.pb which gets added. Again, bundletool will error if this
file is in the zip file.

Once the zip files have been created they are then added to the
AndroidAppBundleModules ItemGroup. This will ensure that when the
final .aab file is generated they are included as asset packs.

@dellis1972 dellis1972 force-pushed the androidassets branch 2 times, most recently from 804967b to cc43575 Compare January 12, 2024 22:06
@dellis1972 dellis1972 marked this pull request as ready for review January 15, 2024 21:37
@dellis1972 dellis1972 changed the title AssetPack Support [WIP] AssetPack Support Jan 16, 2024
@dellis1972 dellis1972 force-pushed the androidassets branch 2 times, most recently from 7a2d771 to 207ed62 Compare January 17, 2024 09:35
@dellis1972
Copy link
Contributor Author

dellis1972 commented Jan 17, 2024

We should add support for doing this.

<ItemGroup>
    <AndroidAsset Update="**/*" AssetPack="asset1" />
   <AndroidAsset Update="Assets/foo.txt" AssetPack="base" />
</ItemGroup>

or have some way to "unset" the value. The reasoning is that you might have ALLOT of assets you want to include in an default "installtime" pack. But only select a few which will end up in the main pack.

@tomspilman
Copy link

This looks great. We use the hack mentioned to make a InstallTime asset pack. This could replace that hack for us.

@dellis1972 dellis1972 force-pushed the androidassets branch 2 times, most recently from 37b5171 to f900a76 Compare February 5, 2024 15:39
@dellis1972 dellis1972 force-pushed the androidassets branch 4 times, most recently from b0f5393 to 8e0d89b Compare February 19, 2024 11:14
Copy link
Member

@jonathanpeppers jonathanpeppers left a comment

Choose a reason for hiding this comment

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

Overall my comments are pretty minimal, LGTM. 👍

@dellis1972 dellis1972 force-pushed the androidassets branch 2 times, most recently from 1d5c2e1 to 7d5ec40 Compare February 21, 2024 16:11
case "AssetPack":
GenerateAssetPackManifest (doc);
break;
default:
Copy link
Member

Choose a reason for hiding this comment

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

Should there be a case "featurepack" here as well?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

At some point I hope there will be a feture pack, we have a number of hoops to go through to get that working. But its just not supported yet.

@jonpryor
Copy link
Member

@dellis1972: are there any constraints on asset pack names / the value of %(AndroidAsset.AssetPack)? I would assume that it can consist of only ASCII characters: no Emoji, no Unicode or latin1. Is that correct? If so, should we check for that within <GetAssetPacks/>?

@dellis1972
Copy link
Contributor Author

@dellis1972: are there any constraints on asset pack names / the value of %(AndroidAsset.AssetPack)? I would assume that it can consist of only ASCII characters: no Emoji, no Unicode or latin1. Is that correct? If so, should we check for that within <GetAssetPacks/>?

Probably the same rules as an apk I guess.

@jonpryor
Copy link
Member

testing locally, and tried:

    <AndroidAsset Update="Assets\movie-install.mp4" AssetPack="install-movies" DeliveryType="InstallTime" />

It errors out:

obj/Release/net9.0-android/assetpacks/demand-movies/AndroidManifest.xml(2): error APT2015: attribute 'split' in <manifest> tag is not a valid split name.

@jonpryor
Copy link
Member

Draft commit message:

Context: https://github.com/xamarin/xamarin-android/issues/4810

Google Android began supporting splitting up the app package into
multiple "packs" with the introduction of the `aab` package format.
Each "pack" can be downloaded to the device either at install time or
on-demand. This allows application developers to save space and
install time by only installing the required parts of the app
initially, then installing other packs as required.

There are two types of packs: Feature packs and Asset packs.

*Feature* pack contains *non-native* Java code and other resources.
Java code in feature packs can be launched via
`Context.StartActivity()`.  Currently, .NET Android cannot support
Feature packs, as Feature packs cannot contain native code.

*Asset* packs contain *only* `@(AndroidAsset)`s.  They *cannot* contain
any code or other resources.  This type of pack can be installed at
install-time, fast-follow, or ondemand.  It is most useful for apps
which contain a lot of Assets, such as Games or Multi Media applications.

Consider a default Android app with an asset:

	% dotnet new android
	% mkdir Assets
	% touch Assets/movie1.mp4
	% dotnet build -c Release
	% unzip -l bin/Release/net9.0-android/*-Signed.aab | grep -Ei 'asset|movie'
	        0  01-01-2010 00:00   base/assets/movie1.mp4
	
	       12  01-01-2010 00:00   base/assets.pb

Note that assets are listed within the `.aab`, within the
`base/assets` directory.

Add support for the following metadata items:

  * `%(AndroidAsset.AssetPack)`: the *name* of the asset pack that
    should contain the asset.
  * `%(AndroidAsset.DeliveryType)`: How the named asset pack should
    be installed.

When `%(AndroidAsset.AssetPack)` is specified, the asset is placed into
a different directory within the `.aab` file.  For example, add
additional assets:

	% touch Assets/movie-install.mp4
	% touch Assets/movie-follow.mp4
	% touch Assets/movie-demand.mp4

Then update the `.csproj` to specify `%(AssetPack)` and
`%(DeliveryType)`:

	<ItemGroup>
	  <AndroidAsset Update="Assets\movie-install.mp4" AssetPack="install_movies" DeliveryType="InstallTime" />
	  <AndroidAsset Update="Assets\movie-follow.mp4"  AssetPack="follow_movies"  DeliveryType="FastFollow" />
	  <AndroidAsset Update="Assets\movie-demand.mp4"  AssetPack="demand_movies"  DeliveryType="OnDemand" />
	</ItemGroup>

Now we see:

	% dotnet build -c Release
	% unzip -l bin/Release/net9.0-android/*-Signed.aab | grep -Ei 'asset|movie'
	        0  01-01-2010 00:00   base/assets/movie1.mp4
	       12  01-01-2010 00:00   base/assets.pb
	        0  01-01-2010 00:00   demand_movies/assets/movie-demand.mp4
	      644  01-01-2010 00:00   demand_movies/manifest/AndroidManifest.xml
	       12  01-01-2010 00:00   demand_movies/assets.pb
	        0  01-01-2010 00:00   follow_movies/assets/movie-follow.mp4
	      646  01-01-2010 00:00   follow_movies/manifest/AndroidManifest.xml
	       12  01-01-2010 00:00   follow_movies/assets.pb
	        0  01-01-2010 00:00   install_movies/assets/movie-install.mp4
	      648  01-01-2010 00:00   install_movies/manifest/AndroidManifest.xml
	       12  01-01-2010 00:00   install_movies/assets.pb

The `AndroidManifest.xml` files tell the Google Play Store how they
asset packs should be deployed.

@jonpryor jonpryor merged commit 4a1cfc1 into dotnet:main Mar 15, 2024
47 checks passed
@dellis1972 dellis1972 deleted the androidassets branch March 15, 2024 15:38
grendello added a commit that referenced this pull request Mar 15, 2024
* main:
  [Xamarin.Android.Build.Tasks] %(AndroidAsset.AssetPack) Support (#8631)
grendello added a commit that referenced this pull request Mar 15, 2024
* main:
  [Xamarin.Android.Build.Tasks] Make all assemblies RID-specific (#8478)
  Localized file check-in by OneLocBuild Task (#8813)
  [Xamarin.Android.Build.Tasks] %(AndroidAsset.AssetPack) Support (#8631)
  [runtime] Remove the last vestiges of desktop builds (#8810)
  [ci] Don't auto-retry APK test suites. (#8811)
  [Microsoft.Android.Templates] Update EN l10n template strings (#8808)
  Bump to xamarin/Java.Interop/main@651de42 (#8809)
grendello added a commit that referenced this pull request Mar 20, 2024
* main:
  LEGO: Merge pull request 8818
  Bump to dotnet/installer@b40c44502d 9.0.100-preview.3.24165.20 (#8817)
  Bump com.android.tools:r8 from 8.2.47 to 8.3.37 (#8816)
  [Mono.Android] Prevent NullPointerException in TranslateStackTrace (#8795)
  Localized file check-in by OneLocBuild Task (#8815)
  [Xamarin.Android.Build.Tasks] Make all assemblies RID-specific (#8478)
  Localized file check-in by OneLocBuild Task (#8813)
  [Xamarin.Android.Build.Tasks] %(AndroidAsset.AssetPack) Support (#8631)
grendello added a commit that referenced this pull request Mar 20, 2024
* main:
  LEGO: Merge pull request 8818
  Bump to dotnet/installer@b40c44502d 9.0.100-preview.3.24165.20 (#8817)
  Bump com.android.tools:r8 from 8.2.47 to 8.3.37 (#8816)
  [Mono.Android] Prevent NullPointerException in TranslateStackTrace (#8795)
  Localized file check-in by OneLocBuild Task (#8815)
  [Xamarin.Android.Build.Tasks] Make all assemblies RID-specific (#8478)
  Localized file check-in by OneLocBuild Task (#8813)
  [Xamarin.Android.Build.Tasks] %(AndroidAsset.AssetPack) Support (#8631)
  [runtime] Remove the last vestiges of desktop builds (#8810)
  [ci] Don't auto-retry APK test suites. (#8811)
  [Microsoft.Android.Templates] Update EN l10n template strings (#8808)
  Bump to xamarin/Java.Interop/main@651de42 (#8809)
  [Mono.Android] is now "trimming safe" (#8778)
  [Mono.Android] Fix missing enum issues that cause BG8800 warnings. (#8707)
  Bump external/Java.Interop from `3436a30` to `5bca8ad` (#8803)
  Bump to xamarin/monodroid@77124dc1 (#8804)
  Bump to dotnet/installer@e911f5c82c 9.0.100-preview.3.24161.2 (#8802)
  Bump to xamarin/Java.Interop/main@3436a30 (#8799)
  [templates] Remove redundant "template" from display name. (#8773)
grendello added a commit that referenced this pull request Mar 20, 2024
* main:
  LEGO: Merge pull request 8818
  Bump to dotnet/installer@b40c44502d 9.0.100-preview.3.24165.20 (#8817)
  Bump com.android.tools:r8 from 8.2.47 to 8.3.37 (#8816)
  [Mono.Android] Prevent NullPointerException in TranslateStackTrace (#8795)
  Localized file check-in by OneLocBuild Task (#8815)
  [Xamarin.Android.Build.Tasks] Make all assemblies RID-specific (#8478)
  Localized file check-in by OneLocBuild Task (#8813)
  [Xamarin.Android.Build.Tasks] %(AndroidAsset.AssetPack) Support (#8631)
  [runtime] Remove the last vestiges of desktop builds (#8810)
  [ci] Don't auto-retry APK test suites. (#8811)
  [Microsoft.Android.Templates] Update EN l10n template strings (#8808)
  Bump to xamarin/Java.Interop/main@651de42 (#8809)
  [Mono.Android] is now "trimming safe" (#8778)
  [Mono.Android] Fix missing enum issues that cause BG8800 warnings. (#8707)
  Bump external/Java.Interop from `3436a30` to `5bca8ad` (#8803)
  Bump to xamarin/monodroid@77124dc1 (#8804)
  Bump to dotnet/installer@e911f5c82c 9.0.100-preview.3.24161.2 (#8802)
  Bump to xamarin/Java.Interop/main@3436a30 (#8799)
grendello added a commit that referenced this pull request Mar 20, 2024
* main:
  LEGO: Merge pull request 8818
  Bump to dotnet/installer@b40c44502d 9.0.100-preview.3.24165.20 (#8817)
  Bump com.android.tools:r8 from 8.2.47 to 8.3.37 (#8816)
  [Mono.Android] Prevent NullPointerException in TranslateStackTrace (#8795)
  Localized file check-in by OneLocBuild Task (#8815)
  [Xamarin.Android.Build.Tasks] Make all assemblies RID-specific (#8478)
  Localized file check-in by OneLocBuild Task (#8813)
  [Xamarin.Android.Build.Tasks] %(AndroidAsset.AssetPack) Support (#8631)
grendello added a commit that referenced this pull request Mar 20, 2024
* main: (99 commits)
  LEGO: Merge pull request 8818
  Bump to dotnet/installer@b40c44502d 9.0.100-preview.3.24165.20 (#8817)
  Bump com.android.tools:r8 from 8.2.47 to 8.3.37 (#8816)
  [Mono.Android] Prevent NullPointerException in TranslateStackTrace (#8795)
  Localized file check-in by OneLocBuild Task (#8815)
  [Xamarin.Android.Build.Tasks] Make all assemblies RID-specific (#8478)
  Localized file check-in by OneLocBuild Task (#8813)
  [Xamarin.Android.Build.Tasks] %(AndroidAsset.AssetPack) Support (#8631)
  [runtime] Remove the last vestiges of desktop builds (#8810)
  [ci] Don't auto-retry APK test suites. (#8811)
  [Microsoft.Android.Templates] Update EN l10n template strings (#8808)
  Bump to xamarin/Java.Interop/main@651de42 (#8809)
  [Mono.Android] is now "trimming safe" (#8778)
  [Mono.Android] Fix missing enum issues that cause BG8800 warnings. (#8707)
  Bump external/Java.Interop from `3436a30` to `5bca8ad` (#8803)
  Bump to xamarin/monodroid@77124dc1 (#8804)
  Bump to dotnet/installer@e911f5c82c 9.0.100-preview.3.24161.2 (#8802)
  Bump to xamarin/Java.Interop/main@3436a30 (#8799)
  [templates] Remove redundant "template" from display name. (#8773)
  Bump to xamarin/Java.Interop/main@a7e09b7 (#8793)
  ...
@github-actions github-actions bot locked and limited conversation to collaborators Apr 15, 2024
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

6 participants