谷歌SEO

谷歌SEO

Products

当前位置:首页 > 谷歌SEO >

How to build an NBA app with Now in Android architecture?

96SEO 2026-05-07 20:34 1


Android 开发者正面临着前所未有的挑战与机遇。随着业务逻辑的日益复杂,传统的单体应用架构逐渐显得力不从心,代码耦合度高、编译速度慢、测试困难等问题接踵而至。为了解决这些痛点,Google 推出了 Now in Android 示例项目。这不仅仅是一个简单的 Demo,geng是 Google官方对于“现代 Android 应用该如何编写”这一宏大命题给出的标准答案。

然而NIA 官方项目本身过于庞大,模块繁多,对于初学者或者想要快速上手的开发者来说入门门槛实在是不低。为了让大家Neng够geng直观地理解这套架构的精髓,我决定动手打造 HoopsNow —— 一款结构清晰、规模适中的 NBA 数据应用。它剥离了 NIA 中复杂的业务背景,专注于架构本身的落地实践。今天我们就以 HoopsNow 为例,深度拆解如何从零开始构建一个符合 NIA 规范的现代 Android 应用。

一、 架构概览:为什么我们需要多模块?

在项目初期,单模块开发确实爽快,想写哪里写哪里。但随着代码量突破几万行,你会发现编译时间越来越长,改动一个角落可Neng引发另一处的崩溃。这时候,多模块架构就成了救命稻草。

HoopsNow 将项目拆分为了 20 个模块,这种拆分并非为了炫技,而是为了明确的职责划分。我们Ke以通过一棵树状图来窥探其全貌:

HoopsNow/
├── app/                          # 应用壳模块 — 负责导航、Scaffold、应用入口
├── build-logic/                  # Convention Plugins — 统一构建逻辑的魔法所在
│   └── convention/
├── feature/                      # 功Neng模块
│   ├── games/
│   │   ├── api/                  # 导航契约:GamesNavKey, GameDetailNavKey
│   │   └── impl/                 # 具体实现:Screen, ViewModel, UiState
│   ├── teams/
│   ├── players/
│   └── favorites/
└── core/                         # 核心模块
    ├── model/                    # 领域模型
    ├── data/                     # Repository 接口 + 离线优先实现
    ├── database/                 # Room 数据库、DAO、Entity
    ├── network/                  # Retrofit API、网络模型
    ├── datastore/                # DataStore 用户偏好
    ├── common/                   # 公共工具类
    ├── designsystem/             # 主题、颜色、通用组件
    ├── ui/                       # 跨功Neng共享 UI 组件
    └── testing/                  # 测试工具、Fake 实现

这种结构的核心在于 解耦。通过 Gradle 的并行编译,独立模块Ke以同时构建,极大地提升了构建速度。geng重要的是模块间的依赖关系变得清晰可见,不再是一团乱麻。

二、 模块通信的艺术:Feature API 与 Impl 分离

在多模块架构中,Zui让人头疼的问题莫过于模块间通信。假设 `feature:games` 模块需要跳转到 `feature:teams` 模块,Ru果直接依赖 `teams` 的实现层,就会导致严重的耦合。

HoopsNow 采用了 NIA 推荐的 api/impl 分离模式,这是整个架构设计中Zui精彩的一笔。

1. API 模块:极简的契约

每个 Feature 模块dou分为 `api` 和 `impl` 两个子模块。`api` 模块极度轻量,只包含导航契约。kankan它的 `build.gradle.kts` 有多干净:

// feature/games/api/build.gradle.kts
plugins {
    alias
    alias
}
dependencies {
    implementation
    implementation
}

在 API 模块中,我们只定义导航目标:

// feature/games/api/.../GamesNavKeys.kt
@Serializable
object GamesNavKey : NavKey
@Serializable
data class GameDetailNavKey : NavKey

这里的 `@Serializable` 注解至关重要,它保证了导航参数Ke以在进程死亡后完美恢复,这是 Navigation 3.0 的基础。

2. Impl 模块:厚重的实现

所有的具体实现——ViewModel、Screen、UI State——dou封装在 `impl` 模块中。这样,其他模块只需要依赖 `games:api`,根本不需要知道 `games:impl` 的存在。

feature:games:impl → feature:teams:api  ✅
feature:teams:impl → feature:teams:api  ✅
feature:games:impl → feature:teams:impl  ❌ 

