@vladislav_kostyukov
I fixed my issue, though it might not apply to you. My setup had a design flaw.
I used a ScriptableObject with SerializedField references to sprites for a splash screen (before Addressables initialized). Some of these sprites are also part of the asset packs (Addressable groups). The sprites were accessed directly via the ScriptableObject for the splash screen and as AssetReferenceSprite elsewhere in the code base. The ScriptableObject was Addressable and loaded through the Addressables API.
The goal was to include the SerializedField referenced sprites in the base build and the rest in asset packs, but instead it seems to preload or bundle the asset pack, that the sprite belongs to, into the base module, even though the loaded ScriptableObject only referenced the “regular” sprites, not AssetReferenceSprite.
I couldn’t find documentation on this specific issue, but mixing serialized references with Addressables might just be bad practice.
When I made the ScriptableObject non-Addressable and stopped loading it as Addressable, the preloading/bundling stopped.
So my suggestion would be to double-check if any of your asset pack Addressable assets are loaded in any way before you check the download size. Addressables.LoadAssetAsync for a single asset will also download the entire asset pack it belongs to.
I also ditched Addressables.GetDownloadSizeAsync and now fully use the AndroidAssetPacks API for checking and downloading asset packs, as it seems more robust.
Here’s my code using the AndroidAssetPacks API. It depends on UniTask but you should be able to port it to Coroutines if you don’t use UniTask. Hope it helps!
PackAvailabilityService.cs:
using Cysharp.Threading.Tasks;
using Runtime.Models;
using UnityEngine;
using UnityEngine.Android;
namespace Runtime.Services.PackAvailability
{
public class PackAvailabilityService : IPackAvailabilityService
{
async UniTask<bool> IPackAvailabilityService.IsPackDownloaded(string key)
{
GetAssetPackStateAsyncOperation stateAsyncOperation = AndroidAssetPacks.GetAssetPackStateAsync(new[] { key });
await stateAsyncOperation;
Debug.Log($"PackAvailabilityService.IsPackDownloaded '{key}' // assetPackPath: {AndroidAssetPacks.GetAssetPackPath(key)}");
Debug.Log($"PackAvailabilityService.IsPackDownloaded '{key}' // stateAsyncOperation // size: {stateAsyncOperation.size} | isDone: {stateAsyncOperation.isDone} | states.Length: {stateAsyncOperation.states.Length}");
if (stateAsyncOperation.states.Length == 0)
return false;
for (int i = 0; i < stateAsyncOperation.states.Length; i++)
{
AndroidAssetPackState state = stateAsyncOperation.states[i];
Debug.Log($"PackAvailabilityService.IsPackDownloaded '{key}' // state ({i}) // name: {state.name} | error: {state.error} | status: {state.status}");
}
return stateAsyncOperation.states[0].status == AndroidAssetPackStatus.Completed;
}
}
}
DownloadService.cs:
using System;
using System.Threading;
using Cysharp.Threading.Tasks;
using Runtime.Models;
using Runtime.Services.PackAvailability;
using UnityEngine;
using UnityEngine.Android;
using Zenject;
namespace Runtime.Services.Download
{
public class DownloadService : IDownloadService
{
[Inject]
private IPackAvailabilityService packAvailabilityService;
private bool isDownloading;
private CancellationTokenSource cancellationTokenSource;
public event Action OnDownloadSuccess;
public event Action<DownloadEventArgs> OnDownloadRequested;
public event Action<float> OnDownloadPercentChanged;
public async UniTaskVoid RequestDownloadAsync(WordPack pack)
{
Debug.Log($"DownloadService.RequestDownloadAsync: {pack.Key}");
// long size = await AddressableUtils.GetDownloadSizeAsync(pack.Key);
GetAssetPackStateAsyncOperation stateAsyncOperation = AndroidAssetPacks.GetAssetPackStateAsync(new[] { pack.Key });
await stateAsyncOperation;
OnDownloadRequested?.Invoke
(
new DownloadEventArgs
(
wordPack: pack,
downloadSizeMb: stateAsyncOperation.size / 1000000f,
networkReachability: Application.internetReachability
)
);
}
public async UniTask DownloadPackAsync(WordPack pack)
{
Debug.Log($"DownloadService.DownloadPackAsync: {pack.Key}");
if (isDownloading)
throw new InvalidOperationException("A download is already in progress.");
isDownloading = true;
cancellationTokenSource = new CancellationTokenSource();
try
{
string[] packNames = { pack.Key };
DownloadAssetPackAsyncOperation downloadOp = AndroidAssetPacks.DownloadAssetPackAsync(packNames);
while (!downloadOp.isDone)
{
if (cancellationTokenSource.Token.IsCancellationRequested)
{
AndroidAssetPacks.CancelAssetPackDownload(packNames);
throw new OperationCanceledException();
}
OnDownloadPercentChanged?.Invoke(downloadOp.progress);
await UniTask.Yield();
}
await UniTask.Yield();
GetAssetPackStateAsyncOperation stateAsyncOperation = AndroidAssetPacks.GetAssetPackStateAsync(packNames);
await stateAsyncOperation;
AndroidAssetPackState state = stateAsyncOperation.states[0];
if (state.status == AndroidAssetPackStatus.Completed)
{
OnDownloadPercentChanged?.Invoke(1f);
await packAvailabilityService.UpdatePackStatusAsync(pack);
OnDownloadSuccess?.Invoke();
}
else
{
// TODO: Add user facing error message
throw new Exception($"Something went wrong downloading pack {pack.Key}. Current pack status: {state.status}");
}
}
catch (Exception e) when (e is not OperationCanceledException)
{
Debug.LogError(e);
}
finally
{
isDownloading = false;
cancellationTokenSource.Dispose();
cancellationTokenSource = null;
}
}
public void CancelDownload()
{
if (!isDownloading || cancellationTokenSource == null)
return;
cancellationTokenSource.Cancel();
isDownloading = false;
}
public void ClearPacksCache(string[] onDemandPacks)
{
try
{
foreach (string packName in onDemandPacks)
{
AndroidAssetPacks.RemoveAssetPack(packName);
Debug.Log
(
Caching.ClearAllCachedVersions(packName)
? $"Cleared all versions of AssetBundle '{packName}'."
: $"Did not clear all versions of AssetBundle '{packName}'."
);
}
}
catch (Exception e)
{
Debug.LogError($"Clear AssetBundle Cache Exception: {e}");
}
}
}
}