Skip to content

Enable R8 optimizations for release builds - #16308

Merged
malinajirka merged 9 commits into
trunkfrom
issue/r8-enable-release-optimizations
Aug 26, 2026
Merged

Enable R8 optimizations for release builds#16308
malinajirka merged 9 commits into
trunkfrom
issue/r8-enable-release-optimizations

Conversation

@malinajirka

@malinajirka malinajirka commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Fixes #WOOMOB-3699

Description

Release builds have been running R8 in shrink-only mode: -dontoptimize, R8 compat mode (android.enableR8.fullMode=false), no resource shrinking, and several broad keep-alls (com.woocommerce.**, fluxc, OkHttp, Gson, Zendesk, Guava) that exempted most of the app from shrinking.

This PR (one logical change per commit):

  • Enables R8 optimization, full mode, and resource shrinking
  • Narrows the com.woocommerce.** and fluxc keep-alls to keepclassmembers for fields/enums — all Gson needs, since we keep -dontobfuscate (names never change; only removal is a risk)
  • Drops keep-alls that duplicate the libraries' own consumer rules (OkHttp, EventBus, Zendesk section, Guava)

Result on vanillaRelease: APK 139.4 → 127.0 MB, dex 97.5 → 60.6 MB (−38%), and R8 can now optimize (inline/merge) across the app.

Notes for reviewers:

  • Stripe Terminal SDK consumer rules are intentionally untouched (issue submitted here).
  • Remaining rules were sanity-checked with Google's R8 configuration analyzer.
  • Verified on release builds with a logged-in store: dashboard stats, orders list/detail, products list/detail, app settings, Payments hub, and full POS flows including completed cash and simulated Tap to Pay card payments. Sentry mapping upload works (R8 emits a mapping even without obfuscation, for retracing inlined frames).
  • I considered whitelisting packages instead of applying com.woocommerce.** {<fields>; }, but AFAICT the gain would be really small and we'd risk runtime failures across many features.

Test Steps

Use a release build (e.g. wasabiRelease) with a logged-in store:

  1. My Store shows stats and charts
  2. Open an order from the order list; open a product from the products tab
  3. POS: add a product to cart, check out, complete a cash payment
  4. Open Help Center (Zendesk) and browse an article
  5. Receive an order push notification

Images/gif

N/A

  • I have considered if this change warrants release notes and have added them to RELEASE-NOTES.txt if necessary. Use the "[Internal]" label for non-user-facing changes.

@malinajirka malinajirka added category: performance Related to performance such as slow loading. type: technical debt Represents or solves tech debt of the project. labels Jul 24, 2026
@malinajirka malinajirka added this to the 25.4 milestone Jul 24, 2026
@malinajirka
malinajirka force-pushed the issue/r8-enable-release-optimizations branch from db1109e to 00e0561 Compare July 24, 2026 17:01
@wpmobilebot

wpmobilebot commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

App Icon📲 You can test the changes from this Pull Request in WooCommerce Android by scanning the QR code below to install the corresponding build.

App NameWooCommerce Android
Platform📱 Mobile
FlavorJalapeno
Build TypeDebug
Build Number778
Version25.4-rc-1
Application IDcom.woocommerce.android.prealpha
Commit77a0f31
Installation URL14d3uv3r5kf7o
Automatticians: You can use our internal self-serve MC tool to give yourself access to those builds if needed.

@malinajirka
malinajirka marked this pull request as ready for review July 27, 2026 11:17

@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.

AI Code Review - Found 2 potential issues

This PR enables R8 full mode, R8 optimizations (-dontoptimize removed), and shrinkResources, and narrows several broad -keep rules. These are release-only changes, so nothing here shows up in a normal debug run; the risk is entirely in what optimization/shrinking removes from a release build. The changes are well-scoped and clearly commented, but the two spots below are the ones most likely to bite silently.

PR housekeeping
  • FYI (not a blocker): this only affects release/minified builds, so verification should be done against a shrunk release (or wasabiRelease) build, exercising reflection-heavy paths (Gson deserialization of API responses, Zendesk support flow, EventBus events, and country-name lookup) rather than a debug build.
  • Applied repo AGENTS.md/CLAUDE.md guidance while reviewing.

Automatic review · claude-opus-4-8 · Workflow run

How to reply to a finding

Reply on this review (or inline at the line the finding refers to) with one of:

  • @claude addressed - I made the change. Bot verifies against the next diff before marking resolved.
  • @claude rejected: <reason> - Will not fix; reason gets quoted on the next review.
  • @claude not-applicable - Finding does not apply (wrong file, already covered elsewhere, etc.).

The bot honours these on the next review pass.

