Mobile App Performance Optimisation

NOVASOLUTIONS.TECHNOLOGY is engaged in the development, support and maintenance of iOS, Android, PWA mobile applications. We have extensive experience and expertise in publishing mobile applications in popular markets like Google Play, App Store, Amazon, AppGallery and others.
Development and support of all types of mobile applications:
Information and entertainment mobile applications
News apps, games, reference guides, online catalogs, weather apps, fitness and health apps, travel apps, educational apps, social networks and messengers, quizzes, blogs and podcasts, forums, aggregators
E-commerce mobile applications
Online stores, B2B apps, marketplaces, online exchanges, cashback services, exchanges, dropshipping platforms, loyalty programs, food and goods delivery, payment systems.
Business process management mobile applications
CRM systems, ERP systems, project management, sales team tools, financial management, production management, logistics and delivery management, HR management, data monitoring systems
Electronic services mobile applications
Classified ads platforms, online schools, online cinemas, electronic service platforms, cashback platforms, video hosting, thematic portals, online booking and scheduling platforms, online trading platforms

These are just some of the types of mobile applications we work with, and each of them may have its own specific features and functionality, tailored to the specific needs and goals of the client.

Showing 35 of 35 servicesAll 1735 services
Medium
from 1 business day to 3 business days
Medium
~3-5 business days
Simple
from 4 hours to 2 business days
Medium
from 4 hours to 2 business days
FAQ
Our competencies:
Development stages
Latest works
  • image_mobile-applications_feedme_467_0.webp
    Development of a mobile application for FEEDME
    756
  • image_mobile-applications_xoomer_471_0.webp
    Development of a mobile application for XOOMER
    624
  • image_mobile-applications_rhl_428_0.webp
    Development of a mobile application for RHL
    1052
  • image_mobile-applications_zippy_411_0.webp
    Development of a mobile application for ZIPPY
    947
  • image_mobile-applications_affhome_429_0.webp
    Development of a mobile application for Affhome
    862
  • image_mobile-applications_flavors_409_0.webp
    Development of a mobile application for the FLAVORS company
    445

Mobile App Optimization: Cold Start, Memory, Battery, FPS, Profiling

An app with a cold start time of 4+ seconds loses users before the first screen. Android Vitals in Google Play Console directly affects search ranking: apps with poor metrics get lower organic reach. Apple similarly monitors crash rate and launch time via MetricKit. Optimization is not "making it faster," but understanding where exactly time is lost and what to do about it.

Cold Start: Where Time is Lost Before First Frame

Cold start is launching an app when the process doesn't exist in memory. On Android, this is the time from tapping the icon to Activity.onWindowFocusChanged(hasFocus = true). On iOS—from tap to viewDidAppear of the first screen.

Android: Main Thread Overloaded at Initialization

Application.onCreate()—the main enemy of fast startup on Android. Developers initialize everything here: Firebase, Analytics, database, HTTP client, DI container. Each SDK adds 20–200 ms to the main thread.

Diagnostic tool: Android Studio Profiler → App Startup. Shows an initialization graph with time for each component. Alternative—Tracing.beginSection("MyInitTag") in code + systrace.

Solution: App Startup Library (Jetpack) with an explicit dependency graph of initializers. Components needed only in specific scenarios are initialized lazily—by lazy {} or initializer with lazyInit flag. Firebase Analytics, for example, isn't needed until the first user action—its initialization can be deferred.

ContentProviders automatically added by SDK via AndroidManifest merge also start at app launch. tools:node="remove" in the manifest lets you disable a specific provider and initialize the SDK manually when needed.

Another trap: Room.databaseBuilder().build() on the main thread. This is a synchronous operation to create/open the database file—on slow devices, it takes 50–300 ms. Move to a coroutine with Dispatchers.IO, in a ViewModel via viewModelScope.launch.

iOS: Dyld Linking and +load

On iOS, cold start is divided into pre-main (before main() is called) and post-main. Pre-main is the time to load dylib, rebase/binding, Objective-C runtime initialization, and execution of +load methods.

Xcode Instruments → App Launch template shows pre-main and post-main times separately. DYLD_PRINT_STATISTICS=1 in the launch scheme outputs detailed timing to the console.

What kills pre-main:

  • Many dynamic libraries (each dylib—linking overhead). CocoaPods adds a separate dylib per pod. Solution: Swift Package Manager with static linking (type: .static) or use_frameworks! :linkage => :static in CocoaPods.
  • +load methods in Objective-C—execute synchronously when the class loads, before main(). Third-party SDKs may abuse this. +initialize—a lazy analog, called on first access to the class.

Post-main is application(_:didFinishLaunchingWithOptions:). Same story as Android: synchronous initialization of everything. lazy var for services not needed immediately. SwiftUI @StateObject initializes the object only when the View appears—this is already built-in laziness.

