Skip to content

ADFA-1167: Recompress APK deflated entries with advzip (7z deflate, optional zopfli) - #1565

Merged
jomen-adfa merged 4 commits into
stagefrom
task/ADFA-1167-zopfli
Jul 23, 2026
Merged

ADFA-1167: Recompress APK deflated entries with advzip (7z deflate, optional zopfli)#1565
jomen-adfa merged 4 commits into
stagefrom
task/ADFA-1167-zopfli

Conversation

@jomen-adfa

@jomen-adfa jomen-adfa commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Jira: ADFA-1167 (Epic ADFA-561, APK diet)

What

recompressApk now compresses the APK's deflatable entries with advzip (AdvanceCOMP) instead of Java's Deflater:

  • Default: advzip -z -3 — 7-Zip's deflate encoder. Output is still standard deflate; only the encoder searches harder.
  • -Pzopfli: switches to advzip -4 -i 5 (zopfli) for an extra ~50 KB at ~4x the CPU time.
  • The final APK is written by a minimal raw zip writer that injects the pre-compressed streams (ZipOutputStream cannot emit raw deflate data). Zeroed timestamps and deterministic output are preserved; re-signing is unchanged.

Scope / gating

  • Applies to release APKs (local and CI) and all CI builds including CI debug. Local dev debug builds are unaffected (they never run recompressApk, and even a manual run skips advzip for debug off-CI).
  • No advzip installed → Deflater BEST_COMPRESSION fallback, byte-equivalent to the previous behavior. The build never breaks over the missing tool.
  • noCompress/STORED handling is untouched: stored entries (.br, .zip, .ttf, ...) are never handed to advzip and were verified byte-identical.
  • recompressApk can now run standalone against an already-built APK: ./gradlew :app:recompressApk -PrecompressAbi=v8 -PrecompressBuildName=release.
  • debug.yml and release.yml gain an idempotent apt-get install advancecomp step (persistent self-hosted runners keep it after the first run). Follow-up: move advancecomp into the flox/base manifest once the ADFA-1938 asset work lands (its manifest.lock is currently in flight).

Measurements (v8 APKs)

Build Before After Saved Task time
Release (-3, default) 744.36 MB 742.21 MB 2.14 MB 90 s
Release (-Pzopfli) 744.36 MB 742.17 MB 2.19 MB ~5 min
CI debug (-3) 79.68 MB 76.72 MB 2.97 MB +~2.3 min

Zopfli iteration count is near-irrelevant (i=1 vs i=15: 0.05% on classes.dex), hence the fixed -i 5. Repeat runs are byte-identical (deterministic).

Verification

  • Entry-by-entry comparison against the original APKs: all names identical, zero uncompressed-size/CRC mismatches, stored payload byte-identical (693 MB on release).
  • apksigner verify passes with the same scheme profile as before (v3).
  • Release APK installed and launched on an arm64 emulator (Pixel 9 Pro AVD): process stays alive, editor activity resumes, no fatal logcat entries.
  • CI debug path exercised locally with GITHUB_ACTIONS=true; Deflater fallback path exercised with advzip absent semantics (debug off-CI).
  • spotlessCheck green.

🤖 Generated with Claude Code

