Optimization

Mobile App Image Optimization: iOS & Android Complete Guide (2025)

Master mobile app image optimization for iOS and Android. Learn HEIC, WebP, AVIF formats, asset catalogs, app bundle optimization, @1x/@2x/@3x resolutions, dark mode images, and CDN strategies to reduce app size and improve performance.

  • 20 min read
  • Updated:
  • By Convert a Document
In this guide:

Master mobile app image optimization for iOS and Android. Learn HEIC, WebP, AVIF formats, asset catalogs, app bundle optimization, @1x/@2x/@3x resolutions, dark mode images, and CDN strategies to reduce app size and improve performance.

Introduction: Why Mobile App Image Optimization Matters

Mobile app images significantly impact three critical factors: app download size, user retention, and device performance. Studies show that 25% of users abandon apps that exceed 100MB, while every 6MB increase in app size leads to a 1% decrease in conversion rates.

In 2025, with modern mobile devices supporting high-density displays (up to @4x on iOS and xxxhdpi on Android), unoptimized images can quickly bloat your app bundle. A typical social media app with 200+ image assets can range from 30MB (optimized) to 150MB+ (unoptimized) in download size.

Real-World Impact:
  • Spotify: Reduced app size from 125MB to 95MB by optimizing images, resulting in 12% increase in installs
  • LinkedIn: Achieved 40% smaller app bundle through WebP adoption and asset optimization
  • Pinterest: Reduced initial load time by 35% with progressive image loading strategies

This comprehensive guide covers iOS and Android image optimization strategies, from choosing the right formats to implementing asset catalogs and CDN delivery systems.

iOS vs Android: Image Format Requirements

iOS Image Format Strategy

iOS supports multiple image formats with varying levels of optimization:

Format iOS Support Use Case File Size vs PNG Recommendation
HEIC iOS 11+ Photos, app content images 50-60% smaller ✅ Best for photos
WebP iOS 14+ Web views, general images 30-40% smaller ✅ Great for web content
PNG All versions UI elements, transparency Baseline ⚠️ Use for transparency only
JPEG All versions Photos without transparency 50-70% smaller ✅ Good legacy support
PDF All versions Vector graphics, icons Varies ✅ Perfect for scalable UI

Android Image Format Strategy

Android offers broader format support with newer versions:

Format Android Support Use Case File Size vs PNG Recommendation
AVIF Android 12+ (API 31+) All image types 60-70% smaller ✅ Best modern format
WebP Android 4.0+ (API 14+) All image types 30-40% smaller ✅ Excellent backward compatibility
PNG All versions UI elements, transparency Baseline ⚠️ Use for transparency only
JPEG All versions Photos without transparency 50-70% smaller ✅ Universal support
Vector Drawable Android 5.0+ (API 21+) Simple icons, shapes 80-95% smaller ✅ Perfect for scalable UI
💡 Format Selection Strategy:
  • iOS: Use HEIC for photos, WebP for web content, PDF for vector UI elements
  • Android: Use WebP as primary format (broad support), AVIF for Android 12+, Vector Drawables for icons
  • Cross-platform: WebP offers the best compromise between size and compatibility (iOS 14+, Android 4.0+)

iOS Image Optimization: Asset Catalogs & Resolution Sets

Understanding iOS Display Densities

iOS devices use a scaling factor system to support various screen densities:

Scale Factor Devices Example Resolution (100pt element) Usage %
@1x Non-Retina (legacy) 100x100 pixels <1%
@2x iPhone 6-14, iPad, iPad Pro 200x200 pixels ~45%
@3x iPhone 12 Pro Max, 13/14/15 Pro 300x300 pixels ~54%

Asset Catalog Best Practices

1. Optimal Resolution Set Strategy

// Recommended approach for 2025
Assets.xcassets/
└── ProductImage.imageset/
    ├── Contents.json
    ├── product@2x.heic     // 800x800px - iPhone 6-14, iPad
    └── product@3x.heic     // 1200x1200px - iPhone Pro models

// Skip @1x entirely - negligible market share
// Focus optimization effort on @2x and @3x
⚠️ Common Mistake: Many developers still include @1x assets. As of 2025, non-Retina devices represent less than 0.5% of active iOS devices. Removing @1x assets can reduce bundle size by 15-20% with virtually no impact on users.

