Back to all articles

Mobile App Performance Optimization: Speed Up Your App by 50%

53% of users abandon apps that take over 3 seconds to load. Every 100ms delay decreases conversion by 7%. This guide shows how to optimize performance and keep users engaged.

Performance Impact

  • 53% abandon if load time > 3 seconds
  • 100ms delay = 7% conversion drop
  • 1-star increase in rating = 8% revenue boost
  • Slow apps get 1-star reviews 2x more

App Startup Optimization

Cold Start Optimization

Target times:
- Cold start: < 1.5 seconds
- Warm start: < 1 second
- Hot start: < 500ms

Optimization strategy:
1. Defer non-critical initialization
2. Lazy load heavy resources
3. Minimize Application onCreate
4. Preload critical data only
5. Use splash screen effectively

iOS Launch Optimization

func application(_ application: UIApplication,
  didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {

  // Critical only
  setupWindow()
  configureFirebase()

  // Defer to background
  DispatchQueue.global(qos: .utility).async {
    self.setupAnalytics()
    self.fetchRemoteConfig()
    self.initializeSDKs()
  }

  return true
}

Android Launch Optimization

class MyApplication : Application() {
  override fun onCreate() {
    super.onCreate()

    // Critical initialization
    Timber.plant(Timber.DebugTree())

    // Use WorkManager for deferred tasks
    WorkManager.getInstance(this).enqueue(
      OneTimeWorkRequestBuilder()
        .setInitialDelay(2, TimeUnit.SECONDS)
        .build()
    )
  }
}

Memory Management

iOS Memory Best Practices

// Use weak references to avoid retain cycles
class ViewController: UIViewController {
  weak var delegate: MyDelegate?

  // Lazy loading
  lazy var heavyResource: HeavyObject = {
    return HeavyObject()
  }()

  // Release resources
  deinit {
    NotificationCenter.default.removeObserver(self)
    heavyResource.cleanup()
  }
}

// Image loading optimization
let imageView = UIImageView()
imageView.contentMode = .scaleAspectFit
imageView.sd_setImage(with: url, placeholderImage: placeholder)
  .retry(maxAttempts: 3)
  .downsampling(size: targetSize)

Android Memory Best Practices

// Use lifecycle-aware components
class MyActivity : AppCompatActivity() {
  // Avoid memory leaks
  private val viewModel: MyViewModel by viewModels()

  override fun onDestroy() {
    super.onDestroy()
    // Clean up references
    binding = null
    adapter = null
  }
}

// Image loading with Glide
Glide.with(context)
  .load(url)
  .diskCacheStrategy(DiskCacheStrategy.ALL)
  .override(targetWidth, targetHeight)
  .placeholder(R.drawable.placeholder)
  .into(imageView)

Network Optimization

API Request Optimization

Best practices:
✓ Compress requests (gzip)
✓ Batch API calls
✓ Implement caching strategy
✓ Use pagination
✓ Implement retry with exponential backoff
✓ Cancel unused requests

// Request batching
class APIBatcher {
  private var pendingRequests: [Request] = []

  func addRequest(_ request: Request) {
    pendingRequests.append(request)

    // Batch every 100ms or 10 requests
    if pendingRequests.count >= 10 || timer.elapsed > 0.1 {
      executeBatch()
    }
  }
}

Image Optimization

  • Use WebP format (30% smaller than PNG)
  • Serve multiple resolutions (1x, 2x, 3x)
  • Lazy load images below fold
  • Implement progressive loading
  • Cache aggressively

Caching Strategy

Cache levels:
1. Memory cache (fastest, limited)
2. Disk cache (fast, larger)
3. Network (slow, always fresh)

Implementation:
let cache = URLCache(
  memoryCapacity: 50_000_000,  // 50 MB
  diskCapacity: 200_000_000,   // 200 MB
  diskPath: "myCache"
)

URLSession.shared.configuration.urlCache = cache
URLSession.shared.configuration.requestCachePolicy = .returnCacheDataElseLoad

UI Performance

Smooth Scrolling

iOS - UITableView/UICollectionView:
- Reuse cells properly
- Avoid heavy calculations in cellForRow
- Preload images before scrolling
- Use opaque views when possible
- Minimize view hierarchy depth

func tableView(_ tableView: UITableView,
  cellForRowAt indexPath: IndexPath) -> UITableViewCell {
  let cell = tableView.dequeueReusableCell(
    withIdentifier: "Cell", for: indexPath
  )

  // Fast operation only
  cell.textLabel?.text = items[indexPath.row].title

  // Defer heavy operations
  DispatchQueue.global().async {
    let image = self.processImage(at: indexPath.row)
    DispatchQueue.main.async {
      cell.imageView?.image = image
    }
  }

  return cell
}

Android RecyclerView Optimization

class MyAdapter : RecyclerView.Adapter() {
  // ViewHolder pattern (built-in)
  class MyViewHolder(view: View) : RecyclerView.ViewHolder(view) {
    val title: TextView = view.findViewById(R.id.title)
    val image: ImageView = view.findViewById(R.id.image)
  }

  override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
    val item = items[position]

    // Fast operations only
    holder.title.text = item.title

    // Async image loading
    Glide.with(holder.itemView.context)
      .load(item.imageUrl)
      .into(holder.image)
  }
}

// Enable optimizations
recyclerView.apply {
  setHasFixedSize(true)
  setItemViewCacheSize(20)
  setDrawingCacheEnabled(true)
  setDrawingCacheQuality(View.DRAWING_CACHE_QUALITY_HIGH)
}