这种设计带来了巨大的好处:

编译隔离修改 `games:impl` 的代码,不会触发依赖 `games:api` 的其他模块重新编译。

杜绝循环依赖模块间只Neng通过轻量的 API 进行通信,物理上隔绝了循环引用的可Neng。

构建加速API 模块非常稳定,极少变动,大部分编译Ke以增量跳过。

高度封装Impl 中的实现细节对外完全不可见,真正的黑盒操作。

三、 驯服 Gradle:Convention Plugins 的威力

多模块项目有一个通病:配置地狱。每个模块的 `build.gradle.kts` dou要写一堆重复的配置——`compileSdk`、`minSdk`、`jvmTarget`,还有 Compose 和 Hilt 的各种插件。Ru果改一个版本号要改 20 个文件,那简直是噩梦。

这时候,Convention Plugins 就成了救世主。HoopsNow 定义了 5 个 Convention Plugins,将公共逻辑封装成插件,模块只需一行代码即可应用。

这是ZuiNeng体现 Convention Plugin 威力的地方,我们定义了一个 `AndroidFeatureConventionPlugin`:

class AndroidFeatureConventionPlugin : Plugin {
    override fun apply {
        with {
            // 自动应用 Library + Compose + Hilt 插件
            pluginManager.apply {
                apply
                apply
                apply
            }
            extensions.configure {
                defaultConfig {
                    testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
                }
            }
            // 自动添加 Feature 模块的公共依赖
            dependencies {
                add)
                add)
                add)
                add.get)
                add.get)
                add.get)
            }
        }
    }
}

一个插件 = Library 配置 + Compose 配置 + Hilt 配置 + 公共依赖。kankan Feature 模块的 `build.gradle.kts` 变得多简洁:

// feature/games/impl/build.gradle.kts
plugins {
    alias
}
android {
    namespace = "com.hoopsnow.nba.feature.games.impl"
}
dependencies {
    implementation
    implementation
}

此外别忘了在 `settings.gradle.kts` 中开启类型安全访问器:

enableFeaturePreview

相比字符串 `project`,类型安全的 `projects.feature.games.api` Neng在编译期就发现拼写错误,避免运行时的尴尬。

四、 数据层:离线优先的哲学

对于一款体育数据应用来说数据的实时性和稳定性至关重要。HoopsNow 采用了 离线优先 的策略,这意味着应用在无网络的情况下也Neng展示缓存数据,并在网络恢复后自动同步。

1. 单一数据源

这是 NIA 架构的核心原则:UI 永远只从数据库读取数据,网络请求只是作为数据geng新的手段。

用户请求 → 订阅 Room Flow → Room 返回缓存数据 → UI 立即展示
              ↓
         onStart 触发网络同步
              ↓
         网络返回新数据 → 写入 Room → Room Flow 自动推送geng新 → UI 自动刷新
2. 三层模型转换

为了解耦不同层级的逻辑,数据在流动过程中经历了三次模型转换:

Network Model → Domain Model → Entity 
NetworkGame   → Game         → GameEntity

为什么要这么折腾?因为每一层的需求不同: * Network Model为了适配 API 返回的 JSON 结构,可Neng包含嵌套对象。 * Domain Model纯 Kotlin 对象,无任何框架依赖,Zui适合业务逻辑处理。 * Entity为了数据库查询优化,通常需要扁平化处理。

模型之间通过 函数进行优雅的转换:

// Network → Domain
fun NetworkGame.asExternalModel: Game = Game(
    id = id,
    date = date,
    homeTeam = homeTeam?.asExternalModel ?: /* fallback */,
    visitorTeam = visitorTeam?.asExternalModel ?: /* fallback */,
    // ...
)
// Entity → Domain
fun GameEntity.asExternalModel: Game = Game
// Domain → Entity
fun Game.asEntity: GameEntity = GameEntity
3. Repository 的实现

Repository 接口定义在 `core:data` 模块中,它返回的是响应式的 Flow:

interface GamesRepository {
    fun getGames: Flow
    fun getGamesByDate: Flow
    fun getGameById: Flow
    fun getGamesByTeamId: Flow
    suspend fun syncGames
    suspend fun syncGamesByDate
}

注意,所有查询方法返回 `Flow` 而不是 `suspend` 函数。这意味着数据是响应式的——数据库一旦有geng新,UI 会自动收到通知并刷新。

具体的实现类 `OfflineFirstGamesRepository` 位于 `core:data` 模块中:

internal class OfflineFirstGamesRepository @Inject constructor(
    private val gameDao: GameDao,
    private val networkDataSource: NbaNetworkDataSource,
) : GamesRepository {
    override fun getGamesByDate: Flow =
        gameDao.getGamesByDate                          // 1. 从数据库读取
            .map { entities -> entities.map { it.asExternalModel } }  // 2. 转为领域模型
            .onStart { syncGamesByDate }                // 3. 启动时触发网络同步
    override suspend fun syncGamesByDate {
        try {
            val networkGames = networkDataSource.getGames(    // 1. 从网络获取
                perPage = 100, dates = listOf
            )
            val games = networkGames.map { it.asExternalModel }
            gameDao.upsertGames })  // 2. 写入数据库
        } catch  {
            // 静默失败 — 离线优先意味着展示缓存数据
        }
    }
}

这里使用了 Room 2.4+ 的 `@Upsert` 注解,替代了传统的 `@Insert`,这是目前geng推荐的Zuo法。

4. 依赖注入

ViewModel 只需要注入接口,具体的实现由 Hilt 自动提供:

@HiltViewModel
class GamesListViewModel @Inject constructor(
    private val gamesRepository: GamesRepository,  // 自动注入接口实现
) : ViewModel

Hilt 通过 `DataModule` 的绑定找到 `OfflineFirstGamesRepository` 并注入。ViewModel 完全不需要知道具体实现是什么这就是依赖倒置原则 的完美实践。

五、 UI 层:Jetpack Compose 与 状态管理

HoopsNow 全面采用 Jetpack Compose 构建 UI。在 NIA 架构中,UI 层的设计遵循单向数据流原则。

1. UI 状态的定义

每个页面dou定义一个密封接口来表示所有可Neng的 UI 状态:

sealed interface GamesUiState {
    data object Loading : GamesUiState
    data object Empty : GamesUiState
    data class Success : GamesUiState
    data class Error : GamesUiState
}

这里使用 `sealed interface` 而不是 `sealed class`,是因为在 Kotlin 中,接口允许多继承,且在性Neng上与类几乎无异,但语义上geng轻量。

2. ViewModel 的业务逻辑

ViewModel 负责处理用户交互并生成 UI 状态。我们使用 `StateFlow` 配合 `flatMapLatest` 来处理日期切换时的数据流切换:

@HiltViewModel
class GamesListViewModel @Inject constructor(
    private val gamesRepository: GamesRepository,
) : ViewModel {
    private val _selectedDateIndex = MutableStateFlow // 今天在中间位置
    @OptIn
    val uiState: StateFlow = _selectedDateIndex
        .flatMapLatest { index ->                       // 1. 日期切换触发新的数据流
            val selectedDate = dates
            flow {
                emit              // 2. 先发射 Loading
                emitAll(
                    gamesRepository.getGamesByDate
                        .map { games ->
                            if ) GamesUiState.Empty
                            else GamesUiState.Success  // 3. 数据到达后发射 Success
                        }
                        .catch { e ->
                            emit)
                        }
                )
            }
        }
        .stateIn(                                       // 4. 转为 StateFlow
            scope = viewModelScope,
            started = SharingStarted.WhileSubscribed, // 5秒内无订阅者则停止
            initialValue = GamesUiState.Loading,
        )
    fun selectDate {
        _selectedDateIndex.value = index                // UI 事件向上流动
    }
}

这里有一个关键细节:`SharingStarted.WhileSubscribed`。为什么是 5 秒?因为屏幕旋转通常在几秒内完成,5 秒足够覆盖配置变化的窗口期,避免不必要的重建,同时也Neng在页面真正关闭时及时释放资源。

3. Screen 中的状态消费

在 Composable 中,我们使用 `collectAsStateWithLifecycle` 来收集状态,确保 UI 只在前台时geng新,节省资源:

@Composable
fun GamesListScreen(
    onGameClick:  -> Unit,
    viewModel: GamesListViewModel = hiltViewModel,
) {
    val uiState by viewModel.uiState.collectAsStateWithLifecycle
    when  {
        is GamesUiState.Loading -> LoadingScreen
        is GamesUiState.Empty -> EmptyScreen
        is GamesUiState.Error -> ErrorScreen
        is GamesUiState.Success -> {
            LazyColumn {
                items { game ->
                    GameCard(
                        game = game,
                        onClick = { onGameClick },
                    )
                }
            }
        }
    }
}
六、 导航:驾驭 Navigation 3.0

Navigation 3.0是 Google Zui新的导航库,相比之前的 Navigation Compose,它提供了类型安全和geng好的状态恢复Neng力。

HoopsNow 采用双层导航设计,以支持底部导航栏的 Tab 切换以及每个 Tab 内部的独立导航栈。

TopLevelStack
├── GamesNavKey ←→ SubStack: 
├── TeamsNavKey ←→ SubStack: 
├── PlayersNavKey ←→ SubStack: 
└── FavoritesNavKey ←→ SubStack: 

TopLevelStack管理底部导航栏的 Tab 切换。

SubStack每个 Tab 有自己的子导航栈,互不干扰。

我们定义了一个 `NavigationState` 类来管理这些栈:

class NavigationState(
    val startKey: NavKey,
    val topLevelStack: NavBackStack,
    val subStacks: Map,
) {
    val currentTopLevelKey: NavKey by derivedStateOf { topLevelStack.last }
    val currentSubStack: NavBackStack
        get = subStacks!!
    val currentKey: NavKey by derivedStateOf { currentSubStack.last }
}

而 `Navigator` 则负责处理具体的跳转逻辑:

class Navigator {
    fun navigate {
        when  {
            // 点击当前 Tab → 清空子栈
            state.currentTopLevelKey -> clearSubStack
            // 点击其他 Tab → 切换顶层栈
            in state.topLevelKeys -> goToTopLevel
            // 其他 → 推入当前子栈
            else -> goToKey
        }
    }
    fun goBack {
        when  {
            state.startKey -> error
            state.currentTopLevelKey -> {
                // 子栈为空,回到上一个 Tab
                state.topLevelStack.removeLastOrNull
            }
            else -> state.currentSubStack.removeLastOrNull
        }
    }
    // 便捷导航方法
    fun navigateToGameDetail = navigate)
    fun navigateToTeamDetail = navigate)
    // ...
}

这种设计虽然初期代码量稍多,但带来的灵活性是巨大的。它完美解决了底部导航栏与页面内导航共存时的状态管理难题。

构建 HoopsNow 的过程,实际上就是一次对 Now in Android 架构的深度学习之旅。从多模块的拆分策略,到 Convention Plugins 的构建优化,再到离线优先的数据层设计,以及 Navigation 3.0 的复杂导航管理,每一个环节dou体现了现代 Android 开发的Zui佳实践。

NIA 架构或许不是银弹,它确实增加了一定的前期搭建成本。但对于那些追求长期维护性、团队协作效率以及应用性Neng的项目来说这无疑是一份值得反复研读的蓝图。

希望这篇深度拆解Neng帮助你geng好地理解如何将 Google 的官方架构应用到实际项目中。Ru果你对 HoopsNow 感兴趣,欢迎去 GitHub 查kan源码,Star 和 PR dou是对我Zui大的支持!

GitHub: github.com/laibinzhi/hoopsnow


标签: 架构

SEO优化服务概述

作为专业的SEO优化服务提供商,我们致力于通过科学、系统的搜索引擎优化策略,帮助企业在百度、Google等搜索引擎中获得更高的排名和流量。我们的服务涵盖网站结构优化、内容优化、技术SEO和链接建设等多个维度。

百度官方合作伙伴 白帽SEO技术 数据驱动优化 效果长期稳定

SEO优化核心服务

网站技术SEO

  • 网站结构优化 - 提升网站爬虫可访问性
  • 页面速度优化 - 缩短加载时间,提高用户体验
  • 移动端适配 - 确保移动设备友好性
  • HTTPS安全协议 - 提升网站安全性与信任度
  • 结构化数据标记 - 增强搜索结果显示效果

内容优化服务

  • 关键词研究与布局 - 精准定位目标关键词
  • 高质量内容创作 - 原创、专业、有价值的内容
  • Meta标签优化 - 提升点击率和相关性
  • 内容更新策略 - 保持网站内容新鲜度
  • 多媒体内容优化 - 图片、视频SEO优化

外链建设策略

  • 高质量外链获取 - 权威网站链接建设
  • 品牌提及监控 - 追踪品牌在线曝光
  • 行业目录提交 - 提升网站基础权威
  • 社交媒体整合 - 增强内容传播力
  • 链接质量分析 - 避免低质量链接风险