2. Xcode Asset Catalog Configuration

Configure your asset catalog for optimal compression in Xcode:

// Contents.json configuration
{
  "images" : [
    {
      "filename" : "product@2x.heic",
      "idiom" : "universal",
      "scale" : "2x"
    },
    {
      "filename" : "product@3x.heic",
      "idiom" : "universal",
      "scale" : "3x"
    }
  ],
  "info" : {
    "author" : "xcode",
    "version" : 1
  },
  "properties" : {
    "compression-type" : "heic",
    "preserves-vector-representation" : true
  }
}

3. Programmatic Image Loading with Format Fallback

// Swift example with format optimization
import UIKit

extension UIImage {
    static func optimizedImage(named name: String) -> UIImage? {
        // Try HEIC first (iOS 11+)
        if #available(iOS 11.0, *) {
            if let heicImage = UIImage(named: "\(name).heic") {
                return heicImage
            }
        }

        // Fallback to WebP (iOS 14+)
        if #available(iOS 14.0, *) {
            if let webpImage = UIImage(named: "\(name).webp") {
                return webpImage
            }
        }

        // Final fallback to standard formats
        return UIImage(named: name)
    }
}

// Usage
let productImage = UIImage.optimizedImage(named: "product-hero")

App Thinning & On-Demand Resources

iOS App Thinning automatically delivers only the assets needed for each device:

Technique How It Works Typical Savings Best For
Slicing Device gets only its resolution assets 30-40% All apps
Bitcode Apple recompiles for each architecture 10-15% Apps targeting multiple iOS versions
On-Demand Resources Assets downloaded when needed 50-70% Games, level-based apps

Implementing On-Demand Resources

// Swift - On-Demand Resource loading
import UIKit

class ImageManager {
    func loadLevelAssets(level: Int, completion: @escaping (Bool) -> Void) {
        let resourceRequest = NSBundleResourceRequest(tags: ["level\(level)"])

        resourceRequest.beginAccessingResources { error in
            if let error = error {
                print("Failed to load resources: \(error)")
                completion(false)
                return
            }

            // Resources are now available
            completion(true)
        }
    }

    func releaseLevelAssets(level: Int) {
        let resourceRequest = NSBundleResourceRequest(tags: ["level\(level)"])
        resourceRequest.endAccessingResources()
    }
}

// Xcode configuration:
// 1. Select asset in Asset Catalog
// 2. Set "On Demand Resource Tags" to "level1", "level2", etc.
// 3. Initial install downloads only essential assets
// 4. Level-specific assets download on-demand
💡 On-Demand Resources Strategy:
  • Initial Download: Include only essential assets (app icon, launch screen, first screen content)
  • Prefetch: Download upcoming level/screen assets in background
  • Purge: Remove old level assets after completion to free device storage
  • Real Example: Angry Birds reduced initial download from 180MB to 45MB using on-demand resources

Android Image Optimization: Drawable Folders & Density Qualifiers

Understanding Android Screen Densities

Android uses a density-independent pixel (dp) system with multiple density buckets:

Density Qualifier Scale Factor Example (48dp icon) Device Distribution
Low ldpi 0.75x 36x36 pixels <1%
Medium mdpi 1x (baseline) 48x48 pixels ~8%
High hdpi 1.5x 72x72 pixels ~15%
Extra-high xhdpi 2x 96x96 pixels ~25%
Extra-extra-high xxhdpi 3x 144x144 pixels ~35%
Extra-extra-extra-high xxxhdpi 4x 192x192 pixels ~17%

Optimized Drawable Folder Structure

1. Recommended Density Strategy for 2025

// Optimal approach - focus on high-density devices
res/
├── drawable-mdpi/          // Skip - only 8% of devices
├── drawable-hdpi/          // Optional - 15% of devices
├── drawable-xhdpi/         // ✅ Required - 25% of devices
├── drawable-xxhdpi/        // ✅ Required - 35% of devices (PRIMARY)
├── drawable-xxxhdpi/       // ✅ Required - 17% of devices
└── drawable-nodpi/         // For images that shouldn't scale