Target metrics (App Store recommendations): cold start < 400 ms for simple apps, < 2 seconds for complex. Warm start (process in memory, but Activity/Scene is recreated) < 1 second.

Memory: Leaks, OOM, Excessive Pressure

A memory leak in iOS is a retention cycle: object A holds a reference to B, B holds to A, neither is freed. Classic: Timer with self in a closure without [weak self]. Timer holds the closure, the closure holds self (ViewController), ViewController is not freed when closed. Instruments → Leaks or Memory Graph Debugger in Xcode—finds live objects that shouldn't exist.

On Android, garbage collector manages memory, but leaks still happen. Activity or Fragment held via static reference, singleton, or Handler/Runnable after onDestroy—classic. LeakCanary is a must-have in debug builds. Added with one dependency debugImplementation "com.squareup.leakcanary:leakcanary-android" and automatically detects leaks with full stack trace.

OutOfMemoryError usually happens from loading images. Bitmap in memory takes width × height × 4 bytes. A 4000×3000 px image—48 MB in memory, regardless of file size on disk. Glide / Coil handle this correctly: load with downsampling to View size, cache in LRU cache. Loading to ImageView without Glide/Coil via BitmapFactory.decodeFile—path to OOM on 2 GB RAM devices.

On Flutter, Dart VM has its own GC, but native resources (images, textures) are not managed by Dart GC. Image.network caches images in memory without automatic release on exit from the widget tree—on long lists with images, use cached_network_image with proper memCacheWidth/memCacheHeight.

FPS and UI Performance

60 FPS—16.67 ms per frame. 120 FPS (ProMotion)—8.33 ms. Anything more on the main thread—jank.

Typical FPS drops causes:

On iOS: synchronous image decoding in cellForRowAt. When a table cell appears, UIImage(contentsOfFile:) decodes JPEG/PNG on the main thread—visible as sluggish scroll on long lists. Solution: UIImage.preparingForDisplay() (iOS 15+) or ImageIO with kCGImageSourceCreateThumbnailWithTransform in a background queue, result via DispatchQueue.main.async.

On Android: RecyclerView.Adapter.onBindViewHolder with synchronous operations. Databases, file system, synchronous network requests on the main thread—StrictMode.ThreadPolicy with detectAll().penaltyLog() in debug builds shows all violations.

On Flutter: build() method is called often, it should be cheap. setState() on a top widget rebuilds the whole tree. const constructors, RepaintBoundary, breaking into small widgets with local state—main tools. Flutter DevTools → Performance shows janky frames (red) with causes.

Profiling Compose: Recomposition Highlighter and tracing via Trace.beginSection in @Composable. remember for expensive computations, derivedStateOf for computed values, LazyColumn instead of Column + forEach for long lists.

Battery: Wake Locks, WorkManager, Network Requests

An app at the top of battery usage—users see it in settings and uninstall. Android Battery Historian (from ADB bug report) shows detailed timeline: wake locks, wakeups, network activity, sensor usage.

Main energy consumers:

  • Constant GPS (see maps-geo)
  • Polling network every N seconds instead of push
  • Holding wake lock longer than needed
  • Excessive AlarmManager wakeups

WorkManager with Constraints—the right way to schedule background tasks: setRequiredNetworkType, setRequiresBatteryNotLow, setRequiresCharging. The OS batches tasks and runs at convenient times.

On iOS, BGTaskScheduler with BGProcessingTaskRequest (for heavy tasks on charging) and BGAppRefreshTaskRequest (for light updates)—the system decides when to run, the developer just registers and implements logic.

Network request batching: instead of 10 separate requests over a minute—one batch request. Fewer radio activations (LTE radio consumes a lot on connection initialization), fewer wakeups.

Profiling Tools

Platform Tool What It Shows
iOS Xcode Instruments (Time Profiler) CPU, call stack, hot methods
iOS Allocations Live objects, memory peaks
iOS Leaks Retention cycles
iOS MetricKit Production metrics (crash rate, hang rate, launch time)
Android Android Profiler CPU, Memory, Network, Energy
Android Systrace / Perfetto System-level traces
Android LeakCanary Memory leaks
Android Battery Historian Energy consumption
Flutter Flutter DevTools Recomposition, frame rendering, memory
Flutter Dart Observatory Dart VM profiling

MetricKit on iOS is especially valuable: real data from user devices, not the emulator. MXMetricManager gets aggregated metrics once a day: MXAppLaunchMetric, MXHangDiagnostic, MXCPUExceptionDiagnostic. Diagnostics for hang and CPU-exceptions contain a stack trace from a real device—gold for diagnosing production issues.

Optimization Process

Start with measurement, not assumptions. The tools above give numbers: specific cold start time, specific memory volume, specific frames with drops. Then—prioritize by impact: what affects user experience most in this specific app.

Auditing existing app performance: 3–5 business days. Implementing optimizations—from a week to two months depending on problem severity and code architecture.