跪拜 Guibai
← All articles
Android

Android Architecture's Seven Iron Rules: The Architect's Real Deliverable Is Order

By 潜龙勿用之化骨龙 ·
Read original on juejin.cn ↗ Google Translate ↗ Alt translation

Android teams get a concrete checklist for the failures that actually kill large apps: orphaned tasks, hidden dependencies, swallowed exceptions, and blocked threads. Each rule is enforceable in code review, not aspirational advice, and they map directly to common production incidents like leaked coroutines and unrecoverable state.

Summary

Android projects fall apart not from a missing diagram but from unclear responsibility: tasks nobody owns, dependencies nobody sees, errors that vanish silently. These seven rules convert architecture from a drawing exercise into enforceable behavioral contracts. The Repository must be main-safe and manage its own thread strategy; any launched coroutine must have an explicit lifecycle owner; each piece of business data needs one authoritative source like Room; one-shot operations use suspend while continuous streams use Flow; global event buses cannot hide business dependencies; exceptions must be handled, narrowed, and rethrown for cancellation; and waiting on async work should suspend threads, not block them.

Each rule attaches to a concrete failure mode and a specific code pattern. The article walks through a real example of a Repository that fires a coroutine with no owner, then shows the fix where the ViewModel's scope owns the task. It flags the CancellationException trap in catch-all handlers and distinguishes thread blocking from coroutine suspension, noting that coroutines don't magically make blocking APIs non-blocking.

The closing argument reframes the architect's job: not layers, not frameworks, but rules that create boundaries, boundaries that assign responsibility, and responsibility that yields order. Clean Architecture provides code boundaries; these engineering rules provide behavioral boundaries. Both together decide whether a system can survive its own growth.

Takeaways
— Repository methods must be safe to call from the main thread; the Repository handles its own thread switching via an injected dispatcher and withContext, not the caller.
— Whoever launches a coroutine owns it: a Repository making its own CoroutineScope detaches the task from any lifecycle, so the caller's ViewModel scope must own launch calls instead.
— State needs a single source of truth; with Room as the authoritive storage for user data, the Repository writes to Room and the UI observes it via Flow.
— One-shot operations are suspend functions that return a result once; continuous data streams are Flow, and mixing them only muddles API semantics.
— A global event bus like FlowBus obscures who sent an event, who receives it, and the order of subscribers; explicit interfaces should carry business dependencies instead.
— Swallowing exceptions with an empty catch block removes error visibility; additionally, catching Exception in a coroutine wrongly catches CancellationException, so rethrow that before handling business errors.
— When an async operation waits for a result, prefer suspending the coroutine over blocking a thread; even with withContext(IO), the blocking still happens, just isolated to a specific dispatcher.
— Different layers own different exception responsibilities: Data handles low-level errors, Domain defines failure semantics, and UI decides how to display them.
Conclusions

The rules treat behavioral contracts as the primary artifact of architecture, with code layers existing only to support those contracts; that reframing is what makes the list useful beyond a style guide.

The CancellationException pitfall is a subtle but widespread bug in production Android code, and its inclusion here signals that the article targets practitioners who've already moved past basic coroutine usage.

The article's emphasis on 'who owns the task' connects directly to the common crash of leaked coroutines and memory leaks, making the rule a practical fix rather than a theoretical principle.

Concepts & terms
Single Source of Truth (SSOT)
The principle that a given piece of business data must have exactly one authoritative storage location, such as Room for local user data, so all reads and writes flow through that source instead of competing copies.
Main-safe
A property of a suspend function that it can be called from the main thread without requiring the caller to switch dispatchers; the function guarantees thread safety internally, usually by delegating to an IO or Default dispatcher via withContext.
CancellationException
A coroutine cancellation signal; when catching generic exceptions in a coroutine, it must be rethrown to preserve cancellation semantics, otherwise the coroutine's cooperative cancellation breaks and downstream operations continue unexpectedly.
From the discussion
Featured comments
hello先生 1 likes

Architecture left to AI

潜龙勿用之化骨龙

AI needs you to guide it

hello先生  → 潜龙勿用之化骨龙

Let AI write templates to define standards

See top comments, translated →
Source: juejin.cn ↗ Google Translate ↗ Backup ↗