// Use WebP format for all densities
drawable-xxhdpi/
├── product_image.webp      // Primary format
├── profile_avatar.webp
└── hero_banner.webp
💡 Density Optimization Strategy:
  • Skip ldpi/mdpi: Combined market share under 10%, not worth bundle size cost
  • xxhdpi as baseline: Design at @3x (xxhdpi), scale down to xhdpi, up to xxxhdpi
  • Android handles scaling: Missing densities automatically scale from nearest available
  • Savings: Providing only xhdpi/xxhdpi/xxxhdpi reduces image assets by 40% vs full density range

2. WebP Conversion for Android

// Build.gradle configuration for automatic WebP conversion
android {
    buildTypes {
        release {
            // Convert PNG to WebP during build
            crunchPngs = true
        }
    }

    defaultConfig {
        // Convert PNGs to WebP automatically
        vectorDrawables.useSupportLibrary = true
    }
}

// Android Studio: Right-click PNG → Convert to WebP
// Recommended settings:
// - Quality: 90% (lossy) or 100% (lossless for UI elements)
// - Skip images smaller than 1KB (overhead not worth it)
// - Preserve transparency

3. Vector Drawables for Icons & Simple Graphics



    






Vector Drawable Benefits:
  • Single file: One XML file replaces 5+ density-specific PNGs
  • Size savings: A vector icon is typically 2-5KB vs 15-50KB for all PNG densities combined
  • Perfect scaling: Sharp at any size, no pixelation
  • Runtime tinting: Change color dynamically without multiple image files
  • Example: Material Icons library provides 2000+ vector icons, total size ~500KB (vs 50MB+ as PNGs)

Android App Bundle (AAB) Optimization

Android App Bundles automatically optimize delivery based on device configuration:

Feature Description Typical Savings Implementation
Density Splits Device gets only its density assets 40-50% Automatic with AAB
Language Splits Device gets only selected languages 20-30% Automatic with AAB
Dynamic Delivery Features downloaded on-demand 50-70% Requires module setup
Asset Packs Large assets delivered separately 60-80% Configure in build.gradle

Dynamic Delivery Implementation

// build.gradle (app module)
android {
    dynamicFeatures = [':feature_premium', ':feature_filters']
}

// build.gradle (dynamic feature module)
apply plugin: 'com.android.dynamic-feature'

android {
    compileSdkVersion 34
}

dependencies {
    implementation project(':app')
}

// Load dynamic feature on-demand
val request = SplitInstallRequest.newBuilder()
    .addModule("feature_filters")
    .build()

splitInstallManager.startInstall(request)
    .addOnSuccessListener { sessionId ->
        // Module installation started
    }
    .addOnFailureListener { exception ->
        // Handle installation failure
    }

Dark Mode Image Optimization

iOS Dark Mode Asset Variants

iOS supports appearance-specific assets within asset catalogs:

// Asset catalog structure for dark mode
Assets.xcassets/
└── Logo.imageset/
    ├── Contents.json
    ├── logo-light@2x.png    // Used in light mode
    ├── logo-light@3x.png
    ├── logo-dark@2x.png     // Used in dark mode
    └── logo-dark@3x.png

// Contents.json with appearance variants
{
  "images" : [
    {
      "filename" : "logo-light@2x.png",
      "idiom" : "universal",
      "scale" : "2x",
      "appearances" : [ { "appearance" : "luminosity", "value" : "light" } ]
    },
    {
      "filename" : "logo-dark@2x.png",
      "idiom" : "universal",
      "scale" : "2x",
      "appearances" : [ { "appearance" : "luminosity", "value" : "dark" } ]
    }
  ]
}

Android Dark Mode Drawable Qualifiers

// Android drawable folder structure
res/
├── drawable-night/             // Dark mode specific
│   ├── logo.webp              // Dark variant
│   └── background.webp
└── drawable/                   // Default (light mode)
    ├── logo.webp
    └── background.webp

// Programmatic access - Android automatically selects correct variant
imageView.setImageResource(R.drawable.logo)  // Switches based on theme
💡 Dark Mode Image Strategy:
  • Logos/Branding: Provide both light and dark variants (especially if logo has dark background)
  • UI Icons: Use programmatic tinting instead of separate images (saves 50% space)
  • Photos: Usually don't need dark variants (maintain visual consistency)
  • Illustrations: Consider providing dark variants if they contain large solid color areas
  • Testing: Test both modes - dark mode can reveal poor contrast or blending issues

CDN Image Delivery for Mobile Apps

When to Use CDN vs Bundled Assets

Asset Type Bundled in App CDN Delivery Reasoning
App Icons ✅ Yes x No Required for launch, must be instant
UI Elements ✅ Yes x No Critical for user experience
Launch Screen ✅ Yes x No Displayed before network available
User-Generated Content x No ✅ Yes Dynamic, constantly changing
Product Images x No ✅ Yes Frequently updated, large catalog
News/Article Images x No ✅ Yes Time-sensitive, constantly changing
Onboarding Screens ⚠️ Hybrid ⚠️ Hybrid Bundle basics, CDN for A/B testing

Responsive Image Loading with CDN

iOS Implementation with Kingfisher

import Kingfisher

class ProductViewController: UIViewController {
    @IBOutlet weak var productImageView: UIImageView!

    func loadProductImage(productId: String) {
        let scale = UIScreen.main.scale
        let size = productImageView.bounds.size

        // Calculate optimal image dimensions for device
        let width = Int(size.width * scale)
        let height = Int(size.height * scale)

        // CDN URL with dynamic sizing (Cloudinary example)
        let urlString = "https://res.cloudinary.com/demo/image/upload/" +
                       "w_\(width),h_\(height),c_fill,f_auto,q_auto/" +
                       "products/\(productId).jpg"

        guard let url = URL(string: urlString) else { return }

        // Load with caching and placeholder
        productImageView.kf.setImage(
            with: url,
            placeholder: UIImage(named: "placeholder"),
            options: [
                .transition(.fade(0.2)),
                .cacheOriginalImage,
                .scaleFactor(scale)
            ]
        )
    }
}

Android Implementation with Glide

import com.bumptech.glide.Glide
import com.bumptech.glide.load.resource.drawable.DrawableTransitionOptions

class ProductActivity : AppCompatActivity() {

    fun loadProductImage(imageView: ImageView, productId: String) {
        val displayMetrics = resources.displayMetrics
        val density = displayMetrics.density
        val width = (imageView.width * density).toInt()
        val height = (imageView.height * density).toInt()

        // CDN URL with automatic format selection
        val imageUrl = "https://cdn.example.com/products/$productId" +
                      "?w=$width&h=$height&format=auto&quality=auto"

        Glide.with(this)
            .load(imageUrl)
            .placeholder(R.drawable.placeholder)
            .transition(DrawableTransitionOptions.withCrossFade())
            .override(width, height)  // Request specific size
            .into(imageView)
    }
}

CDN Configuration Best Practices

