
For years, I've watched Android teams treat memory optimization as something to investigate only when an app started crashing, slowing down, or behaving badly on low-end devices. Honestly, that reactive approach is becoming harder to justify.
In August 2026, Google Play announced new performance requirements covering dynamic memory usage, bitmap memory, and DEX code optimization. With enforcement scheduled to begin in February 2027, apps that exceed the new thresholds may face reduced visibility and publishing capabilities in Google Play.
The more interesting change for engineering teams isn't just the deadline itself. Memory is becoming a factor that can block a release even when developers cannot reproduce an obvious crash locally. I believe this makes memory usage worth treating exactly like startup time, ANR rate, or binary size: a core metric that must be investigated before a build ever reaches production.
As a Senior Android Engineer with a decade of experience building native applications used by millions, I want to focus on the practical side of this shift: how we can investigate memory growth, distinguish normal allocation from a real problem, and start building memory checks into our release processes.
What Google Play is measuring
Google’s new requirements separate memory into several areas rather than treating it as a single heap-size number, which is a much-needed distinction.
Dynamic memory usage is measured as anonymous RSS plus swap. In simplified terms, this covers private memory used by the application, including active and compressed memory, while excluding file-backed data such as code and assets. Google Play plans to evaluate it across different application states and device performance categories.
The second metric is bitmap memory. Google is explicitly looking at whether applications retain bitmaps when they are no longer visible. Keeping a large image in memory while its screen is open can be completely legitimate; keeping a collection of those images after the application moves into the background is a fundamentally different problem.
The third area is DEX optimization. Play-distributed apps will need at least 25% coverage across optimization, shrinking, and obfuscation using tools like R8. I agree with Google's stance here, as DEX optimization directly links to a smaller memory footprint, faster startup, fewer ANRs, and better runtime performance.
In my view, the critical takeaway is that these measurements are contextual. A single memory number recorded on a developer’s personal phone tells us very little about how an application behaves across different RAM buckets, process states, and user flows.
Why memory problems are difficult to reproduce
Dynamic memory usage is measured as anonymous RSS plus swap. In simplified terms, this covers private memory used by the application, including active and compressed memory, while excluding file-backed data such as code and assets. Google Play plans to evaluate it across different application states and device performance categories.
The second metric is bitmap memory. Google is explicitly looking at whether applications retain bitmaps when they are no longer visible. Keeping a large image in memory while its screen is open can be completely legitimate; keeping a collection of those images after the application moves into the background is a different problem.
The third area is DEX optimization. Google says Play-distributed apps will need at least 25% coverage across optimization, shrinking, and obfuscation using R8 or another shrinking tool. Google links DEX optimization to a smaller memory footprint, faster startup, fewer ANRs, and better runtime performance.
The important detail is that these measurements are contextual. A single memory number recorded on a developer’s phone tells very little about how an application behaves across different RAM buckets, process states, and user flows.
Investigating memory growth
For a practical investigation, I always start with Android Studio’s Memory Profiler. Instead of immediately searching for the largest object in the heap, I recommend reproducing a specific user flow several times. For example:
Feed → Product → Gallery → Back → Feed
Watch what happens to memory after each iteration and after garbage collection. If memory repeatedly rises during the gallery phase and then settles near the original baseline, you are likely looking at temporary allocations. If the baseline keeps moving upward, it's time to take a heap dump.
When examining a dump, finding several instances of a Fragment that should have been destroyed is just the first step. The critical question isn't whether those objects consume a lot of memory themselves, but what they retain.
A Fragment might retain its view hierarchy, which retains an ImageView, which references a decoded bitmap. Suddenly, a relatively small lifecycle leak keeps several megabytes of image memory alive. This is why I look at reference paths rather than raw object counts. The goal is to find why an object remains reachable from a GC root after its lifecycle should have ended.
Tools like LeakCanary (now integrated directly into Android Studio Quail 2's Profiler) can significantly shorten this investigation for lifecycle-related leaks. For broader analysis, I turn to Perfetto, which provides system-level context when a heap dump alone isn't enough.
These tools answer different questions: Profiling shows when memory grows, a heap dump explains what remains allocated, and a system trace provides context around what the application and system were doing at the time.
Investigating Retained State: The Gallery Issue
During deep architectural reviews, I frequently encounter retained state issues. QA might not report crashes, but repeated gallery sessions reveal steadily increasing memory consumption.
The instinct is often to blame the image loader’s cache. Disabling caching might reduce the memory growth, but it rarely eliminates it. Taking a heap dump after closing the gallery often shows that an object associated with the gallery remains reachable. Following the reference chain typically reveals a long-lived component holding a callback created by the screen, which captures state that ultimately references the image data.
Fixing this requires correcting the ownership or lifecycle of that callback, not simply reducing cache size. This distinction is vital because cache tuning can mask a leak without fixing it. I advise teams to work from behavior to ownership: identify a repeatable flow, establish whether the baseline grows, inspect retained objects, and only then decide on the architectural fix.
Bitmap memory deserves its own investigation
I always stress to my teams that images deserve special attention because compressed file size is a poor indicator of runtime cost. A JPEG occupying a few hundred kilobytes on disk can easily require several megabytes once decoded. Bitmap memory depends on pixel dimensions and format, not the download size.
The classic mistake I see is decoding a much larger image than the UI needs. A 4000 × 3000 image does not need to remain at that resolution to fill a small thumbnail. Modern image-loading libraries handle much of this, but it is still necessary to understand their configuration and caching behavior. Following Google's guidance, downsampling images to exact UI dimensions is key.
A large bitmap that exists while the user is actively viewing it is perfectly healthy. That same bitmap retained after its screen disappears (or while the application sits in a cached process state) requires immediate investigation. This is exactly why I argue that a blanket rule like “the app must never exceed 300 MB” is far less useful than contextual measuring around defined application states.
Move memory checks before production
Play Console and Android vitals are becoming invaluable sources of real-world memory information. Google is adding dynamic-memory and bitmap-memory metrics broken down by percentile and RAM bucket, OOM-related termination filtering, DEX optimization insights, and proactive warnings.
But I strongly believe that production telemetry should be your last line of detection, not the first. We need to start defining representative memory scenarios for every release candidate: cold launch and idle, navigation through a high-traffic flow, image-heavy flows, repeated entry/exit of complex screens, and foreground-to-background transitions.
The crucial element is repeatability. The same flow must run on the same device category or emulator configuration so we can compare results against previous builds. If a checkout flow historically settles around 170–190 MB, and a new release candidate hits 240 MB, that regression demands an investigation before shipping, even if both numbers remain below a platform threshold.
Build a memory budget instead of waiting for an OOM
Instead of waiting for crashes, I recommend establishing a strict memory budget. Set a baseline after startup, an acceptable peak while opening a media-heavy screen, and an expected post-navigation baseline after that screen closes. Derive these numbers from actual measurements across representative devices, rather than copying them from other applications.
This targeted approach prevents the trap of indiscriminately optimizing every allocation. If memory rises temporarily during a complex operation and returns to baseline, there may be nothing to fix. But if a release adds 30 MB of retained memory every time a user repeats a common flow, tackle it long before an OutOfMemoryError surfaces in production.
Over time, these checks naturally integrate into performance regression testing. Your CI doesn't need to automatically diagnose the leak; simply detecting that a benchmarked flow now consumes substantially more memory gives engineers the signal they need to inspect the build before shipping it.
A short pre-release memory checklist:
- Run important user flows repeatedly and compare the post-flow baseline, not only the peak.
- Test on more than a high-memory flagship device.
- Take a heap dump when memory remains elevated and inspect retention paths.
- Check image dimensions, decoding configuration, and cache behavior on bitmap-heavy screens.
- Test foreground, background, and cached-state transitions.
- Review R8 optimization instead of assuming that enabling R8 means the application is well optimized.
- Compare memory measurements with the previous release and investigate meaningful regressions.
- Check Android vitals and Play Console warnings rather than waiting for user-reported OOM crashes.
Conclusion
Ultimately, preparing for Google Play’s February 2027 enforcement isn’t just about dodging penalties or passing a platform threshold. Instead, it’s an opportunity to shift how we approach app performance from day one. As you refine your own release cycles, I encourage you to treat memory with the same preemptive rigor as compile times or crash rates.
Start benchmarking your key flows now, establish realistic budgets based on careful research, and hold your release candidates accountable to them. By the time Google’s new limits take full effect, you won't just be compliant; you'll be delivering a fundamentally smoother, more resilient experience for your users.







