Skip to content

Reduce APK size: 107.5 KiB → ~104.5 KiB (already near the floor) #181

Closed

Description

@compot-dev

Summary

The release APK is already small: v2.07 = 110,058 bytes (107.5 KiB). The app is plain Java with no runtime dependencies (only junit for tests), no native libraries, and the release build already runs R8 (minifyEnabled true, proguard-android-optimize.txt, full mode by default in AGP 8.13) with shrinkResources true. Classes are already renamed and repackaged into a/*, and resource names are obfuscated (res/hU.webp).

The usual big wins don't apply here: there is no appcompat/material to drop, no .so to strip or split per ABI, no Kotlin stdlib, and no locale resources from dependencies. What is left is our own code. Realistic target: about 104–105 KiB (−3 %). One small PR is enough, and it's fine to defer it.

Measured on the CI-built decoder-v2-2.07.apk (no local build).

Measurement

APK (compressed bytes in the zip)

Entry Stored as Size in APK Share
classes.dex (182,856 B uncompressed) deflate 48 % 94,481 85.8 %
APK Signing Block (v2 sig 1,877 + dependency info 189 + padding to 4 KiB) — 4,096 3.7 %
res/hU.webp launcher icon and TV banner, 400×400 lossy stored 3,932 3.6 %
META-INF/ v1 signature (CERT.RSA/SF, MANIFEST.MF) + AGP metadata deflate 2,539 2.3 %
resources.arsc stored 1,772 1.6 %
AndroidManifest.xml deflate 1,562 1.4 %
res/AM.xml (layout) deflate 466 0.4 %
zip local headers + central directory + EOCD — ~1,210 1.1 %
lib/, assets/, kotlin/, extra dex files — none 0

Inside classes.dex (uncompressed)

117 classes, 1,153 method refs, 669 field refs, 1,766 strings.

Section Bytes Share
code (bytecode, 450 methods) 100,630 55 %
string_data 35,971 20 %
ID tables (string/type/proto/field/method/class) 32,856 18 %
debug_info (21 items) 5,610 3 %
class_data, type_list, map, header 7,789 4 %

Biggest single string: the DebugServer web page (<!doctype html>…), 7,952 B raw, about 3.25 KB deflated. The other human-readable strings (log and UI messages) add about 7 KB raw. The rest of the code is spread across our own sources (Decoder.java alone is about 2,200 lines).

Ideas, ranked by saving

# Idea Estimated saving Risk / notes
1 Re-encode the launcher icon / TV banner at 192–256 px, lossy WebP q≈80 (it is 400×400 now). Measured with Pillow: 192 px q80 = 2,408 B, 160 px q80 = 1,970 B −1.5…−2.0 KB (−1.4…−1.8 %) Low. The same bitmap is used as android:banner, so check how it looks in the leanback launcher on the AG-216 / Allwinner boxes before going below 256 px. A vector icon (API 21 has VectorDrawable) could be smaller still, but old TV launchers may render it differently, so it needs a device check
2 Minify the DebugServer HTML/CSS/JS (indentation, comments). Keep the readable source and minify it in a small Gradle task, or by hand if it stays short −0.6…−1.0 KB (−0.6…−0.9 %) Low for the app, but a build-time step adds upkeep. Moving it to assets/ (gzipped or not) saves nothing: the dex is already deflated
3 Drop the AGP metadata from the APK: dependenciesInfo { includeInApk = false } and vcsInfo.include = false ~0.1 KB in the zip. The 189 B dependency block is absorbed by the 4 KiB signing-block padding, so about 0 there None. Optional tidy-up only, not worth a PR of its own
4 Strip Log.d/Log.v with -assumenosideeffects ~0.3 KB Not recommended: LogBuffer reads the app's own logcat lines for the /log debug endpoint (#96), so this would drop diagnostics
5 Remove the v1 (JAR) signature (v1SigningEnabled false) ~2.5 KB Not possible: minSdk 21 (Android 5–6 TV boxes) needs v1. It only becomes possible with minSdk 24+
6 ABI splits / AAB / localeFilters / useLegacyPackaging / dropping appcompat or material / Kotlin metadata 0 Not applicable: no native libs, no dependencies, one default locale, Java only
7 Refactor or deduplicate code (e.g. split Decoder.java) for size uncertain, likely <2 KB Not worth it for size alone. R8 already inlines and removes dead code

Realistic total: about 2.5–3 KB, so 107.5 KiB → ~104.5 KiB (−3 %).

Related finding (not about size, but needed before any R8 change)

The CI workflow does not archive mapping.txt. Release stack traces (from Crash.java / logcat) show only obfuscated names like a.a0, and nobody can retrace them. Proposal: upload android/app/build/outputs/mapping/release/mapping.txt as a workflow artifact, or as a release asset next to the APK. APK size impact: 0.

Do not add -keepattributes SourceFile,LineNumberTable for readable traces without weighing it: that adds a few KB. The mapping file is the cheaper option.

Proposed PRs (in order)

  1. CI: archive the R8 mapping.txt per build/release (workflow only, no app change, so no version bump).
  2. App: smaller icon/banner + minified DebugServer HTML (+ optional AGP metadata flags). One PR, ~−3 KB. Check the icon and banner on a TV box (leanback launcher) and a phone launcher, and check that the debug web page still works. As with every app PR on v2: versionName +0.01, versionCode +1.

Everything else above is either not applicable or not worth the risk for an APK this size. If nobody needs the extra 3 %, closing this as "already near the floor" is also a valid result.

Activity

  1. added 3 commits that reference this issue on Oct 15, 2026
  2. compot-dev commented on Oct 15, 2026

    @compot-dev
    CollaboratorAuthor

    Done in #183 and #184 (v2.08).

    Checked: the icon on the TECNO KM4 launcher and the Decoder tile in the Android TV app list on the AG-216 are sharp, and the debug page works.

    The APK is now close to its floor, so nothing more is planned here.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions