Optimization & Performance

Shipping TrailLog to the Play Store isn't the end of the work — a published app still needs to install quickly, scroll smoothly, and avoid slowly consuming more memory than it should. This lesson covers app size, the tools Android Studio gives you to actually measure performance rather than guess at it, one real, common performance mistake and its fix, and memory leaks — the quiet kind of bug that only shows up after an app has been running for a while.

Reducing App Size

TechniqueWhat it does
R8 code shrinkingRemoves unused classes and methods, and shortens remaining names — covered in ProGuard / R8
Resource shrinkingDrops unused layouts, drawables, and strings that R8 can't see but Gradle's resource shrinker can detect
WebP instead of PNGTrailLog's trail thumbnails and icons compress noticeably smaller as WebP with no visible quality loss
App Bundle per-device deliveryPlay serves only the density and CPU architecture a given device needs — see App Bundle & Play Store

Android Studio Profiler

Guessing at what's slow rarely finds the real cause — Android Studio's Profiler gives actual measurements while TrailLog runs:

ProfilerWhat to look for
CPU ProfilerWhich function calls are eating time on the main thread during a janky scroll
Memory ProfilerA memory usage graph that keeps climbing and never drops back down after garbage collection — a leak signature
Network ProfilerHow often TrailLog's Retrofit calls actually hit the network, and how large each response is

A Real Mistake: Decoding Images Inside onBindViewHolder()

Here's a genuinely common way TrailLog's trail list could end up janky — decoding each trail photo directly, synchronously, inside the RecyclerView adapter's bind function:

The mistake

override fun onBindViewHolder(holder: TrailViewHolder, position: Int) {
    val trail = trails[position]
    // Decoding a full-size bitmap on the main thread, every single time
    // this row scrolls onto screen — this is what causes the jank
    val bitmap = BitmapFactory.decodeFile(trail.photoUri)
    holder.thumbnail.setImageBitmap(bitmap)
}

BitmapFactory.decodeFile is a slow, synchronous, main-thread operation, and onBindViewHolder() is called constantly as a user scrolls — every one of those calls blocks the main thread just long enough to drop frames, which is exactly what scroll jank feels like.

The Fix: Coil

Coil is an image-loading library built around Kotlin coroutines — it loads and decodes images off the main thread automatically, caches the result, and cancels an in-flight load if its target view is recycled before the load finishes:

The fix

override fun onBindViewHolder(holder: TrailViewHolder, position: Int) {
    val trail = trails[position]
    holder.thumbnail.load(trail.photoUri) {
        crossfade(true)
        placeholder(R.drawable.trail_placeholder)
    }
}

load(...) here is Coil's extension function on ImageView — the decoding happens on a background dispatcher, the result is cached so scrolling back to a row already shown doesn't re-decode it, and the main thread stays free to keep the scroll smooth.

Avoiding Memory Leaks

A memory leak in an Android app usually comes down to one pattern: something with a long lifetime holding a reference to something with a short one, preventing the short-lived object from ever being garbage collected. A Fragment holding onto its View Binding instance past the Fragment view's own lifecycle is a classic example — which is exactly why the nullable backing-property pattern introduced in View Binding clears that reference in onDestroyView(). The same care applies to holding an Activity or Context reference inside a long-lived object, like a singleton or a static field — that reference keeps the entire Activity, and everything it holds onto, alive in memory long after the screen itself should have been destroyed.

Note: Every code sample and profiling scenario shown here reflects real, correct Android performance techniques — but this website's own project doesn't run a live Android Studio build or Profiler, so nothing here executes on the page itself.

Baseline Profiles, Briefly

A Baseline Profile is a file, shipped inside TrailLog's App Bundle, that tells Android ahead of time which classes and methods are used most heavily during critical moments like app startup or scrolling the trail list. Android uses that information to pre-compile exactly those hot paths at install time, instead of interpreting or just-in-time compiling them the first time they run — meaningfully improving startup time and early scroll smoothness for real users, without requiring any change to TrailLog's actual logic.

Up next: Best Practices closes out this track with a checklist-style summary tying together everything covered across all 64 lessons.