Now in Android 架构深度解析

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 启动时有状态可显示
值保留 始终保留最新值,新订阅者立即收到当前状态
热流特性 支持多订阅者共享状态
生命周期友好 配合 stateInWhileSubscribed 实现智能资源管理

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 关键设计模式

  1. Repository Pattern:数据访问抽象
  2. Strategy Pattern:同步策略的可插拔
  3. Observer Pattern:Flow 的响应式订阅
  4. State Pattern:密封接口表示有限状态

十、最佳实践总结

  1. 使用 sealed interface 管理 UI 状态:确保类型安全和穷尽性检查
  2. StateFlow 作为 UI 状态载体:提供初始值和生命周期感知
  3. Offline-First 策略:优先从本地数据库读取
  4. 增量同步:减少网络请求量
  5. 延迟初始化:优化启动性能
  6. 单一数据源:状态管理统一

参考资料


作者 :技术爱好者 日期 :2026年 来源:Now in Android 项目源码分析

相关推荐
杊页1 小时前
系列一:架构思想进阶 | 第1篇 Android 架构演进实录:从 MVC 的“万能类”到 MVVM 的数据驱动
android
Database_Cool_2 小时前
AI Agent 混合检索选型:阿里云 AnalyticDB MySQL 向量+全文一站式方案
android·adb
2501_916008892 小时前
全面解析常用Web前端开发工具:编辑器、调试工具、性能分析器与框架
android·前端·ios·小程序·uni-app·编辑器·iphone
zhangphil2 小时前
Kotlin协程Flow及管道中的buffer和bufferCapacity
android·kotlin
恋猫de小郭2 小时前
一个 Linux 调度器优化,让 Android 多耗 20% 的电,传音工程师如何发现问题?
android·前端·ios
Kapaseker2 小时前
一个圆屏逼得我好好学习 Compose MeasurePolicy
android·kotlin
__Witheart__2 小时前
RK Android OTA U盘升级指南
android
__Witheart__2 小时前
RK Android OTA U盘升级包编译指南
android
我命由我123452 小时前
Android Service - Service 生命周期变化、Service 与 Activity 双向交互
android·java·java-ee·android studio·android jetpack·android-studio·android runtime