跪拜 Guibai
← All articles
Android

Thread Scheduling's Home: Java Hands It to the Caller, Kotlin Coroutines Tuck It Inside the API

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

Android apps that scatter thread-switching logic across ViewModels and Repositories become hard to maintain and test. Coroutines provide a way to push that responsibility into lower layers, letting higher layers stay clean and lifecycle-aware without losing readability.

Summary

Java code either annotates blocking methods with @WorkerThread and leaves the caller to arrange a thread pool and post results back to the main thread, or uses callbacks that still force the caller to wire together asynchronous flow. Both approaches make thread switching a visible part of upper-layer business logic.

Kotlin coroutines flip that. A suspend function can internally use withContext(Dispatchers.IO) to switch execution environments, and the caller just sees a suspension point that resumes on the original context. The article walks through a ViewModel-Repository-DataSource example where only the DataSource touches dispatchers, keeping the other layers clean and lifecycle-aware.

The key principle: whoever best knows an operation's execution requirements should be the one to satisfy them. Suspend functions should be Main-safe, and structured concurrency only works when cancellation signals are actually propagated.

Takeaways
— The @WorkerThread annotation only declares that a method should run off the main thread; it does not switch threads or create a background task.
— A suspend function does not automatically run on a background thread; only withContext(Dispatchers.IO) or an equivalent changes the execution context.
— Suspend functions should be Main-safe, meaning they do not block the main thread even when called from it.
— In a ViewModel-Repository-DataSource stack, the DataSource is often the right layer to own execution-environment requirements, with Retrofit and Room already handling their own async internally.
— Coroutines enable sequential expression of async flows, encapsulation of thread switching, and lifecycle-scoped cancellation via viewModelScope.
— Structured concurrency only works if underlying APIs propagate cancellation signals and do not swallow CancellationException.
— The article's core claim: thread scheduling responsibility should sit with the layer that best understands the operation's execution requirements.
Conclusions

Coroutines change the location of scheduling responsibility, not the existence of scheduling; someone still has to decide which dispatcher runs what.

Java's callback style forces the caller to orchestrate an asynchronous pipeline, while suspend functions let the API hide that orchestration—but hiding it makes main-safety a contractual requirement.

The three-layer example shows that even with coroutines, developers must consciously choose which layer performs the switching; nothing happens automatically.

Viewing coroutines as merely a nicer async syntax misses that they also create a lifecycle contract with the surrounding component, something callbacks or Executors do not provide by default.

Concepts & terms
@WorkerThread
An Android annotation that marks a method as performing off-main-thread work. It is a constraint only; the caller must still provide the background thread.
Main-safe
A property of a suspend function meaning it does not block the main thread even when invoked from it, usually by delegating to an appropriate dispatcher internally.
withContext(Dispatchers.IO)
A coroutine builder that switches the coroutine's execution context to an IO-optimized dispatcher, runs the block, and returns the result to the original context.
Structured concurrency
A coroutine scoping model where coroutines are tied to a lifecycle (like a ViewModel) and are cancelled automatically when that lifecycle ends, provided cancellation signals propagate.
Dispatchers.Default
A coroutine dispatcher optimized for CPU-bound work, used for computations that would block the main thread if run directly.
Source: juejin.cn ↗ Google Translate ↗ Backup ↗