Skip to content

feat(diff): emit copiesCrc for content-based resource matching (fix AAB image hot update)#54

Merged
sunnylqm merged 1 commit into
masterfrom
fix/aab-resource-crc-match
Jun 9, 2026
Merged

feat(diff): emit copiesCrc for content-based resource matching (fix AAB image hot update)#54
sunnylqm merged 1 commit into
masterfrom
fix/aab-resource-crc-match

Conversation

@sunnylqm

@sunnylqm sunnylqm commented Jun 9, 2026

Copy link
Copy Markdown
Collaborator

Problem

热更新「上传 APK 基线 + 设备运行 AAB(Google Play 拆分包)」组合下,WebP 图片加载不出来。

diffFromPackage 把同内容文件以路径写进 copies(copies[to] = movedFrom,Android hdiffFromApktransformPackagePath 为恒等),客户端只能按路径在设备包里找。但:

  • ppk 里图片在根级 drawable-*/foo.webp,APK 基线里在 res/drawable-*-v4/foo.webp → 每张图片都走 moved 分支,from = res/...
  • AAB 设备拆分包对 res/ 做了资源路径压缩,同一张图变成 res/xY.webp,可读全名不存在 → 路径匹配落空 → 图片缺失。
  • assets/ 路径稳定,不受影响 → 只有图片挂。

历史上 #512 曾用 copiesv2(CRC32 内容匹配)修过,后被移除退化成纯路径启发式。

Change

__diff.json 新增可选字段 copiesCrc,与 copies 并列(键=目标路径 to,值=内容 CRC32):

{
  "copies":    { "drawable-xhdpi/x.webp": "res/drawable-xhdpi-v4/x.webp" },
  "copiesCrc": { "drawable-xhdpi/x.webp": 2746328104 }
}

CRC32 对 zip 条目算的是解压后内容,只要图片字节一致(webp 在 APK/AAB 通常原样存储),crc 就一致,天然跨 APK/AAB 格式。客户端在路径找不到时按内容命中。

设计取舍(高效 + 兼容)

  • 只对 moved 分支输出(图片那批);'' 同路径条目稳定,不输出 → 清单只为图片多「一路径+一数字」。
  • 键用 to(唯一),避免同内容(同 crc)互相覆盖(弃用旧 copiesv2 的 crc→to)。
  • 完全向后兼容:旧客户端忽略该字段、行为不变;新客户端遇到没有该字段的旧清单 → 退回路径匹配,不回归。
  • 仅改 diffFromPackage;diffFromPPK(磁盘文件补丁、路径稳定)不动。

Test

  • tests/diff.test.ts 新增用例:断言 moved 的 res/ 图片进 copiesCrc 且值等于其 crc32、assets/ 同路径文件不进 copiesCrc
  • bun test tests/diff.test.ts → 13 pass / 0 fail。

客户端配套

需配合 react-native-update 客户端(Android BundledResourceCopier 增加 CRC 内容兜底层)。对应 PR 见 reactnativecn/react-native-update。

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • Enhanced file relocation tracking: Files moved to different locations now include content checksums, enabling improved identification and validation of relocated content across various package transformation scenarios.
  • Tests

    • Added tests for checksum generation and tracking during file relocation operations, verifying accurate assignment and content correlation for moved entries.

…ontent

When the uploaded baseline is an APK but the app is installed from an AAB
(Play split APKs), res/ drawable paths are shortened on device, so the
path recorded in `copies` (e.g. res/drawable-xhdpi-v4/x.webp) does not
exist verbatim and images (webp) fail to copy during a from-package patch.

diffFromPackage already finds unchanged/moved files by CRC32 internally but
only hands the client a path. This adds an optional `copiesCrc` map to
__diff.json ({ to: crc32 }) for "moved" entries (the res/ resources at
risk of path divergence), letting the client locate them by content when
the path is missing. CRC32 is over the uncompressed content, so it is
stable across APK/AAB packaging.