Animation Performance

  • Target 60 FPS (16.67ms per frame)
  • Use transform animations (translate, scale, rotate)
  • Avoid animating layout properties
  • Use hardware acceleration
  • Keep animations under 300ms

Background Task Optimization

iOS Background Tasks

// Background fetch
func application(_ application: UIApplication,
  performFetchWithCompletionHandler completionHandler:
  @escaping (UIBackgroundFetchResult) -> Void) {

  // Fetch data efficiently
  DataManager.shared.syncData { success in
    if success {
      completionHandler(.newData)
    } else {
      completionHandler(.failed)
    }
  }
}

// Use background URLSession
let config = URLSessionConfiguration.background(
  withIdentifier: "com.app.background"
)
let session = URLSession(configuration: config)

Android WorkManager

// Efficient background work
class SyncWorker(context: Context, params: WorkerParameters)
  : CoroutineWorker(context, params) {

  override suspend fun doWork(): Result {
    return try {
      // Do work efficiently
      syncData()
      Result.success()
    } catch (e: Exception) {
      Result.retry()
    }
  }
}

// Schedule periodic sync
val syncRequest = PeriodicWorkRequestBuilder(
  15, TimeUnit.MINUTES
)
  .setConstraints(
    Constraints.Builder()
      .setRequiredNetworkType(NetworkType.CONNECTED)
      .setRequiresBatteryNotLow(true)
      .build()
  )
  .build()

WorkManager.getInstance(context).enqueue(syncRequest)

Database Performance

SQLite Optimization

Best practices:
✓ Create indexes on frequently queried columns
✓ Use transactions for bulk operations
✓ Normalize data structure
✓ Use prepared statements
✓ VACUUM regularly to defragment

// Batch insert with transaction
database.beginTransaction()
try {
  for (item in items) {
    database.insert("items", null, item.toContentValues())
  }
  database.setTransactionSuccessful()
} finally {
  database.endTransaction()
}

Core Data / Room Optimization

  • Use batch operations
  • Implement pagination
  • Use background contexts
  • Fetch only needed properties
  • Set appropriate fetch batch sizes

Build Optimization

App Size Reduction

iOS:
- Enable bitcode
- Use on-demand resources
- Strip unused architectures
- Compress assets
- Remove unused code (AppThinning)

Android:
- Enable R8/ProGuard
- Use Android App Bundle (AAB)
- Split APKs by ABI
- Compress resources
- Remove unused resources (shrinkResources)

Compilation Speed

  • Modularize codebase
  • Use build caching
  • Incremental builds
  • Parallel compilation
  • Optimize dependencies

Profiling Tools

iOS Instruments

  • Time Profiler: CPU usage
  • Allocations: Memory leaks
  • Leaks: Retain cycles
  • Network: API performance
  • Energy Log: Battery impact

Android Profiler

  • CPU Profiler: Method tracing
  • Memory Profiler: Heap dumps
  • Network Profiler: Request/response
  • Energy Profiler: Battery drain

Performance Metrics

Key Metrics to Track

Launch Time:
- Cold start: < 1.5s
- Warm start: < 1s
- Hot start: < 500ms

Frame Rate:
- Target: 60 FPS (16.67ms/frame)
- Acceptable: 50+ FPS
- Poor: < 30 FPS

Memory:
- Steady state: < 100 MB
- Peak: < 200 MB
- No memory leaks

Network:
- API latency: < 200ms
- Image load: < 1s
- Cache hit rate: > 80%

Battery:
- Background drain: < 2%/hour
- Active use: < 5%/hour

Monitoring and Alerts

Real-Time Monitoring

  • Firebase Performance: Automatic metrics
  • New Relic: Detailed APM
  • AppDynamics: End-to-end monitoring
  • Sentry: Performance tracking

Custom Metrics

// Track custom performance
let trace = Performance.startTrace(name: "image_load")
trace.setValue(imageSize, forMetric: "size")

loadImage(url) { image in
  trace.incrementMetric("images_loaded", by: 1)
  trace.stop()
}

Performance Testing

Load Testing

  • Test with 1000+ items
  • Simulate slow network (3G)
  • Test on low-end devices
  • Memory stress testing
  • Battery drain testing

Automated Testing

// XCTest performance testing
func testPerformanceExample() {
  measure {
    // Code to measure
    viewModel.loadData()
  }
}

// Android benchmark
@Test
fun testListScrolling() {
  benchmarkRule.measureRepeated {
    // Scroll list
    recyclerView.scrollToPosition(100)
  }
}

Platform-Specific Optimizations

iOS-Specific

  • Use Swift over Objective-C (faster)
  • Enable whole module optimization
  • Use value types (struct) when possible
  • Implement prefetching for collections
  • Use background modes judiciously

Android-Specific

  • Use Kotlin coroutines (vs threads)
  • Implement ViewModel + LiveData
  • Use DataBinding for UI
  • Enable Jetpack Compose optimization
  • Use Paging library for lists

Quick Wins Checklist

□ Enable code obfuscation/minification
□ Compress all images
□ Implement image caching
□ Add request caching
□ Lazy load non-critical resources
□ Use pagination for lists
□ Defer analytics initialization
□ Remove unused libraries
□ Enable gzip compression
□ Optimize database queries
□ Profile app with Instruments/Profiler
□ Test on low-end devices
□ Monitor real-user metrics
□ Set performance budgets

Conclusion

Performance optimization is an ongoing process. Profile regularly, set performance budgets, and prioritize user experience. Every millisecond counts in user retention and app store rankings.

Need a Support URL for Your App?

Generate a compliant, professional support page in under a minute. Our easy-to-use generator creates everything you need for App Store and Google Play submissions.