SEO服务方案对比

服务项目 基础套餐 标准套餐 高级定制
关键词优化数量 10-20个核心词 30-50个核心词+长尾词 80-150个全方位覆盖
内容优化 基础页面优化 全站内容优化+每月5篇原创 个性化内容策略+每月15篇原创
技术SEO 基本技术检查 全面技术优化+移动适配 深度技术重构+性能优化
外链建设 每月5-10条 每月20-30条高质量外链 每月50+条多渠道外链
数据报告 月度基础报告 双周详细报告+分析 每周深度报告+策略调整
效果保障 3-6个月见效 2-4个月见效 1-3个月快速见效

SEO优化实施流程

我们的SEO优化服务遵循科学严谨的流程,确保每一步都基于数据分析和行业最佳实践:

1

网站诊断分析

全面检测网站技术问题、内容质量、竞争对手情况,制定个性化优化方案。

2

关键词策略制定

基于用户搜索意图和商业目标,制定全面的关键词矩阵和布局策略。

3

技术优化实施

解决网站技术问题,优化网站结构,提升页面速度和移动端体验。

4

内容优化建设

创作高质量原创内容,优化现有页面,建立内容更新机制。

5

外链建设推广

获取高质量外部链接,建立品牌在线影响力,提升网站权威度。

6

数据监控调整

持续监控排名、流量和转化数据,根据效果调整优化策略。

SEO优化常见问题

SEO优化一般需要多长时间才能看到效果?
SEO是一个渐进的过程,通常需要3-6个月才能看到明显效果。具体时间取决于网站现状、竞争程度和优化强度。我们的标准套餐一般在2-4个月内开始显现效果,高级定制方案可能在1-3个月内就能看到初步成果。
你们使用白帽SEO技术还是黑帽技术?
我们始终坚持使用白帽SEO技术,遵循搜索引擎的官方指南。我们的优化策略注重长期效果和可持续性,绝不使用任何可能导致网站被惩罚的违规手段。作为百度官方合作伙伴,我们承诺提供安全、合规的SEO服务。
SEO优化后效果能持续多久?
通过我们的白帽SEO策略获得的排名和流量具有长期稳定性。一旦网站达到理想排名,只需适当的维护和更新,效果可以持续数年。我们提供优化后维护服务,确保您的网站长期保持竞争优势。
你们提供SEO优化效果保障吗?
我们提供基于数据的SEO效果承诺。根据服务套餐不同,我们承诺在约定时间内将核心关键词优化到指定排名位置,或实现约定的自然流量增长目标。所有承诺都会在服务合同中明确约定,并提供详细的KPI衡量标准。

SEO优化效果数据

基于我们服务的客户数据统计,平均优化效果如下:

+85%
自然搜索流量提升
+120%
关键词排名数量
+60%
网站转化率提升
3-6月
平均见效周期

行业案例 - 制造业

  • 优化前:日均自然流量120,核心词无排名
  • 优化6个月后:日均自然流量950,15个核心词首页排名
  • 效果提升:流量增长692%,询盘量增加320%

行业案例 - 电商

  • 优化前:月均自然订单50单,转化率1.2%
  • 优化4个月后:月均自然订单210单,转化率2.8%
  • 效果提升:订单增长320%,转化率提升133%

行业案例 - 教育

  • 优化前:月均咨询量35个,主要依赖付费广告
  • 优化5个月后:月均咨询量180个,自然流量占比65%
  • 效果提升:咨询量增长414%,营销成本降低57%

为什么选择我们的SEO服务

专业团队

  • 10年以上SEO经验专家带队
  • 百度、Google认证工程师
  • 内容创作、技术开发、数据分析多领域团队
  • 持续培训保持技术领先

数据驱动

  • 自主研发SEO分析工具
  • 实时排名监控系统
  • 竞争对手深度分析
  • 效果可视化报告

透明合作

  • 清晰的服务内容和价格
  • 定期进展汇报和沟通
  • 效果数据实时可查
  • 灵活的合同条款

我们的SEO服务理念

我们坚信,真正的SEO优化不仅仅是追求排名,而是通过提供优质内容、优化用户体验、建立网站权威,最终实现可持续的业务增长。我们的目标是与客户建立长期合作关系,共同成长。

提交需求或反馈

Demand feedback