Comment thread WooCommerce/build.gradle
// R8 shrinks and optimizes the app (dead code removal, inlining, class merging),
// but we don't obfuscate the bytecode (-dontobfuscate in proguard-rules.pro).
minifyEnabled = true
shrinkResources = true

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

AI Code Review [fix here]

Issue: shrinkResources = true strips resources that are only referenced by a dynamically-built name. StringUtils.getCountryByCountryCode() resolves country names via resources.getIdentifier("country_mapping_$countryCode", "string", ...), so the country_mapping_* strings are never referenced statically. Although the shrinker's safe mode tries to detect getIdentifier usage, the lookup name is built by interpolation (only the country_mapping_ prefix is a real constant), so these strings can be removed in a release build, silently breaking country-name resolution (it falls back to null).

Suggestion: Verify on a shrunk release build that country names still resolve, and add a res/raw/keep.xml with tools:keep="@string/country_mapping_*" (plus any other resource resolved via getIdentifier) so they are guaranteed to survive shrinking.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@claude not-applicable — the country_mapping_* strings don't exist in the unshrunk baseline APK either: all 241 were deleted by 5141ca4 ("Remove unused strings", Sept 2025), which static analysis drove precisely because the only reference is the dynamic getIdentifier lookup. getCountryByCountryCode() has returned null in production ever since, so resource shrinking changes nothing here. Pre-existing issue tracked in WOOMOB-3701.

##### WooCommerce - begin
# Gson populates fields reflectively, so keep fields and enum constants of our classes
# (we don't obfuscate, so names are stable). Methods stay eligible for R8 optimization.
-keepclassmembers class com.woocommerce.** { <fields>; }

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

AI Code Review [fix here]

Issue: Replacing -keep class com.woocommerce.** { *; } (and the FluxC equivalent below) with -keepclassmembers ... { <fields>; } is riskier under R8 full mode than it looks: -keepclassmembers preserves members only if the class itself is kept. A Gson model reached purely by reflection (e.g. the element type of a generic collection, or a polymorphic subtype) with no static reference in retained code can be shrunk away entirely under full mode, in which case keeping its fields does nothing and deserialization returns null/crashes.

Suggestion: Confirm on a release build that every API response model still deserializes (orders, products, payments, settings, notifications, etc.). For any model that is only constructed reflectively, keep the class itself (-keep class ... { <fields>; } or @Keep) rather than only its members.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@claude rejected: verified empirically on the shrunk wasabiRelease mapping — the Gson models from all app-module call sites survive, including the worst-case pattern described here: SitePlanRestClient$SitePlanDto is reached only via TypeToken<Map<Int, SitePlanDto>>, yet is retained because the consuming code reads its properties (it.currentPlan). That's the general mechanism: any deserialized model whose data is actually consumed is statically referenced by the consuming code (property reads, checkcasts, signatures), so R8 keeps the class; a model with zero static references couldn't be used by the app at all. The release-notes entry carries the [*****] flag, so full smoke tests on the final APK cover the residual tail.

@malinajirka
malinajirka requested a review from wzieba July 27, 2026 12:06
@wzieba

wzieba commented Jul 28, 2026

Copy link
Copy Markdown
Contributor

@malinajirka ack - but can this wait for a few days? I wanted to take a deep look but I just have some other things to finish.

@malinajirka

Copy link
Copy Markdown
Contributor Author

@wzieba Sure, no rush at all - feel free to look into it whenever. It's not blocking anything, it's just an improvement.

@wpmobilebot

Copy link
Copy Markdown
Collaborator

Version 25.4 has now entered code-freeze, so the milestone of this PR has been updated to 25.5.

@wzieba wzieba 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.

Thanks @malinajirka for working on this! I've left a few comments. None of them are blocking, this PR is a good iteration towards fixing our R8 config.

Comment thread gradle.properties

android.nonTransitiveRClass=true
android.enableR8.fullMode=false
android.enableR8.fullMode=true

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.

Nice, this had to be done before ~end of the year anyway 👏

WARNING: The option setting 'android.enableR8.fullMode=false' is deprecated.
[task 2026-08-17T11:17:30.901+00:00] The current default is 'true'.
[task 2026-08-17T11:17:30.901+00:00] It will be removed in version 10.0 of the Android Gradle plugin.

Comment on lines +41 to +42
-keepclassmembers class org.wordpress.android.fluxc.** { <fields>; }
-keepclassmembers enum org.wordpress.android.fluxc.** { *; }

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.

