Mobile Development 20 min read

Android 17 MemoryLimiter Is Here—Have You Optimized Your Bitmaps?

Android 17 introduces a system‑level MemoryLimiter that silently kills apps exceeding per‑device RAM limits, making bitmap memory the primary optimization target; the article explains the mechanism, detection methods, new Android Studio tools, and five concrete strategies to keep your app alive.

Sohu Tech Products
Sohu Tech Products
Sohu Tech Products
Android 17 MemoryLimiter Is Here—Have You Optimized Your Bitmaps?

MemoryLimiter Overview

Android 17 adds a system component called MemoryLimiter that enforces app‑wide memory limits based on the device’s total RAM. When an app’s anonymous swap usage exceeds the calculated limit, the OS terminates the process silently—no crash stack, no OutOfMemoryError, exit reason REASON_OTHER and description "MemoryLimiter:AnonSwap".

Why Google Introduced It

The change serves two core goals: (1) prevent a single misbehaving app (e.g., severe memory leak with a foreground service) from forcing the Low Memory Killer to kill many healthy background apps, and (2) protect multitask experience and user state by avoiding slow cold starts, lost scroll positions, and extra CPU/battery load when the system must clear cached apps.

How It Works

The runtime flow is simple: device total RAM → compute per‑app memory ceiling → if AnonSwap > limit, MemoryLimiter kills the process with the silent reason described above. Google describes the limits as conservative for extreme leak scenarios, but they will tighten in future releases.

Detecting MemoryLimiter Kills

Online detection:

val exitInfos = activityManager.getHistoricalProcessExitReasons(packageName, 0, 10)
for (info in exitInfos) {
    if (info.reason == ApplicationExitInfo.REASON_OTHER &&
        info.description?.contains("MemoryLimiter") == true) {
        Log.w("Memory", "App killed by MemoryLimiter: ${info.description}")
    }
}

Local debugging with adb:

# View current status
adb shell am memory-limiter status
# Ignore a UID (useful for testing)
adb shell am memory-limiter ignore <uid>
# Manually set a limit for a PID (MB)
adb shell am memory-limiter manual <pid> 256
# Reset to default
adb shell am memory-limiter manual <pid> none

Using the TRIGGER_TYPE_ANOMALY trigger, the system can automatically capture a heap dump just before a MemoryLimiter termination, providing a precise memory snapshot for analysis.

Why Bitmap Is the Biggest Threat

Bitmaps dominate an app’s memory footprint. Examples from the I/O talk:

100 KB JPEG decoded to a 1000×1000 view → 4 MB in memory.

~3 MB 4K photo (3840×2160) → 33 MB .

2000×2000 social image (~500 KB) → 16 MB .

The calculation is width × height × bytesPerPixel. With the default ARGB_8888 format each pixel costs 4 bytes.

Risks Under MemoryLimiter

// Assume a 512 MB per‑app limit
// Loading 20 high‑res images (2000×1500, 4 bytes each)
// 20 × (2000×1500×4) ≈ 240 MB → almost half the quota
// Add duplicate caches and unreleased bitmaps → silent kill

Because bitmap decoding and scaling sit on the UI rendering critical path, the risk grows as screen densities increase (2K, 4K displays).

Five Practical Bitmap‑Optimization Strategies

1. Downsampling (Never feed a 4K image to a thumbnail)

Read dimensions only, compute an appropriate inSampleSize (power of two), then decode the reduced bitmap. The memory reduction is the square of the sample factor (e.g., inSampleSize = 4 → memory ↓ to 1/16).

// Step 1: read bounds only
val options = BitmapFactory.Options().apply { inJustDecodeBounds = true }
BitmapFactory.decodeFile(filePath, options)
// Step 2: compute sample size
options.inSampleSize = calculateInSampleSize(options, targetWidth, targetHeight)
// Step 3: decode with sampling
options.inJustDecodeBounds = false
val bitmap = BitmapFactory.decodeFile(filePath, options)

Libraries handle this automatically: Coil (Compose‑friendly) and Glide both provide .size() or .override() to downsample.

2. Cropping (Avoid padding inside the bitmap)

Instead of embedding transparent borders, use InsetDrawable or view padding.

// InsetDrawable
val insetDrawable = InsetDrawable(bitmapDrawable, left, top, right, bottom)
imageView.setImageDrawable(insetDrawable)
// View padding
imageView.setPadding(left, top, right, bottom)
// Compose
Image(painter = painterResource(id = R.drawable.photo), modifier = Modifier.padding(16.dp))

3. Pixel Config (ARGB_8888 → RGB_565)

Switching to RGB_565 halves per‑pixel memory (2 bytes vs 4 bytes). Ideal for thumbnails and list items that don’t need alpha. Beware of banding in gradient‑rich images.

// Glide example
Glide.with(context)
    .load(url)
    .format(DecodeFormat.PREFER_RGB_565)
    .into(thumbnailView)
// Coil example
val request = ImageRequest.Builder(context)
    .data(url)
    .bitmapConfig(Bitmap.Config.RGB_565)
    .build()

4. Vector Drawables (Replace simple icons with vectors)

VectorDrawable or ShapeDrawable consume zero bitmap memory, are density‑independent, and are only a few kilobytes of XML.

<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="24dp"
    android:height="24dp"
    android:viewportWidth="24"
    android:viewportHeight="24">
    <path android:fillColor="#FF0000"
          android:pathData="M12,21.35l-1.45,-1.32C5.4,15.36 ..."/>
</vector>

5. Bitmap Pool (Reuse instead of reallocating)

Frequent bitmap creation triggers GC pauses that cause UI jank. Reuse via inBitmap or rely on Glide/Coil’s built‑in pool.

// Manual reuse
bitmap.recycle()
val reusableOptions = BitmapFactory.Options().apply {
    inMutable = true
    inBitmap = existingBitmap // reuse memory
}
val newBitmap = BitmapFactory.decodeFile(path, reusableOptions)

Glide and Coil already maintain a robust pool, which is why the platform recommends using these libraries over manual management.

Android Studio New Tools

The Profiler can now highlight duplicate bitmaps with a yellow warning triangle. Steps:

Open Android Studio → Profiler panel.

Select “Analyze Memory Usage” (Heap Dump).

Start snapshot collection.

Look for the yellow triangle or filter by “Duplicate Bitmaps”.

Click a warning to open the Bitmap Preview and trace the source code.

LeakCanary is integrated directly into Android Studio Panda as a dedicated Profiler task, requiring no extra dependency. Compared with the traditional approach, Panda offers zero‑config, on‑device analysis, one‑click “Jump To Source”, and AI‑assisted result export.

ProfilingManager Triggers for Online Dumps

TRIGGER_TYPE_OOM : automatically captures a heap dump when an OutOfMemoryError occurs.

TRIGGER_TYPE_ANOMALY : when a severe performance anomaly such as an imminent MemoryLimiter kill is detected, a heap dump is taken before termination.

val profilingManager = applicationContext.getSystemService(ProfilingManager::class.java)
val triggers = arrayListOf(
    ProfilingTrigger.Builder(ProfilingTrigger.TRIGGER_TYPE_ANOMALY).build()
)
profilingManager.registerForAllProfilingResults(executor) { result ->
    if (result.errorCode == ProfilingResult.ERROR_NONE) {
        uploadHeapDump(result.resultFilePath)
    }
}
profilingManager.addProfilingTriggers(triggers)

Collected dumps can be opened in Perfetto UI’s Heap Dump Explorer, which visualizes allocation hierarchies, retained size, shortest GC‑root paths, and includes an embedded flamegraph.

onTrimMemory Callbacks

Implement ComponentCallbacks2 to release large caches when the UI becomes hidden ( TRIM_MEMORY_UI_HIDDEN) or the app moves to background ( TRIM_MEMORY_BACKGROUND). From Android 14 onward only these two levels remain.

class MyApp : Application(), ComponentCallbacks2 {
    override fun onTrimMemory(level: Int) {
        if (level >= ComponentCallbacks2.TRIM_MEMORY_UI_HIDDEN) {
            // Release large bitmap caches, video buffers, etc.
            imageLoader.memoryCache?.clear()
        }
        if (level >= ComponentCallbacks2.TRIM_MEMORY_BACKGROUND) {
            // Release resources that can be rebuilt later.
            database.close()
        }
    }
}

R8 Optimizations

Enabling full R8 optimization (minify, shrinkResources, and the proguard-android-optimize.txt rules) compresses class/method names, removes dead code, and reduces the resident code footprint, further lowering the baseline memory usage.

android {
    buildTypes {
        release {
            isMinifyEnabled = true
            isShrinkResources = true
            proguardFiles(
                getDefaultProguardFile("proguard-android-optimize.txt"),
                "proguard-rules.pro"
            )
        }
    }
}

Action Checklist (Priorities)

P0 Use Glide/Coil for automatic down‑scaling, pooling, and caching – cuts image memory by 50‑90%.

P0 Enable full R8 optimization – reduces overall memory baseline.

P1 Adopt RGB_565 for all thumbnails – halves bitmap memory.

P1 Run Profiler duplicate‑bitmap detection – eliminates needless copies.

P1 Upgrade to Android Studio Panda with built‑in LeakCanary – speeds leak discovery and fixes.

P2 Implement onTrimMemory callbacks – proactively frees non‑critical memory.

P2 Register ProfilingManager triggers – captures live OOM/anomaly heap dumps.

P2 Test locally with adb am memory-limiter – validates memory safety before release.

Conclusion

MemoryLimiter is already active in Android 17 beta and will roll out to billions of devices. Because bitmaps are the dominant memory consumer, developers must adopt the five strategies above, leverage the upgraded Android Studio tooling, and enable R8 to stay safely under the new limits.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Memory managementAndroidR8Android StudioBitmap optimizationMemoryLimiter
Sohu Tech Products
Written by

Sohu Tech Products

A knowledge-sharing platform for Sohu's technology products. As a leading Chinese internet brand with media, video, search, and gaming services and over 700 million users, Sohu continuously drives tech innovation and practice. We’ll share practical insights and tech news here.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.