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
210 changes: 210 additions & 0 deletions Documentation/guides/AndroidAssetPacks.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
# Android Asset Packs

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 `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.
See the [documentation](https://developer.android.com/guide/playcore/asset-delivery) for details on how this all works.

## Asset Pack Specification

We want to provide our users the ability to use `Asset` packs without
having rely on hacks provided by the community.

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

```xml
<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. If the `AssetPack` attribute is not present, the default behavior will be to include the asset in the main application package.
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.

```xml
<ItemGroup>
<AndroidAsset Update="Asset/movie1.mp4" />
<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`.
The `DeliveryType` attribute will be picked up from the first item which has it
for a specified `AssetPack`. For example the `DeliveryType` attribute in the
code below will be applied to the items for `AssetPack` `assets1`, it will not be applied
to the other `packs` or the `base` pack.

```xml
<ItemGroup>
<AndroidAsset Update="Asset/movie1.mp4" />
<AndroidAsset Update="Asset/movie2.mp4" AssetPack="assets1" DeliveryType="InstallTime" />
<AndroidAsset Update="Asset/movie3.mp4" AssetPack="assets1" />
<AndroidAsset Update="Asset/movie4.mp4" AssetPack="assets2" />
</ItemGroup>
```

See Google's [documentation](https://developer.android.com/guide/playcore/asset-delivery#asset-updates) for details on what each of the `DeliveryType` values do.

If however you have a large number of assets it might be cleaner in the csproj to make use of the `base` value for the `AssetPack` attribute. 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.

```xml
<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.

At this time @(AndroidAsset) build action does not support 'AssetPack' or 'DeliveryType' Metadata in Library Projects.

NOTE: `AssetPacks` are only used when the `AndroidPackageFormat` is set to `aab` (the default for Release). When using the `apk` setting the assets will be placed inside the `apk`.

## Release Configuration

In order for the application to function correctly we need to inform the `R8` linker which java classes we need to keep. To do this we need to add the following lines to a `ProGuard.cfg` file which is in the root of our project folder.

```
-keep com.google.android.play.*
```

Alternatively you can create a file called `ProGuard.cfg` and use the [@(ProguardConfiguration)](~/android/deploy-test/building-apps/build-items.md#proguardconfiguration) built action.
Adding these lines will ensure that all the required java components are not linked away during the Release build.

## Testing and Debugging

In order to test your asset packs in the `Debug` configuration, you will need to make some changes to your `.csproj`. Firstly we need to change the `AndroidPackageFormat` to `aab`. It will be `aab` by default for `Release` builds, but will default to `apk` for `Debug` builds. Setting the `AndroidPackageFormat` to `aab` will disable
fast deployment, so it is advised that you only do this when you need to test your `AssetPacks`.

To test your asset packs add the following to the first `PropertyGroup` in your `.csproj`.

```xml
<AndroidPackageFormat>aab</AndroidPackageFormat>
<AndroidBundleToolExtraArgs Condition=" '$(Configuration)' == 'Debug' ">--local-testing $(AndroidBundleToolExtraArgs)</AndroidBundleToolExtraArgs>
```

The `--local-testing` argument tells the `bundletool` application to install ALL the asset packs in a local cache on the device. `InstallTime` packs will be installed during the app installation process.

`FastFollow` packs behave like `OnDemand` packs. They will not automatically installed when the game is sideloaded. You will need to request them manually when the game starts.

For more details see [https://developer.android.com/guide/playcore/asset-delivery/test](https://developer.android.com/guide/playcore/asset-delivery/test).

## Implementation Details
dellis1972 marked this conversation as resolved.
Show resolved Hide resolved

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.

```dotnetcli
assetpacks/
assets1/
assets/
movie2.mp4
assets2/
assets/
movie3.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.

```dotnetcli
assetpacks/
assets1/
AndroidManifest.xml
assets/
movie2.mp4
assets2/
AndroidManifest.xml
assets/
movie3.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.

## Alternative Methods

An alternative method is available on [github](https://github.com/infinitespace-studios/MauiAndroidAssetPackExample).
This method allows developers to place additional assets in a special
[NoTargets](https://github.com/microsoft/MSBuildSdks/blob/main/src/NoTargets/README.md) project. This project is built just after the final `aab` is
produced. It builds a zip file which is then added to the `@(Modules)`
ItemGroup in the main application. This zip is then included into the
final app as an additional feature.

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.
31 changes: 31 additions & 0 deletions Documentation/guides/building-apps/build-items.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,37 @@ or library project is built.
Supports [Android Assets](https://developer.android.com/guide/topics/resources/providing-resources#OriginalFiles),
files that would be included in the `assets` folder in a Java Android project.

Starting with .NET 9 the `AndroidAsset` ItemGroup also supports additional metadata for generating [Asset Packs](https://developer.android.com/guide/playcore/asset-delivery). Adding the `AssetPack` attribute to and `AndroidAsset` will automatically generate an asset pack of that name. This feature is only supported when the [`$(AndroidPackageFormat)`](#androidpackageformat) is set to `.aab`. The following example will place `movie2.mp4` and `movie3.mp4` in separate asset packs.

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

This feature can be used to include large files in your application which would normally exceed the max
package size limits of Google Play.

If you have a large number of assets it might be more efficient to make use of the `base` asset pack.
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.

```xml
<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.