Recommended CDN Settings for Mobile:
  • Auto Format: Enable automatic format selection (WebP for Android, HEIC for iOS where supported)
  • Auto Quality: Let CDN determine optimal quality based on network speed (85-95% range)
  • Responsive Sizing: Request exact dimensions needed (don't download 2000px image for 300px display)
  • Caching Headers: Set long cache durations (1 year) for product images, short (1 hour) for user content
  • Progressive Loading: Enable progressive JPEG/baseline WebP for faster perceived load
  • Lazy Loading: Only load images as they scroll into viewport (save bandwidth)

Offline Caching Strategy

Caching Strategy Use Case Cache Duration Storage Size
Memory Cache Currently visible screens Until app closes 50-100MB
Disk Cache Recently viewed content 7-30 days 200-500MB
Persistent Cache User favorites, saved items Until user clears Unlimited (user controlled)
Preload Cache Likely next screens Session only 20-50MB

Image Conversion Tools for Mobile Development

Batch Conversion Tools

Tool Platform Best For Key Features Cost
ImageMagick CLI (all platforms) Automated pipelines Batch conversion, scripting, CI/CD integration Free
Xcode macOS iOS development Asset catalog management, built-in optimization Free
Android Studio All platforms Android development WebP conversion, vector import, density generation Free
Squoosh Web-based Manual optimization Visual quality comparison, format testing Free
TinyPNG Web-based + API PNG/JPEG compression Smart lossy compression, batch processing Free tier + paid API

Automated Build Pipeline Integration

iOS Fastlane Integration

# Fastfile - Automated image optimization
lane :optimize_images do
  # Convert all JPEG/PNG to HEIC for iOS 11+
  sh("find ../Assets.xcassets -name '*.jpg' -o -name '*.png' | while read file; do
    heif-enc -q 90 "$file" -o "${file%.*}.heic"
  done")

  # Remove @1x assets (legacy devices <0.5%)
  sh("find ../Assets.xcassets -name '*@1x.*' -delete")

  # Optimize remaining PNGs (for transparency)
  sh("find ../Assets.xcassets -name '*.png' -exec pngquant --quality=85-95 --ext .png --force {} \\;")
end

Android Gradle Integration

// build.gradle - Automatic optimization during build
android {
    buildTypes {
        release {
            // Enable PNG crunching (optimization)
            crunchPngs = true

            // Shrink resources (remove unused)
            shrinkResources = true
            minifyEnabled = true
        }
    }
}

// Custom task for WebP conversion
task convertToWebP {
    doLast {
        fileTree("src/main/res").matching {
            include "**/drawable-*/*.png"
            include "**/mipmap-*/*.png"
        }.each { file ->
            def webpFile = new File(file.parent, file.name.replace('.png', '.webp'))
            exec {
                commandLine 'cwebp', '-q', '90', file.path, '-o', webpFile.path
            }
            file.delete()
        }
    }
}

preBuild.dependsOn convertToWebP

Testing & Performance Measurement

iOS Performance Testing

// Swift - Measure image loading performance
import UIKit

class ImagePerformanceTester {
    func testImageLoadingPerformance(imageURL: URL) {
        let startTime = CFAbsoluteTimeGetCurrent()

        URLSession.shared.dataTask(with: imageURL) { data, response, error in
            guard let data = data else { return }

            let downloadTime = CFAbsoluteTimeGetCurrent() - startTime

            let decodeStart = CFAbsoluteTimeGetCurrent()
            let image = UIImage(data: data)
            let decodeTime = CFAbsoluteTimeGetCurrent() - decodeStart

            print("Download time: \(downloadTime)s")
            print("Decode time: \(decodeTime)s")
            print("Total time: \(downloadTime + decodeTime)s")
            print("Image size: \(data.count / 1024)KB")
        }.resume()
    }
}

Android Performance Testing

// Kotlin - Measure image loading performance
import android.graphics.BitmapFactory
import kotlin.system.measureTimeMillis

class ImagePerformanceTester {
    fun testImageLoading(imageUrl: String) {
        val downloadTime = measureTimeMillis {
            val connection = URL(imageUrl).openConnection()
            val inputStream = connection.getInputStream()
            val bytes = inputStream.readBytes()

            val decodeTime = measureTimeMillis {
                val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
                Log.d("ImageTest", "Bitmap size: ${bitmap.width}x${bitmap.height}")
            }

            Log.d("ImageTest", "Download time: ${downloadTime}ms")
            Log.d("ImageTest", "Decode time: ${decodeTime}ms")
            Log.d("ImageTest", "File size: ${bytes.size / 1024}KB")
        }
    }
}

Key Performance Metrics

Metric Target (4G) Target (WiFi) How to Measure
Initial App Download < 50MB < 100MB App Store Connect, Play Console
First Screen Load < 2 seconds < 1 second Firebase Performance, custom timing
Individual Image Load < 500ms < 200ms Network profiler tools
Memory Usage (images) < 100MB < 150MB Xcode Instruments, Android Profiler

Real-World Case Studies

Case Study 1: E-Commerce App (Fashion Retailer)

Problem: App size was 145MB, causing 35% abandon rate before download completion. Product images loaded slowly on 4G.

Optimization Strategy:

  • Migrated from PNG to WebP for all product images (iOS 14+, Android)
  • Removed @1x assets and mdpi/ldpi densities
  • Implemented CDN delivery for product catalog (moved from bundled assets)
  • Added progressive image loading with blur-up placeholders
  • Implemented dynamic delivery for seasonal collections

Results:

Metric Before After Improvement
App Download Size 145MB 52MB 64% reduction
Download Abandon Rate 35% 12% 66% improvement
Product Image Load Time (4G) 2.8s 0.9s 68% faster
Monthly CDN Bandwidth N/A 850GB Saved from app bundle

Case Study 2: Social Media App (Photo Sharing)

Problem: Users complained about slow feed loading and high data usage (averaging 500MB/week).

Optimization Strategy:

  • Implemented adaptive bitrate image loading based on network speed
  • Added aggressive memory caching (100MB limit)
  • Implemented lazy loading (images load as scrolled into view)
  • Used HEIC for iOS uploads, AVIF for Android 12+
  • Added "Data Saver" mode with lower quality images

Results:

Metric Before After Improvement
Feed Load Time 4.2s 1.8s 57% faster
Weekly Data Usage 500MB 180MB 64% reduction
Data Saver Mode Usage 320MB 85MB 73% reduction
User Session Length 12 min 18 min 50% increase

Case Study 3: Gaming App (Puzzle Game)

Problem: App was 280MB with all level assets bundled. Users had to download entire app before playing.

Optimization Strategy:

  • Implemented On-Demand Resources (iOS) and Dynamic Delivery (Android)
  • Bundled only first 10 levels in initial download
  • Converted all game assets to WebP (was PNG)
  • Created asset packs for level groups (10 levels per pack)
  • Implemented background prefetching of next level pack

Results:

Metric Before After Improvement
Initial Download Size 280MB 45MB 84% reduction
Install Completion Rate 68% 92% 35% improvement
Time to First Gameplay 8 minutes 45 seconds 91% faster
Day 1 Retention 45% 68% 51% improvement

Mobile Image Optimization Checklist

iOS Optimization Checklist

Format Selection:

  • Use HEIC for photos (iOS 11+)
  • Use WebP for web content (iOS 14+)
  • Use PDF/Vector for scalable UI elements

Asset Catalogs:

  • Provide only @2x and @3x assets (skip @1x)
  • Configure appearance variants for dark mode
  • Enable compression in asset catalog settings

App Thinning:

  • Enable app slicing in Xcode
  • Implement on-demand resources for large assets
  • Use prefetching for upcoming content

Performance:

  • Target <50MB initial download size
  • Implement image caching (Kingfisher, SDWebImage)
  • Test on real devices with 4G network throttling

Android Optimization Checklist

Format Selection:

  • Use WebP for all images (Android 4.0+)
  • Use AVIF for Android 12+ (progressive enhancement)
  • Use Vector Drawables for icons and simple graphics

Drawable Resources:

  • Provide xhdpi, xxhdpi, xxxhdpi densities (skip mdpi/ldpi)
  • Create night/ variants for dark mode specific images
  • Use nodpi/ for images that shouldn't scale

App Bundle:

  • Switch from APK to AAB (Android App Bundle)
  • Implement dynamic feature modules for optional content
  • Configure asset packs for large downloads

Performance:

  • Enable crunchPngs and shrinkResources in build.gradle
  • Implement image caching (Glide, Coil)
  • Test on various screen densities and Android versions

Conclusion

Mobile app image optimization in 2025 requires a strategic approach across both iOS and Android platforms. By implementing modern formats (HEIC, WebP, AVIF), leveraging platform-specific features (asset catalogs, app bundles), and using CDN delivery for dynamic content, you can significantly reduce app size while maintaining visual quality.

Key Takeaways

  • Format Strategy: HEIC/WebP reduce file sizes by 40-60% compared to PNG with no visible quality loss
  • Density Optimization: Focus on high-density devices (iOS @2x/@3x, Android xhdpi/xxhdpi/xxxhdpi) - skip legacy densities
  • Dynamic Delivery: Use on-demand resources and dynamic features to reduce initial download by 60-80%
  • CDN Integration: Move non-critical images to CDN to keep app bundle small and enable real-time updates
  • Testing: Measure real-world performance on actual devices with network throttling

Remember: Every 6MB increase in app size leads to 1% decrease in installs. Start with the highest-impact optimizations (format conversion, removing legacy densities) before moving to advanced techniques (dynamic delivery, progressive loading).

Ready to optimize?

Use Convert a Document to shrink files without sacrificing quality.

Related articles

About Convert a Document

Convert a Document helps you understand, convert, and optimize files with simple tools and clear guidance for everyday workflows.