What I Learned Reading Unity's BuildReport After Building 16 APKs
The First Surprise: There Was No Single Build Size
My first instinct was to look for "the build size" in Unity's BuildReport. That turned out to be the wrong mental model. In the final measured Android build, I had three different size numbers:
| Metric | Value |
|---|---|
| Final APK file size | 17.10 MiB |
BuildReport.summary.totalSize |
143.54 MiB |
Sum of BuildReport.packedAssets entries |
5.60 MiB |
At first glance that looks suspicious. How can the same build be 17 MiB, 143 MiB, and 5.6 MiB? The answer is that they are not measuring the same thing.
- APK bytes: the compressed artifact users download
BuildReport.summary.totalSize: Unity's reported build output size- Packed asset sum: serialized asset data inside packed build files
Once I separated those three, the rest of the experiment became much easier to reason about.
The Smallest Parser I Needed
I did not start by building a polished UI. I only wanted a repeatable text report after every build. Unity's IPostprocessBuildWithReport was enough for that.
using UnityEditor.Build;
using UnityEditor.Build.Reporting;
internal sealed class BuildAssetSizeReporter : IPostprocessBuildWithReport
{
public int callbackOrder => 1000;
public void OnPostprocessBuild(BuildReport report)
{
var rankedAssets = CollectAssetSizes(report);
var packedAssetBytes = rankedAssets.Aggregate(
0UL, (total, asset) => total + asset.Value);
var artifactBytes = GetArtifactBytes(report.summary.outputPath);
LogSummary(report, rankedAssets, packedAssetBytes, artifactBytes);
WriteReports(report, rankedAssets, packedAssetBytes, artifactBytes);
}
}
The full source is here: https://github.com/hellohanostudio/BuildAnalyzer/blob/main/Assets/BuildAnalyzer/Editor/BuildAssetSizeReporter.cs
The important part is not the hook itself. It is what I decided to record:
- The final APK file size from disk
BuildReport.summary.totalSize- A ranked list of packed asset entries
I wanted the report to make it hard for me to mix those up later.
Aggregating Packed Assets
BuildReport.packedAssets contains the packed files generated during the build. Each packed file contains PackedAssetInfo entries. The fields I used were:
sourceAssetPathsourceAssetGUIDpackedSize
One thing I had to handle: the same source asset can show up more than once. Unity is reporting packed entries, not a neat "one source file, one row" table. So I grouped by source path before sorting.
private static KeyValuePair<string, ulong>[] CollectAssetSizes(BuildReport report)
{
var sizesByPath = new Dictionary<string, ulong>(StringComparer.Ordinal);
foreach (var packedFile in report.packedAssets)
{
foreach (var asset in packedFile.contents)
{
var path = string.IsNullOrEmpty(asset.sourceAssetPath)
? $"<generated: {asset.sourceAssetGUID}>"
: asset.sourceAssetPath;
sizesByPath.TryGetValue(path, out var currentSize);
sizesByPath[path] = currentSize + asset.packedSize;
}
}
return sizesByPath
.OrderByDescending(pair => pair.Value)
.ThenBy(pair => pair.Key, StringComparer.Ordinal)
.ToArray();
}
This was enough to check the asset-shaped parts of the experiment:
- Did the texture really shrink after ASTC?
- Did the audio data shrink after Vorbis?
- Did the Korean font subset remove real packed bytes?
For those questions, the parser was useful right away.
I Still Measured the APK Directly
I did not use BuildReport.summary.totalSize as the APK size. For the artifact number, I read the file on disk:
private static ulong? GetArtifactBytes(string outputPath)
{
return File.Exists(outputPath)
? (ulong)new FileInfo(outputPath).Length
: null;
}
That produced a plain summary like this:
artifact_bytes=17928357
artifact_mib=17.0978
build_report_total_bytes=150514033
build_report_total_mib=143.5414
packed_asset_bytes=5873204
packed_asset_mib=5.6011
This is not fancy, but that is the point. I wanted boring measurement code because boring code is harder to argue with.
The CSV Had a Small Surprise
The parser also writes a ranked CSV. This is the top of Results/raw/step-07-dotween-runtime-assets.csv from the final experiment step:
rank,packed_bytes,packed_mib,source_asset_path
1,2796376,2.666832,"Built-in Texture2D: Splash Screen Unity Logo"
2,1871556,1.784855,"Assets/Experiment/Generated/ExperimentTexture.png"
3,656420,0.626011,"Resources/unity_builtin_extra"
4,477252,0.455143,"Assets/Experiment/Generated/ExperimentAudio.wav"
5,69510,0.066290,"Assets/Experiment/Generated/NotoSansKR-Regular.ttf"
The biggest packed texture was not even my generated test texture. It was Unity's splash-screen logo. That was a useful reminder. A ranked build report is not a list of "my files only". It includes built-in assets Unity packs into the player too.
The raw reports are here: https://github.com/hellohanostudio/BuildAnalyzer/tree/main/Results/raw
The Place Where the Parser Failed
The parser explained textures, audio, and fonts pretty well. Then I added DOTween. DOTween Free increased the APK by 0.46 MiB. But the packed-asset CSV attributed only 192 bytes to the DOTween DLL path.
That was the moment the shape of the problem changed for me. The parser was not "wrong". It was just looking through one narrow window. DOTween affected generated code and metadata, so most of the growth appeared in files such as libil2cpp.so and global-metadata.dat, not in BuildReport.packedAssets.
That was probably the most useful result of the whole parser exercise: it showed me exactly where the parser stopped being enough.
What I Would Use This For
After this experiment, I would use this kind of parser for asset questions:
- Which texture is taking packed space?
- Did an importer setting change actually reduce build data?
- How much did a font contribute?
- Did one source asset appear through multiple packed entries?
I would not use it alone for code-size questions:
- How much did a plugin increase IL2CPP output?
- Which assemblies affected native binary size?
- Did managed stripping change generated code size?
- Which files inside the APK changed?
For that, I need to inspect the final artifact too.
The Rule I Kept
The main thing I kept from this week is simple: Do not say "build size" as if it is one number. For every build, I now want these three values side by side:
- Final artifact bytes from the file on disk
BuildReport.summary.totalSize- Sum of
BuildReport.packedAssets[].contents[].packedSize
Then I use the packed-asset ranking as evidence, not as the final truth. That small separation prevented the biggest mistake I could have made in the experiment: confusing what Unity packed as assets with what users actually download.
Sources
- Unity API: BuildReport.packedAssets
- Unity API: PackedAssetInfo
- Unity API: BuildSummary.totalSize
- Reproducible project: https://github.com/hellohanostudio/BuildAnalyzer
- Previous article: https://dev.to/hanostudio/what-actually-makes-a-unity-build-so-large-3jh4
Comments
No comments yet. Start the discussion.