recompressApk now pipes deflatable entries through a single advzip -z -3
run (7-Zip's deflate encoder) and writes the APK with a minimal raw zip
writer that injects the pre-compressed streams, since ZipOutputStream
cannot emit raw deflate data. -Pzopfli switches to advzip -4 -i 5.

- Applies to release APKs and CI builds (including CI debug); local dev
  debug builds are unaffected. Without advzip on the machine the task
  falls back to Deflater BEST_COMPRESSION, matching the previous output.
- noCompress/STORED handling, zeroed timestamps, and re-signing are
  unchanged; stored payload verified byte-identical.
- recompressApk can now run standalone against a built APK via
  -PrecompressAbi and -PrecompressBuildName.
- debug.yml and release.yml install advancecomp (idempotent) so the
  self-hosted runners have advzip.

Measured on v8 APKs (content CRCs verified, apksigner passes, app
installs and launches on an arm64 emulator):
- Release: 744.36 MB -> 742.21 MB (-2.14 MB, 90 s); -2.19 MB with
  -Pzopfli (~5 min).
- CI debug: 79.68 MB -> 76.72 MB (-2.97 MB, +~2.3 min).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@claude claude Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Claude Code Review

This repository is configured for manual code reviews. Comment @claude review for a one-time review, or @claude review always to subscribe this PR to a review on every future push.

Tip: disable this comment in your organization's Code Review settings.

@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: fc05eddb-fac3-4507-a9b9-c7fbcd299aca

📥 Commits

Reviewing files that changed from the base of the PR and between 4ced3e1 and a5561e9.

📒 Files selected for processing (1)
  • app/build.gradle.kts
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/build.gradle.kts

📝 Walkthrough
  • Recompress APK deflatable entries with advzip for improved size reduction on release APKs and CI builds (including CI debug), using default args advzip -z -q -3; when zopfli is enabled, uses advzip -z -q -4 -i 5.
  • Preserve STORED and noCompress entries: noCompress extensions are written as ZipEntry.STORED while streaming the original compressed bytes from the input ZIP.
  • Deterministic ZIP rewrite via a custom minimal raw ZIP writer (no ZipOutputStream): writes DOS time/date as 0, forces a zero-padded extra field so STORED data starts on a 4-byte boundary (zipalign parity), and performs deterministic central-directory emission.
  • Keep existing APK re-signing behavior: recompressed output is signed via apksigner and then written back to the original APK path.
  • Standalone recompressApk support: accepts inputs from -PrecompressAbi / -PrecompressBuildName (or extensions.extraProperties when the task is invoked via Gradle finalizers) and fails fast with explicit error(...) messages when inputs are missing.
  • Fallback and scope: advzip is only used when buildName == "release" or running in CI (GITHUB_ACTIONS); if advzip isn’t found (or is intentionally skipped), deflatable entries fall back to Deflater.BEST_COMPRESSION. Local debug builds remain unchanged.
  • CI setup: release and CI debug workflows install advancecomp (providing advzip) idempotently—apt-get runs only when advzip is not already available.
  • Reported savings: 2.14 MB for release builds, 2.19 MB with Zopfli, and 2.97 MB for CI debug builds.
  • Verification: deterministic repeat runs, entry-level ZIP comparisons, APK signing validation, emulator install/launch, and successful spotlessCheck.
  • Risks / best-practice considerations: introduces an external advzip process + CI package-install step; increases maintenance risk due to the custom ZIP writer and its assumptions (e.g., no ZIP64 support and a hard limit on entry count); compression output may differ across advzip versions (fallback to Deflater is intended to mitigate missing advzip).

Walkthrough

The APK recompression pipeline now supports standalone parameters, advzip-based compression with raw-deflate fallback, deterministic ZIP rewriting, and per-extension storage rules. Debug and release workflows install advancecomp when advzip is unavailable.

Changes

APK recompression

Layer / File(s) Summary
Task configuration and build wiring
.github/workflows/debug.yml, .github/workflows/release.yml, app/build.gradle.kts
The task resolves build parameters, selects debug/release defaults, configures advzip usage, and ensures advancecomp is installed in CI.
Raw compression and advzip integration
app/build.gradle.kts
The pipeline discovers advzip, parses raw ZIP entries, invokes advzip for candidates, and falls back to raw deflate when needed.
Deterministic APK ZIP rewriting
app/build.gradle.kts
RawZipWriter emits deterministic ZIP records, while recompressZip applies STORE or compression rules to APK entries.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related PRs

Suggested reviewers: itsaky-adfa, davidschachteradfa

Poem

I’m a bunny with bytes in my burrow,
Advzip makes APK sizes narrow.
Raw ZIPs hop in a deterministic line,
Deflate fallback keeps builds fine.
Signed and compressed, the artifacts gleam.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly names the main change: recompressing APK deflated entries with advzip and optional zopfli.
Description check ✅ Passed The description is detailed and directly matches the APK recompression, gating, fallback, workflow, and verification changes.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch task/ADFA-1167-zopfli

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 Checkov (3.3.8)
app/build.gradle.kts

Traceback (most recent call last):
File "/usr/local/bin/checkov", line 2, in
from checkov.main import Checkov
ModuleNotFoundError: No module named 'checkov'


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/build.gradle.kts`:
- Around line 615-619: Update the APK replacement flow after signApk in the
surrounding build script to use a filesystem atomic move with replacement
semantics instead of unchecked apkFile.delete() and
tempZipFile.renameTo(apkFile). Propagate or fail clearly on move errors, and
ensure the original APK is not removed unless the replacement can be completed
safely.
- Around line 887-905: The RawZipWriter path in recompressApk must preserve
4-byte alignment for STORED APK entries before apksigner runs. Update
RawZipWriter.addEntry or the STORED entry handling around extensionOf and
noCompressExtensions to emit the required alignment padding, rather than using
zero extra-field length; do not rely on a later zipalign step unless that step
is explicitly restored before signing.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8c2f400b-7f55-416e-8b0f-0ffd3f3f72a1

📥 Commits

Reviewing files that changed from the base of the PR and between aade24f and 3478371.

📒 Files selected for processing (3)
  • .github/workflows/debug.yml
  • .github/workflows/release.yml
  • app/build.gradle.kts

Comment thread app/build.gradle.kts Outdated
Comment thread app/build.gradle.kts
jomen-adfa and others added 2 commits July 23, 2026 12:28
- Replace unchecked delete()/renameTo() with Files.move(REPLACE_EXISTING)
  so a failed replace throws instead of silently leaving no .apk.
- RawZipWriter now 4-byte aligns STORED entry data via zero-padded extra
  fields (zipalign parity), so the runtime can mmap uncompressed assets.
  Verified: all 198 stored entries aligned, content CRCs unchanged,
  apksigner passes, APK installs and launches on an arm64 emulator.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Comment thread app/build.gradle.kts Outdated
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@jomen-adfa
jomen-adfa merged commit c6b5981 into stage Jul 23, 2026
4 checks passed
@jomen-adfa
jomen-adfa deleted the task/ADFA-1167-zopfli branch July 23, 2026 07:18
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants