跪拜 Guibai
← All articles
Android · Android Jetpack · Kotlin

The Rendering Bet Behind Every Mobile UI Framework

By 稀有猿诉 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Choosing a mobile UI framework without understanding its rendering strategy means fighting its design rather than leveraging it. The cost shows up as jank, unnecessary recomposition, or state bugs that no compiler catches—problems that surface months later in a profiler.

Summary

Every UI framework makes a bet on how much screen to redo when state changes. Android Views mutates the tree in place—cheap but demanding manual sync between model and view. Flutter rebuilds the widget tree optimistically, diffs against the previous version, and patches only what changed, eliminating state-UI drift at the cost of wasted construction. Jetpack Compose tracks exactly which composable reads which state object and re-executes only those scopes, avoiding both manual sync and optimistic rebuilds.

The three architectures converge on the same expensive layer—measure, layout, paint—but diverge in how they decide what work to skip. Views skips nothing by default; Flutter decides after rebuilding by comparing results; Compose decides before re-running by comparing inputs. Each choice shifts the developer's burden: Views demands discipline in synchronization, Flutter demands discipline in rebuild scoping, and Compose demands understanding of its invisible stability contract.

Production performance problems rarely come from picking the wrong framework. They come from using one framework while mentally operating in another's model—mutating state imperatively inside Flutter, or hoisting reads unnecessarily inside Compose.

Takeaways
Android Views mutates a long-lived tree of stateful objects in place; every state change requires manual synchronization between model and view.
Flutter rebuilds the widget tree on every state change, diffs new against old, and patches only the changed RenderObjects—eliminating manual sync but performing optimistic work.
Jetpack Compose tracks which composable scope reads which state object and re-executes only those scopes, skipping unchanged children by comparing cached inputs before re-running.
Compose's skip mechanism depends on a stability contract: if a parameter type is not provably stable, the runtime silently stops skipping and re-runs the function on every pass with no warning.
Flutter owns every pixel through its own engine (Impeller/Skia), solving cross-platform rendering fragmentation but requiring the team to reimplement OS-level features like text editing and accessibility.
Compose Multiplatform uses the same slot-table and subscription runtime across Android, iOS, and desktop, swapping only the rendering backend—Skia on non-Android platforms, native pipelines on Android.
Most production rendering issues stem from using one framework while thinking in another's mental model, such as mutating state imperatively inside Flutter or hoisting reads unnecessarily inside Compose.
Conclusions

Compose's invisible stability contract is a sharper trade-off than Flutter's visible rebuild cost. A developer who doesn't know the contract exists ships an app that is silently slower, with no crash or lint warning to flag the problem.

The three frameworks converge on the same expensive layer—measure, layout, paint—but diverge entirely in how they decide what work to skip. That decision point is the architecture's real signature.

Flutter's three-tree split (Widgets, Elements, RenderObjects) is not an implementation detail; it is the mechanism that makes optimistic rebuilds viable by protecting the expensive layer from the cheap one.

Compose Multiplatform borrows Flutter's own-every-pixel bet on non-Android platforms, which means two frameworks with opposite philosophies end up sharing the same rendering strategy outside Android.

Concepts & terms
Slot Table
Compose's execution trace that records which composable functions ran, what inputs they received, and what UI nodes they emitted. The runtime walks this table in lockstep during recomposition to skip unchanged children by comparing cached inputs.
Stability Contract
A Compose compiler guarantee that a type's instances can be compared for equality reliably. If a parameter type is not stable (e.g., a data class with a var, a bare List), the runtime silently stops skipping that composable and re-runs it on every pass.
Element Tree
Flutter's persistent middle layer between the disposable Widget tree and the expensive RenderObject tree. Elements survive rebuilds, hold state, and perform the diff that determines which RenderObjects get updated versus torn down.
Choreographer
Android's frame-timing conductor that synchronizes UI work with the display's refresh rate. Views mark themselves dirty and schedule measure/layout/draw passes to run on the next Choreographer tick.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