- Only moved entries get a crc (same-path assets stay path-stable) → minimal
  manifest growth.
- Keyed by `to` (unique) to avoid same-content collisions.
- Fully backward compatible: old clients ignore the field; new clients
  treat its absence as today's path-based behavior.

Consumed by react-native-update (Android BundledResourceCopier).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 9, 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: 2a4966a1-e65b-46ba-ae68-5164ec783150

📥 Commits

Reviewing files that changed from the base of the PR and between 9744312 and fa44097.

📒 Files selected for processing (2)
  • src/diff.ts
  • tests/diff.test.ts

📝 Walkthrough

Walkthrough

This PR extends the diff manifest generation to track CRC32 checksums for relocated file entries. The diffFromPackage function now populates a copiesCrc map when detecting moved (origin-relocated) files and includes this metadata in the __diff.json output alongside the existing copies mapping.

Changes

CRC32 Checksum Tracking for Moved Files

Layer / File(s) Summary
CRC32 tracking for moved entries
src/diff.ts, tests/diff.test.ts
diffFromPackage declares and populates a copiesCrc map recording CRC32 checksums for destination entries whose content originates from different paths, includes it in the manifest output, and a test validates that moved res/ image entries receive copiesCrc entries while same-path assets/ files do not.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 A checksum here, a hash over there,
Moved files tracked with CRC care,
From origin paths to destinations new,
Manifest knows what was where and who! 📦✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: adding copiesCrc emission for content-based resource matching to fix AAB image hot update issues. It is concise, specific, and clearly conveys the primary technical improvement.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ 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 fix/aab-resource-crc-match

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 and usage tips.

@sunnylqm sunnylqm merged commit 94778d8 into master Jun 9, 2026
4 checks passed
sunnylqm added a commit to reactnativecn/react-native-update that referenced this pull request Jun 9, 2026
…s + openRawResource density fix (#577)

* fix(android): match copied resources by content (CRC32) for AAB installs

A from-package (PATCH_FROM_APK) hot update copies unchanged resources out
of the on-device package using the path recorded in __diff.json `copies`.
When the baseline uploaded to the server was an APK but the app is
installed from an AAB (Play split APKs), res/ drawable paths are shortened
on device, so the recorded path (e.g. res/drawable-xhdpi-v4/x.webp) does
not exist verbatim and images (webp) silently fall through and go missing.

Add a CRC32 content-match tier in BundledResourceCopier: build a
crc32 -> entry index while scanning the base + split APKs, and when a
`from` path is not found by exact/normalized path, locate the file by the
content checksum supplied via the new manifest `copiesCrc` map. CRC32 is
over the uncompressed content, so it is stable across APK/AAB packaging.
This tier runs before resolveBundledResource (content match is more
reliable than the resource-id heuristic).

Also fix openResolvedResourceStream: use openRawResource(id, typedValue)
with the density-resolved TypedValue instead of openRawResource(id), which
ignored the requested density and could copy the wrong variant.

Backward/forward compatible: manifests without `copiesCrc` (older CLI)
simply skip the CRC tier and fall back to today's path-based behavior.

Requires CLI support: reactnativecn/react-native-update-cli#54

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* fix(android): address review — exact CRC archive + real density-correct copy

Two follow-ups from review on the resource copier:

1. CRC index stored only the entry name, so resolving it back through
   availableEntries could yield a different-content entry when two APKs
   (base + split) expose the same name with different bytes. Store the
   matched ZipEntry together with its SafeZipFile (ZipSource) and copy from
   that archive directly.

2. openRawResource(id, typedValue) is a no-op for density: AOSP's
   ResourcesImpl re-runs getValue(id, value, true) and overwrites the passed
   TypedValue at the current configuration before opening the stream. Instead
   of going through openRawResource (or the hidden openNonAsset API), take the
   density-correct file path resolved by getValueForDensity and copy that
   exact entry from the already-open archives — public API, correct variant.
   openRawResource(id) remains only as a defensive fallback.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

---------

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
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.

1 participant