FluxC (Gson deserialization; model/network field keeps come from fluxc's consumer-rules.pro) - begin

True - can we move this to FluxC's consumer-rules.pro then?

And generally, these rules are still pretty broad, but maybe it's a good iteration.

Comment on lines +3 to 10
###### OkHttp (the library ships its own consumer rules) - begin
-dontwarn okio.**
-dontwarn okhttp3.**
-keep class okhttp3.** { *; }
-keep interface okhttp3.** { *; }
-dontwarn com.squareup.okhttp.**
-keep class com.squareup.okhttp.** { *; }
-keep interface com.squareup.okhttp.** { *; }

-keepattributes Signature
-keepattributes *Annotation*
###### OkHttp - end

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.

OkHttp (the library ships its own consumer rules) - begin

That's 100% true: can we remove all rules then?

Suggested change
###### OkHttp (the library ships its own consumer rules) - begin
-dontwarn okio.**
-dontwarn okhttp3.**
-keep class okhttp3.** { *; }
-keep interface okhttp3.** { *; }
-dontwarn com.squareup.okhttp.**
-keep class com.squareup.okhttp.** { *; }
-keep interface com.squareup.okhttp.** { *; }
-keepattributes Signature
-keepattributes *Annotation*
###### OkHttp - end

Comment on lines 27 to 30
# Only required if you use AsyncExecutor
-keepclassmembers class * extends de.greenrobot.event.util.ThrowableFailureEvent {
** *(java.lang.Throwable);
}

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.

Image

I haven't dived into it, but have you maybe checked if we can remove this?

Suggested change
}

Comment on lines 23 to 25
-keepclassmembers class ** {
public void onEvent*(**);
}

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.

AFAIU we don't use Event Bus 2 - if these lines are required, can we move them to Event Bus 3 section?

###### FluxC - end

###### FluxC - WellSql (needed for Addon support) - begin
-keep class com.wellsql** { *; }

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.

Normally I'd suggest it too to move to FluxC consumer-rules.pro, but WellSql should be relatively soon removed completely, so it's not worth it.

Comment on lines 49 to 51
###### Dagger - begin
-dontwarn com.google.errorprone.annotations.*
###### Dagger - end

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.

I think we don't really need this, do we?

Suggested change
###### Dagger - end

Comment on lines 68 to 78
###### Google Crypto Tink dependencies - begin
-dontwarn com.google.api.client.http.GenericUrl
-dontwarn com.google.api.client.http.HttpHeaders
-dontwarn com.google.api.client.http.HttpRequest
-dontwarn com.google.api.client.http.HttpRequestFactory
-dontwarn com.google.api.client.http.HttpResponse
-dontwarn com.google.api.client.http.HttpTransport
-dontwarn com.google.api.client.http.javanet.NetHttpTransport$Builder
-dontwarn com.google.api.client.http.javanet.NetHttpTransport
-dontwarn org.joda.time.Instant
###### Google Crypto Tink dependencies - end

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.

I think we no longer use Crypto Tink, do we? At least as a direct dependency. Have you experimented with removing these -dontwarn?

Comment on lines 80 to 84
# This is generated automatically by the Android Gradle plugin.
-dontwarn java.beans.ConstructorProperties
-dontwarn java.beans.Transient
-dontwarn org.slf4j.impl.StaticLoggerBinder
-dontwarn org.slf4j.impl.StaticMDCBinder

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.

As comments says, this was auto-generated by old AGP update - can we check if they're actually still required?

@wzieba

wzieba commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

@malinajirka I just realised: if the goal of the PR was to only enable R8 optimizations, I think it's good to go.

I reviewed it from "fix the R8 config" angle, which I see could be too broad and not actually the intention of this PR.

@malinajirka

Copy link
Copy Markdown
Contributor Author

Thanks for the review @wzieba!

I just realised: if the goal of the PR was to only enable R8 optimizations, I think it's good to go.
I reviewed it from "fix the R8 config" angle, which I see could be too broad and not actually the intention of this PR.

Yeah, I essentially tried to keep the changes to minimum, except of obviously wrong setup. However, I like all your suggestions and I think all of them will work - I recorded them under https://linear.app/a8c/issue/WOOMOB-3903/follow-up-proguard-rule-cleanups-suggested-in-r8-pr-review.

@wzieba wzieba 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.

Got it, thanks!

@wpmobilebot wpmobilebot modified the milestones: 25.5, 25.6 Aug 24, 2026
@wpmobilebot

Copy link
Copy Markdown
Collaborator

Version 25.5 has now entered code-freeze, so the milestone of this PR has been updated to 25.6.

@malinajirka
malinajirka merged commit 9a2d05c into trunk Aug 26, 2026
17 of 18 checks passed
@malinajirka
malinajirka deleted the issue/r8-enable-release-optimizations branch August 26, 2026 10:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

category: performance Related to performance such as slow loading. type: technical debt Represents or solves tech debt of the project.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants