androidengineers.Book a session

Performance Optimization

Rendering Pipeline & Jank Hunting

article20 minHard

At 60 fps, you have 16.67ms to produce each frame. Miss that budget and the user sees jank — a visible stutter. At 120 fps, the budget shrinks to 8.33ms. Understanding the rendering pipeline tells you exactly where to look when frames drop.

The Rendering Pipeline

Main Thread                    Render Thread               GPU
─────────────────────────────────────────────────────────────
Input handling
Animations
Layout (measure/layout)
Drawing (Canvas operations) → DisplayList → GPU rasterization → Screen

Main thread produces the display list (recorded draw commands). Render thread (hardware-accelerated path) replays the display list. GPU rasterizes to the framebuffer.

Jank happens when the main thread exceeds its frame budget, causing the Render thread to skip a frame (Choreographer misses a vsync).

Detecting Jank

Method 1: Android Studio Profiler

Profile → CPU → Select "System Trace"
→ Record while scrolling
→ Look for frames that exceed 16ms in the "Frames" row
→ Click a long frame → see which method consumed the time

Method 2: gfxinfo

adb shell dumpsys gfxinfo com.example.myapp framestats
# Outputs per-frame timing for the last 120 frames
# Look for "Janky frames: N (X.XX%)"

Method 3: StrictMode

// In debug builds — catch IO on main thread
if (BuildConfig.DEBUG) {
    StrictMode.setThreadPolicy(
        StrictMode.ThreadPolicy.Builder()
            .detectDiskReads()
            .detectDiskWrites()
            .detectNetwork()
            .penaltyLog()  // logcat warning
            // .penaltyDeath()  // crash — useful in CI
            .build()
    )
}

Common Jank Causes

1. Synchronous Work on Main Thread

// ❌ Disk read on main thread — StrictMode will catch this
class MainActivity : AppCompatActivity() {
    override fun onResume() {
        val prefs = getSharedPreferences("prefs", MODE_PRIVATE)  // synchronous disk read
        val token = prefs.getString("token", null)
    }
}

// ✅ Move to background thread; use DataStore
lifecycleScope.launch {
    val token = dataStore.data.first()[TOKEN_KEY]
}

2. Layout Thrashing

// ❌ Reading layout properties forces a layout pass mid-frame
view.width  // forces measure if dirty
view.translationX = 50f  // marks layout dirty
view.width  // forces measure AGAIN — thrashing

// ✅ Cache layout properties; batch writes
val currentWidth = view.width  // one read
view.translationX = 50f       // write
// Don't read width again in the same frame

3. Expensive onDraw

// ❌ Path creation in onDraw — called every frame
override fun onDraw(canvas: Canvas) {
    val path = Path()  // allocation every frame
    path.moveTo(0f, 0f)
    canvas.drawPath(path, paint)
}

// ✅ Create path once; update only when data changes
private val path = Path()

fun updatePath(points: List<PointF>) {
    path.reset()
    points.firstOrNull()?.let { path.moveTo(it.x, it.y) }
    points.drop(1).forEach { path.lineTo(it.x, it.y) }
    invalidate()  // trigger redraw with the pre-built path
}

override fun onDraw(canvas: Canvas) {
    canvas.drawPath(path, paint)  // no allocation
}

Frame Metrics API

Record per-frame rendering times programmatically:

window.addOnFrameMetricsAvailableListener({ _, frameMetrics, _ ->
    val totalDuration = frameMetrics.getMetric(FrameMetrics.TOTAL_DURATION) / 1_000_000
    if (totalDuration > 16) {
        Log.w("Jank", "Slow frame: ${totalDuration}ms")
    }
}, Handler(Looper.getMainLooper()))

Render Thread Optimization

Use hardware layers to promote frequently animated views to the GPU:

// Animate a view frequently (alpha, translation, scale)
view.setLayerType(View.LAYER_TYPE_HARDWARE, null)  // cached on GPU
startAnimation()
// After animation completes:
view.setLayerType(View.LAYER_TYPE_NONE, null)  // release GPU layer

Key Takeaways

ToolWhat it finds
Android Studio CPU ProfilerWhich methods consume frame time
gfxinfo framestatsJank rate and frame timing data
StrictModeSynchronous disk/network on main thread
Frame Metrics APIPer-frame timing for production monitoring
LAYER_TYPE_HARDWARECache animated views on GPU to skip re-draw

YOUR LEARNING JOURNEY

0 of 177 available lessons completed

Progress saved in this browser. No account needed.
Rendering Pipeline & Jank Hunting | Android System Design | Android Engineers