The additional metadata is only supported on .NET Android 9 and above.

## AndroidAarLibrary

The Build action of `AndroidAarLibrary` should be used to directly
Expand Down
1 change: 1 addition & 0 deletions Documentation/guides/building-apps/build-process.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ Extension points include:

- [`$(AfterGenerateAndroidManifest)](~/android/deploy-test/building-apps/build-properties.md#aftergenerateandroidmanifest)
- [`$(BeforeGenerateAndroidManifest)](~/android/deploy-test/building-apps/build-properties.md#beforegenerateandroidmanifest)
- [`$(BeforeBuildAndroidAssetPacks)`](~/android/deploy-test/building-apps/build-properties.md#beforebuildandroidassetpacks)

A word of caution about extending the build process: If not
written correctly, build extensions can affect your build
Expand Down
19 changes: 19 additions & 0 deletions Documentation/guides/building-apps/build-properties.md
Original file line number Diff line number Diff line change
Expand Up @@ -832,6 +832,18 @@ APK root directory. The format of the path is `lib\ARCH\wrap.sh` where
+ `x86_64`
+ `x86`

## AndroidIncludeAssetPacksInPackage

This property controls if an Asset Packs build automatically are auto
included in the final `.aab` file. It will default to `true`.
dellis1972 marked this conversation as resolved.
Show resolved Hide resolved

In certain cases the user might want to release an interim release. In
these cases the user does not NEED to update the asset pack. Especially
if the contents of the asset pack have not changed. This property allows
the user to skip the asset packs if they are not required.

Added in .NET 9

## AndroidInstallJavaDependencies

The default value is `true` for command line builds. When set to `true`, enables
Expand Down Expand Up @@ -1674,6 +1686,13 @@ as support for `$(AotAssemblies)` will be removed in a future release.

Extra options to pass to `aprofutil`.

## BeforeBuildAndroidAssetPacks

MSBuild Targets listed in this
property will run directly before the `AssetPack` items are built.

Added in .NET 9

## BeforeGenerateAndroidManifest

MSBuild Targets listed in this
Expand Down
2 changes: 2 additions & 0 deletions Documentation/guides/messages/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -100,6 +100,8 @@ package from all the users on device and try again. If that does not work you ca
Fast Deployment is not currently supported on this device.
Please file an issue with the exact error message using the 'Help->Send Feedback->Report a Problem' menu item in Visual Studio
or 'Help->Report a Problem' in Visual Studio for Mac.
+ [XA0138](xa0138.md): %(AndroidAsset.AssetPack) and %(AndroidAsset.AssetPack) item metadata are only supported when `$(AndroidApplication)` is `true`.
+ [XA0139](xa0139.md): `@(AndroidAsset)` `{0}` has invalid `DeliveryType` metadata of `{1}`. Supported values are `installtime`, `ondemand` or `fastfollow`

## XA1xxx: Project related

Expand Down
13 changes: 13 additions & 0 deletions Documentation/guides/messages/xa0138.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
title: Xamarin.Android error XA0138
description: XA0138 error code
ms.date: 02/05/2024
---
# Xamarin.Android error XA0138

## Issue

%(AndroidAsset.AssetPack) and %(AndroidAsset.AssetPack) item metadata are only supported when `$(AndroidApplication)` is `true`.

## Solution

Remove the 'AssetPack' or 'DeliveryType' Metadata from your `AndroidAsset` build Items in the project the error was raised for.
13 changes: 13 additions & 0 deletions Documentation/guides/messages/xa0139.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
title: Xamarin.Android error XA0138
description: XA0138 error code
ms.date: 02/05/2024
---
# Xamarin.Android error XA0138

## Issue

`@(AndroidAsset)` `{0}` has an invalid `DeliveryType` metadata of `{1}`. Supported values are `installtime`, `ondemand` or `fastfollow`.

## Solution

Make sure that all `DeliveryType` attributes are one of the following valid values, `installtime`, `ondemand` or `fastfollow`.
1 change: 1 addition & 0 deletions build-tools/installers/create-installers.targets
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Aapt2.targets" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Analysis.targets" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Application.targets" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Assets.targets" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Bindings.ClassParse.targets" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Bindings.Core.targets" />
<_MSBuildFiles Include="$(MicrosoftAndroidSdkOutDir)Xamarin.Android.Bindings.Maven.targets" />
Expand Down
Loading