What is Canvas api in Android ?

In Jetpack Compose, the Canvas API is a declarative, stateless way to draw custom shapes, paths, and images. Unlike the traditional View system where you override onDraw(canvas), Compose uses a Canvas composable that provides a DrawScope.

Canvas(modifier = Modifier.size(200.dp)) {
    // The DrawScope starts here
    drawCircle(
        color = Color.Blue,
        radius = size.minDimension / 4,
        center = center // 'center' and 'size' are provided by DrawScope
    )
}

Lifecycle

The Canvas follows the standard Compose "Three Phases" of a frame, but it spends most of its time in the final phase.

  1. Composition (What to show)
    Compose determines that a Canvas should be part of the UI tree. If you use mutableStateOf inside your onDraw block, Compose tracks this as a dependency.

  2. Layout (Where to show it)
    The Modifier.size() or constraints from the parent determine the Canvas's height and width. This is where the size property of the DrawScope is calculated.

  3. Drawing (How to show it)
    This is where the internal "Magic" happens. Unlike Composition, which can be expensive, the Draw Phase is optimized for high frequency (60fps or 120fps).

    • Invalidation: When a state variable used inside onDraw changes, Compose doesn't necessarily re-run the "Composition" phase. It simply invalidates the layer and re-runs the drawing commands.

    • Recording: Modern Android uses a Hardware Accelerated canvas. Your drawCircle commands are actually recorded into a DisplayList. The GPU then "plays back" these commands to render pixels on the screen.

How XML-Based Canvas Works Internally

In the legacy system, you create a custom View and override the onDraw(canvas: Canvas) method.

  • The Invalidation Loop:
    When you want to update the UI, you call invalidate(). This tells the Choreographer that the view is "dirty". On the next VSYNC signal, the system calls draw() on the view hierarchy.

  • The Native Canvas:
    The canvas object passed to onDraw is a direct wrapper around the Skia drawing engine (written in C++).

  • The Paint Object: Unlike Compose, where you pass parameters like color directly into the draw function, XML Views require a Paint object. This object holds the configuration (aliasing, color, stroke width) and must be managed manually.

Comparing the Lifecycles

FeatureXML Canvas (View)Compose Canvas
TriggerManual invalidate() call.Automatic via State observation.
Object LifetimeManual (usually in init).drawWithCache or manual management.
Coordinate SystemPixels (requires DisplayMetrics).Pixels (DrawScope provides Density).
Hardware Accel.Controlled via LayerType.Enabled by default; uses GraphicsLayer.

How to Optimize Both

Optimization strategies differ because of how the two systems handle memory and updates.

Optimizing XML Canvas

  • Avoid Allocations in onDraw: Never use new Paint() or new Path() inside onDraw. These trigger the Garbage Collector, causing "Jank" (dropped frames). Initialize them in the constructor.

  • Use Canvas.clipRect(): If only a small part of your view changes, use invalidate(Rect) to only redraw that specific area, saving GPU cycles.

  • Hardware Layers: For complex animations, use setLayerType(LAYER_TYPE_HARDWARE, paint). This caches the view as a bitmap on the GPU so it doesn't have to re-execute drawing commands every frame.

Optimizing Compose Canvas

  • Modifier.drawWithCache: Use this to initialize paths or shaders. It only re-calculates when the size of the canvas changes or a state inside the cache block changes.

  • Static Parameters: Don't pass raw State objects into the Canvas if they change frequently unless necessary. Use lambda-based modifiers to prevent the entire composable from recomposing.

  • Use GraphicsLayer: Apply Modifier.graphicsLayer { ... } to offload transformations (rotation, scale, alpha) to the GPU. This avoids re-running the onDraw logic entirely.

Internal Rendering Architecture

Both ultimately end up at the Hardware Renderer.

  1. DisplayList: Both systems record your commands (drawRect, drawLine) into a DisplayList.
  2. RenderThread: This list is sent to a dedicated RenderThread that talks to the GPU.
  3. Skia/Vulkan: The GPU uses the Skia engine (or the newer Impeller / Vulkan backends) to turn those commands into actual colored pixels.

Comments

Popular posts from this blog

What is a Coroutine? Why is it better than threads?

What are Coroutine Builders?

Sealed Classes and Sealed Interfaces