Last reviewed against current Google Play Console Help and Android Developers documentation.
Memory has become a publishing concern, not just a performance preference. Google Play has added two memory metrics to the core vitals that already judge crash and ANR behaviour, alongside a minimum bar for how thoroughly your code is optimised — and unlike a slow screen transition, these are measured against published thresholds with consequences for app visibility.
This guide covers what is being enforced — Anonymous RSS + Swap, bitmap memory, and DEX optimisation — how each is measured, and how to test against them. For the general crash/ANR troubleshooting workflow, the Android Vitals guide covers that; this article picks up where it stops.
Quick answer
- Two new memory metrics join Android vitals: memory usage (Anonymous RSS + Swap) and bitmap memory usage. Both are evaluated from the last 28 days of data, using the 90th percentile.
- Thresholds depend on device RAM tier and app state. A 4 GB foreground limit is 2 GB; a 16 GB foreground limit is 4.25 GB. Background and user-perceived-service limits are lower again.
- Bitmap limits apply outside the foreground: above 200 MB in user-perceived-service and background states, above 400 MB when cached.
- DEX optimisation needs a minimum of 25% for shrinking, optimisation and obfuscation — enforced for apps above 10 MB of DEX code and games above 50 MB.
- Enforcement begins in the announced February enforcement period, so treat these as published upcoming requirements rather than present failures. They cover mobile and tablet form factors.
What Google Play's technical quality requirements actually say
Google Play defines bad behaviour thresholds for your app's core vitals. Memory usage is being introduced as new core vitals metrics in two parts: memory usage (Anonymous RSS + Swap) and bitmap memory usage. Three mechanics matter before you look at any number:
- 28-day window. Like existing vitals metrics with bad behaviour thresholds, Play uses the last 28 days of data.
- 90th percentile. Play Console shows several percentiles, and the 90th is the one compared against thresholds. Google's explanation: a P90 of 1.7 GB means 90% of samples were below that and 10% above.
- Sampled from real devices. Values come from opted-in users' devices, anonymised and aggregated — you cannot reproduce the figure from one phone or an emulator.
These metrics sit under the Memory heading inside the Android vitals overview, and are also available through the Google Play Developer Reporting API.
What Anonymous RSS + Swap actually measures
Anonymous Resident Set Size (Anon RSS) is memory allocated directly by your application — the Java/Kotlin heap, native memory allocations, and anonymous memory mappings — that cannot be paged out to disk without swap. Swap accounts for memory compressed or paged into zRAM.
This is worth reading twice, because it rules out several things people assume are included. APK or bundle size is a download cost, not runtime memory. Installed storage size is disk footprint. Java heap size alone is only part of the picture, because native allocations and anonymous mappings sit outside it. Only the RSS + Swap figure describes what your running app actually holds.
Two further points explain why the numbers surprise people. First, advertised physical RAM and usable total memory differ — Google notes Total Memory can be lower than the advertised figure, which is why the RAM tiers are defined by Total Memory ranges. Second, the metric follows your app through its lifecycle, from foreground into background, which is exactly why thresholds are split by app state: an app holding a large cache in the background is judged more strictly than the same app in the foreground.
RAM-tier and app-state memory thresholds
Thresholds are tailored to your application category (apps versus games) and device RAM tier. Below are the published values for apps, at the 90th percentile. Every value here is transcribed from Google's table; a dash in that table means no threshold is published for that combination — it does not mean zero memory.
| Physical RAM (Total Memory) | Foreground | User-perceived services | Background | Cached |
|---|---|---|---|---|
| 0–4 GB (0–3200 MB) | — | — | — | — |
| 4 GB (3200–4800 MB) | 2 GB | 1 GB | 1 GB | — |
| 6 GB (4800–6800 MB) | 2.25 GB | 1.25 GB | 1.25 GB | — |
| 8 GB (6800–9216 MB) | 2.25 GB | 1.5 GB | 1.5 GB | — |
| 12 GB (9216–14336 MB) | 3.25 GB | 1.75 GB | 1.75 GB | — |
| 16 GB (14336–18432 MB) | 4.25 GB | 2 GB | 2 GB | — |
| 16 GB + (above 18432 MB) | — | — | — | — |
Three things to take from that table rather than memorising it:
- The tightest constraint is not the foreground. A 4 GB device allows 2 GB in the foreground but only 1 GB for background and user-perceived services.
- Each tier includes its lowest value, and tiers are defined by Total Memory, not the advertised figure on the box.
- Games have their own, higher table, reflecting their foreground-heavy usage. Do not apply the app numbers to a game or vice versa.
Google notes that what performs acceptably on a 16 GB device may not be acceptable on a 4 GB one, so the practical target is staying inside the thresholds across all tiers your users actually run.
Bitmap memory usage
Bitmaps are the most common cause of a background memory problem, for a simple reason Google states plainly: it is not possible to render bitmaps unless the UI is visible. Holding them in non-foreground states consumes memory that cannot serve any user-visible purpose.
| App state | Bitmap threshold (90th percentile) |
|---|---|
| Foreground | No published threshold |
| User-perceived services | Above 200 MB |
| Background | Above 200 MB |
| Cached | Above 400 MB |
The thresholds are deliberately higher than zero, and Google explains why: your app might be sampled shortly after a state change while it is actively responding to onTrimMemory and releasing memory held for bitmaps. The allowance covers that window rather than licensing a permanent cache.
For reducing bitmap memory, Google's guidance is specific and worth following literally: downsample and decode images to match exact display view dimensions; use a modern image-loading library such as Coil or Glide configured with memory-conscious caches; release unused bitmaps when the UI is hidden by handling TRIM_MEMORY_UI_HIDDEN in onTrimMemory; and avoid static references to Bitmaps or Views, which keep them alive for the process lifetime.
DEX code optimisation: shrinking, obfuscation and R8
DEX is the compiled bytecode format your app runs. Google Play's stated rationale is that good optimisation makes an app load faster, use less memory, render better and produce fewer ANRs — through a combination of shrinking, logical optimisation and obfuscation.
The requirement: a minimum of 25% in each of the three categories, for app uploads to Play Console.
| Category | Minimum required |
|---|---|
| Obfuscation | 25% |
| Optimisation | 25% |
| Shrinking | 25% |
Applicability is not universal, because not every app leans heavily on DEX code. The requirement is enforced where you have non-negligible DEX sizes:
- Apps with more than 10 MB of DEX code
- Games with more than 50 MB of DEX code
Unlike the memory metrics, this requirement applies across all form factors, not only mobile and tablet.
Where to look: your DEX size and your optimisation percentages for each uploaded bundle are visible in the App Bundle Explorer in Play Console — the authoritative view of what you actually shipped, not your local build.
What you can use: Google states you can use any tool, such as R8 or another app shrinker, to reach the minimum 25% thresholds, and mentions the R8 Configuration Analyzer for extra insight. Note what that means and does not: R8 is a permitted means, not a guarantee. Enabling it does not by itself establish 25% in all three categories — you check the numbers. Google also warns that CI/CD and enterprise builds might not perfectly match the uploaded artefact, so verify the bundle Play received.
Inspecting the metrics in Play Console
Open the Android vitals overview, find the Memory heading, and read the 28-day rolling P90 for both metrics. Then filter by app state (foreground, user-perceived service, background, cached), by device RAM tier so you can see whether a breach is confined to 4 GB devices, and by Android version and app release to tell a regression from a long-standing pattern. If you want regression alerts in your own dashboards, the Developer Reporting API exposes the same numbers programmatically.
Two things you cannot do from Play Console: reproduce the P90 locally, and see it for a build you have not shipped. Both matter when proving a fix worked.
Using Android Studio Profiler and other tools
Google points developers at four complementary tools. The Memory Profiler and heap dumps show real-time allocations across Java/Kotlin heap, native memory and graphics, and .hprof dumps are how you find leaks, unreleased Activities, duplicate bitmaps and retaining paths. Perfetto sampling (heapprofd) is lower overhead and gives you the call stacks behind a spike, plus correlation with low-memory-killer events. For a quick on-device read, adb shell dumpsys meminfo <package> breaks out PSS, Private Dirty (Anon RSS), Swap Dirty (zRAM) and Bitmaps. The Developer Reporting API gives you the aggregated vitals data Play itself uses.
A local emulator or single test device cannot reproduce Google's production 28-day percentile — that figure is aggregated across opted-in users. Use local tools to find and fix the cause; use Play Console to confirm the aggregate moved.
For an optional extra signal before release, the pre-launch report runs your app on Google's devices and surfaces crashes and performance issues, and if startup time is your specific concern, slow app startup testing covers that in depth.
A practical memory testing workflow
- Start from the data. Note the current 28-day P90 for both metrics, per app state and RAM tier, and identify which RAM tiers your users actually have.
- Compare across app states. If background usage approaches foreground usage, you are holding something you should not be.
- Reproduce the heavy journey on a physical device with
dumpsys meminfobefore and after the suspect screen. - Take a heap dump while backgrounded and look for retained Activities, duplicate bitmaps and unexpected retaining paths. Use Perfetto if the spike is native.
- Fix the cause — release in
onTrimMemory, decode to display size, drop static references, stop long-running background work. - Check DEX separately: in App Bundle Explorer confirm DEX size and read all three optimisation percentages.
- Review the release configuration and test critical flows after shrinking and obfuscation, where reflection-dependent features break.
- Ship and re-read the vitals. The P90 is a 28-day rolling figure, so improvement appears gradually.
Test the release build, not a debug one. Shrinking and obfuscation only exist in the release artefact, so a debug build passing tells you nothing about whether your uploaded bundle still works.
Test cases worth running
Memory
- Cold launch — record
dumpsys meminfoimmediately after first frame. - Heavy navigation — open and close the most content-rich screen repeatedly; usage should return near baseline, not ratchet upward.
- Long scrolling — an image-loading feed is the classic slow climb.
- Background, resume, and return from cached state — did background RSS drop? This is where bitmap retention shows up.
- Low-RAM device — the 4 GB tier has the tightest real-world pressure; test on hardware.
Bitmap
- One very large image — compare decoded dimensions against view size.
- Many images on one screen — grid or carousel screens.
- Leave and background an image-heavy screen — the case the 200 MB threshold exists for.
- Rotate or fold while images are loaded — catches duplicate decoding on configuration change.
DEX
- Inspect the uploaded bundle in App Bundle Explorer, not your local output.
- Confirm DEX size against the 10 MB (apps) / 50 MB (games) applicability line.
- Read all three percentages, and exercise reflection-heavy features — serialisation, DI, deep links, JSON models — which are what shrinking breaks.
Common mistakes
Confusing bundle size with memory. Shrinking your APK says nothing about Anon RSS + Swap.
Optimising only for the foreground. Background and user-perceived-service limits are tighter, and that is where retained bitmaps and leaks accumulate.
Treating a dash in Google's table as zero. A dash means no published threshold for that combination. Reading it as "must be zero" leads to pointless rearchitecting.
Assuming R8 equals compliance. The requirement is a measured percentage in three categories, verified in App Bundle Explorer.
Expecting an instant fix in the numbers. A 28-day rolling P90 moves slowly by design, and CI/CD builds can differ from what was uploaded.
Changing your store category to reach a friendlier threshold. Google states that re-categorising an app to qualify for different technical thresholds violates the store listing metadata policy.
Pre-release memory and DEX checklist
- Vitals reviewed: 28-day P90 for both memory metrics, filtered by state and RAM tier.
- Tightest tier identified and tested on real hardware if your users include it.
- Background and cached behaviour checked after backgrounding and after returning from cached state.
onTrimMemoryhandlesTRIM_MEMORY_UI_HIDDENand actually releases bitmaps.- Heap dump reviewed for retained Activities and duplicate bitmaps.
- DEX size and all three percentages checked in App Bundle Explorer for the uploaded bundle.
- Release build smoke-tested after shrinking and obfuscation.
- No category change made for threshold purposes.
Frequently asked questions
Are these requirements enforced today?
Google describes memory and optimisation requirements beginning from the announced February enforcement period. Treat them as published upcoming requirements and prepare now, but do not assume your app is currently non-compliant based on a metric you have not checked.
Does exceeding a memory threshold get my app removed?
Google's page describes the thresholds and states that apps and games must meet them to remain compliant, and that compliance is required to qualify for or maintain Apps Experience Program and Level Up eligibility. It does not state that exceeding a metric automatically triggers rejection or removal, so treat those as distinct from a bad behaviour threshold breach and check current policy before drawing conclusions.
Is this measured only on Android 17 and above?
No. Google states Play evaluates all app versions where data is available — which for memory usage and bitmap memory usage means Android 13 and above.
Do I have to use R8?
Google strongly recommends R8 but states that other optimisation tools can be used to meet the requirement. What is measured is the percentage achieved, not the tool used.
Why is my foreground bitmap figure not flagged?
Because the published bitmap thresholds apply to user-perceived services, background and cached states — the states where bitmaps cannot be rendered and therefore cannot justify the memory. Google publishes no foreground bitmap threshold.
Conclusion
Android app memory optimisation has become measurable in a way that has publishing consequences. Two memory thresholds now exist — Anonymous RSS + Swap by RAM tier and app state, and bitmap memory usage outside the foreground — plus a 25% minimum across shrinking, optimisation and obfuscation for apps with meaningful DEX size.
The work splits cleanly. For memory: find what you retain in background and cached states, release bitmaps when the UI hides, decode to display size, and confirm the change in the 28-day P90 rather than a local profile. For DEX: confirm the numbers in App Bundle Explorer for the bundle you actually uploaded, and smoke-test the release build after shrinking.
If you want real people exercising the memory-heavy journeys — long scrolls, image grids, background and resume — before your users do, our team runs Google Play closed tests with progress you can watch. You can see how the 12 testers service works whenever it is useful.
Official sources
- Google Play Console Help — Play Console technical quality requirements (memory and DEX thresholds)
- Android Developers Blog — Elevating app quality: reducing memory usage and improving device migration
- Android Developers — Android vitals
- Android Developers — Android memory
- Android Developers — Profile your app performance
- Android Developers — Enable app optimization with R8
- Android Developers — Handling bitmaps
Requirements and thresholds are updated over time. Verify current values in the Play Console Help page linked above before making release decisions.