Now in Android 架构深度解析
概述
Now in Android 是 Google 官方推出的 Android 应用示例项目,展示了现代 Android 开发的最佳实践。本文将深入剖析其核心架构设计和数据处理流程。
一、UI 状态管理:StateFlow 的应用
1.1 StateFlow 的选择原因
在 MainActivityViewModel 中,UI 状态使用 StateFlow 管理:
kotlin
val uiState: StateFlow<MainActivityUiState> = userDataRepository.userData
.map { Success(it) }
.stateIn(
scope = viewModelScope,
initialValue = Loading,
started = SharingStarted.WhileSubscribed(5_000),
)
选择 StateFlow 的核心原因:
| 特性 | 说明 |
|---|---|
| 有初始值 | 必须提供初始值(如 Loading),确保 UI 启动时有状态可显示 |
| 值保留 | 始终保留最新值,新订阅者立即收到当前状态 |
| 热流特性 | 支持多订阅者共享状态 |
| 生命周期友好 | 配合 stateIn 和 WhileSubscribed 实现智能资源管理 |
1.2 共享策略的设计
SharingStarted.WhileSubscribed(5_000) 的设计考虑:
- 当订阅者数量从 0 变为 1 时开始共享
- 当订阅者数量从 1 变为 0 时,延迟 5 秒后停止共享
- 避免频繁启停,优化性能
二、状态模式:Sealed Interface 的应用
2.1 密封接口的定义
kotlin
sealed interface MainActivityUiState {
data object Loading : MainActivityUiState
data class Success(val userData: UserData) : MainActivityUiState
}
2.2 核心优势
类型安全与穷尽性检查:
kotlin
when (uiState) {
is MainActivityUiState.Loading -> showLoading()
is MainActivityUiState.Success -> showContent(uiState.userData)
// 无需 else 分支,编译器保证覆盖所有状态
}
设计优势:
- 状态集合封闭且有限
- 新增状态时编译器强制更新所有
when表达式 - 表达清晰的有限状态机
三、数据层架构:Topic 的处理流程
3.1 数据模型定义
kotlin
data class Topic(
val id: String,
val name: String,
val shortDescription: String,
val longDescription: String,
val url: String,
val imageUrl: String,
)
3.2 完整数据流程
swift
网络层 → 数据库层 → 仓库层 → 领域层 → UI层
↓ ↓ ↓ ↓ ↓
NetworkTopic → TopicEntity → Topic → FollowableTopic → UI展示
3.3 Offline-First 策略
kotlin
class OfflineFirstTopicsRepository @Inject constructor(
private val topicDao: TopicDao,
private val network: NiaNetworkDataSource,
) : TopicsRepository {
override fun getTopics(): Flow<List<Topic>> {
return topicDao.getTopicEntities()
.map { entities -> entities.map(TopicEntity::asExternalModel) }
}
}
关键设计:所有读取操作从本地数据库获取,支持离线访问。
四、组合模式:FollowableTopic 的设计
4.1 数据结构定义
kotlin
data class FollowableTopic(
val topic: Topic,
val isFollowed: Boolean,
)
4.2 数据生成逻辑
kotlin
class GetFollowableTopicsUseCase @Inject constructor(
private val topicsRepository: TopicsRepository,
private val userDataRepository: UserDataRepository,
) {
operator fun invoke(): Flow<List<FollowableTopic>> {
return combine(
userDataRepository.userData,
topicsRepository.getTopics(),
) { userData, topics ->
topics.map { topic ->
FollowableTopic(
topic = topic,
isFollowed = topic.id in userData.followedTopics,
)
}
}
}
}
4.3 设计优势
| 优势 | 说明 |
|---|---|
| 关注点分离 | Topic 是纯数据,FollowableTopic 添加用户状态 |
| 响应式更新 | 基于 Flow,状态变化自动传播 |
| 单一数据源 | 用户关注状态统一由 UserDataRepository 管理 |
五、新闻资源处理:NewsResource 的完整流程
5.1 数据模型
kotlin
data class NewsResource(
val id: String,
val title: String,
val content: String,
val url: String,
val headerImageUrl: String?, // 可空字段
val publishDate: Instant,
val type: String,
val topics: List<Topic>,
)
5.2 增量同步机制
kotlin
override suspend fun syncWith(synchronizer: Synchronizer): Boolean =
synchronizer.changeListSync(
versionReader = ChangeListVersions::newsResourceVersion,
changeListFetcher = { network.getNewsResourceChangeList(after = it) },
versionUpdater = { copy(newsResourceVersion = it) },
modelDeleter = newsResourceDao::deleteNewsResources,
modelUpdater = { changedIds ->
// 分批处理,每批 40 条
changedIds.chunked(40).forEach { chunkedIds ->
val networkNewsResources = network.getNewsResources(ids = chunkedIds)
topicDao.insertOrIgnoreTopics(...)
newsResourceDao.upsertNewsResources(...)
newsResourceDao.insertOrIgnoreTopicCrossRefEntities(...)
}
},
)
5.3 数据转换链路
| 层级 | 模型 | 说明 |
|---|---|---|
| 网络层 | NetworkNewsResource |
API 响应格式 |
| 数据库层 | NewsResourceEntity |
Room 存储格式 |
| 外部模型 | NewsResource |
纯数据模型 |
| UI 模型 | UserNewsResource |
包含用户状态 |
六、图片处理:headerImageUrl 的加载流程
6.1 可空性设计
kotlin
// 网络层(非空)
data class NetworkNewsResource(val headerImageUrl: String)
// 数据库层(可空)
data class NewsResourceEntity(val headerImageUrl: String?)
6.2 UI 层处理
kotlin
@Composable
fun NewsResourceHeaderImage(headerImageUrl: String?) {
var isLoading by remember { mutableStateOf(true) }
var isError by remember { mutableStateOf(false) }
val imageLoader = rememberAsyncImagePainter(
model = headerImageUrl,
onState = { state ->
isLoading = state is AsyncImagePainter.State.Loading
isError = state is AsyncImagePainter.State.Error
},
)
// 加载中显示进度条,失败显示占位图
Box {
if (isLoading) {
CircularProgressIndicator(/*...*/)
}
Image(
painter = if (isError.not()) imageLoader else placeholderPainter,
contentDescription = null,
)
}
}
七、网络层设计:Retrofit 的封装
7.1 API 接口定义
kotlin
private interface RetrofitNiaNetworkApi {
@GET("topics")
suspend fun getTopics(@Query("id") ids: List<String>?): NetworkResponse<List<NetworkTopic>>
@GET("newsresources")
suspend fun getNewsResources(@Query("id") ids: List<String>?): NetworkResponse<List<NetworkNewsResource>>
@GET("changelists/topics")
suspend fun getTopicChangeList(@Query("after") after: Int?): List<NetworkChangeList>
}
7.2 响应封装
kotlin
@Serializable
private data class NetworkResponse<T>(val data: T)
7.3 延迟初始化优化
kotlin
internal class RetrofitNiaNetwork @Inject constructor(
networkJson: Json,
okhttpCallFactory: dagger.Lazy<Call.Factory>, // 延迟初始化
) : NiaNetworkDataSource {
private val networkApi = Retrofit.Builder()
.callFactory { okhttpCallFactory.get().newCall(it) } // 首次调用时创建
.build()
.create(RetrofitNiaNetworkApi::class.java)
}
八、同步框架:Synchronizer 的设计
8.1 核心接口
kotlin
interface Synchronizer {
suspend fun getChangeListVersions(): ChangeListVersions
suspend fun updateChangeListVersions(update: ChangeListVersions.() -> ChangeListVersions)
}
interface Syncable {
suspend fun syncWith(synchronizer: Synchronizer): Boolean
}
8.2 增量同步流程
kotlin
suspend fun Synchronizer.changeListSync(
versionReader: (ChangeListVersions) -> Int,
changeListFetcher: suspend (Int) -> List<NetworkChangeList>,
versionUpdater: ChangeListVersions.(Int) -> ChangeListVersions,
modelDeleter: suspend (List<String>) -> Unit,
modelUpdater: suspend (List<String>) -> Unit,
) = suspendRunCatching {
// 1. 读取当前版本
val currentVersion = versionReader(getChangeListVersions())
// 2. 获取变更列表
val changeList = changeListFetcher(currentVersion)
if (changeList.isEmpty()) return@suspendRunCatching true
// 3. 分离删除和更新
val (deleted, updated) = changeList.partition(NetworkChangeList::isDelete)
// 4. 执行删除和更新
modelDeleter(deleted.map(NetworkChangeList::id))
modelUpdater(updated.map(NetworkChangeList::id))
// 5. 更新版本号
updateChangeListVersions { versionUpdater(changeList.last().changeListVersion) }
}.isSuccess
8.3 变更列表数据结构
kotlin
data class NetworkChangeList(
val id: String, // 变更的模型 ID
val changeListVersion: Int, // 单调递增的版本号
val isDelete: Boolean, // 是否为删除操作
)
九、架构设计总结
9.1 核心架构原则
| 原则 | 实现方式 |
|---|---|
| 离线优先 | 所有读取从本地数据库,同步在后台进行 |
| 增量同步 | 通过 ChangeList 机制只同步变更数据 |
| 响应式编程 | 使用 Flow 实现数据流的自动传播 |
| 依赖注入 | Hilt 管理依赖,提高可测试性 |
| 单向数据流 | 数据流向清晰:Repository → ViewModel → UI |
9.2 模块划分
bash
core/
├── data/ # 数据层(Repository、同步逻辑)
├── database/ # 数据库层(Room DAO、实体)
├── domain/ # 领域层(UseCase)
├── model/ # 数据模型(Topic、NewsResource)
├── network/ # 网络层(Retrofit、API)
└── ui/ # UI 组件(Compose)
9.3 关键设计模式
- Repository Pattern:数据访问抽象
- Strategy Pattern:同步策略的可插拔
- Observer Pattern:Flow 的响应式订阅
- State Pattern:密封接口表示有限状态
十、最佳实践总结
- 使用 sealed interface 管理 UI 状态:确保类型安全和穷尽性检查
- StateFlow 作为 UI 状态载体:提供初始值和生命周期感知
- Offline-First 策略:优先从本地数据库读取
- 增量同步:减少网络请求量
- 延迟初始化:优化启动性能
- 单一数据源:状态管理统一
参考资料
作者 :技术爱好者 日期 :2026年 来源:Now in Android 项目源码分析